motion 13.4.5 → 13.5.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/dist/motion.dev.js +549 -496
- 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,14 @@
|
|
|
3000
3037
|
/**
|
|
3001
3038
|
* Attaches a timeline to the animation, for instance the `ScrollTimeline`.
|
|
3002
3039
|
*/
|
|
3003
|
-
attachTimeline({ timeline,
|
|
3040
|
+
attachTimeline({ timeline, onAttach, observe, }) {
|
|
3004
3041
|
if (this.allowFlatten) {
|
|
3005
3042
|
this.animation.effect?.updateTiming({ easing: "linear" });
|
|
3006
3043
|
}
|
|
3007
3044
|
this.animation.onfinish = null;
|
|
3008
3045
|
if (timeline && supportsScrollTimeline()) {
|
|
3009
3046
|
this.animation.timeline = timeline;
|
|
3010
|
-
|
|
3011
|
-
this.animation.rangeStart = rangeStart;
|
|
3012
|
-
if (rangeEnd)
|
|
3013
|
-
this.animation.rangeEnd = rangeEnd;
|
|
3047
|
+
onAttach?.(this.animation);
|
|
3014
3048
|
return noop;
|
|
3015
3049
|
}
|
|
3016
3050
|
else {
|
|
@@ -6343,21 +6377,6 @@
|
|
|
6343
6377
|
return typeof a === "function" ? resizeWindow(a) : resizeElement(a, b);
|
|
6344
6378
|
}
|
|
6345
6379
|
|
|
6346
|
-
function observeTimeline(update, timeline) {
|
|
6347
|
-
let prevProgress;
|
|
6348
|
-
const onFrame = () => {
|
|
6349
|
-
const { currentTime } = timeline;
|
|
6350
|
-
const percentage = currentTime === null ? 0 : currentTime.value;
|
|
6351
|
-
const progress = percentage / 100;
|
|
6352
|
-
if (prevProgress !== progress) {
|
|
6353
|
-
update(progress);
|
|
6354
|
-
}
|
|
6355
|
-
prevProgress = progress;
|
|
6356
|
-
};
|
|
6357
|
-
frame.preUpdate(onFrame, true);
|
|
6358
|
-
return () => cancelFrame(onFrame);
|
|
6359
|
-
}
|
|
6360
|
-
|
|
6361
6380
|
const statsBuffer = {
|
|
6362
6381
|
value: null,
|
|
6363
6382
|
addProjectionMetrics: null,
|
|
@@ -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
|
/**
|
|
@@ -8476,7 +8507,15 @@
|
|
|
8476
8507
|
this.removeValue(key);
|
|
8477
8508
|
this.bindToMotionValue(key, value);
|
|
8478
8509
|
this.values.set(key, value);
|
|
8479
|
-
|
|
8510
|
+
/**
|
|
8511
|
+
* An animated value with no base value is undefined until the
|
|
8512
|
+
* keyframe resolver reads its origin. Rendering it before then
|
|
8513
|
+
* writes an invalid placeholder (e.g. points="undefined") that
|
|
8514
|
+
* the resolver can read back from the DOM as the origin.
|
|
8515
|
+
*/
|
|
8516
|
+
const latest = value.get();
|
|
8517
|
+
if (latest !== undefined)
|
|
8518
|
+
this.latestValues[key] = latest;
|
|
8480
8519
|
}
|
|
8481
8520
|
}
|
|
8482
8521
|
/**
|
|
@@ -8504,7 +8543,9 @@
|
|
|
8504
8543
|
}
|
|
8505
8544
|
let value = this.values.get(key);
|
|
8506
8545
|
if (value === undefined && defaultValue !== undefined) {
|
|
8507
|
-
value = motionValue(defaultValue
|
|
8546
|
+
value = motionValue(defaultValue ?? this.getDefaultValue?.(key), {
|
|
8547
|
+
owner: this,
|
|
8548
|
+
});
|
|
8508
8549
|
this.addValue(key, value);
|
|
8509
8550
|
}
|
|
8510
8551
|
return value;
|
|
@@ -8666,86 +8707,7 @@
|
|
|
8666
8707
|
}
|
|
8667
8708
|
}
|
|
8668
8709
|
|
|
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
|
-
};
|
|
8710
|
+
const scaleCorrectors = {};
|
|
8749
8711
|
function addScaleCorrector(correctors) {
|
|
8750
8712
|
for (const key in correctors) {
|
|
8751
8713
|
scaleCorrectors[key] = correctors[key];
|
|
@@ -8919,7 +8881,24 @@
|
|
|
8919
8881
|
this.measureInstanceViewportBox = createBox;
|
|
8920
8882
|
}
|
|
8921
8883
|
getBaseTargetFromProps(props, key) {
|
|
8922
|
-
|
|
8884
|
+
/**
|
|
8885
|
+
* An independent transform's base is never an attribute of the same
|
|
8886
|
+
* name, e.g. <rect x> is a position, not a translate. Like HTML it
|
|
8887
|
+
* comes from style, falling back to the transform's default.
|
|
8888
|
+
*/
|
|
8889
|
+
return transformProps.has(key)
|
|
8890
|
+
? super.getBaseTargetFromProps(props, key) ??
|
|
8891
|
+
defaultTransformValue(key)
|
|
8892
|
+
: props[key];
|
|
8893
|
+
}
|
|
8894
|
+
/**
|
|
8895
|
+
* An independent transform's origin is never read from the DOM, so it
|
|
8896
|
+
* starts from its latest or default value and renders on mount.
|
|
8897
|
+
*/
|
|
8898
|
+
getDefaultValue(key) {
|
|
8899
|
+
return transformProps.has(key)
|
|
8900
|
+
? this.latestValues[key] ?? defaultTransformValue(key)
|
|
8901
|
+
: undefined;
|
|
8923
8902
|
}
|
|
8924
8903
|
readValueFromInstance(instance, key) {
|
|
8925
8904
|
if (transformProps.has(key)) {
|
|
@@ -9532,6 +9511,75 @@
|
|
|
9532
9511
|
return [callback("x"), callback("y")];
|
|
9533
9512
|
}
|
|
9534
9513
|
|
|
9514
|
+
function pixelsToPercent(pixels, axis) {
|
|
9515
|
+
if (axis.max === axis.min)
|
|
9516
|
+
return 0;
|
|
9517
|
+
return (pixels / (axis.max - axis.min)) * 100;
|
|
9518
|
+
}
|
|
9519
|
+
/**
|
|
9520
|
+
* We always correct borderRadius as a percentage rather than pixels to reduce paints.
|
|
9521
|
+
* For example, if you are projecting a box that is 100px wide with a 10px borderRadius
|
|
9522
|
+
* into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
|
|
9523
|
+
* borderRadius in both states. If we animate between the two in pixels that will trigger
|
|
9524
|
+
* a paint each time. If we animate between the two in percentage we'll avoid a paint.
|
|
9525
|
+
*/
|
|
9526
|
+
const correctBorderRadius = {
|
|
9527
|
+
correct: (latest, node) => {
|
|
9528
|
+
if (!node.target)
|
|
9529
|
+
return latest;
|
|
9530
|
+
/**
|
|
9531
|
+
* If latest is a string, if it's a percentage we can return immediately as it's
|
|
9532
|
+
* going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
|
|
9533
|
+
*/
|
|
9534
|
+
if (typeof latest === "string") {
|
|
9535
|
+
if (px.test(latest)) {
|
|
9536
|
+
latest = parseFloat(latest);
|
|
9537
|
+
}
|
|
9538
|
+
else {
|
|
9539
|
+
return latest;
|
|
9540
|
+
}
|
|
9541
|
+
}
|
|
9542
|
+
/**
|
|
9543
|
+
* If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
|
|
9544
|
+
* pixel value as a percentage of each axis
|
|
9545
|
+
*/
|
|
9546
|
+
const x = pixelsToPercent(latest, node.target.x);
|
|
9547
|
+
const y = pixelsToPercent(latest, node.target.y);
|
|
9548
|
+
return `${x}% ${y}%`;
|
|
9549
|
+
},
|
|
9550
|
+
};
|
|
9551
|
+
|
|
9552
|
+
const correctBoxShadow = {
|
|
9553
|
+
correct: (latest, { treeScale, projectionDelta }) => {
|
|
9554
|
+
const original = latest;
|
|
9555
|
+
const shadow = complex.parse(latest);
|
|
9556
|
+
// TODO: Doesn't support multiple shadows
|
|
9557
|
+
if (shadow.length > 5)
|
|
9558
|
+
return original;
|
|
9559
|
+
const template = complex.createTransformer(latest);
|
|
9560
|
+
const offset = typeof shadow[0] !== "number" ? 1 : 0;
|
|
9561
|
+
// Calculate the overall context scale
|
|
9562
|
+
const xScale = projectionDelta.x.scale * treeScale.x;
|
|
9563
|
+
const yScale = projectionDelta.y.scale * treeScale.y;
|
|
9564
|
+
shadow[0 + offset] /= xScale;
|
|
9565
|
+
shadow[1 + offset] /= yScale;
|
|
9566
|
+
/**
|
|
9567
|
+
* Ideally we'd correct x and y scales individually, but because blur and
|
|
9568
|
+
* spread apply to both we have to take a scale average and apply that instead.
|
|
9569
|
+
* We could potentially improve the outcome of this by incorporating the ratio between
|
|
9570
|
+
* the two scales.
|
|
9571
|
+
*/
|
|
9572
|
+
const averageScale = mixNumber$1(xScale, yScale, 0.5);
|
|
9573
|
+
// Blur
|
|
9574
|
+
if (typeof shadow[2 + offset] === "number")
|
|
9575
|
+
shadow[2 + offset] /= averageScale;
|
|
9576
|
+
// Spread
|
|
9577
|
+
if (typeof shadow[3 + offset] === "number")
|
|
9578
|
+
shadow[3 + offset] /= averageScale;
|
|
9579
|
+
return template(shadow);
|
|
9580
|
+
},
|
|
9581
|
+
};
|
|
9582
|
+
|
|
9535
9583
|
function buildProjectionTransform(delta, treeScale, latestTransform) {
|
|
9536
9584
|
let transform = "";
|
|
9537
9585
|
/**
|
|
@@ -9584,7 +9632,6 @@
|
|
|
9584
9632
|
}
|
|
9585
9633
|
|
|
9586
9634
|
const numBorders = cornerRadiusProps.length;
|
|
9587
|
-
const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
|
|
9588
9635
|
const isPx = (value) => typeof value === "number" || px.test(value);
|
|
9589
9636
|
function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
|
|
9590
9637
|
if (shouldCrossfadeOpacity) {
|
|
@@ -9783,6 +9830,18 @@
|
|
|
9783
9830
|
}
|
|
9784
9831
|
}
|
|
9785
9832
|
|
|
9833
|
+
const defaultScaleCorrectors = {
|
|
9834
|
+
borderRadius: {
|
|
9835
|
+
...correctBorderRadius,
|
|
9836
|
+
applyTo: [...cornerRadiusProps],
|
|
9837
|
+
},
|
|
9838
|
+
borderTopLeftRadius: correctBorderRadius,
|
|
9839
|
+
borderTopRightRadius: correctBorderRadius,
|
|
9840
|
+
borderBottomLeftRadius: correctBorderRadius,
|
|
9841
|
+
borderBottomRightRadius: correctBorderRadius,
|
|
9842
|
+
boxShadow: correctBoxShadow,
|
|
9843
|
+
};
|
|
9844
|
+
|
|
9786
9845
|
/**
|
|
9787
9846
|
* This should only ever be modified on the client otherwise it'll
|
|
9788
9847
|
* persist through server requests. If we need instanced states we
|
|
@@ -9801,6 +9860,14 @@
|
|
|
9801
9860
|
hasEverUpdated: false,
|
|
9802
9861
|
};
|
|
9803
9862
|
|
|
9863
|
+
/**
|
|
9864
|
+
* Correctors registered via addScaleCorrector before projection loaded
|
|
9865
|
+
* (e.g. with an async LazyMotion bundle) take precedence over the defaults.
|
|
9866
|
+
*/
|
|
9867
|
+
Object.assign(scaleCorrectors, {
|
|
9868
|
+
...defaultScaleCorrectors,
|
|
9869
|
+
...scaleCorrectors,
|
|
9870
|
+
});
|
|
9804
9871
|
const metrics = {
|
|
9805
9872
|
nodes: 0,
|
|
9806
9873
|
calculatedTargetDeltas: 0,
|
|
@@ -12556,12 +12623,6 @@
|
|
|
12556
12623
|
};
|
|
12557
12624
|
const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
|
|
12558
12625
|
|
|
12559
|
-
function canUseNativeTimeline(target) {
|
|
12560
|
-
if (typeof window === "undefined")
|
|
12561
|
-
return false;
|
|
12562
|
-
return target ? supportsViewTimeline() : supportsScrollTimeline();
|
|
12563
|
-
}
|
|
12564
|
-
|
|
12565
12626
|
/**
|
|
12566
12627
|
* A time in milliseconds, beyond which we consider the scroll velocity to be 0.
|
|
12567
12628
|
*/
|
|
@@ -12701,18 +12762,6 @@
|
|
|
12701
12762
|
}
|
|
12702
12763
|
|
|
12703
12764
|
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
12765
|
All: [
|
|
12717
12766
|
[0, 0],
|
|
12718
12767
|
[1, 1],
|
|
@@ -12964,158 +13013,163 @@
|
|
|
12964
13013
|
};
|
|
12965
13014
|
}
|
|
12966
13015
|
|
|
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)
|
|
13016
|
+
function canUseNativeTimeline(target) {
|
|
13017
|
+
if (typeof window === "undefined")
|
|
13015
13018
|
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;
|
|
13019
|
+
return target ? supportsViewTimeline() : supportsScrollTimeline();
|
|
13034
13020
|
}
|
|
13035
13021
|
|
|
13036
13022
|
const timelineCache = new Map();
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
}
|
|
13044
|
-
function getTimeline({ container, ...options }) {
|
|
13045
|
-
const { axis } = options;
|
|
13023
|
+
/**
|
|
13024
|
+
* Native timelines are only attached to WAAPI animations. Offsets are
|
|
13025
|
+
* applied to each animation as a range, so a timeline is shared by every
|
|
13026
|
+
* offset.
|
|
13027
|
+
*/
|
|
13028
|
+
function getTimeline({ container, target, axis, }) {
|
|
13046
13029
|
let containerCache = timelineCache.get(container);
|
|
13047
13030
|
if (!containerCache) {
|
|
13048
13031
|
containerCache = new Map();
|
|
13049
13032
|
timelineCache.set(container, containerCache);
|
|
13050
13033
|
}
|
|
13051
|
-
const targetKey =
|
|
13034
|
+
const targetKey = target ?? "self";
|
|
13052
13035
|
let targetCache = containerCache.get(targetKey);
|
|
13053
13036
|
if (!targetCache) {
|
|
13054
13037
|
targetCache = {};
|
|
13055
13038
|
containerCache.set(targetKey, targetCache);
|
|
13056
13039
|
}
|
|
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
|
-
|
|
13040
|
+
return (targetCache[axis] || (targetCache[axis] = target
|
|
13041
|
+
? new ViewTimeline({ subject: target, axis })
|
|
13042
|
+
: new ScrollTimeline({ source: container, axis })));
|
|
13043
|
+
}
|
|
13044
|
+
|
|
13045
|
+
/**
|
|
13046
|
+
* Resolved offsets are linear in the target and container lengths, so
|
|
13047
|
+
* probing them gives [target progress, container progress, pixels].
|
|
13048
|
+
* vw/vh can resolve to 0px, so they're rejected up front.
|
|
13049
|
+
*/
|
|
13050
|
+
const toIntersection = (o) => {
|
|
13051
|
+
if (/v/u.test(o))
|
|
13052
|
+
return [];
|
|
13053
|
+
const px = resolveOffset(o, 0, 0, 0);
|
|
13054
|
+
return [resolveOffset(o, 0, 1, 0) - px, px - resolveOffset(o, 1, 0, 0), px];
|
|
13055
|
+
};
|
|
13056
|
+
const toRange = ([t, c, px]) => !px &&
|
|
13057
|
+
(c === 0 || c === 1) &&
|
|
13058
|
+
`${c ? "entry" : "exit"}-crossing ${t * 100}%`;
|
|
13059
|
+
/**
|
|
13060
|
+
* Maps an offset to an equivalent ViewTimeline range. Returns undefined when
|
|
13061
|
+
* there isn't one, which signals the caller to fall back to JS-based scroll
|
|
13062
|
+
* tracking.
|
|
13063
|
+
*/
|
|
13064
|
+
function offsetToViewTimelineRange(offset = ScrollOffset.All) {
|
|
13065
|
+
if (offset.length !== 2)
|
|
13066
|
+
return;
|
|
13067
|
+
const [start, end] = offset.map(toIntersection);
|
|
13068
|
+
const points = [toRange(start), toRange(end)];
|
|
13069
|
+
const a = end[0] - start[0];
|
|
13070
|
+
const b = start[1] - end[1];
|
|
13071
|
+
if (points[0] && points[1] && (a || b)) {
|
|
13072
|
+
return {
|
|
13073
|
+
points: points,
|
|
13074
|
+
a,
|
|
13075
|
+
b,
|
|
13076
|
+
cover: !start[0] && a === 1 && b === 1,
|
|
13077
|
+
};
|
|
13086
13078
|
}
|
|
13087
|
-
return targetCache[axisKey];
|
|
13088
13079
|
}
|
|
13089
13080
|
|
|
13090
13081
|
function attachToAnimation(animation, options) {
|
|
13091
|
-
const
|
|
13092
|
-
const range = options.
|
|
13093
|
-
? offsetToViewTimelineRange(options.offset)
|
|
13094
|
-
: undefined;
|
|
13082
|
+
const { target, container, axis } = options;
|
|
13083
|
+
const range = target && offsetToViewTimelineRange(options.offset);
|
|
13095
13084
|
/**
|
|
13096
13085
|
* Use native timeline when:
|
|
13097
|
-
* - No target: ScrollTimeline
|
|
13086
|
+
* - No target and no offset: ScrollTimeline
|
|
13098
13087
|
* - Target with mappable offset: ViewTimeline with named range
|
|
13099
|
-
* -
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13088
|
+
* - Otherwise: fall back to JS observe. A ScrollTimeline has no offset,
|
|
13089
|
+
* so page offsets are applied by the JS timeline.
|
|
13090
|
+
*/
|
|
13091
|
+
const native = canUseNativeTimeline(target) && (target ? !!range : !options.offset);
|
|
13092
|
+
const animations = new Map();
|
|
13093
|
+
const observed = new Set();
|
|
13094
|
+
let stopObserving;
|
|
13095
|
+
let reverse = false;
|
|
13096
|
+
/**
|
|
13097
|
+
* When an offset's second point comes first, its range is swapped and
|
|
13098
|
+
* the animation played backwards. The range is set after the direction,
|
|
13099
|
+
* as that's what realigns a running animation with its timeline. Chrome
|
|
13100
|
+
* skips that realignment when the range changes in the frame the
|
|
13101
|
+
* animation starts, leaving it offset for good, so it's replayed too.
|
|
13102
|
+
*/
|
|
13103
|
+
const apply = (direction, waapi) => {
|
|
13104
|
+
const [start, end] = range.points;
|
|
13105
|
+
waapi.effect.updateTiming({
|
|
13106
|
+
direction: reverse
|
|
13107
|
+
? direction === "normal"
|
|
13108
|
+
? "reverse"
|
|
13109
|
+
: "alternate-reverse"
|
|
13110
|
+
: direction,
|
|
13111
|
+
});
|
|
13112
|
+
Object.assign(waapi, {
|
|
13113
|
+
rangeStart: reverse ? end : start,
|
|
13114
|
+
rangeEnd: reverse ? start : end,
|
|
13115
|
+
});
|
|
13116
|
+
waapi.play();
|
|
13117
|
+
};
|
|
13118
|
+
const stops = [
|
|
13119
|
+
animation.attachTimeline({
|
|
13120
|
+
/**
|
|
13121
|
+
* Read only by WAAPI animations, so values driven from JS don't
|
|
13122
|
+
* create a native timeline they won't use.
|
|
13123
|
+
*/
|
|
13124
|
+
get timeline() {
|
|
13125
|
+
return native ? getTimeline(options) : undefined;
|
|
13126
|
+
},
|
|
13127
|
+
onAttach: range &&
|
|
13128
|
+
((waapi) => {
|
|
13129
|
+
const direction = waapi.effect.getTiming().direction;
|
|
13130
|
+
animations.set(waapi, direction);
|
|
13131
|
+
apply(direction, waapi);
|
|
13132
|
+
}),
|
|
13133
|
+
/**
|
|
13134
|
+
* Values driven from JS all track the offset with scrollInfo,
|
|
13135
|
+
* which measures once per frame for all of them.
|
|
13136
|
+
*/
|
|
13137
|
+
observe: (valueAnimation) => {
|
|
13138
|
+
valueAnimation.pause();
|
|
13139
|
+
observed.add(valueAnimation);
|
|
13140
|
+
stopObserving || (stopObserving = scrollInfo((info) => {
|
|
13141
|
+
observed.forEach((observedAnimation) => {
|
|
13142
|
+
observedAnimation.time =
|
|
13143
|
+
observedAnimation.iterationDuration *
|
|
13144
|
+
info[axis].progress;
|
|
13145
|
+
});
|
|
13146
|
+
}, options));
|
|
13147
|
+
return () => observed.delete(valueAnimation);
|
|
13148
|
+
},
|
|
13110
13149
|
}),
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13150
|
+
];
|
|
13151
|
+
if (native && range) {
|
|
13152
|
+
const { a, b } = range;
|
|
13153
|
+
const length = axis === "y" ? "clientHeight" : "clientWidth";
|
|
13154
|
+
/**
|
|
13155
|
+
* Offsets like All only run forwards when the target is longer than
|
|
13156
|
+
* the container, so their direction is remeasured on resize.
|
|
13157
|
+
*/
|
|
13158
|
+
const update = () => {
|
|
13159
|
+
if (reverse !==
|
|
13160
|
+
(reverse = a * target[length] + b * container[length] < 0)) {
|
|
13161
|
+
animations.forEach(apply);
|
|
13162
|
+
}
|
|
13163
|
+
};
|
|
13164
|
+
update();
|
|
13165
|
+
if (a * b < 0) {
|
|
13166
|
+
stops.push(resize(update), resize([target, container], update));
|
|
13167
|
+
}
|
|
13168
|
+
}
|
|
13169
|
+
return () => {
|
|
13170
|
+
stops.forEach((stop) => stop());
|
|
13171
|
+
stopObserving?.();
|
|
13172
|
+
};
|
|
13119
13173
|
}
|
|
13120
13174
|
|
|
13121
13175
|
function scroll(onScroll, { axis = "y", source, container = document.scrollingElement, ...options } = {}) {
|
|
@@ -13415,7 +13469,6 @@
|
|
|
13415
13469
|
exports.noop = noop;
|
|
13416
13470
|
exports.number = number;
|
|
13417
13471
|
exports.numberValueTypes = numberValueTypes;
|
|
13418
|
-
exports.observeTimeline = observeTimeline;
|
|
13419
13472
|
exports.optimizedAppearDataAttribute = optimizedAppearDataAttribute;
|
|
13420
13473
|
exports.optimizedAppearDataId = optimizedAppearDataId;
|
|
13421
13474
|
exports.parseAnimateLayoutArgs = parseAnimateLayoutArgs;
|