@tobiasoberpaul/mdk 0.3.0 → 0.4.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 +1 -1
- package/dist/behaviors.js +1 -3
- package/dist/core/animation.js +1 -1
- package/dist/core/resize.js +35 -17
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -1
- package/dist/styles/motion.css +12 -12
- package/dist/transitions/extract.d.ts +2 -2
- package/dist/transitions/extract.js +2 -2
- package/dist/transitions/morph.d.ts +2 -2
- package/dist/transitions/morph.js +2 -2
- package/dist/transitions/{liquid.d.ts → surface-transfer.d.ts} +3 -4
- package/dist/transitions/{liquid.js → surface-transfer.js} +15 -15
- package/docs/LIBRARY.md +12 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Your component manages layout, visibility, focus and accessibility. MDK handles
|
|
|
25
25
|
|
|
26
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
27
|
|
|
28
|
-
The library includes Grow, Extract, Morph, Slide and
|
|
28
|
+
The library includes Grow, Extract, Morph, Slide and Fade. See the [library guide](https://github.com/TOberpaul/animations/blob/main/docs/LIBRARY.md) for the API, CSS tokens and integration patterns.
|
|
29
29
|
|
|
30
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).
|
|
31
31
|
|
package/dist/behaviors.js
CHANGED
|
@@ -7,13 +7,11 @@ export const motionBehaviors = {
|
|
|
7
7
|
};
|
|
8
8
|
/** Canonical naming shared by declarative integration and consuming components. */
|
|
9
9
|
export function getMotionKind(content) {
|
|
10
|
-
const names = [...Object.keys(motionBehaviors), '
|
|
10
|
+
const names = [...Object.keys(motionBehaviors), 'content'];
|
|
11
11
|
const classes = names.filter(name => content.classList.contains(`motion-${name}`));
|
|
12
12
|
const raw = content.dataset.motion ?? (classes.length === 1 ? classes[0] : undefined);
|
|
13
13
|
if (raw === 'content')
|
|
14
14
|
return 'fade';
|
|
15
|
-
if (raw === 'liquid')
|
|
16
|
-
return content.hasAttribute('data-motion-replace-trigger') ? 'morph' : 'extract';
|
|
17
15
|
if (!raw || !Object.prototype.hasOwnProperty.call(motionBehaviors, raw))
|
|
18
16
|
throw new Error('Set data-motion to grow, extract, morph, slide or fade, or use one matching motion-* class.');
|
|
19
17
|
return raw;
|
package/dist/core/animation.js
CHANGED
|
@@ -52,7 +52,7 @@ export function createTransition(options, pattern, tracks, behavior = {}) {
|
|
|
52
52
|
}
|
|
53
53
|
const requestedEasing = token(content, `easing-${pattern}`);
|
|
54
54
|
const easing = CSS.supports('animation-timing-function', requestedEasing) ? requestedEasing : token(content, 'easing-standard');
|
|
55
|
-
const recipe = motionBehaviors[(pattern === 'scale' ? 'grow' : pattern === '
|
|
55
|
+
const recipe = motionBehaviors[(pattern === 'scale' ? 'grow' : pattern === 'transfer' ? 'extract' : pattern === 'content' ? 'fade' : pattern)];
|
|
56
56
|
const descriptions = tracks();
|
|
57
57
|
time *= behavior.durationScale?.() ?? 1;
|
|
58
58
|
const reveal = options.reveal ? (Array.isArray(options.reveal) ? options.reveal : [options.reveal]) : [];
|
package/dist/core/resize.js
CHANGED
|
@@ -152,32 +152,50 @@ function scaleOrigin(content) {
|
|
|
152
152
|
const block = horizontal ? ['top', 'bottom'] : writingMode.endsWith('-rl') ? ['right', 'left'] : ['left', 'right'];
|
|
153
153
|
return origin.replace(/\b(inline|block)-(start|end)\b/g, (_, axis, edge) => (axis === 'inline' ? inline : block)[edge === 'start' ? 0 : 1]);
|
|
154
154
|
}
|
|
155
|
+
function measureScaleOrigin(content, style, origin, width, height) {
|
|
156
|
+
const probe = content.ownerDocument.createElement('span');
|
|
157
|
+
probe.style.cssText = 'all:initial;position:fixed;visibility:hidden;pointer-events:none;transition:none!important;animation:none!important';
|
|
158
|
+
for (const name of ['font-size', 'font-family', 'line-height']) {
|
|
159
|
+
probe.style.setProperty(name, style.getPropertyValue(name));
|
|
160
|
+
}
|
|
161
|
+
const contentBox = style.transformBox === 'content-box' || style.transformBox === 'fill-box';
|
|
162
|
+
const inset = (side) => contentBox
|
|
163
|
+
? parseFloat(style.getPropertyValue('padding-' + side)) + parseFloat(style.getPropertyValue('border-' + side + '-width'))
|
|
164
|
+
: 0;
|
|
165
|
+
const left = inset('left'), top = inset('top');
|
|
166
|
+
probe.style.width = `${Math.max(0, width - left - inset('right'))}px`;
|
|
167
|
+
probe.style.height = `${Math.max(0, height - top - inset('bottom'))}px`;
|
|
168
|
+
const read = () => getComputedStyle(probe).transformOrigin.split(' ').map(parseFloat);
|
|
169
|
+
let hostOrigin = style.transformOrigin.split(' ').map(parseFloat);
|
|
170
|
+
const parent = contentBox ? content : content.ownerDocument.body;
|
|
171
|
+
// Resolve keywords and percentages without starting a CSS transition on the host surface.
|
|
172
|
+
parent.appendChild(probe);
|
|
173
|
+
try {
|
|
174
|
+
// Resolved host styles can report percentages in border-box pixels even
|
|
175
|
+
// when transforms use the content box. Inherit onto the sized probe.
|
|
176
|
+
if (contentBox) {
|
|
177
|
+
probe.style.setProperty('transform-origin', 'inherit', 'important');
|
|
178
|
+
hostOrigin = read();
|
|
179
|
+
}
|
|
180
|
+
probe.style.setProperty('transform-origin', origin, 'important');
|
|
181
|
+
const [x, y] = read();
|
|
182
|
+
return { x: x + left, y: y + top, dx: x - hostOrigin[0], dy: y - hostOrigin[1] };
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
probe.remove();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
155
188
|
function transformGrowTracks(options) {
|
|
156
189
|
const { content, axis } = options;
|
|
157
190
|
const style = getComputedStyle(content);
|
|
158
|
-
const hostOrigin = style.transformOrigin.split(' ').map(parseFloat);
|
|
159
191
|
const opacity = style.opacity;
|
|
160
|
-
const property = 'transform-origin';
|
|
161
|
-
const inlineOrigin = content.style.getPropertyValue(property);
|
|
162
|
-
const priority = content.style.getPropertyPriority(property);
|
|
163
192
|
const origin = scaleOrigin(content);
|
|
164
|
-
if (!CSS.supports(
|
|
193
|
+
if (!CSS.supports('transform-origin', origin))
|
|
165
194
|
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
195
|
const { width, height } = localScale(content, content.getBoundingClientRect());
|
|
178
196
|
if (!width || !height)
|
|
179
197
|
throw new Error('Grow content must be measurable before animation.');
|
|
180
|
-
const
|
|
198
|
+
const { x, y, dx, dy } = measureScaleOrigin(content, style, origin, width, height);
|
|
181
199
|
const end = numberToken(content, 'scale-geometry-end');
|
|
182
200
|
if (end <= 0 || end > 1)
|
|
183
201
|
throw new Error('Scale geometry end must satisfy 0 < end <= 1.');
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export { createMorphTransition } from './transitions/morph.js';
|
|
2
|
-
export { createLiquidMorphTransition } from './transitions/liquid.js';
|
|
3
|
-
export type { LiquidMorphOptions, LiquidMorphTransition } from './transitions/liquid.js';
|
|
4
2
|
export type { MorphOptions } from './transitions/morph.js';
|
|
5
3
|
export { createSlideTransition } from './transitions/slide.js';
|
|
6
4
|
export type { SlideOptions } from './transitions/slide.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { createMorphTransition } from './transitions/morph.js';
|
|
2
|
-
export { createLiquidMorphTransition } from './transitions/liquid.js';
|
|
3
2
|
export { createSlideTransition } from './transitions/slide.js';
|
|
4
3
|
export { createScaleTransition } from './transitions/scale.js';
|
|
5
4
|
export { createContentTransition } from './transitions/content.js';
|
package/dist/styles/motion.css
CHANGED
|
@@ -58,19 +58,19 @@
|
|
|
58
58
|
--motion-scale-origin: center center;
|
|
59
59
|
--motion-slide-distance: 100%;
|
|
60
60
|
--motion-slide-peak-offset: .8;
|
|
61
|
-
--motion-duration-
|
|
62
|
-
--motion-easing-
|
|
63
|
-
--motion-
|
|
64
|
-
--motion-
|
|
65
|
-
--motion-
|
|
66
|
-
--motion-
|
|
67
|
-
--motion-
|
|
68
|
-
--motion-
|
|
69
|
-
--motion-
|
|
70
|
-
--motion-
|
|
71
|
-
--motion-
|
|
61
|
+
--motion-duration-transfer: var(--motion-duration);
|
|
62
|
+
--motion-easing-transfer: var(--motion-easing-standard);
|
|
63
|
+
--motion-transfer-geometry-end: .8;
|
|
64
|
+
--motion-transfer-reveal-offset: .3;
|
|
65
|
+
--motion-transfer-reveal-end: var(--motion-transfer-geometry-end);
|
|
66
|
+
--motion-transfer-reveal-blur: var(--motion-reveal-blur);
|
|
67
|
+
--motion-transfer-volume-offset: .18;
|
|
68
|
+
--motion-transfer-source-hide-start: var(--motion-transfer-reveal-offset);
|
|
69
|
+
--motion-transfer-source-hide-end: var(--motion-transfer-reveal-end);
|
|
70
|
+
--motion-transfer-source-blur: var(--motion-transfer-reveal-blur);
|
|
71
|
+
--motion-transfer-peak-offset: .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; --motion-duration-content: 0ms; --motion-duration-
|
|
75
|
+
:root { --motion-duration-morph: 0ms; --motion-duration-slide: 0ms; --motion-duration-scale: 0ms; --motion-duration-backdrop: 0ms; --motion-duration-content: 0ms; --motion-duration-transfer: 0ms; }
|
|
76
76
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SurfaceTransferOptions } from './surface-transfer.js';
|
|
2
2
|
import type { Transition } from '../core/lifecycle.js';
|
|
3
|
-
export type ExtractOptions = Omit<
|
|
3
|
+
export type ExtractOptions = Omit<SurfaceTransferOptions, 'replaceTrigger'>;
|
|
4
4
|
/** Extract a new surface while retaining the original trigger. */
|
|
5
5
|
export declare function createExtractTransition(options: ExtractOptions): Transition;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createSurfaceTransfer } from './surface-transfer.js';
|
|
2
2
|
/** Extract a new surface while retaining the original trigger. */
|
|
3
3
|
export function createExtractTransition(options) {
|
|
4
|
-
return
|
|
4
|
+
return createSurfaceTransfer({ ...options, replaceTrigger: false });
|
|
5
5
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SurfaceTransferOptions } from './surface-transfer.js';
|
|
2
2
|
import type { Transition } from '../core/lifecycle.js';
|
|
3
|
-
export type MorphOptions = Omit<
|
|
3
|
+
export type MorphOptions = Omit<SurfaceTransferOptions, 'replaceTrigger'>;
|
|
4
4
|
/** The source becomes the target; the integration manages original-trigger visibility. */
|
|
5
5
|
export declare function createMorphTransition(options: MorphOptions): Transition;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createSurfaceTransfer } from './surface-transfer.js';
|
|
2
2
|
/** The source becomes the target; the integration manages original-trigger visibility. */
|
|
3
3
|
export function createMorphTransition(options) {
|
|
4
|
-
return
|
|
4
|
+
return createSurfaceTransfer({ ...options, replaceTrigger: true });
|
|
5
5
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Options, Transition } from '../core/lifecycle.js';
|
|
2
|
-
export interface
|
|
2
|
+
export interface SurfaceTransferOptions extends Options {
|
|
3
3
|
/** Distinct node: retain the source. Same node as content: become the target.
|
|
4
4
|
* For the same node, prepare the expanded layout synchronously on opening
|
|
5
5
|
* and restore its compact layout only on closed. Initialize closed. */
|
|
@@ -8,6 +8,5 @@ export interface LiquidMorphOptions extends Options {
|
|
|
8
8
|
* trigger. The component hides/shows that trigger in onStateChange. */
|
|
9
9
|
replaceTrigger?: boolean;
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export declare function createLiquidMorphTransition(options: LiquidMorphOptions): LiquidMorphTransition;
|
|
11
|
+
/** Shared surface and foreground animation for Extract and Morph. */
|
|
12
|
+
export declare function createSurfaceTransfer(options: SurfaceTransferOptions): Transition;
|
|
@@ -38,12 +38,12 @@ function visibleBackground(element) {
|
|
|
38
38
|
const [r, g, b] = context.getImageData(0, 0, 1, 1).data;
|
|
39
39
|
return `rgb(${r},${g},${b})`;
|
|
40
40
|
}
|
|
41
|
-
/**
|
|
42
|
-
export function
|
|
41
|
+
/** Shared surface and foreground animation for Extract and Morph. */
|
|
42
|
+
export function createSurfaceTransfer(options) {
|
|
43
43
|
const sameElement = options.trigger === options.content;
|
|
44
44
|
const replacesTrigger = sameElement || options.replaceTrigger;
|
|
45
45
|
if (replacesTrigger && options.initiallyOpen)
|
|
46
|
-
throw new Error('A replacing
|
|
46
|
+
throw new Error('A replacing transfer morph must initialize in its closed layout.');
|
|
47
47
|
let previous = options.initiallyOpen ? 'open' : 'closed';
|
|
48
48
|
let closed;
|
|
49
49
|
let overlay;
|
|
@@ -78,7 +78,7 @@ export function createLiquidMorphTransition(options) {
|
|
|
78
78
|
remove();
|
|
79
79
|
}
|
|
80
80
|
options.onStateChange?.(state);
|
|
81
|
-
} }, '
|
|
81
|
+
} }, 'transfer', () => {
|
|
82
82
|
remove();
|
|
83
83
|
const { content, trigger } = options;
|
|
84
84
|
const box = content.getBoundingClientRect(), source = replacesTrigger ? closed.rect : trigger.getBoundingClientRect();
|
|
@@ -88,7 +88,7 @@ export function createLiquidMorphTransition(options) {
|
|
|
88
88
|
const width = px(style.width) + (style.boxSizing === 'border-box' ? 0 : extraX);
|
|
89
89
|
const height = px(style.height) + (style.boxSizing === 'border-box' ? 0 : extraY);
|
|
90
90
|
if (!width || !height || !source.width || !source.height)
|
|
91
|
-
throw new Error('
|
|
91
|
+
throw new Error('Surface transfer elements must be measurable.');
|
|
92
92
|
const scaleX = box.width / width, scaleY = box.height / height;
|
|
93
93
|
const left = (source.left - box.left) / scaleX, top = (source.top - box.top) / scaleY;
|
|
94
94
|
const sw = source.width / scaleX, sh = source.height / scaleY;
|
|
@@ -102,10 +102,10 @@ export function createLiquidMorphTransition(options) {
|
|
|
102
102
|
const borderFrom = replacesTrigger ? closed.border : triggerStyle.borderTopColor, borderTo = style.borderTopColor;
|
|
103
103
|
const swell = overshootToken(content, 'overshoot-max', sw, sh);
|
|
104
104
|
const overshoot = overshootToken(content, 'overshoot-max', width, height);
|
|
105
|
-
const volume = numberToken(content, '
|
|
106
|
-
const end = numberToken(content, '
|
|
105
|
+
const volume = numberToken(content, 'transfer-volume-offset'), peak = numberToken(content, 'transfer-peak-offset');
|
|
106
|
+
const end = numberToken(content, 'transfer-geometry-end');
|
|
107
107
|
if (!(swell >= 0 && overshoot >= 0 && volume > 0 && volume < peak && peak < end && end < 1))
|
|
108
|
-
throw new Error('Invalid
|
|
108
|
+
throw new Error('Invalid transfer geometry tokens.');
|
|
109
109
|
// The initial volume phase swells the trigger symmetrically. Only the target
|
|
110
110
|
// grows away from its component-owned anchor; preserve their envelope.
|
|
111
111
|
const anchor = explicitMotionAnchor(content) ?? growthAnchor(source, box);
|
|
@@ -140,9 +140,9 @@ export function createLiquidMorphTransition(options) {
|
|
|
140
140
|
triggerCopy = replacesTrigger ? closed.copy.cloneNode(true) : copyTrigger(trigger);
|
|
141
141
|
Object.assign(triggerCopy.style, { position: 'absolute', left: (left - px(style.borderLeftWidth)) + 'px', top: (top - px(style.borderTopWidth)) + 'px', width: sw + 'px', height: sh + 'px', boxSizing: 'border-box', margin: '0', transform: 'none', transition: 'none', animation: 'none', background: 'transparent', borderColor: 'transparent', boxShadow: 'none', pointerEvents: 'none', zIndex: '0' });
|
|
142
142
|
content.append(triggerCopy);
|
|
143
|
-
const revealStart = numberToken(content, '
|
|
143
|
+
const revealStart = numberToken(content, 'transfer-reveal-offset'), revealEnd = numberToken(content, 'transfer-reveal-end');
|
|
144
144
|
if (revealStart < 0 || revealStart >= revealEnd || revealEnd > 1)
|
|
145
|
-
throw new Error('Invalid
|
|
145
|
+
throw new Error('Invalid transfer reveal offsets.');
|
|
146
146
|
const clipFrames = [];
|
|
147
147
|
// Include the exact volume/peak poses, so frame inspection
|
|
148
148
|
// reach the configured percentage without resampling error.
|
|
@@ -186,13 +186,13 @@ export function createLiquidMorphTransition(options) {
|
|
|
186
186
|
];
|
|
187
187
|
const overlapsTarget = source.left < box.right && source.right > box.left && source.top < box.bottom && source.bottom > box.top;
|
|
188
188
|
if (replacesTrigger || overlapsTarget) {
|
|
189
|
-
const hideStart = numberToken(content, '
|
|
190
|
-
const hideEnd = numberToken(content, '
|
|
191
|
-
const blur = token(content, '
|
|
189
|
+
const hideStart = numberToken(content, 'transfer-source-hide-start');
|
|
190
|
+
const hideEnd = numberToken(content, 'transfer-source-hide-end');
|
|
191
|
+
const blur = token(content, 'transfer-source-blur');
|
|
192
192
|
if (hideStart < 0 || hideStart >= hideEnd || hideEnd > 1)
|
|
193
|
-
throw new Error('
|
|
193
|
+
throw new Error('Transfer source hide offsets must satisfy 0 <= start < end <= 1.');
|
|
194
194
|
if (!CSS.supports('filter', `blur(${blur})`))
|
|
195
|
-
throw new Error('
|
|
195
|
+
throw new Error('Transfer source blur must be a valid non-negative CSS length.');
|
|
196
196
|
const sourceStyle = getComputedStyle(triggerCopy), filter = sourceStyle.filter;
|
|
197
197
|
const blurred = `${filter === 'none' ? '' : `${filter} `}blur(${blur})`;
|
|
198
198
|
// Crossfade both contents over the same interval, without moving the
|
package/docs/LIBRARY.md
CHANGED
|
@@ -134,6 +134,10 @@ Pass `trigger` only to grow from the trigger's edge. Use
|
|
|
134
134
|
host transform and leaves layout dimensions and insets alone. The whole surface
|
|
135
135
|
scales visually, including its border and content. Set `--motion-overshoot-max: 0`
|
|
136
136
|
to turn off overshoot. This mode works with `createPresenceMotion`.
|
|
137
|
+
MDK resolves the motion origin without changing the surface's own
|
|
138
|
+
`transform-origin`, so host CSS transitions on that property stay independent.
|
|
139
|
+
The anchor follows the surface's `transform-box`, including `content-box`.
|
|
140
|
+
Origin and dimensions are measured at the start of each animation.
|
|
137
141
|
|
|
138
142
|
The default `resize: 'metrics'` still resizes the actual surface. Grow with a
|
|
139
143
|
`trigger` continues to use trigger-edge geometry. The local transform mode
|
|
@@ -147,7 +151,6 @@ supports HTML surfaces without an SVG overlay.
|
|
|
147
151
|
| `createScaleTransition` | Deprecated alias for local Grow | Optional `axis`, `backdrop` |
|
|
148
152
|
| `createExtractTransition` | Extract a new surface while retaining the trigger | `trigger` |
|
|
149
153
|
| `createMorphTransition` | Transform the trigger into the target surface | `trigger` |
|
|
150
|
-
| `createLiquidMorphTransition` | Source volume → encompassing surface → target | `trigger`; optional `replaceTrigger` |
|
|
151
154
|
| `createSlideTransition` | Bounded translate3d entry or two-pane shift | Optional `side`, `mode`, `outgoing`, `backdrop` |
|
|
152
155
|
| `createContentTransition` | Fixed slide + blur + fade | Optional `items`, `reveal`, `timing` |
|
|
153
156
|
| `createMotion` | Selects a factory from markup | See declarative integration below |
|
|
@@ -187,16 +190,17 @@ Public factories, declarative markup and example titles use the same meanings:
|
|
|
187
190
|
|
|
188
191
|
Migration: the earlier `createMorphTransition` name referred to trigger-edge Grow.
|
|
189
192
|
Use `createGrowTransition` for that old behavior. `createMorphTransition` now
|
|
190
|
-
means replacing Morph, consistent with the examples.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
means replacing Morph, consistent with the examples. To migrate from the former
|
|
194
|
+
Liquid API or markup, use Extract when the source stays visible and Morph when
|
|
195
|
+
the source becomes the target. Use the `--motion-transfer-*` and
|
|
196
|
+
`--motion-duration-transfer` tokens for the shared animation settings.
|
|
193
197
|
|
|
194
198
|
Extract retains the original trigger. Replacing Morph snapshots its foreground,
|
|
195
199
|
then crossfades that foreground into target content while one material surface
|
|
196
200
|
moves between the geometries. The integration hides/inerts the real trigger
|
|
197
201
|
until closed and restores it afterward. This avoids nested interactive controls.
|
|
198
202
|
|
|
199
|
-
|
|
203
|
+
Extract and Morph animate a temporary HTML surface with CSS dimensions, corners, background and borders. They remove the temporary surface and foreground
|
|
200
204
|
copy at rest. The resting UI is the real HTML component. Trigger swelling is a
|
|
201
205
|
phase of the click animation; there is no press/hold feature or press/release API.
|
|
202
206
|
|
|
@@ -207,7 +211,7 @@ the right edge. Both buttons remain visible and the main action is independent.
|
|
|
207
211
|
|
|
208
212
|
### Material source and composite inputs
|
|
209
213
|
|
|
210
|
-
|
|
214
|
+
Extract and Morph read material values from `content`. If a component wrapper is
|
|
211
215
|
transparent/borderless but a nested input paints the real material, explicitly
|
|
212
216
|
prepare a surface with the input's background, border and radius before the
|
|
213
217
|
motion snapshot. During travel, prevent the inner control from painting a second
|
|
@@ -263,12 +267,10 @@ const transition = createMotion({
|
|
|
263
267
|
| `data-motion-axis` | Grow axis: `width` or `height` |
|
|
264
268
|
| `data-motion-side` | Slide entry side |
|
|
265
269
|
| `data-motion-timing` | Content: `per-item` (default) or `total` |
|
|
266
|
-
| `data-motion-replace-trigger` | Presence enables replacing Liquid |
|
|
267
270
|
|
|
268
271
|
An explicit `data-motion` takes precedence over classes. Without it, use exactly
|
|
269
272
|
one matching class. Declarative `morph` selects the replacing Morph factory directly.
|
|
270
|
-
|
|
271
|
-
`data-motion-replace-trigger` flag retains its original meaning. Recreate the controller to change behavior after
|
|
273
|
+
Recreate the controller to change behavior after
|
|
272
274
|
initialization. Change CSS tokens for per-trip visual parameters.
|
|
273
275
|
|
|
274
276
|
## Content progression and ordering
|
|
@@ -362,7 +364,7 @@ can be overridden locally on a surface or globally.
|
|
|
362
364
|
|
|
363
365
|
| Token | Library default / meaning |
|
|
364
366
|
| --- | --- |
|
|
365
|
-
| `--motion-duration` | `300ms`; aliases for morph, scale,
|
|
367
|
+
| `--motion-duration` | `300ms`; aliases for morph, scale, transfer, slide, content, backdrop |
|
|
366
368
|
| `--motion-easing-standard` | `cubic-bezier(.42, 0, .58, 1)`; symmetric open/close |
|
|
367
369
|
| `--motion-overshoot-ratio` | `15%` of the surface's shorter side by default. When unset, reads the legacy `--motion-overshoot-min` token. |
|
|
368
370
|
| `--motion-overshoot-max` | `.75rem` cap shared by trigger and target; morph/scale alias this |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tobiasoberpaul/mdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"./motion.css": "./dist/styles/motion.css"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "tsc && node scripts/copy-css.mjs",
|
|
17
|
+
"build": "node scripts/clean-dist.mjs && tsc && node scripts/copy-css.mjs",
|
|
18
18
|
"build:examples": "node scripts/build-db-react.mjs && node scripts/build-shadcn.mjs",
|
|
19
19
|
"build:shadcn": "node scripts/build-shadcn.mjs",
|
|
20
20
|
"analytics": "node context-starter/analytics/serve.mjs . --codex-history",
|