@tobiasoberpaul/mdk 0.2.0 → 0.3.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/README.md +2 -0
- package/dist/core/resize.d.ts +1 -1
- package/dist/core/resize.js +86 -9
- package/dist/core/tokens.js +2 -2
- package/dist/layout.js +2 -0
- package/dist/presence.d.ts +2 -0
- package/dist/presence.js +15 -5
- package/dist/styles/motion.css +1 -1
- package/dist/transitions/grow.d.ts +2 -0
- package/dist/transitions/grow.js +1 -1
- package/dist/transitions/scale.d.ts +1 -1
- package/dist/transitions/scale.js +1 -1
- package/docs/LIBRARY.md +37 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ toggle.addEventListener('click', () => motion.toggle());
|
|
|
23
23
|
|
|
24
24
|
Your component manages layout, visibility, focus and accessibility. MDK handles the visual transition, including reversal and the system's reduced-motion preference.
|
|
25
25
|
|
|
26
|
+
For an existing popover or menu, use `createPresenceMotion` to connect its open and close signals. Use `resize: 'transform'` with `createGrowTransition` for local growth on a surface positioned by the host. [Integration guide](https://github.com/TOberpaul/animations/blob/main/docs/LIBRARY.md#animate-an-existing-component)
|
|
27
|
+
|
|
26
28
|
The library includes Grow, Extract, Morph, Slide and Content transitions. See the [library guide](https://github.com/TOberpaul/animations/blob/main/docs/LIBRARY.md) for the API, CSS tokens and integration patterns.
|
|
27
29
|
|
|
28
30
|
Coding assistants can access the MDK reference and integration examples through the public MCP server. See the [MCP setup](https://github.com/TOberpaul/animations/blob/main/docs/AI_INTEGRATION.md#hosted-mcp).
|
package/dist/core/resize.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ export interface ResizeOptions extends Options {
|
|
|
11
11
|
overlay?: SVGSVGElement;
|
|
12
12
|
}
|
|
13
13
|
/** Shared physical resizing with a trigger edge or local origin. */
|
|
14
|
-
export declare function createResizeTransition(options: ResizeOptions, pattern: 'morph' | 'scale'): Transition;
|
|
14
|
+
export declare function createResizeTransition(options: ResizeOptions, pattern: 'morph' | 'scale', transform?: boolean): Transition;
|
package/dist/core/resize.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { createTransition } from './animation.js';
|
|
2
2
|
import { growthAnchor, growthInsets, localScale, morphGeometry, resizeGeometry } from './geometry.js';
|
|
3
|
-
import { overshootToken, numberToken, token } from './tokens.js';
|
|
3
|
+
import { lengthToken, overshootToken, numberToken, token } from './tokens.js';
|
|
4
4
|
import { fixedContentTracks } from './content-layout.js';
|
|
5
5
|
/** Shared physical resizing with a trigger edge or local origin. */
|
|
6
|
-
export function createResizeTransition(options, pattern) {
|
|
6
|
+
export function createResizeTransition(options, pattern, transform = false) {
|
|
7
7
|
const { content, trigger, overlay } = options;
|
|
8
|
+
if (transform && overlay)
|
|
9
|
+
throw new Error('Transform Grow does not support an SVG surface overlay.');
|
|
8
10
|
const sameElement = trigger === content;
|
|
9
11
|
if (sameElement && options.initiallyOpen)
|
|
10
12
|
throw new Error('A same-element morph must initialize in its closed layout.');
|
|
@@ -32,6 +34,8 @@ export function createResizeTransition(options, pattern) {
|
|
|
32
34
|
}
|
|
33
35
|
options.onStateChange?.(state);
|
|
34
36
|
} }, pattern, () => {
|
|
37
|
+
if (transform)
|
|
38
|
+
return transformGrowTracks(options);
|
|
35
39
|
const edgeGeometry = !trigger || sameElement ? undefined : morphGeometry(trigger, content);
|
|
36
40
|
const factor = trigger ? undefined : numberToken(content, 'scale-from');
|
|
37
41
|
const collapseAxis = !trigger ? factor === 0 ? options.axis ?? 'both' : undefined : sameElement ? undefined : edgeGeometry.from.height === 0 ? 'height' : 'width';
|
|
@@ -39,7 +43,7 @@ export function createResizeTransition(options, pattern) {
|
|
|
39
43
|
left: to.left + to.width * (options.axis === 'height' ? 0 : 1 - factor) * ax, top: to.top + to.height * (options.axis === 'width' ? 0 : 1 - factor) * ay,
|
|
40
44
|
width: to.width * (options.axis === 'height' ? 1 : factor), height: to.height * (options.axis === 'width' ? 1 : factor)
|
|
41
45
|
});
|
|
42
|
-
const g = resizeGeometry(content, source, trigger ? edgeGeometry?.origin :
|
|
46
|
+
const g = resizeGeometry(content, source, trigger ? edgeGeometry?.origin : scaleOrigin(content), collapseAxis);
|
|
43
47
|
const style = getComputedStyle(content);
|
|
44
48
|
const base = style.transform;
|
|
45
49
|
const shadow = style.boxShadow;
|
|
@@ -125,12 +129,85 @@ export function createResizeTransition(options, pattern) {
|
|
|
125
129
|
result.push({ element: overlay, easing: 'linear', frames: frames.map(frame => ({
|
|
126
130
|
...frame, visibility: 'visible'
|
|
127
131
|
})) });
|
|
128
|
-
|
|
129
|
-
const native = options.backdrop === 'native';
|
|
130
|
-
const element = native ? content : options.backdrop;
|
|
131
|
-
const pseudoElement = native ? '::backdrop' : undefined;
|
|
132
|
-
result.push({ element, pseudoElement, easing, frames: [{ opacity: 0 }, { opacity: getComputedStyle(element, pseudoElement).opacity }] });
|
|
133
|
-
}
|
|
132
|
+
addBackdropTrack(result, options, easing);
|
|
134
133
|
return result;
|
|
135
134
|
}, { keepVisibleWhenClosed: sameElement });
|
|
136
135
|
}
|
|
136
|
+
function addBackdropTrack(tracks, { content, backdrop }, easing) {
|
|
137
|
+
if (!backdrop)
|
|
138
|
+
return;
|
|
139
|
+
const element = backdrop === 'native' ? content : backdrop;
|
|
140
|
+
const pseudoElement = backdrop === 'native' ? '::backdrop' : undefined;
|
|
141
|
+
tracks.push({ element, pseudoElement, easing, frames: [{ opacity: 0 }, { opacity: getComputedStyle(element, pseudoElement).opacity }] });
|
|
142
|
+
}
|
|
143
|
+
function scaleOrigin(content) {
|
|
144
|
+
const origin = token(content, 'scale-origin');
|
|
145
|
+
if (!/\b(?:inline|block)-(?:start|end)\b/.test(origin))
|
|
146
|
+
return origin;
|
|
147
|
+
const { direction, writingMode } = getComputedStyle(content);
|
|
148
|
+
const horizontal = writingMode === 'horizontal-tb';
|
|
149
|
+
const inline = horizontal ? ['left', 'right'] : writingMode === 'sideways-lr' ? ['bottom', 'top'] : ['top', 'bottom'];
|
|
150
|
+
if (direction === 'rtl')
|
|
151
|
+
inline.reverse();
|
|
152
|
+
const block = horizontal ? ['top', 'bottom'] : writingMode.endsWith('-rl') ? ['right', 'left'] : ['left', 'right'];
|
|
153
|
+
return origin.replace(/\b(inline|block)-(start|end)\b/g, (_, axis, edge) => (axis === 'inline' ? inline : block)[edge === 'start' ? 0 : 1]);
|
|
154
|
+
}
|
|
155
|
+
function transformGrowTracks(options) {
|
|
156
|
+
const { content, axis } = options;
|
|
157
|
+
const style = getComputedStyle(content);
|
|
158
|
+
const hostOrigin = style.transformOrigin.split(' ').map(parseFloat);
|
|
159
|
+
const opacity = style.opacity;
|
|
160
|
+
const property = 'transform-origin';
|
|
161
|
+
const inlineOrigin = content.style.getPropertyValue(property);
|
|
162
|
+
const priority = content.style.getPropertyPriority(property);
|
|
163
|
+
const origin = scaleOrigin(content);
|
|
164
|
+
if (!CSS.supports(property, origin))
|
|
165
|
+
throw new Error('Invalid --motion-scale-origin.');
|
|
166
|
+
let x, y;
|
|
167
|
+
try {
|
|
168
|
+
content.style.setProperty(property, origin, 'important');
|
|
169
|
+
[x, y] = style.transformOrigin.split(' ').map(parseFloat);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
if (inlineOrigin)
|
|
173
|
+
content.style.setProperty(property, inlineOrigin, priority);
|
|
174
|
+
else
|
|
175
|
+
content.style.removeProperty(property);
|
|
176
|
+
}
|
|
177
|
+
const { width, height } = localScale(content, content.getBoundingClientRect());
|
|
178
|
+
if (!width || !height)
|
|
179
|
+
throw new Error('Grow content must be measurable before animation.');
|
|
180
|
+
const dx = x - hostOrigin[0], dy = y - hostOrigin[1];
|
|
181
|
+
const end = numberToken(content, 'scale-geometry-end');
|
|
182
|
+
if (end <= 0 || end > 1)
|
|
183
|
+
throw new Error('Scale geometry end must satisfy 0 < end <= 1.');
|
|
184
|
+
const requested = token(content, 'easing-scale');
|
|
185
|
+
const easing = CSS.supports('animation-timing-function', requested) ? requested : token(content, 'easing-standard');
|
|
186
|
+
const from = numberToken(content, 'scale-from');
|
|
187
|
+
const frame = (offset, sx, sy) => ({
|
|
188
|
+
offset, easing,
|
|
189
|
+
// Append a local scale around the motion origin while preserving the host origin.
|
|
190
|
+
transform: `translate(${dx * (1 - sx)}px, ${dy * (1 - sy)}px) scale(${sx}, ${sy})`,
|
|
191
|
+
});
|
|
192
|
+
const frames = [frame(0, axis === 'height' ? 1 : from, axis === 'width' ? 1 : from)];
|
|
193
|
+
const overshoot = Math.min(overshootToken(content, 'scale-overshoot', width, height), lengthToken(content, 'overshoot-max'));
|
|
194
|
+
if (overshoot > 0) {
|
|
195
|
+
const peak = numberToken(content, 'scale-peak-offset');
|
|
196
|
+
if (peak <= 0 || peak >= end)
|
|
197
|
+
throw new Error('Scale peak must satisfy 0 < peak < geometry end.');
|
|
198
|
+
frames.push(frame(peak, axis === 'height' ? 1 : 1 + overshoot / Math.max(Math.abs(x), Math.abs(width - x)), axis === 'width' ? 1 : 1 + overshoot / Math.max(Math.abs(y), Math.abs(height - y))));
|
|
199
|
+
}
|
|
200
|
+
frames.push(frame(end, 1, 1));
|
|
201
|
+
if (end < 1)
|
|
202
|
+
frames.push(frame(1, 1, 1));
|
|
203
|
+
const tracks = [
|
|
204
|
+
{ element: content, frames, easing: 'linear', composite: 'add' },
|
|
205
|
+
{ element: content, easing: 'linear', frames: [
|
|
206
|
+
{ offset: 0, easing, opacity: numberToken(content, 'scale-opacity') },
|
|
207
|
+
{ offset: end, opacity },
|
|
208
|
+
...(end < 1 ? [{ offset: 1, opacity }] : []),
|
|
209
|
+
] },
|
|
210
|
+
];
|
|
211
|
+
addBackdropTrack(tracks, options, easing);
|
|
212
|
+
return tracks;
|
|
213
|
+
}
|
package/dist/core/tokens.js
CHANGED
|
@@ -45,9 +45,9 @@ export function lengthToken(element, name) {
|
|
|
45
45
|
}
|
|
46
46
|
/** Proportional overshoot per edge, capped by the pattern's CSS length budget. */
|
|
47
47
|
export function overshootToken(element, name, width, height) {
|
|
48
|
-
const percentage = token(element, 'overshoot-min');
|
|
48
|
+
const percentage = getComputedStyle(element).getPropertyValue('--motion-overshoot-ratio').trim() || token(element, 'overshoot-min');
|
|
49
49
|
if (!/^\d+(?:\.\d+)?%$/.test(percentage))
|
|
50
|
-
throw new Error('--motion-overshoot-
|
|
50
|
+
throw new Error('--motion-overshoot-ratio must be a non-negative percentage.');
|
|
51
51
|
const style = getComputedStyle(element);
|
|
52
52
|
const w = width ?? parseFloat(style.width), h = height ?? parseFloat(style.height);
|
|
53
53
|
return Math.min(Math.min(w, h) * parseFloat(percentage) / 100, lengthToken(element, name));
|
package/dist/layout.js
CHANGED
|
@@ -10,6 +10,8 @@ export function captureMotionLayout(element) {
|
|
|
10
10
|
/** Animate a component-owned CSS layout change from its captured visual state. */
|
|
11
11
|
export function animateMotionLayout(element, from) {
|
|
12
12
|
active.get(element)?.cancel();
|
|
13
|
+
if (typeof element.animate !== 'function')
|
|
14
|
+
return () => { };
|
|
13
15
|
const to = captureMotionLayout(element);
|
|
14
16
|
if (element.ownerDocument.defaultView.matchMedia('(prefers-reduced-motion: reduce)').matches || duration(element, 'morph') === 0)
|
|
15
17
|
return () => { };
|
package/dist/presence.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface PresenceMotionOptions extends Pick<Options, 'initiallyOpen'> {
|
|
|
5
5
|
/** Supply only if the integration can synchronously show and hide the surface.
|
|
6
6
|
* Without this callback, call setOpen(true) after the host has shown the surface. */
|
|
7
7
|
setPresent?(present: boolean): void;
|
|
8
|
+
/** Reflect data-motion-state on this surface. Its CSS must keep the surface visible during closing. */
|
|
9
|
+
stateTarget?: HTMLElement;
|
|
8
10
|
}
|
|
9
11
|
export interface PresenceMotion {
|
|
10
12
|
readonly state: MotionState;
|
package/dist/presence.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/** Drive MDK from a component's existing open/close signals. */
|
|
2
2
|
export function createPresenceMotion(options) {
|
|
3
|
-
const { createTransition, setPresent } = options;
|
|
3
|
+
const { createTransition, setPresent, stateTarget } = options;
|
|
4
|
+
const stateAttribute = 'data-motion-state';
|
|
5
|
+
const originalState = stateTarget?.getAttribute(stateAttribute);
|
|
4
6
|
let open = options.initiallyOpen ?? false;
|
|
5
7
|
let destroyed = false;
|
|
6
8
|
let controller;
|
|
7
|
-
if (setPresent) {
|
|
9
|
+
if (setPresent || stateTarget) {
|
|
8
10
|
controller = createTransition({
|
|
9
11
|
initiallyOpen: open,
|
|
10
12
|
onStateChange(state) {
|
|
13
|
+
if (state === 'destroyed')
|
|
14
|
+
return;
|
|
15
|
+
stateTarget?.setAttribute(stateAttribute, state);
|
|
11
16
|
if (state === 'opening')
|
|
12
|
-
setPresent(true);
|
|
17
|
+
setPresent?.(true);
|
|
13
18
|
if (state === 'closed')
|
|
14
|
-
setPresent(false);
|
|
19
|
+
setPresent?.(false);
|
|
15
20
|
}
|
|
16
21
|
});
|
|
22
|
+
stateTarget?.setAttribute(stateAttribute, controller.state);
|
|
17
23
|
}
|
|
18
24
|
return {
|
|
19
25
|
get state() {
|
|
@@ -29,7 +35,7 @@ export function createPresenceMotion(options) {
|
|
|
29
35
|
controller ?? (controller = createTransition({ initiallyOpen: false }));
|
|
30
36
|
controller.open();
|
|
31
37
|
}
|
|
32
|
-
else if (setPresent) {
|
|
38
|
+
else if (setPresent || stateTarget) {
|
|
33
39
|
controller?.close();
|
|
34
40
|
}
|
|
35
41
|
else {
|
|
@@ -44,6 +50,10 @@ export function createPresenceMotion(options) {
|
|
|
44
50
|
destroyed = true;
|
|
45
51
|
const pendingExit = controller?.state === 'closing';
|
|
46
52
|
controller?.destroy();
|
|
53
|
+
if (originalState == null)
|
|
54
|
+
stateTarget?.removeAttribute(stateAttribute);
|
|
55
|
+
else
|
|
56
|
+
stateTarget?.setAttribute(stateAttribute, originalState);
|
|
47
57
|
if (pendingExit)
|
|
48
58
|
setPresent?.(false);
|
|
49
59
|
}
|
package/dist/styles/motion.css
CHANGED
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
--motion-overshoot-max: .75rem;
|
|
73
73
|
}
|
|
74
74
|
@media (prefers-reduced-motion: reduce) {
|
|
75
|
-
:root { --motion-duration-morph: 0ms; --motion-duration-slide: 0ms; --motion-duration-scale: 0ms; --motion-duration-backdrop: 0ms; }
|
|
75
|
+
:root { --motion-duration-morph: 0ms; --motion-duration-slide: 0ms; --motion-duration-scale: 0ms; --motion-duration-backdrop: 0ms; --motion-duration-content: 0ms; --motion-duration-liquid: 0ms; }
|
|
76
76
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ResizeOptions } from '../core/resize.js';
|
|
2
2
|
import type { Transition } from '../core/lifecycle.js';
|
|
3
3
|
export interface GrowOptions extends ResizeOptions {
|
|
4
|
+
/** Use visual scaling for local Grow. Trigger-edge Grow always uses physical resizing. */
|
|
5
|
+
resize?: 'metrics' | 'transform';
|
|
4
6
|
}
|
|
5
7
|
/** Grow from a trigger edge, or from the configured local origin without a trigger. */
|
|
6
8
|
export declare function createGrowTransition(options: GrowOptions): Transition;
|
package/dist/transitions/grow.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createResizeTransition } from '../core/resize.js';
|
|
2
2
|
/** Grow from a trigger edge, or from the configured local origin without a trigger. */
|
|
3
3
|
export function createGrowTransition(options) {
|
|
4
|
-
return createResizeTransition(options, options.trigger ? 'morph' : 'scale');
|
|
4
|
+
return createResizeTransition(options, options.trigger ? 'morph' : 'scale', !options.trigger && options.resize === 'transform');
|
|
5
5
|
}
|
|
@@ -3,5 +3,5 @@ export interface ScaleOptions extends Options {
|
|
|
3
3
|
backdrop?: HTMLElement | 'native';
|
|
4
4
|
axis?: 'width' | 'height';
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
6
|
+
/** @deprecated Use createGrowTransition for local growth. */
|
|
7
7
|
export declare function createScaleTransition(options: ScaleOptions): Transition;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createGrowTransition } from './grow.js';
|
|
2
|
-
/**
|
|
2
|
+
/** @deprecated Use createGrowTransition for local growth. */
|
|
3
3
|
export function createScaleTransition(options) {
|
|
4
4
|
return createGrowTransition(options);
|
|
5
5
|
}
|
package/docs/LIBRARY.md
CHANGED
|
@@ -23,14 +23,14 @@ content, unless reflow is intentional (the three-column layout).
|
|
|
23
23
|
The package name is `@tobiasoberpaul/mdk`.
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
|
-
import {
|
|
26
|
+
import { createGrowTransition } from '@tobiasoberpaul/mdk';
|
|
27
27
|
import '@tobiasoberpaul/mdk/motion.css';
|
|
28
28
|
|
|
29
29
|
const surface = document.querySelector<HTMLElement>('#dialog')!;
|
|
30
30
|
const trigger = document.querySelector<HTMLButtonElement>('#open')!;
|
|
31
31
|
surface.inert = true;
|
|
32
32
|
|
|
33
|
-
const transition =
|
|
33
|
+
const transition = createGrowTransition({
|
|
34
34
|
content: surface,
|
|
35
35
|
reveal: [...surface.children].filter((node): node is HTMLElement => node instanceof HTMLElement),
|
|
36
36
|
onStateChange(state) {
|
|
@@ -104,18 +104,47 @@ mechanism in this callback. The same surface must remain mounted through exit.
|
|
|
104
104
|
Keep requested open state separate from visual presence so a new open request
|
|
105
105
|
can reverse an in-progress exit.
|
|
106
106
|
|
|
107
|
-
If the host
|
|
107
|
+
If the host can hold presence through CSS, pass `stateTarget: surface`. The
|
|
108
|
+
adapter reflects `opening`, `open`, `closing` and `closed` in
|
|
109
|
+
`data-motion-state` and restores any previous value on teardown. Let the
|
|
110
|
+
host's CSS keep the surface rendered at its normal open opacity during
|
|
111
|
+
`closing`, so the animation can measure and fade it.
|
|
112
|
+
Forward the host's requested state, independently of the held visibility,
|
|
113
|
+
so reopening can reverse the exit. Keep focus and interaction rules in the host.
|
|
114
|
+
|
|
115
|
+
If the host hides itself immediately, omit both `setPresent` and `stateTarget`. Call `setOpen(true)`
|
|
108
116
|
after the host has made its surface visible and positioned it, then call
|
|
109
117
|
`setOpen(false)` when it hides. This mode plays entry and releases the effect
|
|
110
118
|
on close. It requires a reliable signal from the host integration. Disable any
|
|
111
119
|
competing host animation on the same surface.
|
|
112
120
|
|
|
121
|
+
## Grow on a host-positioned surface
|
|
122
|
+
|
|
123
|
+
For a popover positioned by its host, opt into transform-based local Grow:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const motion = createGrowTransition({ content: surface, resize: 'transform' });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Use `--motion-scale-origin: inline-end block-start` for logical placement in
|
|
130
|
+
either text direction or writing mode. Use `right top` for a physical anchor.
|
|
131
|
+
Omit `trigger` when the component owns positioning and you want local growth.
|
|
132
|
+
Pass `trigger` only to grow from the trigger's edge. Use
|
|
133
|
+
`--motion-scale-from` for its starting scale. MDK adds the animation to the
|
|
134
|
+
host transform and leaves layout dimensions and insets alone. The whole surface
|
|
135
|
+
scales visually, including its border and content. Set `--motion-overshoot-max: 0`
|
|
136
|
+
to turn off overshoot. This mode works with `createPresenceMotion`.
|
|
137
|
+
|
|
138
|
+
The default `resize: 'metrics'` still resizes the actual surface. Grow with a
|
|
139
|
+
`trigger` continues to use trigger-edge geometry. The local transform mode
|
|
140
|
+
supports HTML surfaces without an SVG overlay.
|
|
141
|
+
|
|
113
142
|
## Public behavior factories
|
|
114
143
|
|
|
115
144
|
| Factory | Behavior | Required additions to shared options |
|
|
116
145
|
| --- | --- | --- |
|
|
117
|
-
| `createGrowTransition` | Grow from a configured origin or trigger edge | Optional `trigger`, `axis`, `backdrop` |
|
|
118
|
-
| `createScaleTransition` |
|
|
146
|
+
| `createGrowTransition` | Grow from a configured origin or trigger edge | Optional `trigger`, `axis`, `backdrop`, `resize` |
|
|
147
|
+
| `createScaleTransition` | Deprecated alias for local Grow | Optional `axis`, `backdrop` |
|
|
119
148
|
| `createExtractTransition` | Extract a new surface while retaining the trigger | `trigger` |
|
|
120
149
|
| `createMorphTransition` | Transform the trigger into the target surface | `trigger` |
|
|
121
150
|
| `createLiquidMorphTransition` | Source volume → encompassing surface → target | `trigger`; optional `replaceTrigger` |
|
|
@@ -132,7 +161,7 @@ opacity and blur. Explicit `items` for Content must not be nested.
|
|
|
132
161
|
|
|
133
162
|
### Grow
|
|
134
163
|
|
|
135
|
-
`
|
|
164
|
+
`createGrowTransition` preserves the component's base transform and reads its
|
|
136
165
|
origin from `--motion-scale-origin`. The library default is `.92` for
|
|
137
166
|
`--motion-scale-from`; the demos set `0` for fully collapsed opening.
|
|
138
167
|
|
|
@@ -238,7 +267,7 @@ const transition = createMotion({
|
|
|
238
267
|
|
|
239
268
|
An explicit `data-motion` takes precedence over classes. Without it, use exactly
|
|
240
269
|
one matching class. Declarative `morph` selects the replacing Morph factory directly.
|
|
241
|
-
The
|
|
270
|
+
The deprecated `liquid` / `motion-liquid` spelling remains a compatibility path; its
|
|
242
271
|
`data-motion-replace-trigger` flag retains its original meaning. Recreate the controller to change behavior after
|
|
243
272
|
initialization. Change CSS tokens for per-trip visual parameters.
|
|
244
273
|
|
|
@@ -335,7 +364,7 @@ can be overridden locally on a surface or globally.
|
|
|
335
364
|
| --- | --- |
|
|
336
365
|
| `--motion-duration` | `300ms`; aliases for morph, scale, liquid, slide, content, backdrop |
|
|
337
366
|
| `--motion-easing-standard` | `cubic-bezier(.42, 0, .58, 1)`; symmetric open/close |
|
|
338
|
-
| `--motion-overshoot-
|
|
367
|
+
| `--motion-overshoot-ratio` | `15%` of the surface's shorter side by default. When unset, reads the legacy `--motion-overshoot-min` token. |
|
|
339
368
|
| `--motion-overshoot-max` | `.75rem` cap shared by trigger and target; morph/scale alias this |
|
|
340
369
|
| `--motion-content-progressive` | `1` (enabled by default); set to `0` to disable |
|
|
341
370
|
| `--motion-content-stagger` | `.08`, fraction of base duration for Content |
|