motion 13.4.6 → 13.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/motion.dev.js +822 -928
- package/dist/motion.js +1 -1
- package/package.json +3 -3
package/dist/motion.dev.js
CHANGED
|
@@ -40,8 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
exports.warning = () => { };
|
|
42
42
|
exports.invariant = () => { };
|
|
43
|
-
if (typeof process !== "undefined" &&
|
|
44
|
-
process.env?.NODE_ENV !== "production") {
|
|
43
|
+
if (typeof process !== "undefined" && "development" !== "production") {
|
|
45
44
|
exports.warning = (check, message, errorCode) => {
|
|
46
45
|
if (!check && typeof console !== "undefined") {
|
|
47
46
|
console.warn(formatErrorMessage(message, errorCode));
|
|
@@ -310,170 +309,15 @@
|
|
|
310
309
|
return definition;
|
|
311
310
|
};
|
|
312
311
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
];
|
|
323
|
-
|
|
324
|
-
function createRenderStep(runNextFrame) {
|
|
325
|
-
/**
|
|
326
|
-
* We create and reuse two queues, one to queue jobs for the current frame
|
|
327
|
-
* and one for the next. We reuse to avoid triggering GC after x frames.
|
|
328
|
-
*/
|
|
329
|
-
let thisFrame = new Set();
|
|
330
|
-
let nextFrame = new Set();
|
|
331
|
-
/**
|
|
332
|
-
* Track whether we're currently processing jobs in this step. This way
|
|
333
|
-
* we can decide whether to schedule new jobs for this frame or next.
|
|
334
|
-
*/
|
|
335
|
-
let isProcessing = false;
|
|
336
|
-
let flushNextFrame = false;
|
|
337
|
-
/**
|
|
338
|
-
* A set of processes which were marked keepAlive when scheduled.
|
|
339
|
-
* A keepAlive process is always also held by a frame queue until
|
|
340
|
-
* it's cancelled, so a Set has the same lifetime semantics as a
|
|
341
|
-
* WeakSet here while being considerably faster to query every frame.
|
|
342
|
-
*/
|
|
343
|
-
const toKeepAlive = new Set();
|
|
344
|
-
let latestFrameData = {
|
|
345
|
-
delta: 0.0,
|
|
346
|
-
timestamp: 0.0,
|
|
347
|
-
isProcessing: false,
|
|
348
|
-
};
|
|
349
|
-
function triggerCallback(callback) {
|
|
350
|
-
if (toKeepAlive.has(callback)) {
|
|
351
|
-
nextFrame.add(callback);
|
|
352
|
-
runNextFrame();
|
|
353
|
-
}
|
|
354
|
-
callback(latestFrameData);
|
|
355
|
-
}
|
|
356
|
-
const step = {
|
|
357
|
-
/**
|
|
358
|
-
* Schedule a process to run on the next frame.
|
|
359
|
-
*/
|
|
360
|
-
schedule: (callback, keepAlive = false, immediate = false) => {
|
|
361
|
-
const addToCurrentFrame = immediate && isProcessing;
|
|
362
|
-
const queue = addToCurrentFrame ? thisFrame : nextFrame;
|
|
363
|
-
if (keepAlive)
|
|
364
|
-
toKeepAlive.add(callback);
|
|
365
|
-
queue.add(callback);
|
|
366
|
-
return callback;
|
|
367
|
-
},
|
|
368
|
-
/**
|
|
369
|
-
* Cancel the provided callback from running on the next frame.
|
|
370
|
-
*/
|
|
371
|
-
cancel: (callback) => {
|
|
372
|
-
nextFrame.delete(callback);
|
|
373
|
-
toKeepAlive.delete(callback);
|
|
374
|
-
},
|
|
375
|
-
/**
|
|
376
|
-
* Execute all schedule callbacks.
|
|
377
|
-
*/
|
|
378
|
-
process: (frameData) => {
|
|
379
|
-
latestFrameData = frameData;
|
|
380
|
-
/**
|
|
381
|
-
* If we're already processing we've probably been triggered by a flushSync
|
|
382
|
-
* inside an existing process. Instead of executing, mark flushNextFrame
|
|
383
|
-
* as true and ensure we flush the following frame at the end of this one.
|
|
384
|
-
*/
|
|
385
|
-
if (isProcessing) {
|
|
386
|
-
flushNextFrame = true;
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
isProcessing = true;
|
|
390
|
-
// Swap this frame and the next to avoid GC
|
|
391
|
-
const prevFrame = thisFrame;
|
|
392
|
-
thisFrame = nextFrame;
|
|
393
|
-
nextFrame = prevFrame;
|
|
394
|
-
// Execute this frame
|
|
395
|
-
thisFrame.forEach(triggerCallback);
|
|
396
|
-
// Clear the frame so no callbacks remain. This is to avoid
|
|
397
|
-
// memory leaks should this render step not run for a while.
|
|
398
|
-
thisFrame.clear();
|
|
399
|
-
isProcessing = false;
|
|
400
|
-
if (flushNextFrame) {
|
|
401
|
-
flushNextFrame = false;
|
|
402
|
-
step.process(frameData);
|
|
403
|
-
}
|
|
404
|
-
},
|
|
405
|
-
};
|
|
406
|
-
return step;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const maxElapsed$1 = 40;
|
|
410
|
-
function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
|
|
411
|
-
let runNextFrame = false;
|
|
412
|
-
let useDefaultElapsed = true;
|
|
413
|
-
const state = {
|
|
414
|
-
delta: 0.0,
|
|
415
|
-
timestamp: 0.0,
|
|
416
|
-
isProcessing: false,
|
|
417
|
-
};
|
|
418
|
-
const flagRunNextFrame = () => (runNextFrame = true);
|
|
419
|
-
const steps = stepsOrder.reduce((acc, key) => {
|
|
420
|
-
acc[key] = createRenderStep(flagRunNextFrame);
|
|
421
|
-
return acc;
|
|
422
|
-
}, {});
|
|
423
|
-
const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
|
|
424
|
-
const processBatch = () => {
|
|
425
|
-
const useManualTiming = MotionGlobalConfig.useManualTiming;
|
|
426
|
-
const timestamp = useManualTiming
|
|
427
|
-
? state.timestamp
|
|
428
|
-
: performance.now();
|
|
429
|
-
runNextFrame = false;
|
|
430
|
-
if (!useManualTiming) {
|
|
431
|
-
state.delta = useDefaultElapsed
|
|
432
|
-
? 1000 / 60
|
|
433
|
-
: Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1);
|
|
434
|
-
}
|
|
435
|
-
state.timestamp = timestamp;
|
|
436
|
-
state.isProcessing = true;
|
|
437
|
-
// Unrolled render loop for better per-frame performance
|
|
438
|
-
setup.process(state);
|
|
439
|
-
read.process(state);
|
|
440
|
-
resolveKeyframes.process(state);
|
|
441
|
-
preUpdate.process(state);
|
|
442
|
-
update.process(state);
|
|
443
|
-
preRender.process(state);
|
|
444
|
-
render.process(state);
|
|
445
|
-
postRender.process(state);
|
|
446
|
-
state.isProcessing = false;
|
|
447
|
-
if (runNextFrame && allowKeepAlive) {
|
|
448
|
-
useDefaultElapsed = false;
|
|
449
|
-
scheduleNextBatch(processBatch);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
const wake = () => {
|
|
453
|
-
runNextFrame = true;
|
|
454
|
-
useDefaultElapsed = true;
|
|
455
|
-
if (!state.isProcessing) {
|
|
456
|
-
scheduleNextBatch(processBatch);
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
const schedule = stepsOrder.reduce((acc, key) => {
|
|
460
|
-
const step = steps[key];
|
|
461
|
-
acc[key] = (process, keepAlive = false, immediate = false) => {
|
|
462
|
-
if (!runNextFrame)
|
|
463
|
-
wake();
|
|
464
|
-
return step.schedule(process, keepAlive, immediate);
|
|
465
|
-
};
|
|
466
|
-
return acc;
|
|
467
|
-
}, {});
|
|
468
|
-
const cancel = (process) => {
|
|
469
|
-
for (let i = 0; i < stepsOrder.length; i++) {
|
|
470
|
-
steps[stepsOrder[i]].cancel(process);
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
return { schedule, cancel, state, steps };
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
|
|
312
|
+
/**
|
|
313
|
+
* The frameloop's state, kept apart from the frameloop itself so that
|
|
314
|
+
* reading it doesn't bundle the scheduler.
|
|
315
|
+
*/
|
|
316
|
+
const frameData = {
|
|
317
|
+
delta: 0.0,
|
|
318
|
+
timestamp: 0.0,
|
|
319
|
+
isProcessing: false,
|
|
320
|
+
};
|
|
477
321
|
|
|
478
322
|
let now;
|
|
479
323
|
function clearTime() {
|
|
@@ -1057,6 +901,170 @@
|
|
|
1057
901
|
return mixer(from, to);
|
|
1058
902
|
}
|
|
1059
903
|
|
|
904
|
+
const stepsOrder = [
|
|
905
|
+
"setup", // Compute
|
|
906
|
+
"read", // Read
|
|
907
|
+
"resolveKeyframes", // Write/Read/Write/Read
|
|
908
|
+
"preUpdate", // Compute
|
|
909
|
+
"update", // Compute
|
|
910
|
+
"preRender", // Compute
|
|
911
|
+
"render", // Write
|
|
912
|
+
"postRender", // Compute
|
|
913
|
+
];
|
|
914
|
+
|
|
915
|
+
function createRenderStep(runNextFrame) {
|
|
916
|
+
/**
|
|
917
|
+
* We create and reuse two queues, one to queue jobs for the current frame
|
|
918
|
+
* and one for the next. We reuse to avoid triggering GC after x frames.
|
|
919
|
+
*/
|
|
920
|
+
let thisFrame = new Set();
|
|
921
|
+
let nextFrame = new Set();
|
|
922
|
+
/**
|
|
923
|
+
* Track whether we're currently processing jobs in this step. This way
|
|
924
|
+
* we can decide whether to schedule new jobs for this frame or next.
|
|
925
|
+
*/
|
|
926
|
+
let isProcessing = false;
|
|
927
|
+
let flushNextFrame = false;
|
|
928
|
+
/**
|
|
929
|
+
* A set of processes which were marked keepAlive when scheduled.
|
|
930
|
+
* A keepAlive process is always also held by a frame queue until
|
|
931
|
+
* it's cancelled, so a Set has the same lifetime semantics as a
|
|
932
|
+
* WeakSet here while being considerably faster to query every frame.
|
|
933
|
+
*/
|
|
934
|
+
const toKeepAlive = new Set();
|
|
935
|
+
let latestFrameData = {
|
|
936
|
+
delta: 0.0,
|
|
937
|
+
timestamp: 0.0,
|
|
938
|
+
isProcessing: false,
|
|
939
|
+
};
|
|
940
|
+
function triggerCallback(callback) {
|
|
941
|
+
if (toKeepAlive.has(callback)) {
|
|
942
|
+
nextFrame.add(callback);
|
|
943
|
+
runNextFrame();
|
|
944
|
+
}
|
|
945
|
+
callback(latestFrameData);
|
|
946
|
+
}
|
|
947
|
+
const step = {
|
|
948
|
+
/**
|
|
949
|
+
* Schedule a process to run on the next frame.
|
|
950
|
+
*/
|
|
951
|
+
schedule: (callback, keepAlive = false, immediate = false) => {
|
|
952
|
+
const addToCurrentFrame = immediate && isProcessing;
|
|
953
|
+
const queue = addToCurrentFrame ? thisFrame : nextFrame;
|
|
954
|
+
if (keepAlive)
|
|
955
|
+
toKeepAlive.add(callback);
|
|
956
|
+
queue.add(callback);
|
|
957
|
+
return callback;
|
|
958
|
+
},
|
|
959
|
+
/**
|
|
960
|
+
* Cancel the provided callback from running on the next frame.
|
|
961
|
+
*/
|
|
962
|
+
cancel: (callback) => {
|
|
963
|
+
nextFrame.delete(callback);
|
|
964
|
+
toKeepAlive.delete(callback);
|
|
965
|
+
},
|
|
966
|
+
/**
|
|
967
|
+
* Execute all schedule callbacks.
|
|
968
|
+
*/
|
|
969
|
+
process: (frameData) => {
|
|
970
|
+
latestFrameData = frameData;
|
|
971
|
+
/**
|
|
972
|
+
* If we're already processing we've probably been triggered by a flushSync
|
|
973
|
+
* inside an existing process. Instead of executing, mark flushNextFrame
|
|
974
|
+
* as true and ensure we flush the following frame at the end of this one.
|
|
975
|
+
*/
|
|
976
|
+
if (isProcessing) {
|
|
977
|
+
flushNextFrame = true;
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
isProcessing = true;
|
|
981
|
+
// Swap this frame and the next to avoid GC
|
|
982
|
+
const prevFrame = thisFrame;
|
|
983
|
+
thisFrame = nextFrame;
|
|
984
|
+
nextFrame = prevFrame;
|
|
985
|
+
// Execute this frame
|
|
986
|
+
thisFrame.forEach(triggerCallback);
|
|
987
|
+
// Clear the frame so no callbacks remain. This is to avoid
|
|
988
|
+
// memory leaks should this render step not run for a while.
|
|
989
|
+
thisFrame.clear();
|
|
990
|
+
isProcessing = false;
|
|
991
|
+
if (flushNextFrame) {
|
|
992
|
+
flushNextFrame = false;
|
|
993
|
+
step.process(frameData);
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
};
|
|
997
|
+
return step;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const maxElapsed$1 = 40;
|
|
1001
|
+
function createRenderBatcher(scheduleNextBatch, allowKeepAlive, state = {
|
|
1002
|
+
delta: 0.0,
|
|
1003
|
+
timestamp: 0.0,
|
|
1004
|
+
isProcessing: false,
|
|
1005
|
+
}) {
|
|
1006
|
+
let runNextFrame = false;
|
|
1007
|
+
let useDefaultElapsed = true;
|
|
1008
|
+
const flagRunNextFrame = () => (runNextFrame = true);
|
|
1009
|
+
const steps = stepsOrder.reduce((acc, key) => {
|
|
1010
|
+
acc[key] = createRenderStep(flagRunNextFrame);
|
|
1011
|
+
return acc;
|
|
1012
|
+
}, {});
|
|
1013
|
+
const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
|
|
1014
|
+
const processBatch = () => {
|
|
1015
|
+
const useManualTiming = MotionGlobalConfig.useManualTiming;
|
|
1016
|
+
const timestamp = useManualTiming
|
|
1017
|
+
? state.timestamp
|
|
1018
|
+
: performance.now();
|
|
1019
|
+
runNextFrame = false;
|
|
1020
|
+
if (!useManualTiming) {
|
|
1021
|
+
state.delta = useDefaultElapsed
|
|
1022
|
+
? 1000 / 60
|
|
1023
|
+
: Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1);
|
|
1024
|
+
}
|
|
1025
|
+
state.timestamp = timestamp;
|
|
1026
|
+
state.isProcessing = true;
|
|
1027
|
+
// Unrolled render loop for better per-frame performance
|
|
1028
|
+
setup.process(state);
|
|
1029
|
+
read.process(state);
|
|
1030
|
+
resolveKeyframes.process(state);
|
|
1031
|
+
preUpdate.process(state);
|
|
1032
|
+
update.process(state);
|
|
1033
|
+
preRender.process(state);
|
|
1034
|
+
render.process(state);
|
|
1035
|
+
postRender.process(state);
|
|
1036
|
+
state.isProcessing = false;
|
|
1037
|
+
if (runNextFrame && allowKeepAlive) {
|
|
1038
|
+
useDefaultElapsed = false;
|
|
1039
|
+
scheduleNextBatch(processBatch);
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
const wake = () => {
|
|
1043
|
+
runNextFrame = true;
|
|
1044
|
+
useDefaultElapsed = true;
|
|
1045
|
+
if (!state.isProcessing) {
|
|
1046
|
+
scheduleNextBatch(processBatch);
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
const schedule = stepsOrder.reduce((acc, key) => {
|
|
1050
|
+
const step = steps[key];
|
|
1051
|
+
acc[key] = (process, keepAlive = false, immediate = false) => {
|
|
1052
|
+
if (!runNextFrame)
|
|
1053
|
+
wake();
|
|
1054
|
+
return step.schedule(process, keepAlive, immediate);
|
|
1055
|
+
};
|
|
1056
|
+
return acc;
|
|
1057
|
+
}, {});
|
|
1058
|
+
const cancel = (process) => {
|
|
1059
|
+
for (let i = 0; i < stepsOrder.length; i++) {
|
|
1060
|
+
steps[stepsOrder[i]].cancel(process);
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
return { schedule, cancel, state, steps };
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const { schedule: frame, cancel: cancelFrame, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true, frameData);
|
|
1067
|
+
|
|
1060
1068
|
const frameloopDriver = (update) => {
|
|
1061
1069
|
const passTimestamp = ({ timestamp }) => update(timestamp);
|
|
1062
1070
|
return {
|
|
@@ -1135,8 +1143,37 @@
|
|
|
1135
1143
|
minDuration: 0.01, // in seconds
|
|
1136
1144
|
maxDuration: 10.0, // in seconds
|
|
1137
1145
|
minDamping: 0.05,
|
|
1138
|
-
maxDamping: 1,
|
|
1139
1146
|
};
|
|
1147
|
+
/**
|
|
1148
|
+
* Maps bounce to a damping ratio as SwiftUI does: 0 is critically damped, and
|
|
1149
|
+
* it approaches undamped towards 1 and infinitely overdamped towards -1. Both
|
|
1150
|
+
* ends are limited so the spring still moves and settles.
|
|
1151
|
+
*/
|
|
1152
|
+
const bounceToDampingRatio = (bounce) => bounce < 0
|
|
1153
|
+
? 1 / Math.max(1 + bounce, springDefaults.minDamping)
|
|
1154
|
+
: Math.max(1 - bounce, springDefaults.minDamping);
|
|
1155
|
+
/**
|
|
1156
|
+
* Scales an overdamped spring's undamped frequency so that, at its defining
|
|
1157
|
+
* time, it has as far left to go as the critically damped spring that's
|
|
1158
|
+
* criticalFreqTime radians in by then. A negative bounce then changes the
|
|
1159
|
+
* shape of the curve, not when it arrives. 1 at a damping ratio of 1, so
|
|
1160
|
+
* there's no jump at a bounce of 0.
|
|
1161
|
+
*
|
|
1162
|
+
* Solved by Newton on the remaining distance (times fast - slow), which is
|
|
1163
|
+
* convex from criticalFreqTime onwards. criticalFreqTime is always below the
|
|
1164
|
+
* answer, so it converges from below without overshooting.
|
|
1165
|
+
*/
|
|
1166
|
+
function overdampedFreqScale(dampingRatio, criticalFreqTime) {
|
|
1167
|
+
if (!(dampingRatio > 1))
|
|
1168
|
+
return 1;
|
|
1169
|
+
const root = Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
1170
|
+
const slow = dampingRatio - root;
|
|
1171
|
+
const fast = dampingRatio + root;
|
|
1172
|
+
const target = 2 * root * Math.exp(-criticalFreqTime) * (1 + criticalFreqTime);
|
|
1173
|
+
return (approximateRoot((freqTime) => fast * Math.exp(-slow * freqTime) -
|
|
1174
|
+
slow * Math.exp(-fast * freqTime) -
|
|
1175
|
+
target, (freqTime) => Math.exp(-fast * freqTime) - Math.exp(-slow * freqTime), criticalFreqTime) / criticalFreqTime);
|
|
1176
|
+
}
|
|
1140
1177
|
function calcAngularFreq(undampedFreq, dampingRatio) {
|
|
1141
1178
|
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
|
1142
1179
|
}
|
|
@@ -1161,11 +1198,7 @@
|
|
|
1161
1198
|
let envelope;
|
|
1162
1199
|
let derivative;
|
|
1163
1200
|
exports.warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
|
|
1164
|
-
|
|
1165
|
-
/**
|
|
1166
|
-
* Restrict dampingRatio and duration to within acceptable ranges.
|
|
1167
|
-
*/
|
|
1168
|
-
dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
|
|
1201
|
+
const dampingRatio = bounceToDampingRatio(bounce);
|
|
1169
1202
|
duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
|
|
1170
1203
|
if (dampingRatio < 1) {
|
|
1171
1204
|
/**
|
|
@@ -1194,7 +1227,8 @@
|
|
|
1194
1227
|
}
|
|
1195
1228
|
else {
|
|
1196
1229
|
/**
|
|
1197
|
-
* Critically-damped spring
|
|
1230
|
+
* Critically-damped spring. Overdamped springs reuse this root,
|
|
1231
|
+
* scaled below.
|
|
1198
1232
|
*/
|
|
1199
1233
|
envelope = (undampedFreq) => {
|
|
1200
1234
|
const a = Math.exp(-undampedFreq * duration);
|
|
@@ -1208,7 +1242,9 @@
|
|
|
1208
1242
|
};
|
|
1209
1243
|
}
|
|
1210
1244
|
const initialGuess = 5 / duration;
|
|
1211
|
-
const
|
|
1245
|
+
const criticalFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
1246
|
+
const undampedFreq = criticalFreq *
|
|
1247
|
+
overdampedFreqScale(dampingRatio, criticalFreq * duration);
|
|
1212
1248
|
const stiffness = undampedFreq * undampedFreq;
|
|
1213
1249
|
return {
|
|
1214
1250
|
stiffness,
|
|
@@ -1258,12 +1294,12 @@
|
|
|
1258
1294
|
};
|
|
1259
1295
|
if (springOptions.isTimeDefined) {
|
|
1260
1296
|
if (options.visualDuration) {
|
|
1261
|
-
const
|
|
1297
|
+
const dampingRatio = bounceToDampingRatio(options.bounce || 0);
|
|
1298
|
+
const root = ((2 * Math.PI) / (options.visualDuration * 1.2)) *
|
|
1299
|
+
overdampedFreqScale(dampingRatio, (2 * Math.PI) / 1.2);
|
|
1262
1300
|
springOptions.stiffness = root * root;
|
|
1263
1301
|
springOptions.damping =
|
|
1264
|
-
2 *
|
|
1265
|
-
clamp(0.05, 1, 1 - (options.bounce || 0)) *
|
|
1266
|
-
Math.sqrt(springOptions.stiffness);
|
|
1302
|
+
2 * dampingRatio * Math.sqrt(springOptions.stiffness);
|
|
1267
1303
|
}
|
|
1268
1304
|
else {
|
|
1269
1305
|
Object.assign(springOptions, findSpring(springOptions));
|
|
@@ -1398,31 +1434,32 @@
|
|
|
1398
1434
|
(undampedAngularFreq * c.C * t - s.velocity);
|
|
1399
1435
|
}
|
|
1400
1436
|
else {
|
|
1401
|
-
|
|
1437
|
+
/**
|
|
1438
|
+
* Overdamped spring: the sum of a slow and a fast decaying
|
|
1439
|
+
* exponential, so no term can overflow however heavily it's damped.
|
|
1440
|
+
*/
|
|
1402
1441
|
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
const
|
|
1442
|
+
const slow = decay - dampedAngularFreq;
|
|
1443
|
+
const fast = decay + dampedAngularFreq;
|
|
1444
|
+
/**
|
|
1445
|
+
* Physics-defined springs keep a non-physical limit: once
|
|
1446
|
+
* dampedAngularFreq * t passes 300, both terms decay at the damping
|
|
1447
|
+
* rate. Heavily overdamped drag springs (dragElastic: 0,
|
|
1448
|
+
* dragTransition) rely on this to settle almost at once rather than
|
|
1449
|
+
* creep at stiffness / damping. Negative bounce needs the exact
|
|
1450
|
+
* curve, so time-defined springs are exempt.
|
|
1451
|
+
*/
|
|
1452
|
+
const limit = isTimeDefined ? Infinity : 300 / dampedAngularFreq;
|
|
1453
|
+
const decayAt = (rate, t) => Math.exp(t > limit ? -rate * limit - decay * (t - limit) : -rate * t);
|
|
1454
|
+
const c = { S: 0, F: 0 };
|
|
1415
1455
|
update = () => {
|
|
1416
|
-
|
|
1417
|
-
c.
|
|
1418
|
-
c.
|
|
1419
|
-
};
|
|
1420
|
-
resolveVelocity = (t) => {
|
|
1421
|
-
const envelope = Math.exp(-decay * t);
|
|
1422
|
-
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
1423
|
-
return (envelope *
|
|
1424
|
-
(c.sinh * Math.sinh(freqForT) + c.cosh * Math.cosh(freqForT)));
|
|
1456
|
+
const P = (s.velocity + decay * s.delta) / dampedAngularFreq;
|
|
1457
|
+
c.S = (s.delta + P) / 2;
|
|
1458
|
+
c.F = (s.delta - P) / 2;
|
|
1425
1459
|
};
|
|
1460
|
+
resolveSpring = (t) => s.target - c.S * decayAt(slow, t) - c.F * decayAt(fast, t);
|
|
1461
|
+
// Analytical derivative of overdamped spring (px/ms)
|
|
1462
|
+
resolveVelocity = (t) => slow * c.S * decayAt(slow, t) + fast * c.F * decayAt(fast, t);
|
|
1426
1463
|
}
|
|
1427
1464
|
update();
|
|
1428
1465
|
const calculatedDuration = isResolvedFromDuration ? duration || null : null;
|
|
@@ -3000,17 +3037,17 @@
|
|
|
3000
3037
|
/**
|
|
3001
3038
|
* Attaches a timeline to the animation, for instance the `ScrollTimeline`.
|
|
3002
3039
|
*/
|
|
3003
|
-
attachTimeline({ timeline,
|
|
3040
|
+
attachTimeline({ timeline, onAttach, observe,
|
|
3041
|
+
// Deprecated rangeStart/rangeEnd from framer-motion 13.0–13.4. Removed in v14.
|
|
3042
|
+
...range }) {
|
|
3004
3043
|
if (this.allowFlatten) {
|
|
3005
3044
|
this.animation.effect?.updateTiming({ easing: "linear" });
|
|
3006
3045
|
}
|
|
3007
3046
|
this.animation.onfinish = null;
|
|
3008
3047
|
if (timeline && supportsScrollTimeline()) {
|
|
3009
3048
|
this.animation.timeline = timeline;
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
if (rangeEnd)
|
|
3013
|
-
this.animation.rangeEnd = rangeEnd;
|
|
3049
|
+
Object.assign(this.animation, range);
|
|
3050
|
+
onAttach?.(this.animation);
|
|
3014
3051
|
return noop;
|
|
3015
3052
|
}
|
|
3016
3053
|
else {
|
|
@@ -4347,36 +4384,29 @@
|
|
|
4347
4384
|
return state;
|
|
4348
4385
|
}
|
|
4349
4386
|
function resolveVariantFromProps(props, definition, custom, visualElement) {
|
|
4387
|
+
const resolveFunction = (def) => typeof def === "function"
|
|
4388
|
+
? def(custom !== undefined ? custom : props.custom, ...getValueState(visualElement))
|
|
4389
|
+
: def;
|
|
4350
4390
|
/**
|
|
4351
|
-
*
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
const [current, velocity] = getValueState(visualElement);
|
|
4355
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
4356
|
-
}
|
|
4357
|
-
/**
|
|
4358
|
-
* If the variant definition is a variant label, or
|
|
4359
|
-
* the function returned a variant label, resolve.
|
|
4391
|
+
* Resolve a function, then a variant label (which the function may have
|
|
4392
|
+
* returned), then a function again as the label may point to one. The
|
|
4393
|
+
* final function can only return a target object.
|
|
4360
4394
|
*/
|
|
4395
|
+
definition = resolveFunction(definition);
|
|
4361
4396
|
if (typeof definition === "string") {
|
|
4362
4397
|
definition = props.variants && props.variants[definition];
|
|
4363
4398
|
}
|
|
4364
|
-
|
|
4365
|
-
* At this point we've resolved both functions and variant labels,
|
|
4366
|
-
* but the resolved variant label might itself have been a function.
|
|
4367
|
-
* If so, resolve. This can only have returned a valid target object.
|
|
4368
|
-
*/
|
|
4369
|
-
if (typeof definition === "function") {
|
|
4370
|
-
const [current, velocity] = getValueState(visualElement);
|
|
4371
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
4372
|
-
}
|
|
4373
|
-
return definition;
|
|
4399
|
+
return resolveFunction(definition);
|
|
4374
4400
|
}
|
|
4375
4401
|
|
|
4376
4402
|
function resolveVariant(visualElement, definition, custom) {
|
|
4377
|
-
|
|
4378
|
-
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
|
|
4403
|
+
return resolveVariantFromProps(visualElement.getProps(), definition, custom, visualElement);
|
|
4379
4404
|
}
|
|
4405
|
+
/**
|
|
4406
|
+
* Exit variants resolve with AnimatePresence's custom, every other type
|
|
4407
|
+
* with the element's own.
|
|
4408
|
+
*/
|
|
4409
|
+
const getTypeCustom = (visualElement, type) => type === "exit" ? visualElement.presenceContext?.custom : undefined;
|
|
4380
4410
|
|
|
4381
4411
|
const positionalKeys = new Set([
|
|
4382
4412
|
"width",
|
|
@@ -4459,7 +4489,7 @@
|
|
|
4459
4489
|
* had been set to true in the meantime.
|
|
4460
4490
|
*/
|
|
4461
4491
|
function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
|
|
4462
|
-
const shouldBlock =
|
|
4492
|
+
const shouldBlock = key in protectedKeys && !needsAnimating[key];
|
|
4463
4493
|
needsAnimating[key] = false;
|
|
4464
4494
|
return shouldBlock;
|
|
4465
4495
|
}
|
|
@@ -4474,9 +4504,7 @@
|
|
|
4474
4504
|
if (transitionOverride)
|
|
4475
4505
|
transition = transitionOverride;
|
|
4476
4506
|
const animations = [];
|
|
4477
|
-
const animationTypeState = type &&
|
|
4478
|
-
visualElement.animationState &&
|
|
4479
|
-
visualElement.animationState.getState()[type];
|
|
4507
|
+
const animationTypeState = type && visualElement.animationState?.getState()[type];
|
|
4480
4508
|
const path = transition?.path;
|
|
4481
4509
|
if (path) {
|
|
4482
4510
|
// path mutates `target` to claim x/y; loop below skips them.
|
|
@@ -4550,75 +4578,46 @@
|
|
|
4550
4578
|
}
|
|
4551
4579
|
|
|
4552
4580
|
function animateVariant(visualElement, variant, options = {}) {
|
|
4553
|
-
const resolved = resolveVariant(visualElement, variant, options.type
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
? [getAnimation, getChildAnimations]
|
|
4585
|
-
: [getChildAnimations, getAnimation];
|
|
4586
|
-
return first().then(() => last());
|
|
4587
|
-
}
|
|
4588
|
-
else {
|
|
4589
|
-
return Promise.all([getAnimation(), getChildAnimations(options.delay)]);
|
|
4590
|
-
}
|
|
4591
|
-
}
|
|
4592
|
-
function animateChildren(visualElement, variant, delay = 0, delayChildren = 0, staggerChildren = 0, staggerDirection = 1, options) {
|
|
4593
|
-
const animations = [];
|
|
4594
|
-
for (const child of visualElement.variantChildren) {
|
|
4595
|
-
child.notify("AnimationStart", variant);
|
|
4596
|
-
animations.push(animateVariant(child, variant, {
|
|
4597
|
-
...options,
|
|
4598
|
-
delay: delay +
|
|
4599
|
-
(typeof delayChildren === "function" ? 0 : delayChildren) +
|
|
4600
|
-
calcChildStagger(visualElement.variantChildren, child, delayChildren, staggerChildren, staggerDirection),
|
|
4601
|
-
}).then(() => child.notify("AnimationComplete", variant)));
|
|
4602
|
-
}
|
|
4603
|
-
return Promise.all(animations);
|
|
4581
|
+
const resolved = resolveVariant(visualElement, variant, getTypeCustom(visualElement, options.type));
|
|
4582
|
+
const transition = options.transitionOverride ||
|
|
4583
|
+
(resolved?.transition ?? (visualElement.getDefaultTransition() || {}));
|
|
4584
|
+
const getAnimation = () => Promise.all(resolved ? animateTarget(visualElement, resolved, options) : []);
|
|
4585
|
+
const getChildAnimations = (delay = 0) => {
|
|
4586
|
+
const { variantChildren } = visualElement;
|
|
4587
|
+
const { delayChildren = 0, staggerChildren, staggerDirection, } = transition;
|
|
4588
|
+
const animations = [];
|
|
4589
|
+
variantChildren?.forEach((child) => {
|
|
4590
|
+
child.notify("AnimationStart", variant);
|
|
4591
|
+
animations.push(animateVariant(child, variant, {
|
|
4592
|
+
...options,
|
|
4593
|
+
delay: delay +
|
|
4594
|
+
(typeof delayChildren === "function"
|
|
4595
|
+
? 0
|
|
4596
|
+
: delayChildren) +
|
|
4597
|
+
calcChildStagger(variantChildren, child, delayChildren, staggerChildren, staggerDirection),
|
|
4598
|
+
}).then(() => child.notify("AnimationComplete", variant)));
|
|
4599
|
+
});
|
|
4600
|
+
return Promise.all(animations);
|
|
4601
|
+
};
|
|
4602
|
+
/**
|
|
4603
|
+
* when: "beforeChildren" | "afterChildren" runs this element's animation
|
|
4604
|
+
* and its children's in sequence.
|
|
4605
|
+
*/
|
|
4606
|
+
const { when } = transition;
|
|
4607
|
+
return when
|
|
4608
|
+
? when === "beforeChildren"
|
|
4609
|
+
? getAnimation().then(() => getChildAnimations())
|
|
4610
|
+
: getChildAnimations().then(getAnimation)
|
|
4611
|
+
: Promise.all([getAnimation(), getChildAnimations(options.delay)]);
|
|
4604
4612
|
}
|
|
4605
4613
|
|
|
4606
4614
|
function animateVisualElement(visualElement, definition, options = {}) {
|
|
4607
4615
|
visualElement.notify("AnimationStart", definition);
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
else if (typeof definition === "string") {
|
|
4614
|
-
animation = animateVariant(visualElement, definition, options);
|
|
4615
|
-
}
|
|
4616
|
-
else {
|
|
4617
|
-
const resolvedDefinition = typeof definition === "function"
|
|
4618
|
-
? resolveVariant(visualElement, definition, options.custom)
|
|
4619
|
-
: definition;
|
|
4620
|
-
animation = Promise.all(animateTarget(visualElement, resolvedDefinition, options));
|
|
4621
|
-
}
|
|
4616
|
+
const animation = Array.isArray(definition)
|
|
4617
|
+
? Promise.all(definition.map((variant) => animateVariant(visualElement, variant, options)))
|
|
4618
|
+
: typeof definition === "string"
|
|
4619
|
+
? animateVariant(visualElement, definition, options)
|
|
4620
|
+
: Promise.all(animateTarget(visualElement, resolveVariant(visualElement, definition, options.custom), options));
|
|
4622
4621
|
return animation.then(() => {
|
|
4623
4622
|
visualElement.notify("AnimationComplete", definition);
|
|
4624
4623
|
});
|
|
@@ -5144,7 +5143,12 @@
|
|
|
5144
5143
|
const onRemove = () => {
|
|
5145
5144
|
cancelOnChange();
|
|
5146
5145
|
render && !computed && this.cancel(render);
|
|
5147
|
-
|
|
5146
|
+
/**
|
|
5147
|
+
* A later set() of the same name has already removed this
|
|
5148
|
+
* entry, so leave the one that replaced it.
|
|
5149
|
+
*/
|
|
5150
|
+
this.values.get(name)?.onRemove === onRemove &&
|
|
5151
|
+
this.values.delete(name);
|
|
5148
5152
|
};
|
|
5149
5153
|
this.values.set(name, {
|
|
5150
5154
|
value,
|
|
@@ -5623,24 +5627,35 @@
|
|
|
5623
5627
|
else if (key.startsWith("attr")) {
|
|
5624
5628
|
return addAttrValue(element, state, key, value, convertAttrKey(key));
|
|
5625
5629
|
}
|
|
5626
|
-
|
|
5630
|
+
/**
|
|
5631
|
+
* Transforms and origins aren't CSS properties (bar `x`/`y` in some
|
|
5632
|
+
* browsers), so check them by name rather than `key in element.style`.
|
|
5633
|
+
*/
|
|
5634
|
+
const handler = transformProps.has(key) ||
|
|
5635
|
+
originProps.has(key) ||
|
|
5636
|
+
isCSSVar(key) ||
|
|
5637
|
+
key in element.style
|
|
5638
|
+
? addStyleValue
|
|
5639
|
+
: addAttrValue;
|
|
5627
5640
|
return handler(element, state, key, value);
|
|
5628
5641
|
};
|
|
5629
5642
|
/**
|
|
5630
5643
|
* Reads the current value of `key` from an SVG element as the origin of
|
|
5631
5644
|
* an animation, as the SVG VisualElement does: transforms start from
|
|
5632
5645
|
* their defaults, CSS variables and the few CSS-only properties come from
|
|
5633
|
-
* computed style
|
|
5634
|
-
* and everything else is read from the
|
|
5635
|
-
* (`strokeWidth` -> `stroke-width`) or, failing
|
|
5636
|
-
* (`baseFrequency`).
|
|
5646
|
+
* computed style, falling back to the attribute when that's empty (e.g.
|
|
5647
|
+
* the element isn't rendered), and everything else is read from the
|
|
5648
|
+
* attribute, dash-cased (`strokeWidth` -> `stroke-width`) or, failing
|
|
5649
|
+
* that, as written (`baseFrequency`).
|
|
5637
5650
|
*/
|
|
5638
5651
|
const readSVGValue = (element, key) => {
|
|
5639
5652
|
if (transformProps.has(key)) {
|
|
5640
5653
|
return numberValueTypes[key]?.default || 0;
|
|
5641
5654
|
}
|
|
5642
5655
|
if (isCSSVar(key) || cssStyleProperties.includes(key)) {
|
|
5643
|
-
return readStyleValue(element, key)
|
|
5656
|
+
return (readStyleValue(element, key) ||
|
|
5657
|
+
element.getAttribute(camelToDash(key)) ||
|
|
5658
|
+
0);
|
|
5644
5659
|
}
|
|
5645
5660
|
key = convertAttrKey(key);
|
|
5646
5661
|
return (element.getAttribute(camelToDash(key)) ??
|
|
@@ -6343,6 +6358,10 @@
|
|
|
6343
6358
|
return typeof a === "function" ? resizeWindow(a) : resizeElement(a, b);
|
|
6344
6359
|
}
|
|
6345
6360
|
|
|
6361
|
+
/**
|
|
6362
|
+
* @deprecated Kept because framer-motion 13.0–13.4 import it from
|
|
6363
|
+
* motion-dom with a ^ range. Removed in v14.
|
|
6364
|
+
*/
|
|
6346
6365
|
function observeTimeline(update, timeline) {
|
|
6347
6366
|
let prevProgress;
|
|
6348
6367
|
const onFrame = () => {
|
|
@@ -6471,8 +6490,7 @@
|
|
|
6471
6490
|
this.options.onStop?.();
|
|
6472
6491
|
};
|
|
6473
6492
|
this.options = options;
|
|
6474
|
-
|
|
6475
|
-
this.factory = options.type || keyframes;
|
|
6493
|
+
this.factory = options.type;
|
|
6476
6494
|
this.generator = this.factory(options);
|
|
6477
6495
|
const { driver } = options;
|
|
6478
6496
|
if (driver)
|
|
@@ -6573,41 +6591,15 @@
|
|
|
6573
6591
|
}
|
|
6574
6592
|
}
|
|
6575
6593
|
|
|
6594
|
+
const asNumber = (v) => typeof v === "number" ? v : parseFloat(v);
|
|
6595
|
+
|
|
6576
6596
|
/**
|
|
6577
|
-
*
|
|
6578
|
-
*
|
|
6579
|
-
*
|
|
6580
|
-
*
|
|
6581
|
-
* const x = motionValue(0)
|
|
6582
|
-
* const y = followValue(x, { type: "spring", stiffness: 300 })
|
|
6583
|
-
* // or with tween
|
|
6584
|
-
* const z = followValue(x, { type: "tween", duration: 0.5, ease: "easeOut" })
|
|
6585
|
-
* ```
|
|
6586
|
-
*
|
|
6587
|
-
* @param source - Initial value or MotionValue to track
|
|
6588
|
-
* @param options - Animation transition options
|
|
6589
|
-
* @returns `MotionValue`
|
|
6590
|
-
*
|
|
6591
|
-
* @public
|
|
6592
|
-
*/
|
|
6593
|
-
function followValue(source, options) {
|
|
6594
|
-
const initialValue = isMotionValue(source) ? source.get() : source;
|
|
6595
|
-
const value = motionValue(initialValue);
|
|
6596
|
-
attachFollow(value, source, options);
|
|
6597
|
-
return value;
|
|
6598
|
-
}
|
|
6599
|
-
/**
|
|
6600
|
-
* Attach an animation to a MotionValue that will animate whenever the value changes.
|
|
6601
|
-
* Similar to attachSpring but supports any transition type (spring, tween, inertia, etc.)
|
|
6602
|
-
*
|
|
6603
|
-
* @param value - The MotionValue to animate
|
|
6604
|
-
* @param source - Initial value or MotionValue to track
|
|
6605
|
-
* @param options - Animation transition options
|
|
6606
|
-
* @returns Cleanup function
|
|
6607
|
-
*
|
|
6608
|
-
* @public
|
|
6597
|
+
* Animate `value` to its latest value with the generator in
|
|
6598
|
+
* `options.type`, tracking `source` if it's a `MotionValue`. Takes a
|
|
6599
|
+
* generator rather than a type name, so spring-only callers don't
|
|
6600
|
+
* bundle every generator.
|
|
6609
6601
|
*/
|
|
6610
|
-
function
|
|
6602
|
+
function follow(value, source, options) {
|
|
6611
6603
|
const initialValue = value.get();
|
|
6612
6604
|
let activeAnimation = null;
|
|
6613
6605
|
let set;
|
|
@@ -6625,7 +6617,7 @@
|
|
|
6625
6617
|
};
|
|
6626
6618
|
value.attach((v, safeSet) => {
|
|
6627
6619
|
set = safeSet;
|
|
6628
|
-
const target = asNumber
|
|
6620
|
+
const target = asNumber(v);
|
|
6629
6621
|
if (activeAnimation?.state === "running") {
|
|
6630
6622
|
/**
|
|
6631
6623
|
* Steer the running animation rather than replacing it. This
|
|
@@ -6636,7 +6628,7 @@
|
|
|
6636
6628
|
activeAnimation.setTarget(target, options.velocity);
|
|
6637
6629
|
return;
|
|
6638
6630
|
}
|
|
6639
|
-
const current = asNumber
|
|
6631
|
+
const current = asNumber(value.get());
|
|
6640
6632
|
const velocity = activeAnimation
|
|
6641
6633
|
? activeAnimation.getGeneratorVelocity()
|
|
6642
6634
|
: value.getVelocity();
|
|
@@ -6647,8 +6639,6 @@
|
|
|
6647
6639
|
const animationOptions = {
|
|
6648
6640
|
keyframes: [current, target],
|
|
6649
6641
|
velocity,
|
|
6650
|
-
// Default to spring if no type specified (matches useSpring behavior)
|
|
6651
|
-
type: "spring",
|
|
6652
6642
|
restDelta: 0.001,
|
|
6653
6643
|
restSpeed: 0.01,
|
|
6654
6644
|
...options,
|
|
@@ -6690,8 +6680,47 @@
|
|
|
6690
6680
|
function parseValue(v, unit) {
|
|
6691
6681
|
return unit ? v + unit : v;
|
|
6692
6682
|
}
|
|
6693
|
-
|
|
6694
|
-
|
|
6683
|
+
|
|
6684
|
+
/**
|
|
6685
|
+
* Create a `MotionValue` that animates to its latest value using any transition type.
|
|
6686
|
+
* Can either be a value or track another `MotionValue`.
|
|
6687
|
+
*
|
|
6688
|
+
* ```jsx
|
|
6689
|
+
* const x = motionValue(0)
|
|
6690
|
+
* const y = followValue(x, { type: "spring", stiffness: 300 })
|
|
6691
|
+
* // or with tween
|
|
6692
|
+
* const z = followValue(x, { type: "tween", duration: 0.5, ease: "easeOut" })
|
|
6693
|
+
* ```
|
|
6694
|
+
*
|
|
6695
|
+
* @param source - Initial value or MotionValue to track
|
|
6696
|
+
* @param options - Animation transition options
|
|
6697
|
+
* @returns `MotionValue`
|
|
6698
|
+
*
|
|
6699
|
+
* @public
|
|
6700
|
+
*/
|
|
6701
|
+
function followValue(source, options) {
|
|
6702
|
+
const initialValue = isMotionValue(source) ? source.get() : source;
|
|
6703
|
+
const value = motionValue(initialValue);
|
|
6704
|
+
attachFollow(value, source, options);
|
|
6705
|
+
return value;
|
|
6706
|
+
}
|
|
6707
|
+
/**
|
|
6708
|
+
* Attach an animation to a MotionValue that will animate whenever the value changes.
|
|
6709
|
+
* Similar to attachSpring but supports any transition type (spring, tween, inertia, etc.)
|
|
6710
|
+
*
|
|
6711
|
+
* @param value - The MotionValue to animate
|
|
6712
|
+
* @param source - Initial value or MotionValue to track
|
|
6713
|
+
* @param options - Animation transition options
|
|
6714
|
+
* @returns Cleanup function
|
|
6715
|
+
*
|
|
6716
|
+
* @public
|
|
6717
|
+
*/
|
|
6718
|
+
function attachFollow(value, source, options = {}) {
|
|
6719
|
+
// Default to spring if no type specified (matches useSpring behavior)
|
|
6720
|
+
const transition = { type: "spring", ...options };
|
|
6721
|
+
replaceTransitionType(transition);
|
|
6722
|
+
transition.type = transition.type || keyframes;
|
|
6723
|
+
return follow(value, source, transition);
|
|
6695
6724
|
}
|
|
6696
6725
|
|
|
6697
6726
|
function subscribeValue(inputValues, outputValue, getLatest) {
|
|
@@ -6793,7 +6822,9 @@
|
|
|
6793
6822
|
* @public
|
|
6794
6823
|
*/
|
|
6795
6824
|
function springValue(source, options) {
|
|
6796
|
-
|
|
6825
|
+
const value = motionValue(isMotionValue(source) ? source.get() : source);
|
|
6826
|
+
attachSpring(value, source, options);
|
|
6827
|
+
return value;
|
|
6797
6828
|
}
|
|
6798
6829
|
/**
|
|
6799
6830
|
* Attach a spring animation to a MotionValue that will animate whenever the value changes.
|
|
@@ -6806,7 +6837,7 @@
|
|
|
6806
6837
|
* @public
|
|
6807
6838
|
*/
|
|
6808
6839
|
function attachSpring(value, source, options) {
|
|
6809
|
-
return
|
|
6840
|
+
return follow(value, source, { ...options, type: spring });
|
|
6810
6841
|
}
|
|
6811
6842
|
|
|
6812
6843
|
/**
|
|
@@ -8228,8 +8259,20 @@
|
|
|
8228
8259
|
if (this.projection && !this.projection.instance) {
|
|
8229
8260
|
this.projection.mount(instance);
|
|
8230
8261
|
}
|
|
8231
|
-
|
|
8232
|
-
|
|
8262
|
+
/**
|
|
8263
|
+
* Join the closest variant node above, so its variant changes
|
|
8264
|
+
* propagate here, unless inherit={false} cuts the chain.
|
|
8265
|
+
*/
|
|
8266
|
+
if (this.isVariantNode && !this.isControllingVariants) {
|
|
8267
|
+
let node = this;
|
|
8268
|
+
do
|
|
8269
|
+
node = node.props.inherit !== false && node.parent;
|
|
8270
|
+
while (node && !node.isVariantNode);
|
|
8271
|
+
if (node) {
|
|
8272
|
+
const { variantChildren } = node;
|
|
8273
|
+
variantChildren.add(this);
|
|
8274
|
+
this.removeFromVariantTree = () => variantChildren.delete(this);
|
|
8275
|
+
}
|
|
8233
8276
|
}
|
|
8234
8277
|
this.values.forEach((value, key) => this.bindToMotionValue(key, value));
|
|
8235
8278
|
/**
|
|
@@ -8339,9 +8382,11 @@
|
|
|
8339
8382
|
}
|
|
8340
8383
|
sortNodePosition(other) {
|
|
8341
8384
|
/**
|
|
8342
|
-
* If
|
|
8385
|
+
* If either node isn't mounted, or they aren't even of the same type,
|
|
8386
|
+
* we can't compare their document order.
|
|
8343
8387
|
*/
|
|
8344
8388
|
if (!this.current ||
|
|
8389
|
+
!other.current ||
|
|
8345
8390
|
!this.sortInstanceNodePosition ||
|
|
8346
8391
|
this.type !== other.type) {
|
|
8347
8392
|
return 0;
|
|
@@ -8447,24 +8492,6 @@
|
|
|
8447
8492
|
getTransformPagePoint() {
|
|
8448
8493
|
return this.props.transformPagePoint;
|
|
8449
8494
|
}
|
|
8450
|
-
getClosestVariantNode() {
|
|
8451
|
-
return this.isVariantNode
|
|
8452
|
-
? this
|
|
8453
|
-
: this.parent
|
|
8454
|
-
? this.parent.getClosestVariantNode()
|
|
8455
|
-
: undefined;
|
|
8456
|
-
}
|
|
8457
|
-
/**
|
|
8458
|
-
* Add a child visual element to our set of children.
|
|
8459
|
-
*/
|
|
8460
|
-
addVariantChild(child) {
|
|
8461
|
-
const closestVariantNode = this.getClosestVariantNode();
|
|
8462
|
-
if (closestVariantNode) {
|
|
8463
|
-
closestVariantNode.variantChildren &&
|
|
8464
|
-
closestVariantNode.variantChildren.add(child);
|
|
8465
|
-
return () => closestVariantNode.variantChildren.delete(child);
|
|
8466
|
-
}
|
|
8467
|
-
}
|
|
8468
8495
|
/**
|
|
8469
8496
|
* Add a motion value and bind it to this visual element.
|
|
8470
8497
|
*/
|
|
@@ -8476,7 +8503,15 @@
|
|
|
8476
8503
|
this.removeValue(key);
|
|
8477
8504
|
this.bindToMotionValue(key, value);
|
|
8478
8505
|
this.values.set(key, value);
|
|
8479
|
-
|
|
8506
|
+
/**
|
|
8507
|
+
* An animated value with no base value is undefined until the
|
|
8508
|
+
* keyframe resolver reads its origin. Rendering it before then
|
|
8509
|
+
* writes an invalid placeholder (e.g. points="undefined") that
|
|
8510
|
+
* the resolver can read back from the DOM as the origin.
|
|
8511
|
+
*/
|
|
8512
|
+
const latest = value.get();
|
|
8513
|
+
if (latest !== undefined)
|
|
8514
|
+
this.latestValues[key] = latest;
|
|
8480
8515
|
}
|
|
8481
8516
|
}
|
|
8482
8517
|
/**
|
|
@@ -8504,7 +8539,9 @@
|
|
|
8504
8539
|
}
|
|
8505
8540
|
let value = this.values.get(key);
|
|
8506
8541
|
if (value === undefined && defaultValue !== undefined) {
|
|
8507
|
-
value = motionValue(defaultValue
|
|
8542
|
+
value = motionValue(defaultValue ?? this.getDefaultValue?.(key), {
|
|
8543
|
+
owner: this,
|
|
8544
|
+
});
|
|
8508
8545
|
this.addValue(key, value);
|
|
8509
8546
|
}
|
|
8510
8547
|
return value;
|
|
@@ -8549,41 +8586,6 @@
|
|
|
8549
8586
|
setBaseTarget(key, value) {
|
|
8550
8587
|
this.baseTarget[key] = value;
|
|
8551
8588
|
}
|
|
8552
|
-
/**
|
|
8553
|
-
* Find the base target for a value thats been removed from all animation
|
|
8554
|
-
* props.
|
|
8555
|
-
*/
|
|
8556
|
-
getBaseTarget(key) {
|
|
8557
|
-
const { initial } = this.props;
|
|
8558
|
-
let valueFromInitial;
|
|
8559
|
-
if (typeof initial === "string" || typeof initial === "object") {
|
|
8560
|
-
const variant = resolveVariantFromProps(this.props, initial, this.presenceContext?.custom);
|
|
8561
|
-
if (variant) {
|
|
8562
|
-
valueFromInitial = variant[key];
|
|
8563
|
-
}
|
|
8564
|
-
}
|
|
8565
|
-
/**
|
|
8566
|
-
* If this value still exists in the current initial variant, read that.
|
|
8567
|
-
*/
|
|
8568
|
-
if (initial && valueFromInitial !== undefined) {
|
|
8569
|
-
return valueFromInitial;
|
|
8570
|
-
}
|
|
8571
|
-
/**
|
|
8572
|
-
* Alternatively, if this VisualElement config has defined a getBaseTarget
|
|
8573
|
-
* so we can read the value from an alternative source, try that.
|
|
8574
|
-
*/
|
|
8575
|
-
const target = this.getBaseTargetFromProps(this.props, key);
|
|
8576
|
-
if (target !== undefined && !isMotionValue(target))
|
|
8577
|
-
return target;
|
|
8578
|
-
/**
|
|
8579
|
-
* If the value was initially defined on initial, but it doesn't any more,
|
|
8580
|
-
* return undefined. Otherwise return the value as initially read from the DOM.
|
|
8581
|
-
*/
|
|
8582
|
-
return this.initialValues[key] !== undefined &&
|
|
8583
|
-
valueFromInitial === undefined
|
|
8584
|
-
? undefined
|
|
8585
|
-
: this.baseTarget[key];
|
|
8586
|
-
}
|
|
8587
8589
|
on(eventName, callback) {
|
|
8588
8590
|
if (!this.events[eventName]) {
|
|
8589
8591
|
this.events[eventName] = new SubscriptionManager();
|
|
@@ -8666,86 +8668,7 @@
|
|
|
8666
8668
|
}
|
|
8667
8669
|
}
|
|
8668
8670
|
|
|
8669
|
-
|
|
8670
|
-
if (axis.max === axis.min)
|
|
8671
|
-
return 0;
|
|
8672
|
-
return (pixels / (axis.max - axis.min)) * 100;
|
|
8673
|
-
}
|
|
8674
|
-
/**
|
|
8675
|
-
* We always correct borderRadius as a percentage rather than pixels to reduce paints.
|
|
8676
|
-
* For example, if you are projecting a box that is 100px wide with a 10px borderRadius
|
|
8677
|
-
* into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
|
|
8678
|
-
* borderRadius in both states. If we animate between the two in pixels that will trigger
|
|
8679
|
-
* a paint each time. If we animate between the two in percentage we'll avoid a paint.
|
|
8680
|
-
*/
|
|
8681
|
-
const correctBorderRadius = {
|
|
8682
|
-
correct: (latest, node) => {
|
|
8683
|
-
if (!node.target)
|
|
8684
|
-
return latest;
|
|
8685
|
-
/**
|
|
8686
|
-
* If latest is a string, if it's a percentage we can return immediately as it's
|
|
8687
|
-
* going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
|
|
8688
|
-
*/
|
|
8689
|
-
if (typeof latest === "string") {
|
|
8690
|
-
if (px.test(latest)) {
|
|
8691
|
-
latest = parseFloat(latest);
|
|
8692
|
-
}
|
|
8693
|
-
else {
|
|
8694
|
-
return latest;
|
|
8695
|
-
}
|
|
8696
|
-
}
|
|
8697
|
-
/**
|
|
8698
|
-
* If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
|
|
8699
|
-
* pixel value as a percentage of each axis
|
|
8700
|
-
*/
|
|
8701
|
-
const x = pixelsToPercent(latest, node.target.x);
|
|
8702
|
-
const y = pixelsToPercent(latest, node.target.y);
|
|
8703
|
-
return `${x}% ${y}%`;
|
|
8704
|
-
},
|
|
8705
|
-
};
|
|
8706
|
-
|
|
8707
|
-
const correctBoxShadow = {
|
|
8708
|
-
correct: (latest, { treeScale, projectionDelta }) => {
|
|
8709
|
-
const original = latest;
|
|
8710
|
-
const shadow = complex.parse(latest);
|
|
8711
|
-
// TODO: Doesn't support multiple shadows
|
|
8712
|
-
if (shadow.length > 5)
|
|
8713
|
-
return original;
|
|
8714
|
-
const template = complex.createTransformer(latest);
|
|
8715
|
-
const offset = typeof shadow[0] !== "number" ? 1 : 0;
|
|
8716
|
-
// Calculate the overall context scale
|
|
8717
|
-
const xScale = projectionDelta.x.scale * treeScale.x;
|
|
8718
|
-
const yScale = projectionDelta.y.scale * treeScale.y;
|
|
8719
|
-
shadow[0 + offset] /= xScale;
|
|
8720
|
-
shadow[1 + offset] /= yScale;
|
|
8721
|
-
/**
|
|
8722
|
-
* Ideally we'd correct x and y scales individually, but because blur and
|
|
8723
|
-
* spread apply to both we have to take a scale average and apply that instead.
|
|
8724
|
-
* We could potentially improve the outcome of this by incorporating the ratio between
|
|
8725
|
-
* the two scales.
|
|
8726
|
-
*/
|
|
8727
|
-
const averageScale = mixNumber$1(xScale, yScale, 0.5);
|
|
8728
|
-
// Blur
|
|
8729
|
-
if (typeof shadow[2 + offset] === "number")
|
|
8730
|
-
shadow[2 + offset] /= averageScale;
|
|
8731
|
-
// Spread
|
|
8732
|
-
if (typeof shadow[3 + offset] === "number")
|
|
8733
|
-
shadow[3 + offset] /= averageScale;
|
|
8734
|
-
return template(shadow);
|
|
8735
|
-
},
|
|
8736
|
-
};
|
|
8737
|
-
|
|
8738
|
-
const scaleCorrectors = {
|
|
8739
|
-
borderRadius: {
|
|
8740
|
-
...correctBorderRadius,
|
|
8741
|
-
applyTo: [...cornerRadiusProps],
|
|
8742
|
-
},
|
|
8743
|
-
borderTopLeftRadius: correctBorderRadius,
|
|
8744
|
-
borderTopRightRadius: correctBorderRadius,
|
|
8745
|
-
borderBottomLeftRadius: correctBorderRadius,
|
|
8746
|
-
borderBottomRightRadius: correctBorderRadius,
|
|
8747
|
-
boxShadow: correctBoxShadow,
|
|
8748
|
-
};
|
|
8671
|
+
const scaleCorrectors = {};
|
|
8749
8672
|
function addScaleCorrector(correctors) {
|
|
8750
8673
|
for (const key in correctors) {
|
|
8751
8674
|
scaleCorrectors[key] = correctors[key];
|
|
@@ -8919,7 +8842,24 @@
|
|
|
8919
8842
|
this.measureInstanceViewportBox = createBox;
|
|
8920
8843
|
}
|
|
8921
8844
|
getBaseTargetFromProps(props, key) {
|
|
8922
|
-
|
|
8845
|
+
/**
|
|
8846
|
+
* An independent transform's base is never an attribute of the same
|
|
8847
|
+
* name, e.g. <rect x> is a position, not a translate. Like HTML it
|
|
8848
|
+
* comes from style, falling back to the transform's default.
|
|
8849
|
+
*/
|
|
8850
|
+
return transformProps.has(key)
|
|
8851
|
+
? super.getBaseTargetFromProps(props, key) ??
|
|
8852
|
+
defaultTransformValue(key)
|
|
8853
|
+
: props[key];
|
|
8854
|
+
}
|
|
8855
|
+
/**
|
|
8856
|
+
* An independent transform's origin is never read from the DOM, so it
|
|
8857
|
+
* starts from its latest or default value and renders on mount.
|
|
8858
|
+
*/
|
|
8859
|
+
getDefaultValue(key) {
|
|
8860
|
+
return transformProps.has(key)
|
|
8861
|
+
? this.latestValues[key] ?? defaultTransformValue(key)
|
|
8862
|
+
: undefined;
|
|
8923
8863
|
}
|
|
8924
8864
|
readValueFromInstance(instance, key) {
|
|
8925
8865
|
if (transformProps.has(key)) {
|
|
@@ -8950,34 +8890,6 @@
|
|
|
8950
8890
|
}
|
|
8951
8891
|
}
|
|
8952
8892
|
|
|
8953
|
-
const numVariantProps = variantProps.length;
|
|
8954
|
-
/**
|
|
8955
|
-
* Get variant context from a visual element's parent chain.
|
|
8956
|
-
* Uses `any` type for visualElement to avoid circular dependencies.
|
|
8957
|
-
*/
|
|
8958
|
-
function getVariantContext(visualElement) {
|
|
8959
|
-
if (!visualElement)
|
|
8960
|
-
return undefined;
|
|
8961
|
-
if (!visualElement.isControllingVariants) {
|
|
8962
|
-
const context = visualElement.parent
|
|
8963
|
-
? getVariantContext(visualElement.parent) || {}
|
|
8964
|
-
: {};
|
|
8965
|
-
if (visualElement.props.initial !== undefined) {
|
|
8966
|
-
context.initial = visualElement.props.initial;
|
|
8967
|
-
}
|
|
8968
|
-
return context;
|
|
8969
|
-
}
|
|
8970
|
-
const context = {};
|
|
8971
|
-
for (let i = 0; i < numVariantProps; i++) {
|
|
8972
|
-
const name = variantProps[i];
|
|
8973
|
-
const prop = visualElement.props[name];
|
|
8974
|
-
if (isVariantLabel(prop) || prop === false) {
|
|
8975
|
-
context[name] = prop;
|
|
8976
|
-
}
|
|
8977
|
-
}
|
|
8978
|
-
return context;
|
|
8979
|
-
}
|
|
8980
|
-
|
|
8981
8893
|
function shallowCompare(next, prev) {
|
|
8982
8894
|
if (!Array.isArray(prev))
|
|
8983
8895
|
return false;
|
|
@@ -8992,332 +8904,211 @@
|
|
|
8992
8904
|
}
|
|
8993
8905
|
|
|
8994
8906
|
const reversePriorityOrder = [...variantPriorityOrder].reverse();
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
8907
|
+
/**
|
|
8908
|
+
* Diffs the element's animation props (one layer per AnimationType, highest
|
|
8909
|
+
* priority wins each value) against the previous call and starts animations
|
|
8910
|
+
* for what changed.
|
|
8911
|
+
*
|
|
8912
|
+
* Uses `any` type for visualElement to avoid circular dependencies. It reads
|
|
8913
|
+
* props, parent, presenceContext, variantChildren, enteringChildren,
|
|
8914
|
+
* manuallyAnimateOnMount, blockInitialAnimation, getValue(),
|
|
8915
|
+
* getBaseTargetFromProps(), baseTarget and initialValues.
|
|
8916
|
+
*/
|
|
9001
8917
|
function createAnimationState(visualElement) {
|
|
9002
|
-
let animate =
|
|
8918
|
+
let animate = (animations) => Promise.all(animations.map(({ animation, options }) => animateVisualElement(visualElement, animation, options)));
|
|
9003
8919
|
let state = createState();
|
|
9004
8920
|
let isInitialRender = true;
|
|
9005
8921
|
/**
|
|
9006
|
-
*
|
|
9007
|
-
*
|
|
9008
|
-
* animateChanges() call after a reset we need to behave like the initial
|
|
9009
|
-
* render for variant-inheritance checks, even though isInitialRender is
|
|
9010
|
-
* already false.
|
|
8922
|
+
* After a reset (StrictMode, Suspense, AnimatePresence re-entry) the next
|
|
8923
|
+
* animateChanges() mounts again, but initial={false} no longer applies.
|
|
9011
8924
|
*/
|
|
9012
8925
|
let wasReset = false;
|
|
9013
|
-
/**
|
|
9014
|
-
* This function will be used to reduce the animation definitions for
|
|
9015
|
-
* each active animation type into an object of resolved values for it.
|
|
9016
|
-
*/
|
|
9017
|
-
const buildResolvedTypeValues = (type) => (acc, definition) => {
|
|
9018
|
-
const resolved = resolveVariant(visualElement, definition, type === "exit"
|
|
9019
|
-
? visualElement.presenceContext?.custom
|
|
9020
|
-
: undefined);
|
|
9021
|
-
if (resolved) {
|
|
9022
|
-
const { transition, transitionEnd, ...target } = resolved;
|
|
9023
|
-
acc = { ...acc, ...target, ...transitionEnd };
|
|
9024
|
-
}
|
|
9025
|
-
return acc;
|
|
9026
|
-
};
|
|
9027
|
-
/**
|
|
9028
|
-
* This just allows us to inject mocked animation functions
|
|
9029
|
-
* @internal
|
|
9030
|
-
*/
|
|
9031
|
-
function setAnimateFunction(makeAnimator) {
|
|
9032
|
-
animate = makeAnimator(visualElement);
|
|
9033
|
-
}
|
|
9034
|
-
/**
|
|
9035
|
-
* When we receive new props, we need to:
|
|
9036
|
-
* 1. Create a list of protected keys for each type. This is a directory of
|
|
9037
|
-
* value keys that are currently being "handled" by types of a higher priority
|
|
9038
|
-
* so that whenever an animation is played of a given type, these values are
|
|
9039
|
-
* protected from being animated.
|
|
9040
|
-
* 2. Determine if an animation type needs animating.
|
|
9041
|
-
* 3. Determine if any values have been removed from a type and figure out
|
|
9042
|
-
* what to animate those to.
|
|
9043
|
-
*/
|
|
9044
8926
|
function animateChanges(changedActiveType) {
|
|
9045
|
-
const { props } = visualElement;
|
|
9046
|
-
const
|
|
9047
|
-
/**
|
|
9048
|
-
* A list of animations that we'll build into as we iterate through the animation
|
|
9049
|
-
* types. This will get executed at the end of the function.
|
|
9050
|
-
*/
|
|
8927
|
+
const { props, parent, manuallyAnimateOnMount } = visualElement;
|
|
8928
|
+
const isMounting = isInitialRender || wasReset;
|
|
9051
8929
|
const animations = [];
|
|
9052
8930
|
/**
|
|
9053
|
-
*
|
|
9054
|
-
*
|
|
8931
|
+
* Values removed from a type. A lower-priority type that defines
|
|
8932
|
+
* them animates them back, otherwise they fall back to a base target.
|
|
9055
8933
|
*/
|
|
9056
8934
|
const removedKeys = new Set();
|
|
9057
8935
|
/**
|
|
9058
|
-
*
|
|
9059
|
-
* copy it without iteration. Each time we hit an animation type we set its protected
|
|
9060
|
-
* keys - the keys its not allowed to animate - to the latest version of this object.
|
|
8936
|
+
* Values claimed by active, higher-priority types.
|
|
9061
8937
|
*/
|
|
9062
8938
|
let encounteredKeys = {};
|
|
9063
8939
|
/**
|
|
9064
|
-
*
|
|
9065
|
-
*
|
|
8940
|
+
* Once a type has been deactivated, lower-priority variant labels
|
|
8941
|
+
* re-animate so variant children animate back too.
|
|
9066
8942
|
*/
|
|
9067
8943
|
let removedVariantIndex = Infinity;
|
|
9068
8944
|
/**
|
|
9069
|
-
*
|
|
9070
|
-
*
|
|
9071
|
-
* need to be animated). If any values have been removed, we want to detect those in
|
|
9072
|
-
* lower priority props and flag for animation.
|
|
8945
|
+
* Variant labels are inherited from the closest variant-controlling
|
|
8946
|
+
* ancestor, unless an element on the way has inherit={false}.
|
|
9073
8947
|
*/
|
|
9074
|
-
|
|
9075
|
-
|
|
8948
|
+
let source = visualElement;
|
|
8949
|
+
do
|
|
8950
|
+
source = source.props.inherit !== false && source.parent;
|
|
8951
|
+
while (source && !source.isControllingVariants);
|
|
8952
|
+
reversePriorityOrder.forEach((type, i) => {
|
|
9076
8953
|
const typeState = state[type];
|
|
9077
|
-
const
|
|
9078
|
-
|
|
9079
|
-
|
|
8954
|
+
const ownProp = props[type];
|
|
8955
|
+
const inheritedProp = source && source.props[type];
|
|
8956
|
+
const prop = ownProp !== undefined
|
|
8957
|
+
? ownProp
|
|
8958
|
+
: isVariantLabel(inheritedProp) || inheritedProp === false
|
|
8959
|
+
? inheritedProp
|
|
8960
|
+
: undefined;
|
|
9080
8961
|
const propIsVariant = isVariantLabel(prop);
|
|
9081
|
-
/**
|
|
9082
|
-
* If this type has *just* changed isActive status, set activeDelta
|
|
9083
|
-
* to that status. Otherwise set to null.
|
|
9084
|
-
*/
|
|
9085
8962
|
const activeDelta = type === changedActiveType ? typeState.isActive : null;
|
|
9086
8963
|
if (activeDelta === false)
|
|
9087
8964
|
removedVariantIndex = i;
|
|
9088
8965
|
/**
|
|
9089
|
-
*
|
|
9090
|
-
*
|
|
9091
|
-
*
|
|
9092
|
-
* TODO: Can probably change this to a !isControllingVariants check
|
|
9093
|
-
*/
|
|
9094
|
-
let isInherited = prop === context[type] &&
|
|
9095
|
-
prop !== props[type] &&
|
|
9096
|
-
propIsVariant;
|
|
9097
|
-
if (isInherited &&
|
|
9098
|
-
(isInitialRender || wasReset) &&
|
|
9099
|
-
visualElement.manuallyAnimateOnMount) {
|
|
9100
|
-
isInherited = false;
|
|
9101
|
-
}
|
|
9102
|
-
/**
|
|
9103
|
-
* Set all encountered keys so far as the protected keys for this type. This will
|
|
9104
|
-
* be any key that has been animated or otherwise handled by active, higher-priority types.
|
|
8966
|
+
* Inherited labels are animated by the parent through
|
|
8967
|
+
* variantChildren, unless this element mounted after its parent.
|
|
9105
8968
|
*/
|
|
8969
|
+
const isInherited = propIsVariant &&
|
|
8970
|
+
ownProp === undefined &&
|
|
8971
|
+
!(isMounting && manuallyAnimateOnMount);
|
|
9106
8972
|
typeState.protectedKeys = { ...encounteredKeys };
|
|
9107
|
-
|
|
9108
|
-
if (
|
|
9109
|
-
// If it isn't active and hasn't *just* been set as inactive
|
|
9110
|
-
(!typeState.isActive && activeDelta === null) ||
|
|
9111
|
-
// If we didn't and don't have any defined prop for this animation type
|
|
8973
|
+
if ((!typeState.isActive && activeDelta === null) ||
|
|
9112
8974
|
(!prop && !typeState.prevProp) ||
|
|
9113
|
-
// Or if the prop doesn't define an animation
|
|
9114
8975
|
isAnimationControls(prop) ||
|
|
9115
|
-
typeof prop === "boolean") {
|
|
9116
|
-
|
|
9117
|
-
}
|
|
9118
|
-
/**
|
|
9119
|
-
* If exit is already active and wasn't just activated, skip
|
|
9120
|
-
* re-processing to prevent interrupting running exit animations.
|
|
9121
|
-
* Re-resolving exit with a changed custom value can start new
|
|
9122
|
-
* value animations that stop the originals, leaving the exit
|
|
9123
|
-
* animation promise unresolved and the component stuck in the DOM.
|
|
9124
|
-
*/
|
|
9125
|
-
if (type === "exit" && typeState.isActive && activeDelta !== true) {
|
|
9126
|
-
if (typeState.prevResolvedValues) {
|
|
9127
|
-
encounteredKeys = {
|
|
9128
|
-
...encounteredKeys,
|
|
9129
|
-
...typeState.prevResolvedValues,
|
|
9130
|
-
};
|
|
9131
|
-
}
|
|
9132
|
-
continue;
|
|
8976
|
+
typeof prop === "boolean") {
|
|
8977
|
+
return;
|
|
9133
8978
|
}
|
|
9134
8979
|
/**
|
|
9135
|
-
*
|
|
9136
|
-
*
|
|
9137
|
-
*
|
|
8980
|
+
* Don't re-resolve a running exit: a changed custom would start
|
|
8981
|
+
* new value animations that stop the originals, leaving the exit
|
|
8982
|
+
* promise unresolved and the element stuck in the DOM.
|
|
9138
8983
|
*/
|
|
8984
|
+
if (type === "exit" && typeState.isActive && activeDelta !== true) {
|
|
8985
|
+
encounteredKeys = {
|
|
8986
|
+
...encounteredKeys,
|
|
8987
|
+
...typeState.prevResolvedValues,
|
|
8988
|
+
};
|
|
8989
|
+
return;
|
|
8990
|
+
}
|
|
9139
8991
|
const variantDidChange = checkVariantsDidChange(typeState.prevProp, prop);
|
|
9140
8992
|
let shouldAnimateType = variantDidChange ||
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
typeState.isActive &&
|
|
9144
|
-
!isInherited &&
|
|
9145
|
-
propIsVariant) ||
|
|
9146
|
-
// If we removed a higher-priority variant (i is in reverse order)
|
|
9147
|
-
(i > removedVariantIndex && propIsVariant);
|
|
8993
|
+
(propIsVariant &&
|
|
8994
|
+
((activeDelta && !isInherited) || i > removedVariantIndex));
|
|
9148
8995
|
let handledRemovedValues = false;
|
|
9149
|
-
/**
|
|
9150
|
-
* As animations can be set as variant lists, variants or target objects, we
|
|
9151
|
-
* coerce everything to an array if it isn't one already
|
|
9152
|
-
*/
|
|
9153
8996
|
const definitionList = Array.isArray(prop) ? prop : [prop];
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
* and decide:
|
|
9164
|
-
* 1. If the value has changed, and needs animating
|
|
9165
|
-
* 2. If it has been removed, and needs adding to the removedKeys set
|
|
9166
|
-
* 3. If it has been removed in a higher priority type and needs animating
|
|
9167
|
-
* 4. If it hasn't been removed in a higher priority but hasn't changed, and
|
|
9168
|
-
* needs adding to the type's protectedKeys list.
|
|
9169
|
-
*/
|
|
9170
|
-
const { prevResolvedValues = {} } = typeState;
|
|
9171
|
-
const allKeys = {
|
|
9172
|
-
...prevResolvedValues,
|
|
9173
|
-
...resolvedValues,
|
|
9174
|
-
};
|
|
9175
|
-
const markToAnimate = (key) => {
|
|
9176
|
-
shouldAnimateType = true;
|
|
9177
|
-
if (removedKeys.has(key)) {
|
|
9178
|
-
handledRemovedValues = true;
|
|
9179
|
-
removedKeys.delete(key);
|
|
8997
|
+
let resolvedValues = {};
|
|
8998
|
+
if (activeDelta !== false) {
|
|
8999
|
+
for (const definition of definitionList) {
|
|
9000
|
+
const { transition, transitionEnd, ...target } = resolveVariant(visualElement, definition, getTypeCustom(visualElement, type)) || {};
|
|
9001
|
+
resolvedValues = {
|
|
9002
|
+
...resolvedValues,
|
|
9003
|
+
...target,
|
|
9004
|
+
...transitionEnd,
|
|
9005
|
+
};
|
|
9180
9006
|
}
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
for (const key in allKeys) {
|
|
9007
|
+
}
|
|
9008
|
+
const { prevResolvedValues } = typeState;
|
|
9009
|
+
for (const key in { ...prevResolvedValues, ...resolvedValues }) {
|
|
9010
|
+
if (key in encounteredKeys)
|
|
9011
|
+
continue;
|
|
9187
9012
|
const next = resolvedValues[key];
|
|
9188
9013
|
const prev = prevResolvedValues[key];
|
|
9189
|
-
// If we've already handled this we can just skip ahead
|
|
9190
|
-
if (encounteredKeys.hasOwnProperty(key))
|
|
9191
|
-
continue;
|
|
9192
9014
|
/**
|
|
9193
|
-
*
|
|
9015
|
+
* Keyframes compare by value, but replay whenever the
|
|
9016
|
+
* variant label changed.
|
|
9194
9017
|
*/
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
valueHasChanged = next !== prev;
|
|
9202
|
-
}
|
|
9203
|
-
if (valueHasChanged) {
|
|
9204
|
-
if (next !== undefined && next !== null) {
|
|
9205
|
-
// If next is defined and doesn't equal prev, it needs animating
|
|
9206
|
-
markToAnimate(key);
|
|
9207
|
-
}
|
|
9208
|
-
else {
|
|
9209
|
-
// If it's undefined, it's been removed.
|
|
9210
|
-
removedKeys.add(key);
|
|
9211
|
-
}
|
|
9018
|
+
const valueHasChanged = Array.isArray(next) && Array.isArray(prev)
|
|
9019
|
+
? !shallowCompare(next, prev) || variantDidChange
|
|
9020
|
+
: next !== prev;
|
|
9021
|
+
if (valueHasChanged &&
|
|
9022
|
+
(next === undefined || next === null)) {
|
|
9023
|
+
removedKeys.add(key);
|
|
9212
9024
|
}
|
|
9213
|
-
else if (
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9025
|
+
else if (valueHasChanged ||
|
|
9026
|
+
(next !== undefined && removedKeys.has(key))) {
|
|
9027
|
+
shouldAnimateType = true;
|
|
9028
|
+
if (removedKeys.delete(key))
|
|
9029
|
+
handledRemovedValues = true;
|
|
9030
|
+
typeState.needsAnimating[key] = true;
|
|
9031
|
+
const motionValue = visualElement.getValue(key);
|
|
9032
|
+
if (motionValue)
|
|
9033
|
+
motionValue.liveStyle = false;
|
|
9219
9034
|
}
|
|
9220
9035
|
else {
|
|
9221
|
-
/**
|
|
9222
|
-
* If it hasn't changed, we add it to the list of protected values
|
|
9223
|
-
* to ensure it doesn't get animated.
|
|
9224
|
-
*/
|
|
9225
9036
|
typeState.protectedKeys[key] = true;
|
|
9226
9037
|
}
|
|
9227
9038
|
}
|
|
9228
|
-
/**
|
|
9229
|
-
* Update the typeState so next time animateChanges is called we can compare the
|
|
9230
|
-
* latest prop and resolvedValues to these.
|
|
9231
|
-
*/
|
|
9232
9039
|
typeState.prevProp = prop;
|
|
9233
9040
|
typeState.prevResolvedValues = resolvedValues;
|
|
9234
9041
|
if (typeState.isActive) {
|
|
9235
9042
|
encounteredKeys = { ...encounteredKeys, ...resolvedValues };
|
|
9236
9043
|
}
|
|
9237
|
-
if (
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
}
|
|
9241
|
-
/**
|
|
9242
|
-
* If this is an inherited prop we want to skip this animation
|
|
9243
|
-
* unless the inherited variants haven't changed on this render.
|
|
9244
|
-
*/
|
|
9245
|
-
const willAnimateViaParent = isInherited && variantDidChange;
|
|
9246
|
-
const needsAnimating = !willAnimateViaParent || handledRemovedValues;
|
|
9247
|
-
if (shouldAnimateType && needsAnimating) {
|
|
9248
|
-
animations.push(...definitionList.map((animation) => {
|
|
9044
|
+
if (shouldAnimateType &&
|
|
9045
|
+
(!(isInherited && variantDidChange) || handledRemovedValues)) {
|
|
9046
|
+
for (const animation of definitionList) {
|
|
9249
9047
|
const options = { type };
|
|
9250
9048
|
/**
|
|
9251
|
-
*
|
|
9252
|
-
*
|
|
9253
|
-
*
|
|
9049
|
+
* Elements that mount into an already-mounted parent
|
|
9050
|
+
* animate themselves, staggered by the parent variant's
|
|
9051
|
+
* delayChildren.
|
|
9254
9052
|
*/
|
|
9255
9053
|
if (typeof animation === "string" &&
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
if (parent.enteringChildren && parentVariant) {
|
|
9263
|
-
const { delayChildren } = parentVariant.transition || {};
|
|
9264
|
-
options.delay = calcChildStagger(parent.enteringChildren, visualElement, delayChildren);
|
|
9265
|
-
}
|
|
9054
|
+
isMounting &&
|
|
9055
|
+
manuallyAnimateOnMount &&
|
|
9056
|
+
parent?.enteringChildren) {
|
|
9057
|
+
const delayChildren = resolveVariant(parent, animation)
|
|
9058
|
+
?.transition?.delayChildren;
|
|
9059
|
+
options.delay = calcChildStagger(parent.enteringChildren, visualElement, delayChildren);
|
|
9266
9060
|
}
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
options,
|
|
9270
|
-
};
|
|
9271
|
-
}));
|
|
9061
|
+
animations.push({ animation, options });
|
|
9062
|
+
}
|
|
9272
9063
|
}
|
|
9273
|
-
}
|
|
9064
|
+
});
|
|
9274
9065
|
/**
|
|
9275
|
-
*
|
|
9276
|
-
*
|
|
9277
|
-
*
|
|
9066
|
+
* Removed values that no lower-priority type defines animate back to
|
|
9067
|
+
* initial, then style, then the value as first read. If initial no
|
|
9068
|
+
* longer defines a value it once did, it stays where it is.
|
|
9278
9069
|
*/
|
|
9279
9070
|
if (removedKeys.size) {
|
|
9071
|
+
const { initial } = props;
|
|
9280
9072
|
const fallbackAnimation = {};
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
if (typeof props.initial !== "boolean") {
|
|
9286
|
-
const initialTransition = resolveVariant(visualElement, Array.isArray(props.initial)
|
|
9287
|
-
? props.initial[0]
|
|
9288
|
-
: props.initial);
|
|
9289
|
-
if (initialTransition && initialTransition.transition) {
|
|
9290
|
-
fallbackAnimation.transition = initialTransition.transition;
|
|
9291
|
-
}
|
|
9073
|
+
const resolvedInitial = typeof initial !== "boolean" &&
|
|
9074
|
+
resolveVariant(visualElement, Array.isArray(initial) ? initial[0] : initial, visualElement.presenceContext?.custom);
|
|
9075
|
+
if (resolvedInitial && resolvedInitial.transition) {
|
|
9076
|
+
fallbackAnimation.transition = resolvedInitial.transition;
|
|
9292
9077
|
}
|
|
9293
9078
|
removedKeys.forEach((key) => {
|
|
9294
|
-
const fallbackTarget = visualElement.getBaseTarget(key);
|
|
9295
9079
|
const motionValue = visualElement.getValue(key);
|
|
9296
9080
|
if (motionValue)
|
|
9297
9081
|
motionValue.liveStyle = true;
|
|
9298
|
-
|
|
9299
|
-
|
|
9082
|
+
const fromInitial = resolvedInitial && !Array.isArray(initial)
|
|
9083
|
+
? resolvedInitial[key]
|
|
9084
|
+
: undefined;
|
|
9085
|
+
const fromProps = visualElement.getBaseTargetFromProps(props, key);
|
|
9086
|
+
fallbackAnimation[key] =
|
|
9087
|
+
(fromInitial !== undefined
|
|
9088
|
+
? fromInitial
|
|
9089
|
+
: fromProps !== undefined && !isMotionValue(fromProps)
|
|
9090
|
+
? fromProps
|
|
9091
|
+
: visualElement.initialValues[key] === undefined
|
|
9092
|
+
? visualElement.baseTarget[key]
|
|
9093
|
+
: undefined) ?? null;
|
|
9300
9094
|
});
|
|
9301
9095
|
animations.push({ animation: fallbackAnimation });
|
|
9302
9096
|
}
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
return shouldAnimate ? animate(animations) : Promise.resolve();
|
|
9097
|
+
const blockAnimation = (isMounting && visualElement.blockInitialAnimation) ||
|
|
9098
|
+
(isInitialRender &&
|
|
9099
|
+
!manuallyAnimateOnMount &&
|
|
9100
|
+
(props.initial === false || props.initial === props.animate));
|
|
9101
|
+
isInitialRender = wasReset = false;
|
|
9102
|
+
return !blockAnimation && animations.length
|
|
9103
|
+
? animate(animations)
|
|
9104
|
+
: Promise.resolve();
|
|
9312
9105
|
}
|
|
9313
9106
|
/**
|
|
9314
9107
|
* Change whether a certain animation type is active.
|
|
9315
9108
|
*/
|
|
9316
9109
|
function setActive(type, isActive) {
|
|
9317
|
-
// If the active state hasn't changed, we can safely do nothing here
|
|
9318
9110
|
if (state[type].isActive === isActive)
|
|
9319
9111
|
return Promise.resolve();
|
|
9320
|
-
// Propagate active change to children
|
|
9321
9112
|
visualElement.variantChildren?.forEach((child) => child.animationState?.setActive(type, isActive));
|
|
9322
9113
|
state[type].isActive = isActive;
|
|
9323
9114
|
const animations = animateChanges(type);
|
|
@@ -9329,7 +9120,13 @@
|
|
|
9329
9120
|
return {
|
|
9330
9121
|
animateChanges,
|
|
9331
9122
|
setActive,
|
|
9332
|
-
|
|
9123
|
+
/**
|
|
9124
|
+
* Allows tests to inject a mocked animate function.
|
|
9125
|
+
* @internal
|
|
9126
|
+
*/
|
|
9127
|
+
setAnimateFunction: (makeAnimator) => {
|
|
9128
|
+
animate = makeAnimator(visualElement);
|
|
9129
|
+
},
|
|
9333
9130
|
getState: () => state,
|
|
9334
9131
|
reset: () => {
|
|
9335
9132
|
state = createState();
|
|
@@ -9346,24 +9143,45 @@
|
|
|
9346
9143
|
}
|
|
9347
9144
|
return false;
|
|
9348
9145
|
}
|
|
9349
|
-
function createTypeState(isActive = false) {
|
|
9350
|
-
return {
|
|
9351
|
-
isActive,
|
|
9352
|
-
protectedKeys: {},
|
|
9353
|
-
needsAnimating: {},
|
|
9354
|
-
prevResolvedValues: {},
|
|
9355
|
-
};
|
|
9356
|
-
}
|
|
9357
9146
|
function createState() {
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
}
|
|
9147
|
+
const state = {};
|
|
9148
|
+
for (const type of variantPriorityOrder) {
|
|
9149
|
+
state[type] = {
|
|
9150
|
+
isActive: type === "animate",
|
|
9151
|
+
protectedKeys: {},
|
|
9152
|
+
needsAnimating: {},
|
|
9153
|
+
prevResolvedValues: {},
|
|
9154
|
+
};
|
|
9155
|
+
}
|
|
9156
|
+
return state;
|
|
9157
|
+
}
|
|
9158
|
+
|
|
9159
|
+
const numVariantProps = variantProps.length;
|
|
9160
|
+
/**
|
|
9161
|
+
* Get variant context from a visual element's parent chain.
|
|
9162
|
+
* Uses `any` type for visualElement to avoid circular dependencies.
|
|
9163
|
+
*/
|
|
9164
|
+
function getVariantContext(visualElement) {
|
|
9165
|
+
if (!visualElement)
|
|
9166
|
+
return undefined;
|
|
9167
|
+
if (!visualElement.isControllingVariants) {
|
|
9168
|
+
const context = visualElement.parent
|
|
9169
|
+
? getVariantContext(visualElement.parent) || {}
|
|
9170
|
+
: {};
|
|
9171
|
+
if (visualElement.props.initial !== undefined) {
|
|
9172
|
+
context.initial = visualElement.props.initial;
|
|
9173
|
+
}
|
|
9174
|
+
return context;
|
|
9175
|
+
}
|
|
9176
|
+
const context = {};
|
|
9177
|
+
for (let i = 0; i < numVariantProps; i++) {
|
|
9178
|
+
const name = variantProps[i];
|
|
9179
|
+
const prop = visualElement.props[name];
|
|
9180
|
+
if (isVariantLabel(prop) || prop === false) {
|
|
9181
|
+
context[name] = prop;
|
|
9182
|
+
}
|
|
9183
|
+
}
|
|
9184
|
+
return context;
|
|
9367
9185
|
}
|
|
9368
9186
|
|
|
9369
9187
|
/**
|
|
@@ -9532,6 +9350,75 @@
|
|
|
9532
9350
|
return [callback("x"), callback("y")];
|
|
9533
9351
|
}
|
|
9534
9352
|
|
|
9353
|
+
function pixelsToPercent(pixels, axis) {
|
|
9354
|
+
if (axis.max === axis.min)
|
|
9355
|
+
return 0;
|
|
9356
|
+
return (pixels / (axis.max - axis.min)) * 100;
|
|
9357
|
+
}
|
|
9358
|
+
/**
|
|
9359
|
+
* We always correct borderRadius as a percentage rather than pixels to reduce paints.
|
|
9360
|
+
* For example, if you are projecting a box that is 100px wide with a 10px borderRadius
|
|
9361
|
+
* into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
|
|
9362
|
+
* borderRadius in both states. If we animate between the two in pixels that will trigger
|
|
9363
|
+
* a paint each time. If we animate between the two in percentage we'll avoid a paint.
|
|
9364
|
+
*/
|
|
9365
|
+
const correctBorderRadius = {
|
|
9366
|
+
correct: (latest, node) => {
|
|
9367
|
+
if (!node.target)
|
|
9368
|
+
return latest;
|
|
9369
|
+
/**
|
|
9370
|
+
* If latest is a string, if it's a percentage we can return immediately as it's
|
|
9371
|
+
* going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
|
|
9372
|
+
*/
|
|
9373
|
+
if (typeof latest === "string") {
|
|
9374
|
+
if (px.test(latest)) {
|
|
9375
|
+
latest = parseFloat(latest);
|
|
9376
|
+
}
|
|
9377
|
+
else {
|
|
9378
|
+
return latest;
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
9381
|
+
/**
|
|
9382
|
+
* If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
|
|
9383
|
+
* pixel value as a percentage of each axis
|
|
9384
|
+
*/
|
|
9385
|
+
const x = pixelsToPercent(latest, node.target.x);
|
|
9386
|
+
const y = pixelsToPercent(latest, node.target.y);
|
|
9387
|
+
return `${x}% ${y}%`;
|
|
9388
|
+
},
|
|
9389
|
+
};
|
|
9390
|
+
|
|
9391
|
+
const correctBoxShadow = {
|
|
9392
|
+
correct: (latest, { treeScale, projectionDelta }) => {
|
|
9393
|
+
const original = latest;
|
|
9394
|
+
const shadow = complex.parse(latest);
|
|
9395
|
+
// TODO: Doesn't support multiple shadows
|
|
9396
|
+
if (shadow.length > 5)
|
|
9397
|
+
return original;
|
|
9398
|
+
const template = complex.createTransformer(latest);
|
|
9399
|
+
const offset = typeof shadow[0] !== "number" ? 1 : 0;
|
|
9400
|
+
// Calculate the overall context scale
|
|
9401
|
+
const xScale = projectionDelta.x.scale * treeScale.x;
|
|
9402
|
+
const yScale = projectionDelta.y.scale * treeScale.y;
|
|
9403
|
+
shadow[0 + offset] /= xScale;
|
|
9404
|
+
shadow[1 + offset] /= yScale;
|
|
9405
|
+
/**
|
|
9406
|
+
* Ideally we'd correct x and y scales individually, but because blur and
|
|
9407
|
+
* spread apply to both we have to take a scale average and apply that instead.
|
|
9408
|
+
* We could potentially improve the outcome of this by incorporating the ratio between
|
|
9409
|
+
* the two scales.
|
|
9410
|
+
*/
|
|
9411
|
+
const averageScale = mixNumber$1(xScale, yScale, 0.5);
|
|
9412
|
+
// Blur
|
|
9413
|
+
if (typeof shadow[2 + offset] === "number")
|
|
9414
|
+
shadow[2 + offset] /= averageScale;
|
|
9415
|
+
// Spread
|
|
9416
|
+
if (typeof shadow[3 + offset] === "number")
|
|
9417
|
+
shadow[3 + offset] /= averageScale;
|
|
9418
|
+
return template(shadow);
|
|
9419
|
+
},
|
|
9420
|
+
};
|
|
9421
|
+
|
|
9535
9422
|
function buildProjectionTransform(delta, treeScale, latestTransform) {
|
|
9536
9423
|
let transform = "";
|
|
9537
9424
|
/**
|
|
@@ -9584,7 +9471,6 @@
|
|
|
9584
9471
|
}
|
|
9585
9472
|
|
|
9586
9473
|
const numBorders = cornerRadiusProps.length;
|
|
9587
|
-
const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
|
|
9588
9474
|
const isPx = (value) => typeof value === "number" || px.test(value);
|
|
9589
9475
|
function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
|
|
9590
9476
|
if (shouldCrossfadeOpacity) {
|
|
@@ -9783,6 +9669,18 @@
|
|
|
9783
9669
|
}
|
|
9784
9670
|
}
|
|
9785
9671
|
|
|
9672
|
+
const defaultScaleCorrectors = {
|
|
9673
|
+
borderRadius: {
|
|
9674
|
+
...correctBorderRadius,
|
|
9675
|
+
applyTo: [...cornerRadiusProps],
|
|
9676
|
+
},
|
|
9677
|
+
borderTopLeftRadius: correctBorderRadius,
|
|
9678
|
+
borderTopRightRadius: correctBorderRadius,
|
|
9679
|
+
borderBottomLeftRadius: correctBorderRadius,
|
|
9680
|
+
borderBottomRightRadius: correctBorderRadius,
|
|
9681
|
+
boxShadow: correctBoxShadow,
|
|
9682
|
+
};
|
|
9683
|
+
|
|
9786
9684
|
/**
|
|
9787
9685
|
* This should only ever be modified on the client otherwise it'll
|
|
9788
9686
|
* persist through server requests. If we need instanced states we
|
|
@@ -9801,6 +9699,14 @@
|
|
|
9801
9699
|
hasEverUpdated: false,
|
|
9802
9700
|
};
|
|
9803
9701
|
|
|
9702
|
+
/**
|
|
9703
|
+
* Correctors registered via addScaleCorrector before projection loaded
|
|
9704
|
+
* (e.g. with an async LazyMotion bundle) take precedence over the defaults.
|
|
9705
|
+
*/
|
|
9706
|
+
Object.assign(scaleCorrectors, {
|
|
9707
|
+
...defaultScaleCorrectors,
|
|
9708
|
+
...scaleCorrectors,
|
|
9709
|
+
});
|
|
9804
9710
|
const metrics = {
|
|
9805
9711
|
nodes: 0,
|
|
9806
9712
|
calculatedTargetDeltas: 0,
|
|
@@ -12556,12 +12462,6 @@
|
|
|
12556
12462
|
};
|
|
12557
12463
|
const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
|
|
12558
12464
|
|
|
12559
|
-
function canUseNativeTimeline(target) {
|
|
12560
|
-
if (typeof window === "undefined")
|
|
12561
|
-
return false;
|
|
12562
|
-
return target ? supportsViewTimeline() : supportsScrollTimeline();
|
|
12563
|
-
}
|
|
12564
|
-
|
|
12565
12465
|
/**
|
|
12566
12466
|
* A time in milliseconds, beyond which we consider the scroll velocity to be 0.
|
|
12567
12467
|
*/
|
|
@@ -12701,18 +12601,6 @@
|
|
|
12701
12601
|
}
|
|
12702
12602
|
|
|
12703
12603
|
const ScrollOffset = {
|
|
12704
|
-
Enter: [
|
|
12705
|
-
[0, 1],
|
|
12706
|
-
[1, 1],
|
|
12707
|
-
],
|
|
12708
|
-
Exit: [
|
|
12709
|
-
[0, 0],
|
|
12710
|
-
[1, 0],
|
|
12711
|
-
],
|
|
12712
|
-
Any: [
|
|
12713
|
-
[1, 0],
|
|
12714
|
-
[0, 1],
|
|
12715
|
-
],
|
|
12716
12604
|
All: [
|
|
12717
12605
|
[0, 0],
|
|
12718
12606
|
[1, 1],
|
|
@@ -12964,158 +12852,163 @@
|
|
|
12964
12852
|
};
|
|
12965
12853
|
}
|
|
12966
12854
|
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
* ViewTimeline named ranges. Returns undefined for unrecognised patterns,
|
|
12970
|
-
* which signals the caller to fall back to JS-based scroll tracking.
|
|
12971
|
-
*/
|
|
12972
|
-
const presets = [
|
|
12973
|
-
[ScrollOffset.Enter, "entry"],
|
|
12974
|
-
[ScrollOffset.Exit, "exit"],
|
|
12975
|
-
[ScrollOffset.Any, "cover"],
|
|
12976
|
-
[ScrollOffset.All, "contain"],
|
|
12977
|
-
];
|
|
12978
|
-
const stringToProgress = {
|
|
12979
|
-
start: 0,
|
|
12980
|
-
end: 1,
|
|
12981
|
-
};
|
|
12982
|
-
function parseStringOffset(s) {
|
|
12983
|
-
const parts = s.trim().split(/\s+/);
|
|
12984
|
-
if (parts.length !== 2)
|
|
12985
|
-
return undefined;
|
|
12986
|
-
const a = stringToProgress[parts[0]];
|
|
12987
|
-
const b = stringToProgress[parts[1]];
|
|
12988
|
-
if (a === undefined || b === undefined)
|
|
12989
|
-
return undefined;
|
|
12990
|
-
return [a, b];
|
|
12991
|
-
}
|
|
12992
|
-
function normaliseOffset(offset) {
|
|
12993
|
-
if (offset.length !== 2)
|
|
12994
|
-
return undefined;
|
|
12995
|
-
const result = [];
|
|
12996
|
-
for (const item of offset) {
|
|
12997
|
-
if (Array.isArray(item)) {
|
|
12998
|
-
result.push(item);
|
|
12999
|
-
}
|
|
13000
|
-
else if (typeof item === "string") {
|
|
13001
|
-
const parsed = parseStringOffset(item);
|
|
13002
|
-
if (!parsed)
|
|
13003
|
-
return undefined;
|
|
13004
|
-
result.push(parsed);
|
|
13005
|
-
}
|
|
13006
|
-
else {
|
|
13007
|
-
return undefined;
|
|
13008
|
-
}
|
|
13009
|
-
}
|
|
13010
|
-
return result;
|
|
13011
|
-
}
|
|
13012
|
-
function matchesPreset(offset, preset) {
|
|
13013
|
-
const normalised = normaliseOffset(offset);
|
|
13014
|
-
if (!normalised)
|
|
12855
|
+
function canUseNativeTimeline(target) {
|
|
12856
|
+
if (typeof window === "undefined")
|
|
13015
12857
|
return false;
|
|
13016
|
-
|
|
13017
|
-
const o = normalised[i];
|
|
13018
|
-
const p = preset[i];
|
|
13019
|
-
if (o[0] !== p[0] || o[1] !== p[1])
|
|
13020
|
-
return false;
|
|
13021
|
-
}
|
|
13022
|
-
return true;
|
|
13023
|
-
}
|
|
13024
|
-
function offsetToViewTimelineRange(offset) {
|
|
13025
|
-
if (!offset) {
|
|
13026
|
-
return { rangeStart: "contain 0%", rangeEnd: "contain 100%" };
|
|
13027
|
-
}
|
|
13028
|
-
for (const [preset, name] of presets) {
|
|
13029
|
-
if (matchesPreset(offset, preset)) {
|
|
13030
|
-
return { rangeStart: `${name} 0%`, rangeEnd: `${name} 100%` };
|
|
13031
|
-
}
|
|
13032
|
-
}
|
|
13033
|
-
return undefined;
|
|
12858
|
+
return target ? supportsViewTimeline() : supportsScrollTimeline();
|
|
13034
12859
|
}
|
|
13035
12860
|
|
|
13036
12861
|
const timelineCache = new Map();
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
}
|
|
13044
|
-
function getTimeline({ container, ...options }) {
|
|
13045
|
-
const { axis } = options;
|
|
12862
|
+
/**
|
|
12863
|
+
* Native timelines are only attached to WAAPI animations. Offsets are
|
|
12864
|
+
* applied to each animation as a range, so a timeline is shared by every
|
|
12865
|
+
* offset.
|
|
12866
|
+
*/
|
|
12867
|
+
function getTimeline({ container, target, axis, }) {
|
|
13046
12868
|
let containerCache = timelineCache.get(container);
|
|
13047
12869
|
if (!containerCache) {
|
|
13048
12870
|
containerCache = new Map();
|
|
13049
12871
|
timelineCache.set(container, containerCache);
|
|
13050
12872
|
}
|
|
13051
|
-
const targetKey =
|
|
12873
|
+
const targetKey = target ?? "self";
|
|
13052
12874
|
let targetCache = containerCache.get(targetKey);
|
|
13053
12875
|
if (!targetCache) {
|
|
13054
12876
|
targetCache = {};
|
|
13055
12877
|
containerCache.set(targetKey, targetCache);
|
|
13056
12878
|
}
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
|
|
13079
|
-
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
12879
|
+
return (targetCache[axis] || (targetCache[axis] = target
|
|
12880
|
+
? new ViewTimeline({ subject: target, axis })
|
|
12881
|
+
: new ScrollTimeline({ source: container, axis })));
|
|
12882
|
+
}
|
|
12883
|
+
|
|
12884
|
+
/**
|
|
12885
|
+
* Resolved offsets are linear in the target and container lengths, so
|
|
12886
|
+
* probing them gives [target progress, container progress, pixels].
|
|
12887
|
+
* vw/vh can resolve to 0px, so they're rejected up front.
|
|
12888
|
+
*/
|
|
12889
|
+
const toIntersection = (o) => {
|
|
12890
|
+
if (/v/u.test(o))
|
|
12891
|
+
return [];
|
|
12892
|
+
const px = resolveOffset(o, 0, 0, 0);
|
|
12893
|
+
return [resolveOffset(o, 0, 1, 0) - px, px - resolveOffset(o, 1, 0, 0), px];
|
|
12894
|
+
};
|
|
12895
|
+
const toRange = ([t, c, px]) => !px &&
|
|
12896
|
+
(c === 0 || c === 1) &&
|
|
12897
|
+
`${c ? "entry" : "exit"}-crossing ${t * 100}%`;
|
|
12898
|
+
/**
|
|
12899
|
+
* Maps an offset to an equivalent ViewTimeline range. Returns undefined when
|
|
12900
|
+
* there isn't one, which signals the caller to fall back to JS-based scroll
|
|
12901
|
+
* tracking.
|
|
12902
|
+
*/
|
|
12903
|
+
function offsetToViewTimelineRange(offset = ScrollOffset.All) {
|
|
12904
|
+
if (offset.length !== 2)
|
|
12905
|
+
return;
|
|
12906
|
+
const [start, end] = offset.map(toIntersection);
|
|
12907
|
+
const points = [toRange(start), toRange(end)];
|
|
12908
|
+
const a = end[0] - start[0];
|
|
12909
|
+
const b = start[1] - end[1];
|
|
12910
|
+
if (points[0] && points[1] && (a || b)) {
|
|
12911
|
+
return {
|
|
12912
|
+
points: points,
|
|
12913
|
+
a,
|
|
12914
|
+
b,
|
|
12915
|
+
cover: !start[0] && a === 1 && b === 1,
|
|
12916
|
+
};
|
|
13086
12917
|
}
|
|
13087
|
-
return targetCache[axisKey];
|
|
13088
12918
|
}
|
|
13089
12919
|
|
|
13090
12920
|
function attachToAnimation(animation, options) {
|
|
13091
|
-
const
|
|
13092
|
-
const range = options.
|
|
13093
|
-
? offsetToViewTimelineRange(options.offset)
|
|
13094
|
-
: undefined;
|
|
12921
|
+
const { target, container, axis } = options;
|
|
12922
|
+
const range = target && offsetToViewTimelineRange(options.offset);
|
|
13095
12923
|
/**
|
|
13096
12924
|
* Use native timeline when:
|
|
13097
|
-
* - No target: ScrollTimeline
|
|
12925
|
+
* - No target and no offset: ScrollTimeline
|
|
13098
12926
|
* - Target with mappable offset: ViewTimeline with named range
|
|
13099
|
-
* -
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
12927
|
+
* - Otherwise: fall back to JS observe. A ScrollTimeline has no offset,
|
|
12928
|
+
* so page offsets are applied by the JS timeline.
|
|
12929
|
+
*/
|
|
12930
|
+
const native = canUseNativeTimeline(target) && (target ? !!range : !options.offset);
|
|
12931
|
+
const animations = new Map();
|
|
12932
|
+
const observed = new Set();
|
|
12933
|
+
let stopObserving;
|
|
12934
|
+
let reverse = false;
|
|
12935
|
+
/**
|
|
12936
|
+
* When an offset's second point comes first, its range is swapped and
|
|
12937
|
+
* the animation played backwards. The range is set after the direction,
|
|
12938
|
+
* as that's what realigns a running animation with its timeline. Chrome
|
|
12939
|
+
* skips that realignment when the range changes in the frame the
|
|
12940
|
+
* animation starts, leaving it offset for good, so it's replayed too.
|
|
12941
|
+
*/
|
|
12942
|
+
const apply = (direction, waapi) => {
|
|
12943
|
+
const [start, end] = range.points;
|
|
12944
|
+
waapi.effect.updateTiming({
|
|
12945
|
+
direction: reverse
|
|
12946
|
+
? direction === "normal"
|
|
12947
|
+
? "reverse"
|
|
12948
|
+
: "alternate-reverse"
|
|
12949
|
+
: direction,
|
|
12950
|
+
});
|
|
12951
|
+
Object.assign(waapi, {
|
|
12952
|
+
rangeStart: reverse ? end : start,
|
|
12953
|
+
rangeEnd: reverse ? start : end,
|
|
12954
|
+
});
|
|
12955
|
+
waapi.play();
|
|
12956
|
+
};
|
|
12957
|
+
const stops = [
|
|
12958
|
+
animation.attachTimeline({
|
|
12959
|
+
/**
|
|
12960
|
+
* Read only by WAAPI animations, so values driven from JS don't
|
|
12961
|
+
* create a native timeline they won't use.
|
|
12962
|
+
*/
|
|
12963
|
+
get timeline() {
|
|
12964
|
+
return native ? getTimeline(options) : undefined;
|
|
12965
|
+
},
|
|
12966
|
+
onAttach: range &&
|
|
12967
|
+
((waapi) => {
|
|
12968
|
+
const direction = waapi.effect.getTiming().direction;
|
|
12969
|
+
animations.set(waapi, direction);
|
|
12970
|
+
apply(direction, waapi);
|
|
12971
|
+
}),
|
|
12972
|
+
/**
|
|
12973
|
+
* Values driven from JS all track the offset with scrollInfo,
|
|
12974
|
+
* which measures once per frame for all of them.
|
|
12975
|
+
*/
|
|
12976
|
+
observe: (valueAnimation) => {
|
|
12977
|
+
valueAnimation.pause();
|
|
12978
|
+
observed.add(valueAnimation);
|
|
12979
|
+
stopObserving || (stopObserving = scrollInfo((info) => {
|
|
12980
|
+
observed.forEach((observedAnimation) => {
|
|
12981
|
+
observedAnimation.time =
|
|
12982
|
+
observedAnimation.iterationDuration *
|
|
12983
|
+
info[axis].progress;
|
|
12984
|
+
});
|
|
12985
|
+
}, options));
|
|
12986
|
+
return () => observed.delete(valueAnimation);
|
|
12987
|
+
},
|
|
13110
12988
|
}),
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
12989
|
+
];
|
|
12990
|
+
if (native && range) {
|
|
12991
|
+
const { a, b } = range;
|
|
12992
|
+
const length = axis === "y" ? "clientHeight" : "clientWidth";
|
|
12993
|
+
/**
|
|
12994
|
+
* Offsets like All only run forwards when the target is longer than
|
|
12995
|
+
* the container, so their direction is remeasured on resize.
|
|
12996
|
+
*/
|
|
12997
|
+
const update = () => {
|
|
12998
|
+
if (reverse !==
|
|
12999
|
+
(reverse = a * target[length] + b * container[length] < 0)) {
|
|
13000
|
+
animations.forEach(apply);
|
|
13001
|
+
}
|
|
13002
|
+
};
|
|
13003
|
+
update();
|
|
13004
|
+
if (a * b < 0) {
|
|
13005
|
+
stops.push(resize(update), resize([target, container], update));
|
|
13006
|
+
}
|
|
13007
|
+
}
|
|
13008
|
+
return () => {
|
|
13009
|
+
stops.forEach((stop) => stop());
|
|
13010
|
+
stopObserving?.();
|
|
13011
|
+
};
|
|
13119
13012
|
}
|
|
13120
13013
|
|
|
13121
13014
|
function scroll(onScroll, { axis = "y", source, container = document.scrollingElement, ...options } = {}) {
|
|
@@ -13418,6 +13311,7 @@
|
|
|
13418
13311
|
exports.observeTimeline = observeTimeline;
|
|
13419
13312
|
exports.optimizedAppearDataAttribute = optimizedAppearDataAttribute;
|
|
13420
13313
|
exports.optimizedAppearDataId = optimizedAppearDataId;
|
|
13314
|
+
exports.originProps = originProps;
|
|
13421
13315
|
exports.parseAnimateLayoutArgs = parseAnimateLayoutArgs;
|
|
13422
13316
|
exports.parseCSSVariable = parseCSSVariable;
|
|
13423
13317
|
exports.parseValueFromTransform = parseValueFromTransform;
|