motion 12.38.0 → 12.40.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 -3
- package/dist/motion.dev.js +711 -400
- package/dist/motion.js +1 -1
- package/package.json +3 -3
package/dist/motion.dev.js
CHANGED
|
@@ -61,9 +61,7 @@
|
|
|
61
61
|
*/
|
|
62
62
|
const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
return typeof value === "object" && value !== null;
|
|
66
|
-
}
|
|
64
|
+
const isObject = (value) => typeof value === "object" && value !== null;
|
|
67
65
|
|
|
68
66
|
/**
|
|
69
67
|
* Check if the value is a zero value string like "0px" or "0%"
|
|
@@ -90,8 +88,7 @@
|
|
|
90
88
|
* @param {...functions} transformers
|
|
91
89
|
* @return {function}
|
|
92
90
|
*/
|
|
93
|
-
const
|
|
94
|
-
const pipe = (...transformers) => transformers.reduce(combineFunctions);
|
|
91
|
+
const pipe = (...transformers) => transformers.reduce((a, b) => (v) => b(a(v)));
|
|
95
92
|
|
|
96
93
|
/*
|
|
97
94
|
Progress within given range
|
|
@@ -99,16 +96,11 @@
|
|
|
99
96
|
Given a lower limit and an upper limit, we return the progress
|
|
100
97
|
(expressed as a number 0-1) represented by the given value, and
|
|
101
98
|
limit that progress to within 0-1.
|
|
102
|
-
|
|
103
|
-
@param [number]: Lower limit
|
|
104
|
-
@param [number]: Upper limit
|
|
105
|
-
@param [number]: Value to find progress within given range
|
|
106
|
-
@return [number]: Progress of value within range as expressed 0-1
|
|
107
99
|
*/
|
|
108
100
|
/*#__NO_SIDE_EFFECTS__*/
|
|
109
101
|
const progress = (from, to, value) => {
|
|
110
|
-
const
|
|
111
|
-
return
|
|
102
|
+
const range = to - from;
|
|
103
|
+
return range ? (value - from) / range : 1;
|
|
112
104
|
};
|
|
113
105
|
|
|
114
106
|
class SubscriptionManager {
|
|
@@ -161,13 +153,9 @@
|
|
|
161
153
|
|
|
162
154
|
/*
|
|
163
155
|
Convert velocity into velocity per second
|
|
164
|
-
|
|
165
|
-
@param [number]: Unit per frame
|
|
166
|
-
@param [number]: Frame duration in ms
|
|
167
156
|
*/
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
157
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
158
|
+
const velocityPerSecond = (velocity, frameDuration) => frameDuration ? velocity * (1000 / frameDuration) : 0;
|
|
171
159
|
|
|
172
160
|
const warned = new Set();
|
|
173
161
|
function hasWarned(message) {
|
|
@@ -224,6 +212,7 @@
|
|
|
224
212
|
++i < subdivisionMaxIterations);
|
|
225
213
|
return currentT;
|
|
226
214
|
}
|
|
215
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
227
216
|
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
228
217
|
// If this is a linear gradient, return linear easing
|
|
229
218
|
if (mX1 === mY1 && mX2 === mY2)
|
|
@@ -235,10 +224,12 @@
|
|
|
235
224
|
|
|
236
225
|
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
237
226
|
// the second half of the animation. Turns easeIn into easeInOut.
|
|
227
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
238
228
|
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
239
229
|
|
|
240
230
|
// Accepts an easing function and returns a new one that outputs reversed values.
|
|
241
231
|
// Turns easeIn into easeOut.
|
|
232
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
242
233
|
const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
|
|
243
234
|
|
|
244
235
|
const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99);
|
|
@@ -259,6 +250,7 @@
|
|
|
259
250
|
const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
|
|
260
251
|
const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1);
|
|
261
252
|
|
|
253
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
262
254
|
function steps(numSteps, direction = "end") {
|
|
263
255
|
return (progress) => {
|
|
264
256
|
progress =
|
|
@@ -271,14 +263,17 @@
|
|
|
271
263
|
};
|
|
272
264
|
}
|
|
273
265
|
|
|
266
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
274
267
|
const isEasingArray = (ease) => {
|
|
275
268
|
return Array.isArray(ease) && typeof ease[0] !== "number";
|
|
276
269
|
};
|
|
277
270
|
|
|
271
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
278
272
|
function getEasingForSegment(easing, i) {
|
|
279
273
|
return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
|
|
280
274
|
}
|
|
281
275
|
|
|
276
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
282
277
|
const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
|
|
283
278
|
|
|
284
279
|
const easingLookup = {
|
|
@@ -2166,8 +2161,15 @@
|
|
|
2166
2161
|
];
|
|
2167
2162
|
/**
|
|
2168
2163
|
* A quick lookup for transform props.
|
|
2164
|
+
*
|
|
2165
|
+
* `pathRotation` is a transform for routing purposes (skipped from raw
|
|
2166
|
+
* style application, wired to the transform composite, flags transform
|
|
2167
|
+
* dirty) but is intentionally NOT in `transformPropOrder` — it is
|
|
2168
|
+
* composed onto `rotate` at the build sites, not serialized in its own
|
|
2169
|
+
* slot, and must stay out of the order-array consumers (parse-transform,
|
|
2170
|
+
* unit-conversion, keys-position).
|
|
2169
2171
|
*/
|
|
2170
|
-
const transformProps = /*@__PURE__*/ (() => new Set(transformPropOrder))();
|
|
2172
|
+
const transformProps = /*@__PURE__*/ (() => new Set([...transformPropOrder, "pathRotation"]))();
|
|
2171
2173
|
|
|
2172
2174
|
const isNumOrPxType = (v) => v === number || v === px;
|
|
2173
2175
|
const transformKeys = new Set(["x", "y", "z"]);
|
|
@@ -3180,270 +3182,6 @@
|
|
|
3180
3182
|
: maxStaggerDuration - index * staggerChildren;
|
|
3181
3183
|
}
|
|
3182
3184
|
|
|
3183
|
-
/**
|
|
3184
|
-
* Parse Framer's special CSS variable format into a CSS token and a fallback.
|
|
3185
|
-
*
|
|
3186
|
-
* ```
|
|
3187
|
-
* `var(--foo, #fff)` => [`--foo`, '#fff']
|
|
3188
|
-
* ```
|
|
3189
|
-
*
|
|
3190
|
-
* @param current
|
|
3191
|
-
*/
|
|
3192
|
-
const splitCSSVariableRegex =
|
|
3193
|
-
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
|
|
3194
|
-
/^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
|
|
3195
|
-
function parseCSSVariable(current) {
|
|
3196
|
-
const match = splitCSSVariableRegex.exec(current);
|
|
3197
|
-
if (!match)
|
|
3198
|
-
return [,];
|
|
3199
|
-
const [, token1, token2, fallback] = match;
|
|
3200
|
-
return [`--${token1 ?? token2}`, fallback];
|
|
3201
|
-
}
|
|
3202
|
-
const maxDepth = 4;
|
|
3203
|
-
function getVariableValue(current, element, depth = 1) {
|
|
3204
|
-
exports.invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`, "max-css-var-depth");
|
|
3205
|
-
const [token, fallback] = parseCSSVariable(current);
|
|
3206
|
-
// No CSS variable detected
|
|
3207
|
-
if (!token)
|
|
3208
|
-
return;
|
|
3209
|
-
// Attempt to read this CSS variable off the element
|
|
3210
|
-
const resolved = window.getComputedStyle(element).getPropertyValue(token);
|
|
3211
|
-
if (resolved) {
|
|
3212
|
-
const trimmed = resolved.trim();
|
|
3213
|
-
return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
|
|
3214
|
-
}
|
|
3215
|
-
return isCSSVariableToken(fallback)
|
|
3216
|
-
? getVariableValue(fallback, element, depth + 1)
|
|
3217
|
-
: fallback;
|
|
3218
|
-
}
|
|
3219
|
-
|
|
3220
|
-
const underDampedSpring = {
|
|
3221
|
-
type: "spring",
|
|
3222
|
-
stiffness: 500,
|
|
3223
|
-
damping: 25,
|
|
3224
|
-
restSpeed: 10,
|
|
3225
|
-
};
|
|
3226
|
-
const criticallyDampedSpring = (target) => ({
|
|
3227
|
-
type: "spring",
|
|
3228
|
-
stiffness: 550,
|
|
3229
|
-
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
3230
|
-
restSpeed: 10,
|
|
3231
|
-
});
|
|
3232
|
-
const keyframesTransition = {
|
|
3233
|
-
type: "keyframes",
|
|
3234
|
-
duration: 0.8,
|
|
3235
|
-
};
|
|
3236
|
-
/**
|
|
3237
|
-
* Default easing curve is a slightly shallower version of
|
|
3238
|
-
* the default browser easing curve.
|
|
3239
|
-
*/
|
|
3240
|
-
const ease = {
|
|
3241
|
-
type: "keyframes",
|
|
3242
|
-
ease: [0.25, 0.1, 0.35, 1],
|
|
3243
|
-
duration: 0.3,
|
|
3244
|
-
};
|
|
3245
|
-
const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
3246
|
-
if (keyframes.length > 2) {
|
|
3247
|
-
return keyframesTransition;
|
|
3248
|
-
}
|
|
3249
|
-
else if (transformProps.has(valueKey)) {
|
|
3250
|
-
return valueKey.startsWith("scale")
|
|
3251
|
-
? criticallyDampedSpring(keyframes[1])
|
|
3252
|
-
: underDampedSpring;
|
|
3253
|
-
}
|
|
3254
|
-
return ease;
|
|
3255
|
-
};
|
|
3256
|
-
|
|
3257
|
-
/**
|
|
3258
|
-
* If `transition` has `inherit: true`, shallow-merge it with
|
|
3259
|
-
* `parentTransition` (child keys win) and strip the `inherit` key.
|
|
3260
|
-
* Otherwise return `transition` unchanged.
|
|
3261
|
-
*/
|
|
3262
|
-
function resolveTransition(transition, parentTransition) {
|
|
3263
|
-
if (transition?.inherit && parentTransition) {
|
|
3264
|
-
const { inherit: _, ...rest } = transition;
|
|
3265
|
-
return { ...parentTransition, ...rest };
|
|
3266
|
-
}
|
|
3267
|
-
return transition;
|
|
3268
|
-
}
|
|
3269
|
-
|
|
3270
|
-
function getValueTransition$1(transition, key) {
|
|
3271
|
-
const valueTransition = transition?.[key] ??
|
|
3272
|
-
transition?.["default"] ??
|
|
3273
|
-
transition;
|
|
3274
|
-
if (valueTransition !== transition) {
|
|
3275
|
-
return resolveTransition(valueTransition, transition);
|
|
3276
|
-
}
|
|
3277
|
-
return valueTransition;
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
const orchestrationKeys = new Set([
|
|
3281
|
-
"when",
|
|
3282
|
-
"delay",
|
|
3283
|
-
"delayChildren",
|
|
3284
|
-
"staggerChildren",
|
|
3285
|
-
"staggerDirection",
|
|
3286
|
-
"repeat",
|
|
3287
|
-
"repeatType",
|
|
3288
|
-
"repeatDelay",
|
|
3289
|
-
"from",
|
|
3290
|
-
"elapsed",
|
|
3291
|
-
]);
|
|
3292
|
-
/**
|
|
3293
|
-
* Decide whether a transition is defined on a given Transition.
|
|
3294
|
-
* This filters out orchestration options and returns true
|
|
3295
|
-
* if any options are left.
|
|
3296
|
-
*/
|
|
3297
|
-
function isTransitionDefined(transition) {
|
|
3298
|
-
for (const key in transition) {
|
|
3299
|
-
if (!orchestrationKeys.has(key))
|
|
3300
|
-
return true;
|
|
3301
|
-
}
|
|
3302
|
-
return false;
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
|
|
3306
|
-
const valueTransition = getValueTransition$1(transition, name) || {};
|
|
3307
|
-
/**
|
|
3308
|
-
* Most transition values are currently completely overwritten by value-specific
|
|
3309
|
-
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
3310
|
-
* delay actually does inherit from the root transition if not value-specific.
|
|
3311
|
-
*/
|
|
3312
|
-
const delay = valueTransition.delay || transition.delay || 0;
|
|
3313
|
-
/**
|
|
3314
|
-
* Elapsed isn't a public transition option but can be passed through from
|
|
3315
|
-
* optimized appear effects in milliseconds.
|
|
3316
|
-
*/
|
|
3317
|
-
let { elapsed = 0 } = transition;
|
|
3318
|
-
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
3319
|
-
const options = {
|
|
3320
|
-
keyframes: Array.isArray(target) ? target : [null, target],
|
|
3321
|
-
ease: "easeOut",
|
|
3322
|
-
velocity: value.getVelocity(),
|
|
3323
|
-
...valueTransition,
|
|
3324
|
-
delay: -elapsed,
|
|
3325
|
-
onUpdate: (v) => {
|
|
3326
|
-
value.set(v);
|
|
3327
|
-
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
3328
|
-
},
|
|
3329
|
-
onComplete: () => {
|
|
3330
|
-
onComplete();
|
|
3331
|
-
valueTransition.onComplete && valueTransition.onComplete();
|
|
3332
|
-
},
|
|
3333
|
-
name,
|
|
3334
|
-
motionValue: value,
|
|
3335
|
-
element: isHandoff ? undefined : element,
|
|
3336
|
-
};
|
|
3337
|
-
/**
|
|
3338
|
-
* If there's no transition defined for this value, we can generate
|
|
3339
|
-
* unique transition settings for this value.
|
|
3340
|
-
*/
|
|
3341
|
-
if (!isTransitionDefined(valueTransition)) {
|
|
3342
|
-
Object.assign(options, getDefaultTransition(name, options));
|
|
3343
|
-
}
|
|
3344
|
-
/**
|
|
3345
|
-
* Both WAAPI and our internal animation functions use durations
|
|
3346
|
-
* as defined by milliseconds, while our external API defines them
|
|
3347
|
-
* as seconds.
|
|
3348
|
-
*/
|
|
3349
|
-
options.duration && (options.duration = secondsToMilliseconds(options.duration));
|
|
3350
|
-
options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay));
|
|
3351
|
-
/**
|
|
3352
|
-
* Support deprecated way to set initial value. Prefer keyframe syntax.
|
|
3353
|
-
*/
|
|
3354
|
-
if (options.from !== undefined) {
|
|
3355
|
-
options.keyframes[0] = options.from;
|
|
3356
|
-
}
|
|
3357
|
-
let shouldSkip = false;
|
|
3358
|
-
if (options.type === false ||
|
|
3359
|
-
(options.duration === 0 && !options.repeatDelay)) {
|
|
3360
|
-
makeAnimationInstant(options);
|
|
3361
|
-
if (options.delay === 0) {
|
|
3362
|
-
shouldSkip = true;
|
|
3363
|
-
}
|
|
3364
|
-
}
|
|
3365
|
-
if (MotionGlobalConfig.instantAnimations ||
|
|
3366
|
-
MotionGlobalConfig.skipAnimations ||
|
|
3367
|
-
element?.shouldSkipAnimations) {
|
|
3368
|
-
shouldSkip = true;
|
|
3369
|
-
makeAnimationInstant(options);
|
|
3370
|
-
options.delay = 0;
|
|
3371
|
-
}
|
|
3372
|
-
/**
|
|
3373
|
-
* If the transition type or easing has been explicitly set by the user
|
|
3374
|
-
* then we don't want to allow flattening the animation.
|
|
3375
|
-
*/
|
|
3376
|
-
options.allowFlatten = !valueTransition.type && !valueTransition.ease;
|
|
3377
|
-
/**
|
|
3378
|
-
* If we can or must skip creating the animation, and apply only
|
|
3379
|
-
* the final keyframe, do so. We also check once keyframes are resolved but
|
|
3380
|
-
* this early check prevents the need to create an animation at all.
|
|
3381
|
-
*/
|
|
3382
|
-
if (shouldSkip && !isHandoff && value.get() !== undefined) {
|
|
3383
|
-
const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
|
|
3384
|
-
if (finalKeyframe !== undefined) {
|
|
3385
|
-
frame.update(() => {
|
|
3386
|
-
options.onUpdate(finalKeyframe);
|
|
3387
|
-
options.onComplete();
|
|
3388
|
-
});
|
|
3389
|
-
return;
|
|
3390
|
-
}
|
|
3391
|
-
}
|
|
3392
|
-
return valueTransition.isSync
|
|
3393
|
-
? new JSAnimation(options)
|
|
3394
|
-
: new AsyncMotionValueAnimation(options);
|
|
3395
|
-
};
|
|
3396
|
-
|
|
3397
|
-
function getValueState(visualElement) {
|
|
3398
|
-
const state = [{}, {}];
|
|
3399
|
-
visualElement?.values.forEach((value, key) => {
|
|
3400
|
-
state[0][key] = value.get();
|
|
3401
|
-
state[1][key] = value.getVelocity();
|
|
3402
|
-
});
|
|
3403
|
-
return state;
|
|
3404
|
-
}
|
|
3405
|
-
function resolveVariantFromProps(props, definition, custom, visualElement) {
|
|
3406
|
-
/**
|
|
3407
|
-
* If the variant definition is a function, resolve.
|
|
3408
|
-
*/
|
|
3409
|
-
if (typeof definition === "function") {
|
|
3410
|
-
const [current, velocity] = getValueState(visualElement);
|
|
3411
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
3412
|
-
}
|
|
3413
|
-
/**
|
|
3414
|
-
* If the variant definition is a variant label, or
|
|
3415
|
-
* the function returned a variant label, resolve.
|
|
3416
|
-
*/
|
|
3417
|
-
if (typeof definition === "string") {
|
|
3418
|
-
definition = props.variants && props.variants[definition];
|
|
3419
|
-
}
|
|
3420
|
-
/**
|
|
3421
|
-
* At this point we've resolved both functions and variant labels,
|
|
3422
|
-
* but the resolved variant label might itself have been a function.
|
|
3423
|
-
* If so, resolve. This can only have returned a valid target object.
|
|
3424
|
-
*/
|
|
3425
|
-
if (typeof definition === "function") {
|
|
3426
|
-
const [current, velocity] = getValueState(visualElement);
|
|
3427
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
3428
|
-
}
|
|
3429
|
-
return definition;
|
|
3430
|
-
}
|
|
3431
|
-
|
|
3432
|
-
function resolveVariant(visualElement, definition, custom) {
|
|
3433
|
-
const props = visualElement.getProps();
|
|
3434
|
-
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
|
|
3435
|
-
}
|
|
3436
|
-
|
|
3437
|
-
const positionalKeys = new Set([
|
|
3438
|
-
"width",
|
|
3439
|
-
"height",
|
|
3440
|
-
"top",
|
|
3441
|
-
"left",
|
|
3442
|
-
"right",
|
|
3443
|
-
"bottom",
|
|
3444
|
-
...transformPropOrder,
|
|
3445
|
-
]);
|
|
3446
|
-
|
|
3447
3185
|
/**
|
|
3448
3186
|
* Maximum time between the value of two frames, beyond which we
|
|
3449
3187
|
* assume the velocity has since been 0.
|
|
@@ -3652,116 +3390,562 @@
|
|
|
3652
3390
|
}
|
|
3653
3391
|
}
|
|
3654
3392
|
/**
|
|
3655
|
-
* Returns the latest state of `MotionValue`
|
|
3656
|
-
*
|
|
3657
|
-
* @returns - The latest state of `MotionValue`
|
|
3658
|
-
*
|
|
3659
|
-
* @public
|
|
3393
|
+
* Returns the latest state of `MotionValue`
|
|
3394
|
+
*
|
|
3395
|
+
* @returns - The latest state of `MotionValue`
|
|
3396
|
+
*
|
|
3397
|
+
* @public
|
|
3398
|
+
*/
|
|
3399
|
+
get() {
|
|
3400
|
+
if (collectMotionValues.current) {
|
|
3401
|
+
collectMotionValues.current.push(this);
|
|
3402
|
+
}
|
|
3403
|
+
return this.current;
|
|
3404
|
+
}
|
|
3405
|
+
/**
|
|
3406
|
+
* @public
|
|
3407
|
+
*/
|
|
3408
|
+
getPrevious() {
|
|
3409
|
+
return this.prev;
|
|
3410
|
+
}
|
|
3411
|
+
/**
|
|
3412
|
+
* Returns the latest velocity of `MotionValue`
|
|
3413
|
+
*
|
|
3414
|
+
* @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
|
|
3415
|
+
*
|
|
3416
|
+
* @public
|
|
3417
|
+
*/
|
|
3418
|
+
getVelocity() {
|
|
3419
|
+
const currentTime = time.now();
|
|
3420
|
+
if (!this.canTrackVelocity ||
|
|
3421
|
+
this.prevFrameValue === undefined ||
|
|
3422
|
+
currentTime - this.updatedAt > MAX_VELOCITY_DELTA) {
|
|
3423
|
+
return 0;
|
|
3424
|
+
}
|
|
3425
|
+
const delta = Math.min(this.updatedAt - this.prevUpdatedAt, MAX_VELOCITY_DELTA);
|
|
3426
|
+
// Casts because of parseFloat's poor typing
|
|
3427
|
+
return velocityPerSecond(parseFloat(this.current) -
|
|
3428
|
+
parseFloat(this.prevFrameValue), delta);
|
|
3429
|
+
}
|
|
3430
|
+
/**
|
|
3431
|
+
* Registers a new animation to control this `MotionValue`. Only one
|
|
3432
|
+
* animation can drive a `MotionValue` at one time.
|
|
3433
|
+
*
|
|
3434
|
+
* ```jsx
|
|
3435
|
+
* value.start()
|
|
3436
|
+
* ```
|
|
3437
|
+
*
|
|
3438
|
+
* @param animation - A function that starts the provided animation
|
|
3439
|
+
*/
|
|
3440
|
+
start(startAnimation) {
|
|
3441
|
+
this.stop();
|
|
3442
|
+
return new Promise((resolve) => {
|
|
3443
|
+
this.hasAnimated = true;
|
|
3444
|
+
this.animation = startAnimation(resolve);
|
|
3445
|
+
if (this.events.animationStart) {
|
|
3446
|
+
this.events.animationStart.notify();
|
|
3447
|
+
}
|
|
3448
|
+
}).then(() => {
|
|
3449
|
+
if (this.events.animationComplete) {
|
|
3450
|
+
this.events.animationComplete.notify();
|
|
3451
|
+
}
|
|
3452
|
+
this.clearAnimation();
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
/**
|
|
3456
|
+
* Stop the currently active animation.
|
|
3457
|
+
*
|
|
3458
|
+
* @public
|
|
3459
|
+
*/
|
|
3460
|
+
stop() {
|
|
3461
|
+
if (this.animation) {
|
|
3462
|
+
this.animation.stop();
|
|
3463
|
+
if (this.events.animationCancel) {
|
|
3464
|
+
this.events.animationCancel.notify();
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
this.clearAnimation();
|
|
3468
|
+
}
|
|
3469
|
+
/**
|
|
3470
|
+
* Returns `true` if this value is currently animating.
|
|
3471
|
+
*
|
|
3472
|
+
* @public
|
|
3473
|
+
*/
|
|
3474
|
+
isAnimating() {
|
|
3475
|
+
return !!this.animation;
|
|
3476
|
+
}
|
|
3477
|
+
clearAnimation() {
|
|
3478
|
+
delete this.animation;
|
|
3479
|
+
}
|
|
3480
|
+
/**
|
|
3481
|
+
* Destroy and clean up subscribers to this `MotionValue`.
|
|
3482
|
+
*
|
|
3483
|
+
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
|
|
3484
|
+
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
3485
|
+
* created a `MotionValue` via the `motionValue` function.
|
|
3486
|
+
*
|
|
3487
|
+
* @public
|
|
3488
|
+
*/
|
|
3489
|
+
destroy() {
|
|
3490
|
+
this.dependents?.clear();
|
|
3491
|
+
this.events.destroy?.notify();
|
|
3492
|
+
this.clearListeners();
|
|
3493
|
+
this.stop();
|
|
3494
|
+
if (this.stopPassiveEffect) {
|
|
3495
|
+
this.stopPassiveEffect();
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
function motionValue(init, options) {
|
|
3500
|
+
return new MotionValue(init, options);
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
/**
|
|
3504
|
+
* If `transition` has `inherit: true`, shallow-merge it with
|
|
3505
|
+
* `parentTransition` (child keys win) and strip the `inherit` key.
|
|
3506
|
+
* Otherwise return `transition` unchanged.
|
|
3507
|
+
*/
|
|
3508
|
+
function resolveTransition(transition, parentTransition) {
|
|
3509
|
+
if (transition?.inherit && parentTransition) {
|
|
3510
|
+
const { inherit: _, ...rest } = transition;
|
|
3511
|
+
return { ...parentTransition, ...rest };
|
|
3512
|
+
}
|
|
3513
|
+
return transition;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
function getValueTransition$1(transition, key) {
|
|
3517
|
+
const valueTransition = transition?.[key] ??
|
|
3518
|
+
transition?.["default"] ??
|
|
3519
|
+
transition;
|
|
3520
|
+
if (valueTransition !== transition) {
|
|
3521
|
+
return resolveTransition(valueTransition, transition);
|
|
3522
|
+
}
|
|
3523
|
+
return valueTransition;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
const underDampedSpring = {
|
|
3527
|
+
type: "spring",
|
|
3528
|
+
stiffness: 500,
|
|
3529
|
+
damping: 25,
|
|
3530
|
+
restSpeed: 10,
|
|
3531
|
+
};
|
|
3532
|
+
const criticallyDampedSpring = (target) => ({
|
|
3533
|
+
type: "spring",
|
|
3534
|
+
stiffness: 550,
|
|
3535
|
+
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
3536
|
+
restSpeed: 10,
|
|
3537
|
+
});
|
|
3538
|
+
const keyframesTransition = {
|
|
3539
|
+
type: "keyframes",
|
|
3540
|
+
duration: 0.8,
|
|
3541
|
+
};
|
|
3542
|
+
/**
|
|
3543
|
+
* Default easing curve is a slightly shallower version of
|
|
3544
|
+
* the default browser easing curve.
|
|
3545
|
+
*/
|
|
3546
|
+
const ease = {
|
|
3547
|
+
type: "keyframes",
|
|
3548
|
+
ease: [0.25, 0.1, 0.35, 1],
|
|
3549
|
+
duration: 0.3,
|
|
3550
|
+
};
|
|
3551
|
+
const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
3552
|
+
if (keyframes.length > 2) {
|
|
3553
|
+
return keyframesTransition;
|
|
3554
|
+
}
|
|
3555
|
+
else if (transformProps.has(valueKey)) {
|
|
3556
|
+
return valueKey.startsWith("scale")
|
|
3557
|
+
? criticallyDampedSpring(keyframes[1])
|
|
3558
|
+
: underDampedSpring;
|
|
3559
|
+
}
|
|
3560
|
+
return ease;
|
|
3561
|
+
};
|
|
3562
|
+
|
|
3563
|
+
const orchestrationKeys = new Set([
|
|
3564
|
+
"when",
|
|
3565
|
+
"delay",
|
|
3566
|
+
"delayChildren",
|
|
3567
|
+
"staggerChildren",
|
|
3568
|
+
"staggerDirection",
|
|
3569
|
+
"repeat",
|
|
3570
|
+
"repeatType",
|
|
3571
|
+
"repeatDelay",
|
|
3572
|
+
"from",
|
|
3573
|
+
"elapsed",
|
|
3574
|
+
]);
|
|
3575
|
+
/**
|
|
3576
|
+
* Decide whether a transition is defined on a given Transition.
|
|
3577
|
+
* This filters out orchestration options and returns true
|
|
3578
|
+
* if any options are left.
|
|
3579
|
+
*/
|
|
3580
|
+
function isTransitionDefined(transition) {
|
|
3581
|
+
for (const key in transition) {
|
|
3582
|
+
if (!orchestrationKeys.has(key))
|
|
3583
|
+
return true;
|
|
3584
|
+
}
|
|
3585
|
+
return false;
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
|
|
3589
|
+
const valueTransition = getValueTransition$1(transition, name) || {};
|
|
3590
|
+
/**
|
|
3591
|
+
* Most transition values are currently completely overwritten by value-specific
|
|
3592
|
+
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
3593
|
+
* delay actually does inherit from the root transition if not value-specific.
|
|
3594
|
+
*/
|
|
3595
|
+
const delay = valueTransition.delay || transition.delay || 0;
|
|
3596
|
+
/**
|
|
3597
|
+
* Elapsed isn't a public transition option but can be passed through from
|
|
3598
|
+
* optimized appear effects in milliseconds.
|
|
3599
|
+
*/
|
|
3600
|
+
let { elapsed = 0 } = transition;
|
|
3601
|
+
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
3602
|
+
const options = {
|
|
3603
|
+
keyframes: Array.isArray(target) ? target : [null, target],
|
|
3604
|
+
ease: "easeOut",
|
|
3605
|
+
velocity: value.getVelocity(),
|
|
3606
|
+
...valueTransition,
|
|
3607
|
+
delay: -elapsed,
|
|
3608
|
+
onUpdate: (v) => {
|
|
3609
|
+
value.set(v);
|
|
3610
|
+
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
3611
|
+
},
|
|
3612
|
+
onComplete: () => {
|
|
3613
|
+
onComplete();
|
|
3614
|
+
valueTransition.onComplete && valueTransition.onComplete();
|
|
3615
|
+
},
|
|
3616
|
+
name,
|
|
3617
|
+
motionValue: value,
|
|
3618
|
+
element: isHandoff ? undefined : element,
|
|
3619
|
+
};
|
|
3620
|
+
/**
|
|
3621
|
+
* If there's no transition defined for this value, we can generate
|
|
3622
|
+
* unique transition settings for this value.
|
|
3623
|
+
*/
|
|
3624
|
+
if (!isTransitionDefined(valueTransition)) {
|
|
3625
|
+
Object.assign(options, getDefaultTransition(name, options));
|
|
3626
|
+
}
|
|
3627
|
+
/**
|
|
3628
|
+
* Both WAAPI and our internal animation functions use durations
|
|
3629
|
+
* as defined by milliseconds, while our external API defines them
|
|
3630
|
+
* as seconds.
|
|
3660
3631
|
*/
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3632
|
+
options.duration && (options.duration = secondsToMilliseconds(options.duration));
|
|
3633
|
+
options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay));
|
|
3634
|
+
/**
|
|
3635
|
+
* Support deprecated way to set initial value. Prefer keyframe syntax.
|
|
3636
|
+
*/
|
|
3637
|
+
if (options.from !== undefined) {
|
|
3638
|
+
options.keyframes[0] = options.from;
|
|
3639
|
+
}
|
|
3640
|
+
let shouldSkip = false;
|
|
3641
|
+
if (options.type === false ||
|
|
3642
|
+
(options.duration === 0 && !options.repeatDelay)) {
|
|
3643
|
+
makeAnimationInstant(options);
|
|
3644
|
+
if (options.delay === 0) {
|
|
3645
|
+
shouldSkip = true;
|
|
3664
3646
|
}
|
|
3665
|
-
|
|
3647
|
+
}
|
|
3648
|
+
if (MotionGlobalConfig.instantAnimations ||
|
|
3649
|
+
MotionGlobalConfig.skipAnimations ||
|
|
3650
|
+
element?.shouldSkipAnimations ||
|
|
3651
|
+
valueTransition.skipAnimations) {
|
|
3652
|
+
shouldSkip = true;
|
|
3653
|
+
makeAnimationInstant(options);
|
|
3654
|
+
options.delay = 0;
|
|
3666
3655
|
}
|
|
3667
3656
|
/**
|
|
3668
|
-
*
|
|
3657
|
+
* If the transition type or easing has been explicitly set by the user
|
|
3658
|
+
* then we don't want to allow flattening the animation.
|
|
3669
3659
|
*/
|
|
3670
|
-
|
|
3671
|
-
return this.prev;
|
|
3672
|
-
}
|
|
3660
|
+
options.allowFlatten = !valueTransition.type && !valueTransition.ease;
|
|
3673
3661
|
/**
|
|
3674
|
-
*
|
|
3675
|
-
*
|
|
3676
|
-
*
|
|
3677
|
-
*
|
|
3678
|
-
* @public
|
|
3662
|
+
* If we can or must skip creating the animation, and apply only
|
|
3663
|
+
* the final keyframe, do so. We also check once keyframes are resolved but
|
|
3664
|
+
* this early check prevents the need to create an animation at all.
|
|
3679
3665
|
*/
|
|
3680
|
-
|
|
3681
|
-
const
|
|
3682
|
-
if (
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3666
|
+
if (shouldSkip && !isHandoff && value.get() !== undefined) {
|
|
3667
|
+
const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
|
|
3668
|
+
if (finalKeyframe !== undefined) {
|
|
3669
|
+
frame.update(() => {
|
|
3670
|
+
options.onUpdate(finalKeyframe);
|
|
3671
|
+
options.onComplete();
|
|
3672
|
+
});
|
|
3673
|
+
return;
|
|
3686
3674
|
}
|
|
3687
|
-
const delta = Math.min(this.updatedAt - this.prevUpdatedAt, MAX_VELOCITY_DELTA);
|
|
3688
|
-
// Casts because of parseFloat's poor typing
|
|
3689
|
-
return velocityPerSecond(parseFloat(this.current) -
|
|
3690
|
-
parseFloat(this.prevFrameValue), delta);
|
|
3691
3675
|
}
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3676
|
+
return valueTransition.isSync
|
|
3677
|
+
? new JSAnimation(options)
|
|
3678
|
+
: new AsyncMotionValueAnimation(options);
|
|
3679
|
+
};
|
|
3680
|
+
|
|
3681
|
+
const MIN_LAYOUT_DISTANCE = 20;
|
|
3682
|
+
function bezierPoint(t, origin, control, target) {
|
|
3683
|
+
const inv = 1 - t;
|
|
3684
|
+
return inv * inv * origin + 2 * inv * t * control + t * t * target;
|
|
3685
|
+
}
|
|
3686
|
+
function bezierTangentAngle(t, originX, controlX, targetX, originY, controlY, targetY) {
|
|
3687
|
+
const dx = 2 * (1 - t) * (controlX - originX) + 2 * t * (targetX - controlX);
|
|
3688
|
+
const dy = 2 * (1 - t) * (controlY - originY) + 2 * t * (targetY - controlY);
|
|
3689
|
+
return Math.atan2(dy, dx) * (180 / Math.PI);
|
|
3690
|
+
}
|
|
3691
|
+
function computeArcControlPoint(fromX, fromY, toX, toY, strength, peak) {
|
|
3692
|
+
const deltaX = toX - fromX;
|
|
3693
|
+
const deltaY = toY - fromY;
|
|
3694
|
+
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
3695
|
+
if (distance > 0) {
|
|
3696
|
+
const normalPerpX = -deltaY / distance;
|
|
3697
|
+
const normalPerpY = deltaX / distance;
|
|
3698
|
+
const desiredHeight = strength * distance;
|
|
3699
|
+
return {
|
|
3700
|
+
x: fromX + deltaX * peak + normalPerpX * desiredHeight,
|
|
3701
|
+
y: fromY + deltaY * peak + normalPerpY * desiredHeight,
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
return { x: fromX, y: fromY };
|
|
3705
|
+
}
|
|
3706
|
+
/**
|
|
3707
|
+
* The pure sampling factory: `(from, to) => (t) => point`. Internal —
|
|
3708
|
+
* used by {@link arc} and the unit tests. Not part of the public surface.
|
|
3709
|
+
*/
|
|
3710
|
+
function createArcPath({ strength = 0.5, peak = 0.5, direction, rotate = false, } = {}) {
|
|
3711
|
+
const rotationScale = rotate === true ? 1 : typeof rotate === "number" ? rotate : 0;
|
|
3712
|
+
// Auto-direction only: persists across calls to flip the bulge back
|
|
3713
|
+
// onto the same screen side when the dominant axis changes between
|
|
3714
|
+
// calls. Reuse the factory (module scope / useMemo) to keep this alive.
|
|
3715
|
+
let prevBulgeSign;
|
|
3716
|
+
const createInterpolator = (from, to) => {
|
|
3717
|
+
const dx = to.x - from.x;
|
|
3718
|
+
const dy = to.y - from.y;
|
|
3719
|
+
let signed;
|
|
3720
|
+
if (direction === "cw") {
|
|
3721
|
+
signed = -strength;
|
|
3722
|
+
}
|
|
3723
|
+
else if (direction === "ccw") {
|
|
3724
|
+
signed = strength;
|
|
3725
|
+
}
|
|
3726
|
+
else {
|
|
3727
|
+
const dom = Math.abs(dx) >= Math.abs(dy) ? dx : dy;
|
|
3728
|
+
signed = dom < 0 ? -strength : strength;
|
|
3729
|
+
}
|
|
3730
|
+
let control = computeArcControlPoint(from.x, from.y, to.x, to.y, signed, peak);
|
|
3731
|
+
if (direction === undefined) {
|
|
3732
|
+
const isVertical = Math.abs(dx) < Math.abs(dy);
|
|
3733
|
+
const midX = from.x + dx * peak;
|
|
3734
|
+
const midY = from.y + dy * peak;
|
|
3735
|
+
const bulgeSign = isVertical
|
|
3736
|
+
? Math.sign(control.x - midX)
|
|
3737
|
+
: Math.sign(control.y - midY);
|
|
3738
|
+
if (prevBulgeSign !== undefined &&
|
|
3739
|
+
bulgeSign !== 0 &&
|
|
3740
|
+
bulgeSign !== prevBulgeSign) {
|
|
3741
|
+
signed = -signed;
|
|
3742
|
+
control = computeArcControlPoint(from.x, from.y, to.x, to.y, signed, peak);
|
|
3743
|
+
}
|
|
3744
|
+
else if (bulgeSign !== 0) {
|
|
3745
|
+
prevBulgeSign = bulgeSign;
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
const tangent0 = rotationScale
|
|
3749
|
+
? bezierTangentAngle(0, from.x, control.x, to.x, from.y, control.y, to.y)
|
|
3750
|
+
: 0;
|
|
3751
|
+
const tangent1 = rotationScale
|
|
3752
|
+
? bezierTangentAngle(1, from.x, control.x, to.x, from.y, control.y, to.y)
|
|
3753
|
+
: 0;
|
|
3754
|
+
const tangentDelta = rotationScale
|
|
3755
|
+
? wrap(-180, 180, tangent1 - tangent0)
|
|
3756
|
+
: 0;
|
|
3757
|
+
return (t) => {
|
|
3758
|
+
const out = {
|
|
3759
|
+
x: bezierPoint(t, from.x, control.x, to.x),
|
|
3760
|
+
y: bezierPoint(t, from.y, control.y, to.y),
|
|
3761
|
+
};
|
|
3762
|
+
if (rotationScale) {
|
|
3763
|
+
const raw = bezierTangentAngle(t, from.x, control.x, to.x, from.y, control.y, to.y);
|
|
3764
|
+
const baseline = tangent0 + tangentDelta * t;
|
|
3765
|
+
out.rotate = wrap(-180, 180, raw - baseline) * rotationScale;
|
|
3709
3766
|
}
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3767
|
+
return out;
|
|
3768
|
+
};
|
|
3769
|
+
};
|
|
3770
|
+
return createInterpolator;
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Creates a curved path for `transition.path`:
|
|
3774
|
+
*
|
|
3775
|
+
* ```ts
|
|
3776
|
+
* <motion.div animate={{ x: 200, y: 100 }} transition={{ path: arc() }} />
|
|
3777
|
+
* ```
|
|
3778
|
+
*
|
|
3779
|
+
* Reuse the returned value (module scope / useMemo / useRef) so its
|
|
3780
|
+
* continuity closure survives re-renders — a fresh `arc()` has no memory.
|
|
3781
|
+
*/
|
|
3782
|
+
function arc(options = {}) {
|
|
3783
|
+
const sample = createArcPath(options);
|
|
3784
|
+
const path = {
|
|
3785
|
+
interpolateProjection(delta) {
|
|
3786
|
+
// `from` is the current translate offset (carries any in-flight
|
|
3787
|
+
// displacement when interrupted); `to` is the new layout origin.
|
|
3788
|
+
// The distance floor avoids visible wobble on tiny shifts.
|
|
3789
|
+
const tx = delta.x.translate;
|
|
3790
|
+
const ty = delta.y.translate;
|
|
3791
|
+
if (Math.sqrt(tx * tx + ty * ty) < MIN_LAYOUT_DISTANCE) {
|
|
3792
|
+
return undefined;
|
|
3713
3793
|
}
|
|
3714
|
-
|
|
3715
|
-
}
|
|
3794
|
+
return sample({ x: tx, y: ty }, { x: 0, y: 0 });
|
|
3795
|
+
},
|
|
3796
|
+
animateVisualElement(visualElement, target, transition, delay, animations) {
|
|
3797
|
+
if (!("x" in target || "y" in target))
|
|
3798
|
+
return;
|
|
3799
|
+
const xValue = visualElement.getValue("x", visualElement.latestValues["x"] ?? 0);
|
|
3800
|
+
const yValue = visualElement.getValue("y", visualElement.latestValues["y"] ?? 0);
|
|
3801
|
+
const xRaw = target.x;
|
|
3802
|
+
const yRaw = target.y;
|
|
3803
|
+
const xFrom = (Array.isArray(xRaw) && xRaw[0] != null
|
|
3804
|
+
? xRaw[0]
|
|
3805
|
+
: xValue?.get()) ?? 0;
|
|
3806
|
+
const yFrom = (Array.isArray(yRaw) && yRaw[0] != null
|
|
3807
|
+
? yRaw[0]
|
|
3808
|
+
: yValue?.get()) ?? 0;
|
|
3809
|
+
const xTo = (Array.isArray(xRaw)
|
|
3810
|
+
? xRaw[xRaw.length - 1]
|
|
3811
|
+
: xRaw ?? xFrom);
|
|
3812
|
+
const yTo = (Array.isArray(yRaw)
|
|
3813
|
+
? yRaw[yRaw.length - 1]
|
|
3814
|
+
: yRaw ?? yFrom);
|
|
3815
|
+
// Interruption needs no flag: x/y already hold the displaced
|
|
3816
|
+
// mid-arc position, so xFrom/yFrom carry the continuity geometry.
|
|
3817
|
+
const interpolate = sample({ x: xFrom, y: yFrom }, { x: xTo, y: yTo });
|
|
3818
|
+
// Drive a dedicated `pathRotation` value (composed onto `rotate`
|
|
3819
|
+
// at the build sites) rather than `rotate` itself, so a
|
|
3820
|
+
// concurrent rotate animation composes and nothing accumulates
|
|
3821
|
+
// on interrupt.
|
|
3822
|
+
const pathRotationValue = interpolate(0).rotate !== undefined
|
|
3823
|
+
? visualElement.getValue("pathRotation", 0)
|
|
3824
|
+
: undefined;
|
|
3825
|
+
const pathTransition = {
|
|
3826
|
+
delay,
|
|
3827
|
+
...getValueTransition$1(transition || {}, "x"),
|
|
3828
|
+
};
|
|
3829
|
+
delete pathTransition.path;
|
|
3830
|
+
const progress = motionValue(0);
|
|
3831
|
+
progress.start(animateMotionValue("", progress, [0, 1000], {
|
|
3832
|
+
...pathTransition,
|
|
3833
|
+
isSync: true,
|
|
3834
|
+
velocity: 0,
|
|
3835
|
+
onUpdate: (latest) => {
|
|
3836
|
+
const point = interpolate(latest / 1000);
|
|
3837
|
+
xValue?.set(point.x);
|
|
3838
|
+
yValue?.set(point.y);
|
|
3839
|
+
if (pathRotationValue && point.rotate !== undefined) {
|
|
3840
|
+
pathRotationValue.set(point.rotate);
|
|
3841
|
+
}
|
|
3842
|
+
},
|
|
3843
|
+
onComplete: () => {
|
|
3844
|
+
xValue?.set(xTo);
|
|
3845
|
+
yValue?.set(yTo);
|
|
3846
|
+
pathRotationValue?.set(0);
|
|
3847
|
+
},
|
|
3848
|
+
// Interrupt/cancel must clear our additive contribution
|
|
3849
|
+
// so it can't linger on top of the user's `rotate`.
|
|
3850
|
+
onStop: () => pathRotationValue?.set(0),
|
|
3851
|
+
onCancel: () => pathRotationValue?.set(0),
|
|
3852
|
+
}));
|
|
3853
|
+
if (progress.animation)
|
|
3854
|
+
animations.push(progress.animation);
|
|
3855
|
+
delete target.x;
|
|
3856
|
+
delete target.y;
|
|
3857
|
+
},
|
|
3858
|
+
};
|
|
3859
|
+
return path;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
/**
|
|
3863
|
+
* Parse Framer's special CSS variable format into a CSS token and a fallback.
|
|
3864
|
+
*
|
|
3865
|
+
* ```
|
|
3866
|
+
* `var(--foo, #fff)` => [`--foo`, '#fff']
|
|
3867
|
+
* ```
|
|
3868
|
+
*
|
|
3869
|
+
* @param current
|
|
3870
|
+
*/
|
|
3871
|
+
const splitCSSVariableRegex =
|
|
3872
|
+
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
|
|
3873
|
+
/^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
|
|
3874
|
+
function parseCSSVariable(current) {
|
|
3875
|
+
const match = splitCSSVariableRegex.exec(current);
|
|
3876
|
+
if (!match)
|
|
3877
|
+
return [,];
|
|
3878
|
+
const [, token1, token2, fallback] = match;
|
|
3879
|
+
return [`--${token1 ?? token2}`, fallback];
|
|
3880
|
+
}
|
|
3881
|
+
const maxDepth = 4;
|
|
3882
|
+
function getVariableValue(current, element, depth = 1) {
|
|
3883
|
+
exports.invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`, "max-css-var-depth");
|
|
3884
|
+
const [token, fallback] = parseCSSVariable(current);
|
|
3885
|
+
// No CSS variable detected
|
|
3886
|
+
if (!token)
|
|
3887
|
+
return;
|
|
3888
|
+
// Attempt to read this CSS variable off the element
|
|
3889
|
+
const resolved = window.getComputedStyle(element).getPropertyValue(token);
|
|
3890
|
+
if (resolved) {
|
|
3891
|
+
const trimmed = resolved.trim();
|
|
3892
|
+
return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
|
|
3716
3893
|
}
|
|
3894
|
+
return isCSSVariableToken(fallback)
|
|
3895
|
+
? getVariableValue(fallback, element, depth + 1)
|
|
3896
|
+
: fallback;
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
function getValueState(visualElement) {
|
|
3900
|
+
const state = [{}, {}];
|
|
3901
|
+
visualElement?.values.forEach((value, key) => {
|
|
3902
|
+
state[0][key] = value.get();
|
|
3903
|
+
state[1][key] = value.getVelocity();
|
|
3904
|
+
});
|
|
3905
|
+
return state;
|
|
3906
|
+
}
|
|
3907
|
+
function resolveVariantFromProps(props, definition, custom, visualElement) {
|
|
3717
3908
|
/**
|
|
3718
|
-
*
|
|
3719
|
-
*
|
|
3720
|
-
* @public
|
|
3909
|
+
* If the variant definition is a function, resolve.
|
|
3721
3910
|
*/
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
if (this.events.animationCancel) {
|
|
3726
|
-
this.events.animationCancel.notify();
|
|
3727
|
-
}
|
|
3728
|
-
}
|
|
3729
|
-
this.clearAnimation();
|
|
3911
|
+
if (typeof definition === "function") {
|
|
3912
|
+
const [current, velocity] = getValueState(visualElement);
|
|
3913
|
+
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
3730
3914
|
}
|
|
3731
3915
|
/**
|
|
3732
|
-
*
|
|
3733
|
-
*
|
|
3734
|
-
* @public
|
|
3916
|
+
* If the variant definition is a variant label, or
|
|
3917
|
+
* the function returned a variant label, resolve.
|
|
3735
3918
|
*/
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
}
|
|
3739
|
-
clearAnimation() {
|
|
3740
|
-
delete this.animation;
|
|
3919
|
+
if (typeof definition === "string") {
|
|
3920
|
+
definition = props.variants && props.variants[definition];
|
|
3741
3921
|
}
|
|
3742
3922
|
/**
|
|
3743
|
-
*
|
|
3744
|
-
*
|
|
3745
|
-
*
|
|
3746
|
-
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
3747
|
-
* created a `MotionValue` via the `motionValue` function.
|
|
3748
|
-
*
|
|
3749
|
-
* @public
|
|
3923
|
+
* At this point we've resolved both functions and variant labels,
|
|
3924
|
+
* but the resolved variant label might itself have been a function.
|
|
3925
|
+
* If so, resolve. This can only have returned a valid target object.
|
|
3750
3926
|
*/
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
this.clearListeners();
|
|
3755
|
-
this.stop();
|
|
3756
|
-
if (this.stopPassiveEffect) {
|
|
3757
|
-
this.stopPassiveEffect();
|
|
3758
|
-
}
|
|
3927
|
+
if (typeof definition === "function") {
|
|
3928
|
+
const [current, velocity] = getValueState(visualElement);
|
|
3929
|
+
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
3759
3930
|
}
|
|
3931
|
+
return definition;
|
|
3760
3932
|
}
|
|
3761
|
-
|
|
3762
|
-
|
|
3933
|
+
|
|
3934
|
+
function resolveVariant(visualElement, definition, custom) {
|
|
3935
|
+
const props = visualElement.getProps();
|
|
3936
|
+
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
|
|
3763
3937
|
}
|
|
3764
3938
|
|
|
3939
|
+
const positionalKeys = new Set([
|
|
3940
|
+
"width",
|
|
3941
|
+
"height",
|
|
3942
|
+
"top",
|
|
3943
|
+
"left",
|
|
3944
|
+
"right",
|
|
3945
|
+
"bottom",
|
|
3946
|
+
...transformPropOrder,
|
|
3947
|
+
]);
|
|
3948
|
+
|
|
3765
3949
|
const isKeyframesTarget = (v) => {
|
|
3766
3950
|
return Array.isArray(v);
|
|
3767
3951
|
};
|
|
@@ -3843,12 +4027,18 @@
|
|
|
3843
4027
|
? resolveTransition(transition, defaultTransition)
|
|
3844
4028
|
: defaultTransition;
|
|
3845
4029
|
const reduceMotion = transition?.reduceMotion;
|
|
4030
|
+
const skipAnimations = transition?.skipAnimations;
|
|
3846
4031
|
if (transitionOverride)
|
|
3847
4032
|
transition = transitionOverride;
|
|
3848
4033
|
const animations = [];
|
|
3849
4034
|
const animationTypeState = type &&
|
|
3850
4035
|
visualElement.animationState &&
|
|
3851
4036
|
visualElement.animationState.getState()[type];
|
|
4037
|
+
const path = transition?.path;
|
|
4038
|
+
if (path) {
|
|
4039
|
+
// path mutates `target` to claim x/y; loop below skips them.
|
|
4040
|
+
path.animateVisualElement(visualElement, target, transition, delay, animations);
|
|
4041
|
+
}
|
|
3852
4042
|
for (const key in target) {
|
|
3853
4043
|
const value = visualElement.getValue(key, visualElement.latestValues[key] ?? null);
|
|
3854
4044
|
const valueTarget = target[key];
|
|
@@ -3861,6 +4051,8 @@
|
|
|
3861
4051
|
delay,
|
|
3862
4052
|
...getValueTransition$1(transition || {}, key),
|
|
3863
4053
|
};
|
|
4054
|
+
if (skipAnimations)
|
|
4055
|
+
valueTransition.skipAnimations = true;
|
|
3864
4056
|
/**
|
|
3865
4057
|
* If the value is already at the defined target, skip the animation.
|
|
3866
4058
|
* We still re-assert the value via frame.update to take precedence
|
|
@@ -4065,6 +4257,12 @@
|
|
|
4065
4257
|
|
|
4066
4258
|
const transformValueTypes = {
|
|
4067
4259
|
rotate: degrees,
|
|
4260
|
+
/**
|
|
4261
|
+
* Internal channel for `transition.path` orientToPath. Composed onto
|
|
4262
|
+
* `rotate` at the transform-build sites so the user's `rotate` is
|
|
4263
|
+
* never read or overwritten. Not part of `transformPropOrder`.
|
|
4264
|
+
*/
|
|
4265
|
+
pathRotation: degrees,
|
|
4068
4266
|
rotateX: degrees,
|
|
4069
4267
|
rotateY: degrees,
|
|
4070
4268
|
rotateZ: degrees,
|
|
@@ -4616,6 +4814,15 @@
|
|
|
4616
4814
|
transform += `${transformName}(${value}) `;
|
|
4617
4815
|
}
|
|
4618
4816
|
}
|
|
4817
|
+
// See build-transform.ts: additive `rotate()` so user `rotate` isn't
|
|
4818
|
+
// clobbered. Not a `transformPropOrder` slot.
|
|
4819
|
+
const pathRotation = state.latest.pathRotation;
|
|
4820
|
+
if (pathRotation) {
|
|
4821
|
+
transformIsDefault = false;
|
|
4822
|
+
transform += `rotate(${typeof pathRotation === "number"
|
|
4823
|
+
? `${pathRotation}deg`
|
|
4824
|
+
: pathRotation}) `;
|
|
4825
|
+
}
|
|
4619
4826
|
return transformIsDefault ? "none" : transform.trim();
|
|
4620
4827
|
}
|
|
4621
4828
|
|
|
@@ -6225,8 +6432,8 @@
|
|
|
6225
6432
|
removeOnChange();
|
|
6226
6433
|
if (removeSyncCheck)
|
|
6227
6434
|
removeSyncCheck();
|
|
6228
|
-
|
|
6229
|
-
|
|
6435
|
+
// Defer to MotionValue.on("change") auto-stop so React 19 remounts
|
|
6436
|
+
// can resubscribe before the animation is cancelled (#3315).
|
|
6230
6437
|
});
|
|
6231
6438
|
}
|
|
6232
6439
|
sortNodePosition(other) {
|
|
@@ -6768,6 +6975,14 @@
|
|
|
6768
6975
|
}
|
|
6769
6976
|
}
|
|
6770
6977
|
}
|
|
6978
|
+
// `pathRotation` composes onto `rotate` as a separate additive term so
|
|
6979
|
+
// the user's `rotate` is never clobbered. Deliberately not a slot in
|
|
6980
|
+
// `transformPropOrder`.
|
|
6981
|
+
const pathRotation = latestValues.pathRotation;
|
|
6982
|
+
if (pathRotation) {
|
|
6983
|
+
transformIsDefault = false;
|
|
6984
|
+
transformString += `rotate(${getValueAsType(pathRotation, numberValueTypes.pathRotation)}) `;
|
|
6985
|
+
}
|
|
6771
6986
|
transformString = transformString.trim();
|
|
6772
6987
|
// If we have a custom `transform` template, pass our transform values and
|
|
6773
6988
|
// generated transformString to that before returning
|
|
@@ -7470,7 +7685,8 @@
|
|
|
7470
7685
|
*/
|
|
7471
7686
|
let valueHasChanged = false;
|
|
7472
7687
|
if (isKeyframesTarget(next) && isKeyframesTarget(prev)) {
|
|
7473
|
-
valueHasChanged =
|
|
7688
|
+
valueHasChanged =
|
|
7689
|
+
!shallowCompare(next, prev) || variantDidChange;
|
|
7474
7690
|
}
|
|
7475
7691
|
else {
|
|
7476
7692
|
valueHasChanged = next !== prev;
|
|
@@ -7829,11 +8045,14 @@
|
|
|
7829
8045
|
transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
|
|
7830
8046
|
}
|
|
7831
8047
|
if (latestTransform) {
|
|
7832
|
-
const { transformPerspective, rotate, rotateX, rotateY, skewX, skewY } = latestTransform;
|
|
8048
|
+
const { transformPerspective, rotate, pathRotation, rotateX, rotateY, skewX, skewY, } = latestTransform;
|
|
7833
8049
|
if (transformPerspective)
|
|
7834
8050
|
transform = `perspective(${transformPerspective}px) ${transform}`;
|
|
7835
8051
|
if (rotate)
|
|
7836
8052
|
transform += `rotate(${rotate}deg) `;
|
|
8053
|
+
// Additive `rotate()` so user `rotate` isn't clobbered.
|
|
8054
|
+
if (pathRotation)
|
|
8055
|
+
transform += `rotate(${pathRotation}deg) `;
|
|
7837
8056
|
if (rotateX)
|
|
7838
8057
|
transform += `rotateX(${rotateX}deg) `;
|
|
7839
8058
|
if (rotateY)
|
|
@@ -8388,7 +8607,8 @@
|
|
|
8388
8607
|
* Set animation origin after starting animation to avoid layout jump
|
|
8389
8608
|
* caused by stopping previous layout animation
|
|
8390
8609
|
*/
|
|
8391
|
-
this.setAnimationOrigin(delta, hasOnlyRelativeTargetChanged
|
|
8610
|
+
this.setAnimationOrigin(delta, hasOnlyRelativeTargetChanged, animationOptions
|
|
8611
|
+
.path);
|
|
8392
8612
|
}
|
|
8393
8613
|
else {
|
|
8394
8614
|
/**
|
|
@@ -9104,7 +9324,7 @@
|
|
|
9104
9324
|
this.projectionDelta = createDelta();
|
|
9105
9325
|
this.projectionDeltaWithTransform = createDelta();
|
|
9106
9326
|
}
|
|
9107
|
-
setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false) {
|
|
9327
|
+
setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false, pathFn) {
|
|
9108
9328
|
const snapshot = this.snapshot;
|
|
9109
9329
|
const snapshotLatestValues = snapshot ? snapshot.latestValues : {};
|
|
9110
9330
|
const mixedValues = { ...this.latestValues };
|
|
@@ -9126,10 +9346,26 @@
|
|
|
9126
9346
|
!this.path.some(hasOpacityCrossfade));
|
|
9127
9347
|
this.animationProgress = 0;
|
|
9128
9348
|
let prevRelativeTarget;
|
|
9349
|
+
// The path decides whether the layout shift is worth curving
|
|
9350
|
+
// (distance floor) and resolves the interpolator from the delta.
|
|
9351
|
+
const interpolate = pathFn?.interpolateProjection(delta);
|
|
9129
9352
|
this.mixTargetDelta = (latest) => {
|
|
9130
9353
|
const progress = latest / 1000;
|
|
9131
|
-
|
|
9132
|
-
|
|
9354
|
+
const point = interpolate?.(progress);
|
|
9355
|
+
if (point) {
|
|
9356
|
+
targetDelta.x.translate = point.x;
|
|
9357
|
+
targetDelta.x.scale = mixNumber$1(delta.x.scale, 1, progress);
|
|
9358
|
+
targetDelta.x.origin = delta.x.origin;
|
|
9359
|
+
targetDelta.x.originPoint = delta.x.originPoint;
|
|
9360
|
+
targetDelta.y.translate = point.y;
|
|
9361
|
+
targetDelta.y.scale = mixNumber$1(delta.y.scale, 1, progress);
|
|
9362
|
+
targetDelta.y.origin = delta.y.origin;
|
|
9363
|
+
targetDelta.y.originPoint = delta.y.originPoint;
|
|
9364
|
+
}
|
|
9365
|
+
else {
|
|
9366
|
+
mixAxisDeltaLinear(targetDelta.x, delta.x, progress);
|
|
9367
|
+
mixAxisDeltaLinear(targetDelta.y, delta.y, progress);
|
|
9368
|
+
}
|
|
9133
9369
|
this.setTargetDelta(targetDelta);
|
|
9134
9370
|
if (this.relativeTarget &&
|
|
9135
9371
|
this.relativeTargetOrigin &&
|
|
@@ -9154,6 +9390,13 @@
|
|
|
9154
9390
|
this.animationValues = mixedValues;
|
|
9155
9391
|
mixValues(mixedValues, snapshotLatestValues, this.latestValues, progress, shouldCrossfadeOpacity, isOnlyMember);
|
|
9156
9392
|
}
|
|
9393
|
+
if (point && point.rotate !== undefined) {
|
|
9394
|
+
// Dedicated `pathRotation` channel, not `rotate`, so an
|
|
9395
|
+
// animating `rotate` is composed with, never clobbered.
|
|
9396
|
+
if (!this.animationValues)
|
|
9397
|
+
this.animationValues = mixedValues;
|
|
9398
|
+
this.animationValues.pathRotation = point.rotate;
|
|
9399
|
+
}
|
|
9157
9400
|
this.root.scheduleUpdateProjection();
|
|
9158
9401
|
this.scheduleRender();
|
|
9159
9402
|
this.animationProgress = progress;
|
|
@@ -9674,7 +9917,7 @@
|
|
|
9674
9917
|
function removeLeadSnapshots(stack) {
|
|
9675
9918
|
stack.removeLeadSnapshot();
|
|
9676
9919
|
}
|
|
9677
|
-
function
|
|
9920
|
+
function mixAxisDeltaLinear(output, delta, p) {
|
|
9678
9921
|
output.translate = mixNumber$1(delta.translate, 0, p);
|
|
9679
9922
|
output.scale = mixNumber$1(delta.scale, 1, p);
|
|
9680
9923
|
output.origin = delta.origin;
|
|
@@ -10075,8 +10318,8 @@
|
|
|
10075
10318
|
}
|
|
10076
10319
|
}
|
|
10077
10320
|
|
|
10078
|
-
function calculateRepeatDuration(duration, repeat,
|
|
10079
|
-
return duration * (repeat + 1);
|
|
10321
|
+
function calculateRepeatDuration(duration, repeat, repeatDelay) {
|
|
10322
|
+
return duration * (repeat + 1) + repeatDelay * repeat;
|
|
10080
10323
|
}
|
|
10081
10324
|
|
|
10082
10325
|
/**
|
|
@@ -10132,10 +10375,14 @@
|
|
|
10132
10375
|
* if we have original times of [0, 0.5, 1] then our repeated times will
|
|
10133
10376
|
* be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
|
|
10134
10377
|
* down to a 0-1 scale.
|
|
10378
|
+
*
|
|
10379
|
+
* `repeatDelayUnits` is the repeatDelay expressed in units of a single
|
|
10380
|
+
* iteration's duration, so the total span equals `(repeat + 1) + repeat * repeatDelayUnits`.
|
|
10135
10381
|
*/
|
|
10136
|
-
function normalizeTimes(times, repeat) {
|
|
10382
|
+
function normalizeTimes(times, repeat, repeatDelayUnits = 0) {
|
|
10383
|
+
const totalUnits = repeat + 1 + repeat * repeatDelayUnits;
|
|
10137
10384
|
for (let i = 0; i < times.length; i++) {
|
|
10138
|
-
times[i] = times[i] /
|
|
10385
|
+
times[i] = times[i] / totalUnits;
|
|
10139
10386
|
}
|
|
10140
10387
|
}
|
|
10141
10388
|
|
|
@@ -10256,25 +10503,73 @@
|
|
|
10256
10503
|
valueKeyframesAsList.length === 1 &&
|
|
10257
10504
|
valueKeyframesAsList.unshift(null);
|
|
10258
10505
|
/**
|
|
10259
|
-
*
|
|
10506
|
+
* Segments can't express `repeat: Infinity` or very large
|
|
10507
|
+
* counts — they'd leave dead time after the segment or
|
|
10508
|
+
* explode the keyframe array. Ignore with a warning.
|
|
10260
10509
|
*/
|
|
10261
10510
|
if (repeat) {
|
|
10262
|
-
exports.
|
|
10263
|
-
|
|
10511
|
+
exports.warning(repeat < MAX_REPEAT, `Sequence segments can't repeat ${repeat} times — ignoring repeat option. Use a value below ${MAX_REPEAT} or apply repeat at the sequence level instead.`);
|
|
10512
|
+
}
|
|
10513
|
+
if (repeat && repeat < MAX_REPEAT) {
|
|
10514
|
+
/**
|
|
10515
|
+
* Express repeatDelay in units of a single iteration's duration
|
|
10516
|
+
* so it can be added to the per-iteration time offsets below
|
|
10517
|
+
* before they're normalized to 0-1.
|
|
10518
|
+
*/
|
|
10519
|
+
const repeatDelayUnits = duration > 0 ? repeatDelay / duration : 0;
|
|
10520
|
+
duration = calculateRepeatDuration(duration, repeat, repeatDelay);
|
|
10264
10521
|
const originalKeyframes = [...valueKeyframesAsList];
|
|
10265
10522
|
const originalTimes = [...times];
|
|
10266
10523
|
ease = Array.isArray(ease) ? [...ease] : [ease];
|
|
10267
10524
|
const originalEase = [...ease];
|
|
10525
|
+
/**
|
|
10526
|
+
* For reverse/mirror, alternate iterations play the segment
|
|
10527
|
+
* backwards. mirror matches JSAnimation's mirroredGenerator:
|
|
10528
|
+
* reversed keyframes, easings unchanged. reverse matches
|
|
10529
|
+
* JSAnimation's iterationProgress = 1 - p: reversed
|
|
10530
|
+
* keyframes, easing array reversed AND each function easing
|
|
10531
|
+
* mapped through reverseEasing (string easings unchanged —
|
|
10532
|
+
* they're resolved later by the keyframes engine).
|
|
10533
|
+
*/
|
|
10534
|
+
const isFlipping = repeatType === "reverse" || repeatType === "mirror";
|
|
10535
|
+
let flippedKeyframes = originalKeyframes;
|
|
10536
|
+
let flippedEases = originalEase;
|
|
10537
|
+
if (isFlipping) {
|
|
10538
|
+
flippedKeyframes = [...originalKeyframes].reverse();
|
|
10539
|
+
if (repeatType === "reverse") {
|
|
10540
|
+
flippedEases = [...originalEase]
|
|
10541
|
+
.reverse()
|
|
10542
|
+
.map((e) => typeof e === "function"
|
|
10543
|
+
? reverseEasing(e)
|
|
10544
|
+
: e);
|
|
10545
|
+
}
|
|
10546
|
+
}
|
|
10268
10547
|
for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10548
|
+
const isFlipped = isFlipping && repeatIndex % 2 === 0;
|
|
10549
|
+
const iterKeyframes = isFlipped
|
|
10550
|
+
? flippedKeyframes
|
|
10551
|
+
: originalKeyframes;
|
|
10552
|
+
const iterEase = isFlipped ? flippedEases : originalEase;
|
|
10553
|
+
const iterStartOffset = (repeatIndex + 1) * (1 + repeatDelayUnits);
|
|
10554
|
+
/**
|
|
10555
|
+
* If repeatDelay is set, hold the previous iteration's
|
|
10556
|
+
* final value through the delay by inserting a keyframe
|
|
10557
|
+
* at the moment the next iteration begins.
|
|
10558
|
+
*/
|
|
10559
|
+
if (repeatDelayUnits > 0) {
|
|
10560
|
+
valueKeyframesAsList.push(valueKeyframesAsList[valueKeyframesAsList.length - 1]);
|
|
10561
|
+
times.push(iterStartOffset);
|
|
10562
|
+
ease.push("linear");
|
|
10563
|
+
}
|
|
10564
|
+
valueKeyframesAsList.push(...iterKeyframes);
|
|
10565
|
+
for (let keyframeIndex = 0; keyframeIndex < iterKeyframes.length; keyframeIndex++) {
|
|
10566
|
+
times.push(originalTimes[keyframeIndex] + iterStartOffset);
|
|
10272
10567
|
ease.push(keyframeIndex === 0
|
|
10273
10568
|
? "linear"
|
|
10274
|
-
: getEasingForSegment(
|
|
10569
|
+
: getEasingForSegment(iterEase, keyframeIndex - 1));
|
|
10275
10570
|
}
|
|
10276
10571
|
}
|
|
10277
|
-
normalizeTimes(times, repeat);
|
|
10572
|
+
normalizeTimes(times, repeat, repeatDelayUnits);
|
|
10278
10573
|
}
|
|
10279
10574
|
const targetTime = startTime + duration;
|
|
10280
10575
|
/**
|
|
@@ -10522,21 +10817,24 @@
|
|
|
10522
10817
|
* to a specific element.
|
|
10523
10818
|
*/
|
|
10524
10819
|
function createScopedAnimate(options = {}) {
|
|
10525
|
-
const { scope, reduceMotion } = options;
|
|
10820
|
+
const { scope, reduceMotion, skipAnimations } = options;
|
|
10526
10821
|
/**
|
|
10527
10822
|
* Implementation
|
|
10528
10823
|
*/
|
|
10529
10824
|
function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
|
|
10530
10825
|
let animations = [];
|
|
10531
10826
|
let animationOnComplete;
|
|
10827
|
+
const inherited = {};
|
|
10828
|
+
if (reduceMotion !== undefined)
|
|
10829
|
+
inherited.reduceMotion = reduceMotion;
|
|
10830
|
+
if (skipAnimations !== undefined)
|
|
10831
|
+
inherited.skipAnimations = skipAnimations;
|
|
10532
10832
|
if (isSequence(subjectOrSequence)) {
|
|
10533
10833
|
const { onComplete, ...sequenceOptions } = optionsOrKeyframes || {};
|
|
10534
10834
|
if (typeof onComplete === "function") {
|
|
10535
10835
|
animationOnComplete = onComplete;
|
|
10536
10836
|
}
|
|
10537
|
-
animations = animateSequence(subjectOrSequence,
|
|
10538
|
-
? { reduceMotion, ...sequenceOptions }
|
|
10539
|
-
: sequenceOptions, scope);
|
|
10837
|
+
animations = animateSequence(subjectOrSequence, { ...inherited, ...sequenceOptions }, scope);
|
|
10540
10838
|
}
|
|
10541
10839
|
else {
|
|
10542
10840
|
// Extract top-level onComplete so it doesn't get applied per-value
|
|
@@ -10544,9 +10842,7 @@
|
|
|
10544
10842
|
if (typeof onComplete === "function") {
|
|
10545
10843
|
animationOnComplete = onComplete;
|
|
10546
10844
|
}
|
|
10547
|
-
animations = animateSubject(subjectOrSequence, optionsOrKeyframes,
|
|
10548
|
-
? { reduceMotion, ...rest }
|
|
10549
|
-
: rest), scope);
|
|
10845
|
+
animations = animateSubject(subjectOrSequence, optionsOrKeyframes, { ...inherited, ...rest }, scope);
|
|
10550
10846
|
}
|
|
10551
10847
|
const animation = new GroupAnimationWithThen(animations);
|
|
10552
10848
|
if (animationOnComplete) {
|
|
@@ -10948,10 +11244,16 @@
|
|
|
10948
11244
|
info.y.containerLength = container.clientHeight;
|
|
10949
11245
|
/**
|
|
10950
11246
|
* In development mode ensure scroll containers aren't position: static as this makes
|
|
10951
|
-
* it difficult to measure their relative positions.
|
|
11247
|
+
* it difficult to measure their relative positions. The document scrolling element
|
|
11248
|
+
* is exempt: offsetParent measurements naturally resolve relative to the document.
|
|
10952
11249
|
*/
|
|
10953
11250
|
{
|
|
10954
|
-
if (container &&
|
|
11251
|
+
if (container &&
|
|
11252
|
+
target &&
|
|
11253
|
+
target !== container &&
|
|
11254
|
+
container !== document.documentElement &&
|
|
11255
|
+
container !== document.scrollingElement &&
|
|
11256
|
+
container !== document.body) {
|
|
10955
11257
|
warnOnce(getComputedStyle(container).position !== "static", "Please ensure that the container has a non-static position, like 'relative', 'fixed', or 'absolute' to ensure scroll offset is calculated correctly.");
|
|
10956
11258
|
}
|
|
10957
11259
|
}
|
|
@@ -11233,6 +11535,14 @@
|
|
|
11233
11535
|
});
|
|
11234
11536
|
}
|
|
11235
11537
|
|
|
11538
|
+
/**
|
|
11539
|
+
* Currently, we only support element tracking with `scrollInfo`, though in
|
|
11540
|
+
* the future we can also offer ViewTimeline support.
|
|
11541
|
+
*/
|
|
11542
|
+
function isElementTracking(options) {
|
|
11543
|
+
return options && (options.target || options.offset);
|
|
11544
|
+
}
|
|
11545
|
+
|
|
11236
11546
|
/**
|
|
11237
11547
|
* If the onScroll function has two arguments, it's expecting
|
|
11238
11548
|
* more specific information about the scroll from scrollInfo.
|
|
@@ -11241,7 +11551,7 @@
|
|
|
11241
11551
|
return onScroll.length === 2;
|
|
11242
11552
|
}
|
|
11243
11553
|
function attachToFunction(onScroll, options) {
|
|
11244
|
-
if (isOnScrollWithInfo(onScroll)) {
|
|
11554
|
+
if (isOnScrollWithInfo(onScroll) || isElementTracking(options)) {
|
|
11245
11555
|
return scrollInfo((info) => {
|
|
11246
11556
|
onScroll(info[options.axis].progress, info);
|
|
11247
11557
|
}, options);
|
|
@@ -11359,6 +11669,7 @@
|
|
|
11359
11669
|
exports.applyPointDelta = applyPointDelta;
|
|
11360
11670
|
exports.applyPxDefaults = applyPxDefaults;
|
|
11361
11671
|
exports.applyTreeDeltas = applyTreeDeltas;
|
|
11672
|
+
exports.arc = arc;
|
|
11362
11673
|
exports.aspectRatio = aspectRatio;
|
|
11363
11674
|
exports.attachFollow = attachFollow;
|
|
11364
11675
|
exports.attachSpring = attachSpring;
|