BOC v1.0: RS256 auth, Ledger integration, Prometheus metrics, CI/CD, backup
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 [Motion](https://motion.dev) B.V.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+11030
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
+199
@@ -0,0 +1,199 @@
|
||||
import { MotionGlobalConfig, noop } from 'motion-utils';
|
||||
import { time } from '../frameloop/sync-time.mjs';
|
||||
import { JSAnimation } from './JSAnimation.mjs';
|
||||
import { getFinalKeyframe } from './keyframes/get-final.mjs';
|
||||
import { KeyframeResolver, flushKeyframeResolvers } from './keyframes/KeyframesResolver.mjs';
|
||||
import { NativeAnimationExtended } from './NativeAnimationExtended.mjs';
|
||||
import { canAnimate } from './utils/can-animate.mjs';
|
||||
import { makeAnimationInstant } from './utils/make-animation-instant.mjs';
|
||||
import { WithPromise } from './utils/WithPromise.mjs';
|
||||
import { supportsBrowserAnimation } from './waapi/supports/waapi.mjs';
|
||||
|
||||
/**
|
||||
* Maximum time allowed between an animation being created and it being
|
||||
* resolved for us to use the latter as the start time.
|
||||
*
|
||||
* This is to ensure that while we prefer to "start" an animation as soon
|
||||
* as it's triggered, we also want to avoid a visual jump if there's a big delay
|
||||
* between these two moments.
|
||||
*/
|
||||
const MAX_RESOLVE_DELAY = 40;
|
||||
class AsyncMotionValueAnimation extends WithPromise {
|
||||
constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", keyframes, name, motionValue, element, ...options }) {
|
||||
super();
|
||||
/**
|
||||
* Bound to support return animation.stop pattern
|
||||
*/
|
||||
this.stop = () => {
|
||||
if (this._animation) {
|
||||
this._animation.stop();
|
||||
this.stopTimeline?.();
|
||||
}
|
||||
this.keyframeResolver?.cancel();
|
||||
};
|
||||
this.createdAt = time.now();
|
||||
const optionsWithDefaults = {
|
||||
autoplay,
|
||||
delay,
|
||||
type,
|
||||
repeat,
|
||||
repeatDelay,
|
||||
repeatType,
|
||||
name,
|
||||
motionValue,
|
||||
element,
|
||||
...options,
|
||||
};
|
||||
const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver;
|
||||
this.keyframeResolver = new KeyframeResolver$1(keyframes, (resolvedKeyframes, finalKeyframe, forced) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe, optionsWithDefaults, !forced), name, motionValue, element);
|
||||
this.keyframeResolver?.scheduleResolve();
|
||||
}
|
||||
onKeyframesResolved(keyframes, finalKeyframe, options, sync) {
|
||||
this.keyframeResolver = undefined;
|
||||
const { name, type, velocity, delay, isHandoff, onUpdate } = options;
|
||||
this.resolvedAt = time.now();
|
||||
/**
|
||||
* If we can't animate this value with the resolved keyframes
|
||||
* then we should complete it immediately.
|
||||
*/
|
||||
let canAnimateValue = true;
|
||||
if (!canAnimate(keyframes, name, type, velocity)) {
|
||||
canAnimateValue = false;
|
||||
if (MotionGlobalConfig.instantAnimations || !delay) {
|
||||
onUpdate?.(getFinalKeyframe(keyframes, options, finalKeyframe));
|
||||
}
|
||||
keyframes[0] = keyframes[keyframes.length - 1];
|
||||
makeAnimationInstant(options);
|
||||
options.repeat = 0;
|
||||
}
|
||||
/**
|
||||
* Resolve startTime for the animation.
|
||||
*
|
||||
* This method uses the createdAt and resolvedAt to calculate the
|
||||
* animation startTime. *Ideally*, we would use the createdAt time as t=0
|
||||
* as the following frame would then be the first frame of the animation in
|
||||
* progress, which would feel snappier.
|
||||
*
|
||||
* However, if there's a delay (main thread work) between the creation of
|
||||
* the animation and the first committed frame, we prefer to use resolvedAt
|
||||
* to avoid a sudden jump into the animation.
|
||||
*/
|
||||
const startTime = sync
|
||||
? !this.resolvedAt
|
||||
? this.createdAt
|
||||
: this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY
|
||||
? this.resolvedAt
|
||||
: this.createdAt
|
||||
: undefined;
|
||||
const resolvedOptions = {
|
||||
startTime,
|
||||
finalKeyframe,
|
||||
...options,
|
||||
keyframes,
|
||||
};
|
||||
/**
|
||||
* Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
|
||||
* WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
|
||||
* optimised animation.
|
||||
*
|
||||
* Also skip WAAPI when keyframes aren't animatable, as the resolved
|
||||
* values may not be valid CSS and would trigger browser warnings.
|
||||
*/
|
||||
const useWaapi = canAnimateValue &&
|
||||
!isHandoff &&
|
||||
supportsBrowserAnimation(resolvedOptions);
|
||||
const element = resolvedOptions.motionValue?.owner?.current;
|
||||
let animation;
|
||||
if (useWaapi) {
|
||||
try {
|
||||
animation = new NativeAnimationExtended({
|
||||
...resolvedOptions,
|
||||
element,
|
||||
});
|
||||
}
|
||||
catch {
|
||||
animation = new JSAnimation(resolvedOptions);
|
||||
}
|
||||
}
|
||||
else {
|
||||
animation = new JSAnimation(resolvedOptions);
|
||||
}
|
||||
animation.finished.then(() => {
|
||||
this.notifyFinished();
|
||||
}).catch(noop);
|
||||
if (this.pendingTimeline) {
|
||||
this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
|
||||
this.pendingTimeline = undefined;
|
||||
}
|
||||
this._animation = animation;
|
||||
}
|
||||
get finished() {
|
||||
if (!this._animation) {
|
||||
return this._finished;
|
||||
}
|
||||
else {
|
||||
return this.animation.finished;
|
||||
}
|
||||
}
|
||||
then(onResolve, _onReject) {
|
||||
return this.finished.finally(onResolve).then(() => { });
|
||||
}
|
||||
get animation() {
|
||||
if (!this._animation) {
|
||||
this.keyframeResolver?.resume();
|
||||
flushKeyframeResolvers();
|
||||
}
|
||||
return this._animation;
|
||||
}
|
||||
get duration() {
|
||||
return this.animation.duration;
|
||||
}
|
||||
get iterationDuration() {
|
||||
return this.animation.iterationDuration;
|
||||
}
|
||||
get time() {
|
||||
return this.animation.time;
|
||||
}
|
||||
set time(newTime) {
|
||||
this.animation.time = newTime;
|
||||
}
|
||||
get speed() {
|
||||
return this.animation.speed;
|
||||
}
|
||||
get state() {
|
||||
return this.animation.state;
|
||||
}
|
||||
set speed(newSpeed) {
|
||||
this.animation.speed = newSpeed;
|
||||
}
|
||||
get startTime() {
|
||||
return this.animation.startTime;
|
||||
}
|
||||
attachTimeline(timeline) {
|
||||
if (this._animation) {
|
||||
this.stopTimeline = this.animation.attachTimeline(timeline);
|
||||
}
|
||||
else {
|
||||
this.pendingTimeline = timeline;
|
||||
}
|
||||
return () => this.stop();
|
||||
}
|
||||
play() {
|
||||
this.animation.play();
|
||||
}
|
||||
pause() {
|
||||
this.animation.pause();
|
||||
}
|
||||
complete() {
|
||||
this.animation.complete();
|
||||
}
|
||||
cancel() {
|
||||
if (this._animation) {
|
||||
this.animation.cancel();
|
||||
}
|
||||
this.keyframeResolver?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
export { AsyncMotionValueAnimation };
|
||||
//# sourceMappingURL=AsyncMotionValueAnimation.mjs.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
+82
@@ -0,0 +1,82 @@
|
||||
class GroupAnimation {
|
||||
constructor(animations) {
|
||||
// Bound to accomadate common `return animation.stop` pattern
|
||||
this.stop = () => this.runAll("stop");
|
||||
this.animations = animations.filter(Boolean);
|
||||
}
|
||||
get finished() {
|
||||
return Promise.all(this.animations.map((animation) => animation.finished));
|
||||
}
|
||||
/**
|
||||
* TODO: Filter out cancelled or stopped animations before returning
|
||||
*/
|
||||
getAll(propName) {
|
||||
return this.animations[0][propName];
|
||||
}
|
||||
setAll(propName, newValue) {
|
||||
for (let i = 0; i < this.animations.length; i++) {
|
||||
this.animations[i][propName] = newValue;
|
||||
}
|
||||
}
|
||||
attachTimeline(timeline) {
|
||||
const subscriptions = this.animations.map((animation) => animation.attachTimeline(timeline));
|
||||
return () => {
|
||||
subscriptions.forEach((cancel, i) => {
|
||||
cancel && cancel();
|
||||
this.animations[i].stop();
|
||||
});
|
||||
};
|
||||
}
|
||||
get time() {
|
||||
return this.getAll("time");
|
||||
}
|
||||
set time(time) {
|
||||
this.setAll("time", time);
|
||||
}
|
||||
get speed() {
|
||||
return this.getAll("speed");
|
||||
}
|
||||
set speed(speed) {
|
||||
this.setAll("speed", speed);
|
||||
}
|
||||
get state() {
|
||||
return this.getAll("state");
|
||||
}
|
||||
get startTime() {
|
||||
return this.getAll("startTime");
|
||||
}
|
||||
get duration() {
|
||||
return getMax(this.animations, "duration");
|
||||
}
|
||||
get iterationDuration() {
|
||||
return getMax(this.animations, "iterationDuration");
|
||||
}
|
||||
runAll(methodName) {
|
||||
this.animations.forEach((controls) => controls[methodName]());
|
||||
}
|
||||
play() {
|
||||
this.runAll("play");
|
||||
}
|
||||
pause() {
|
||||
this.runAll("pause");
|
||||
}
|
||||
cancel() {
|
||||
this.runAll("cancel");
|
||||
}
|
||||
complete() {
|
||||
this.runAll("complete");
|
||||
}
|
||||
}
|
||||
function getMax(animations, propName) {
|
||||
let max = 0;
|
||||
for (let i = 0; i < animations.length; i++) {
|
||||
const value = animations[i][propName];
|
||||
if (value !== null && value > max) {
|
||||
max = value;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
export { GroupAnimation };
|
||||
//# sourceMappingURL=GroupAnimation.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+10
@@ -0,0 +1,10 @@
|
||||
import { GroupAnimation } from './GroupAnimation.mjs';
|
||||
|
||||
class GroupAnimationWithThen extends GroupAnimation {
|
||||
then(onResolve, _onReject) {
|
||||
return this.finished.finally(onResolve).then(() => { });
|
||||
}
|
||||
}
|
||||
|
||||
export { GroupAnimationWithThen };
|
||||
//# sourceMappingURL=GroupAnimationWithThen.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"GroupAnimationWithThen.mjs","sources":["../../../src/animation/GroupAnimationWithThen.ts"],"sourcesContent":["import { GroupAnimation } from \"./GroupAnimation\"\nimport { AnimationPlaybackControlsWithThen } from \"./types\"\n\nexport class GroupAnimationWithThen\n extends GroupAnimation\n implements AnimationPlaybackControlsWithThen\n{\n then(onResolve: VoidFunction, _onReject?: VoidFunction) {\n return this.finished.finally(onResolve).then(() => {})\n }\n}\n"],"names":[],"mappings":";;AAGM,MAAO,sBACT,SAAQ,cAAc,CAAA;IAGtB,IAAI,CAAC,SAAuB,EAAE,SAAwB,EAAA;AAClD,QAAA,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAK,EAAE,CAAC,CAAC;IAC1D;AACH;;;;"}
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
import { invariant, pipe, clamp, millisecondsToSeconds, secondsToMilliseconds } from 'motion-utils';
|
||||
import { time } from '../frameloop/sync-time.mjs';
|
||||
import { mix } from '../utils/mix/index.mjs';
|
||||
import { frameloopDriver } from './drivers/frame.mjs';
|
||||
import { inertia } from './generators/inertia.mjs';
|
||||
import { keyframes } from './generators/keyframes.mjs';
|
||||
import { calcGeneratorDuration } from './generators/utils/calc-duration.mjs';
|
||||
import { getGeneratorVelocity } from './generators/utils/velocity.mjs';
|
||||
import { getFinalKeyframe } from './keyframes/get-final.mjs';
|
||||
import { replaceTransitionType } from './utils/replace-transition-type.mjs';
|
||||
import { WithPromise } from './utils/WithPromise.mjs';
|
||||
|
||||
const percentToProgress = (percent) => percent / 100;
|
||||
class JSAnimation extends WithPromise {
|
||||
constructor(options) {
|
||||
super();
|
||||
this.state = "idle";
|
||||
this.startTime = null;
|
||||
this.isStopped = false;
|
||||
/**
|
||||
* The current time of the animation.
|
||||
*/
|
||||
this.currentTime = 0;
|
||||
/**
|
||||
* The time at which the animation was paused.
|
||||
*/
|
||||
this.holdTime = null;
|
||||
/**
|
||||
* Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
|
||||
*/
|
||||
this.playbackSpeed = 1;
|
||||
/**
|
||||
* Reusable state object for the delay phase to avoid
|
||||
* allocating a new object every frame.
|
||||
*/
|
||||
this.delayState = {
|
||||
done: false,
|
||||
value: undefined,
|
||||
};
|
||||
/**
|
||||
* This method is bound to the instance to fix a pattern where
|
||||
* animation.stop is returned as a reference from a useEffect.
|
||||
*/
|
||||
this.stop = () => {
|
||||
const { motionValue } = this.options;
|
||||
if (motionValue && motionValue.updatedAt !== time.now()) {
|
||||
this.tick(time.now());
|
||||
}
|
||||
this.isStopped = true;
|
||||
if (this.state === "idle")
|
||||
return;
|
||||
this.teardown();
|
||||
this.options.onStop?.();
|
||||
};
|
||||
this.options = options;
|
||||
this.initAnimation();
|
||||
this.play();
|
||||
if (options.autoplay === false)
|
||||
this.pause();
|
||||
}
|
||||
initAnimation() {
|
||||
const { options } = this;
|
||||
replaceTransitionType(options);
|
||||
const { type = keyframes, repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = options;
|
||||
let { keyframes: keyframes$1 } = options;
|
||||
const generatorFactory = type || keyframes;
|
||||
if (process.env.NODE_ENV !== "production" &&
|
||||
generatorFactory !== keyframes) {
|
||||
invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`, "spring-two-frames");
|
||||
}
|
||||
if (generatorFactory !== keyframes &&
|
||||
typeof keyframes$1[0] !== "number") {
|
||||
this.mixKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
|
||||
keyframes$1 = [0, 100];
|
||||
}
|
||||
const generator = generatorFactory({ ...options, keyframes: keyframes$1 });
|
||||
/**
|
||||
* If we have a mirror repeat type we need to create a second generator that outputs the
|
||||
* mirrored (not reversed) animation and later ping pong between the two generators.
|
||||
*/
|
||||
if (repeatType === "mirror") {
|
||||
this.mirroredGenerator = generatorFactory({
|
||||
...options,
|
||||
keyframes: [...keyframes$1].reverse(),
|
||||
velocity: -velocity,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* If duration is undefined and we have repeat options,
|
||||
* we need to calculate a duration from the generator.
|
||||
*
|
||||
* We set it to the generator itself to cache the duration.
|
||||
* Any timeline resolver will need to have already precalculated
|
||||
* the duration by this step.
|
||||
*/
|
||||
if (generator.calculatedDuration === null) {
|
||||
generator.calculatedDuration = calcGeneratorDuration(generator);
|
||||
}
|
||||
const { calculatedDuration } = generator;
|
||||
this.calculatedDuration = calculatedDuration;
|
||||
this.resolvedDuration = calculatedDuration + repeatDelay;
|
||||
this.totalDuration = this.resolvedDuration * (repeat + 1) - repeatDelay;
|
||||
this.generator = generator;
|
||||
}
|
||||
updateTime(timestamp) {
|
||||
const animationTime = Math.round(timestamp - this.startTime) * this.playbackSpeed;
|
||||
// Update currentTime
|
||||
if (this.holdTime !== null) {
|
||||
this.currentTime = this.holdTime;
|
||||
}
|
||||
else {
|
||||
// Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
|
||||
// 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
|
||||
// example.
|
||||
this.currentTime = animationTime;
|
||||
}
|
||||
}
|
||||
tick(timestamp, sample = false) {
|
||||
const { generator, totalDuration, mixKeyframes, mirroredGenerator, resolvedDuration, calculatedDuration, } = this;
|
||||
if (this.startTime === null)
|
||||
return generator.next(0);
|
||||
const { delay = 0, keyframes, repeat, repeatType, repeatDelay, type, onUpdate, finalKeyframe, } = this.options;
|
||||
/**
|
||||
* requestAnimationFrame timestamps can come through as lower than
|
||||
* the startTime as set by performance.now(). Here we prevent this,
|
||||
* though in the future it could be possible to make setting startTime
|
||||
* a pending operation that gets resolved here.
|
||||
*/
|
||||
if (this.speed > 0) {
|
||||
this.startTime = Math.min(this.startTime, timestamp);
|
||||
}
|
||||
else if (this.speed < 0) {
|
||||
this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime);
|
||||
}
|
||||
if (sample) {
|
||||
this.currentTime = timestamp;
|
||||
}
|
||||
else {
|
||||
this.updateTime(timestamp);
|
||||
}
|
||||
// Rebase on delay
|
||||
const timeWithoutDelay = this.currentTime - delay * (this.playbackSpeed >= 0 ? 1 : -1);
|
||||
const isInDelayPhase = this.playbackSpeed >= 0
|
||||
? timeWithoutDelay < 0
|
||||
: timeWithoutDelay > totalDuration;
|
||||
this.currentTime = Math.max(timeWithoutDelay, 0);
|
||||
// If this animation has finished, set the current time to the total duration.
|
||||
if (this.state === "finished" && this.holdTime === null) {
|
||||
this.currentTime = totalDuration;
|
||||
}
|
||||
let elapsed = this.currentTime;
|
||||
let frameGenerator = generator;
|
||||
if (repeat) {
|
||||
/**
|
||||
* Get the current progress (0-1) of the animation. If t is >
|
||||
* than duration we'll get values like 2.5 (midway through the
|
||||
* third iteration)
|
||||
*/
|
||||
const progress = Math.min(this.currentTime, totalDuration) / resolvedDuration;
|
||||
/**
|
||||
* Get the current iteration (0 indexed). For instance the floor of
|
||||
* 2.5 is 2.
|
||||
*/
|
||||
let currentIteration = Math.floor(progress);
|
||||
/**
|
||||
* Get the current progress of the iteration by taking the remainder
|
||||
* so 2.5 is 0.5 through iteration 2
|
||||
*/
|
||||
let iterationProgress = progress % 1.0;
|
||||
/**
|
||||
* If iteration progress is 1 we count that as the end
|
||||
* of the previous iteration.
|
||||
*/
|
||||
if (!iterationProgress && progress >= 1) {
|
||||
iterationProgress = 1;
|
||||
}
|
||||
iterationProgress === 1 && currentIteration--;
|
||||
currentIteration = Math.min(currentIteration, repeat + 1);
|
||||
/**
|
||||
* Reverse progress if we're not running in "normal" direction
|
||||
*/
|
||||
const isOddIteration = Boolean(currentIteration % 2);
|
||||
if (isOddIteration) {
|
||||
if (repeatType === "reverse") {
|
||||
iterationProgress = 1 - iterationProgress;
|
||||
if (repeatDelay) {
|
||||
iterationProgress -= repeatDelay / resolvedDuration;
|
||||
}
|
||||
}
|
||||
else if (repeatType === "mirror") {
|
||||
frameGenerator = mirroredGenerator;
|
||||
}
|
||||
}
|
||||
elapsed = clamp(0, 1, iterationProgress) * resolvedDuration;
|
||||
}
|
||||
/**
|
||||
* If we're in negative time, set state as the initial keyframe.
|
||||
* This prevents delay: x, duration: 0 animations from finishing
|
||||
* instantly.
|
||||
*/
|
||||
let state;
|
||||
if (isInDelayPhase) {
|
||||
this.delayState.value = keyframes[0];
|
||||
state = this.delayState;
|
||||
}
|
||||
else {
|
||||
state = frameGenerator.next(elapsed);
|
||||
}
|
||||
if (mixKeyframes && !isInDelayPhase) {
|
||||
state.value = mixKeyframes(state.value);
|
||||
}
|
||||
let { done } = state;
|
||||
if (!isInDelayPhase && calculatedDuration !== null) {
|
||||
done =
|
||||
this.playbackSpeed >= 0
|
||||
? this.currentTime >= totalDuration
|
||||
: this.currentTime <= 0;
|
||||
}
|
||||
const isAnimationFinished = this.holdTime === null &&
|
||||
(this.state === "finished" || (this.state === "running" && done));
|
||||
// TODO: The exception for inertia could be cleaner here
|
||||
if (isAnimationFinished && type !== inertia) {
|
||||
state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
|
||||
}
|
||||
if (onUpdate) {
|
||||
onUpdate(state.value);
|
||||
}
|
||||
if (isAnimationFinished) {
|
||||
this.finish();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
/**
|
||||
* Allows the returned animation to be awaited or promise-chained. Currently
|
||||
* resolves when the animation finishes at all but in a future update could/should
|
||||
* reject if its cancels.
|
||||
*/
|
||||
then(resolve, reject) {
|
||||
return this.finished.then(resolve, reject);
|
||||
}
|
||||
get duration() {
|
||||
return millisecondsToSeconds(this.calculatedDuration);
|
||||
}
|
||||
get iterationDuration() {
|
||||
const { delay = 0 } = this.options || {};
|
||||
return this.duration + millisecondsToSeconds(delay);
|
||||
}
|
||||
get time() {
|
||||
return millisecondsToSeconds(this.currentTime);
|
||||
}
|
||||
set time(newTime) {
|
||||
newTime = secondsToMilliseconds(newTime);
|
||||
this.currentTime = newTime;
|
||||
if (this.startTime === null ||
|
||||
this.holdTime !== null ||
|
||||
this.playbackSpeed === 0) {
|
||||
this.holdTime = newTime;
|
||||
}
|
||||
else if (this.driver) {
|
||||
this.startTime = this.driver.now() - newTime / this.playbackSpeed;
|
||||
}
|
||||
if (this.driver) {
|
||||
this.driver.start(false);
|
||||
}
|
||||
else {
|
||||
this.startTime = 0;
|
||||
this.state = "paused";
|
||||
this.holdTime = newTime;
|
||||
this.tick(newTime);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the generator's velocity at the current time in units/second.
|
||||
* Uses the analytical derivative when available (springs), avoiding
|
||||
* the MotionValue's frame-dependent velocity estimation.
|
||||
*/
|
||||
getGeneratorVelocity() {
|
||||
const t = this.currentTime;
|
||||
if (t <= 0)
|
||||
return this.options.velocity || 0;
|
||||
if (this.generator.velocity) {
|
||||
return this.generator.velocity(t);
|
||||
}
|
||||
// Fallback: finite difference
|
||||
const current = this.generator.next(t).value;
|
||||
return getGeneratorVelocity((s) => this.generator.next(s).value, t, current);
|
||||
}
|
||||
get speed() {
|
||||
return this.playbackSpeed;
|
||||
}
|
||||
set speed(newSpeed) {
|
||||
const hasChanged = this.playbackSpeed !== newSpeed;
|
||||
if (hasChanged && this.driver) {
|
||||
this.updateTime(time.now());
|
||||
}
|
||||
this.playbackSpeed = newSpeed;
|
||||
if (hasChanged && this.driver) {
|
||||
this.time = millisecondsToSeconds(this.currentTime);
|
||||
}
|
||||
}
|
||||
play() {
|
||||
if (this.isStopped)
|
||||
return;
|
||||
const { driver = frameloopDriver, startTime } = this.options;
|
||||
if (!this.driver) {
|
||||
this.driver = driver((timestamp) => this.tick(timestamp));
|
||||
}
|
||||
this.options.onPlay?.();
|
||||
const now = this.driver.now();
|
||||
if (this.state === "finished") {
|
||||
this.updateFinished();
|
||||
this.startTime = now;
|
||||
}
|
||||
else if (this.holdTime !== null) {
|
||||
this.startTime = now - this.holdTime;
|
||||
}
|
||||
else if (!this.startTime) {
|
||||
this.startTime = startTime ?? now;
|
||||
}
|
||||
if (this.state === "finished" && this.speed < 0) {
|
||||
this.startTime += this.calculatedDuration;
|
||||
}
|
||||
this.holdTime = null;
|
||||
/**
|
||||
* Set playState to running only after we've used it in
|
||||
* the previous logic.
|
||||
*/
|
||||
this.state = "running";
|
||||
this.driver.start();
|
||||
}
|
||||
pause() {
|
||||
this.state = "paused";
|
||||
this.updateTime(time.now());
|
||||
this.holdTime = this.currentTime;
|
||||
}
|
||||
complete() {
|
||||
if (this.state !== "running") {
|
||||
this.play();
|
||||
}
|
||||
this.state = "finished";
|
||||
this.holdTime = null;
|
||||
}
|
||||
finish() {
|
||||
this.notifyFinished();
|
||||
this.teardown();
|
||||
this.state = "finished";
|
||||
this.options.onComplete?.();
|
||||
}
|
||||
cancel() {
|
||||
this.holdTime = null;
|
||||
this.startTime = 0;
|
||||
this.tick(0);
|
||||
this.teardown();
|
||||
this.options.onCancel?.();
|
||||
}
|
||||
teardown() {
|
||||
this.state = "idle";
|
||||
this.stopDriver();
|
||||
this.startTime = this.holdTime = null;
|
||||
}
|
||||
stopDriver() {
|
||||
if (!this.driver)
|
||||
return;
|
||||
this.driver.stop();
|
||||
this.driver = undefined;
|
||||
}
|
||||
sample(sampleTime) {
|
||||
this.startTime = 0;
|
||||
return this.tick(sampleTime, true);
|
||||
}
|
||||
attachTimeline(timeline) {
|
||||
if (this.options.allowFlatten) {
|
||||
this.options.type = "keyframes";
|
||||
this.options.ease = "linear";
|
||||
this.initAnimation();
|
||||
}
|
||||
this.driver?.stop();
|
||||
return timeline.observe(this);
|
||||
}
|
||||
}
|
||||
// Legacy function support
|
||||
function animateValue(options) {
|
||||
return new JSAnimation(options);
|
||||
}
|
||||
|
||||
export { JSAnimation, animateValue };
|
||||
//# sourceMappingURL=JSAnimation.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+179
@@ -0,0 +1,179 @@
|
||||
import { invariant, millisecondsToSeconds, secondsToMilliseconds, noop } from 'motion-utils';
|
||||
import { setStyle } from '../render/dom/style-set.mjs';
|
||||
import { supportsScrollTimeline } from '../utils/supports/scroll-timeline.mjs';
|
||||
import { getFinalKeyframe } from './keyframes/get-final.mjs';
|
||||
import { WithPromise } from './utils/WithPromise.mjs';
|
||||
import { startWaapiAnimation } from './waapi/start-waapi-animation.mjs';
|
||||
import { applyGeneratorOptions } from './waapi/utils/apply-generator.mjs';
|
||||
|
||||
/**
|
||||
* NativeAnimation implements AnimationPlaybackControls for the browser's Web Animations API.
|
||||
*/
|
||||
class NativeAnimation extends WithPromise {
|
||||
constructor(options) {
|
||||
super();
|
||||
this.finishedTime = null;
|
||||
this.isStopped = false;
|
||||
/**
|
||||
* Tracks a manually-set start time that takes precedence over WAAPI's
|
||||
* dynamic startTime. This is cleared when play() or time setter is called,
|
||||
* allowing WAAPI to take over timing.
|
||||
*/
|
||||
this.manualStartTime = null;
|
||||
if (!options)
|
||||
return;
|
||||
const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, onComplete, } = options;
|
||||
this.isPseudoElement = Boolean(pseudoElement);
|
||||
this.allowFlatten = allowFlatten;
|
||||
this.options = options;
|
||||
invariant(typeof options.type !== "string", `Mini animate() doesn't support "type" as a string.`, "mini-spring");
|
||||
const transition = applyGeneratorOptions(options);
|
||||
this.animation = startWaapiAnimation(element, name, keyframes, transition, pseudoElement);
|
||||
if (transition.autoplay === false) {
|
||||
this.animation.pause();
|
||||
}
|
||||
this.animation.onfinish = () => {
|
||||
this.finishedTime = this.time;
|
||||
if (!pseudoElement) {
|
||||
const keyframe = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
|
||||
if (this.updateMotionValue) {
|
||||
this.updateMotionValue(keyframe);
|
||||
}
|
||||
/**
|
||||
* If we can, we want to commit the final style as set by the user,
|
||||
* rather than the computed keyframe value supplied by the animation.
|
||||
* We always do this, even when a motion value is present, to prevent
|
||||
* a visual flash in Firefox where the WAAPI animation's fill is removed
|
||||
* during cancel() before the scheduled render can apply the correct value.
|
||||
*/
|
||||
setStyle(element, name, keyframe);
|
||||
this.animation.cancel();
|
||||
}
|
||||
onComplete?.();
|
||||
this.notifyFinished();
|
||||
};
|
||||
}
|
||||
play() {
|
||||
if (this.isStopped)
|
||||
return;
|
||||
this.manualStartTime = null;
|
||||
this.animation.play();
|
||||
if (this.state === "finished") {
|
||||
this.updateFinished();
|
||||
}
|
||||
}
|
||||
pause() {
|
||||
this.animation.pause();
|
||||
}
|
||||
complete() {
|
||||
this.animation.finish?.();
|
||||
}
|
||||
cancel() {
|
||||
try {
|
||||
this.animation.cancel();
|
||||
}
|
||||
catch (e) { }
|
||||
}
|
||||
stop() {
|
||||
if (this.isStopped)
|
||||
return;
|
||||
this.isStopped = true;
|
||||
const { state } = this;
|
||||
if (state === "idle" || state === "finished") {
|
||||
return;
|
||||
}
|
||||
if (this.updateMotionValue) {
|
||||
this.updateMotionValue();
|
||||
}
|
||||
else {
|
||||
this.commitStyles();
|
||||
}
|
||||
if (!this.isPseudoElement)
|
||||
this.cancel();
|
||||
}
|
||||
/**
|
||||
* WAAPI doesn't natively have any interruption capabilities.
|
||||
*
|
||||
* In this method, we commit styles back to the DOM before cancelling
|
||||
* the animation.
|
||||
*
|
||||
* This is designed to be overridden by NativeAnimationExtended, which
|
||||
* will create a renderless JS animation and sample it twice to calculate
|
||||
* its current value, "previous" value, and therefore allow
|
||||
* Motion to also correctly calculate velocity for any subsequent animation
|
||||
* while deferring the commit until the next animation frame.
|
||||
*/
|
||||
commitStyles() {
|
||||
const element = this.options?.element;
|
||||
if (!this.isPseudoElement && element?.isConnected) {
|
||||
this.animation.commitStyles?.();
|
||||
}
|
||||
}
|
||||
get duration() {
|
||||
const duration = this.animation.effect?.getComputedTiming?.().duration || 0;
|
||||
return millisecondsToSeconds(Number(duration));
|
||||
}
|
||||
get iterationDuration() {
|
||||
const { delay = 0 } = this.options || {};
|
||||
return this.duration + millisecondsToSeconds(delay);
|
||||
}
|
||||
get time() {
|
||||
return millisecondsToSeconds(Number(this.animation.currentTime) || 0);
|
||||
}
|
||||
set time(newTime) {
|
||||
const wasFinished = this.finishedTime !== null;
|
||||
this.manualStartTime = null;
|
||||
this.finishedTime = null;
|
||||
this.animation.currentTime = secondsToMilliseconds(newTime);
|
||||
if (wasFinished) {
|
||||
this.animation.pause();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The playback speed of the animation.
|
||||
* 1 = normal speed, 2 = double speed, 0.5 = half speed.
|
||||
*/
|
||||
get speed() {
|
||||
return this.animation.playbackRate;
|
||||
}
|
||||
set speed(newSpeed) {
|
||||
// Allow backwards playback after finishing
|
||||
if (newSpeed < 0)
|
||||
this.finishedTime = null;
|
||||
this.animation.playbackRate = newSpeed;
|
||||
}
|
||||
get state() {
|
||||
return this.finishedTime !== null
|
||||
? "finished"
|
||||
: this.animation.playState;
|
||||
}
|
||||
get startTime() {
|
||||
return this.manualStartTime ?? Number(this.animation.startTime);
|
||||
}
|
||||
set startTime(newStartTime) {
|
||||
this.manualStartTime = this.animation.startTime = newStartTime;
|
||||
}
|
||||
/**
|
||||
* Attaches a timeline to the animation, for instance the `ScrollTimeline`.
|
||||
*/
|
||||
attachTimeline({ timeline, rangeStart, rangeEnd, observe, }) {
|
||||
if (this.allowFlatten) {
|
||||
this.animation.effect?.updateTiming({ easing: "linear" });
|
||||
}
|
||||
this.animation.onfinish = null;
|
||||
if (timeline && supportsScrollTimeline()) {
|
||||
this.animation.timeline = timeline;
|
||||
if (rangeStart)
|
||||
this.animation.rangeStart = rangeStart;
|
||||
if (rangeEnd)
|
||||
this.animation.rangeEnd = rangeEnd;
|
||||
return noop;
|
||||
}
|
||||
else {
|
||||
return observe(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { NativeAnimation };
|
||||
//# sourceMappingURL=NativeAnimation.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+88
@@ -0,0 +1,88 @@
|
||||
import { clamp } from 'motion-utils';
|
||||
import { time } from '../frameloop/sync-time.mjs';
|
||||
import { setStyle } from '../render/dom/style-set.mjs';
|
||||
import { JSAnimation } from './JSAnimation.mjs';
|
||||
import { NativeAnimation } from './NativeAnimation.mjs';
|
||||
import { replaceTransitionType } from './utils/replace-transition-type.mjs';
|
||||
import { replaceStringEasing } from './waapi/utils/unsupported-easing.mjs';
|
||||
|
||||
/**
|
||||
* 10ms is chosen here as it strikes a balance between smooth
|
||||
* results (more than one keyframe per frame at 60fps) and
|
||||
* keyframe quantity.
|
||||
*/
|
||||
const sampleDelta = 10; //ms
|
||||
class NativeAnimationExtended extends NativeAnimation {
|
||||
constructor(options) {
|
||||
/**
|
||||
* The base NativeAnimation function only supports a subset
|
||||
* of Motion easings, and WAAPI also only supports some
|
||||
* easing functions via string/cubic-bezier definitions.
|
||||
*
|
||||
* This function replaces those unsupported easing functions
|
||||
* with a JS easing function. This will later get compiled
|
||||
* to a linear() easing function.
|
||||
*/
|
||||
replaceStringEasing(options);
|
||||
/**
|
||||
* Ensure we replace the transition type with a generator function
|
||||
* before passing to WAAPI.
|
||||
*
|
||||
* TODO: Does this have a better home? It could be shared with
|
||||
* JSAnimation.
|
||||
*/
|
||||
replaceTransitionType(options);
|
||||
super(options);
|
||||
/**
|
||||
* Only set startTime when the animation should autoplay.
|
||||
* Setting startTime on a paused WAAPI animation unpauses it
|
||||
* (per the WAAPI spec), which breaks autoplay: false.
|
||||
*/
|
||||
if (options.startTime !== undefined && options.autoplay !== false) {
|
||||
this.startTime = options.startTime;
|
||||
}
|
||||
this.options = options;
|
||||
}
|
||||
/**
|
||||
* WAAPI doesn't natively have any interruption capabilities.
|
||||
*
|
||||
* Rather than read committed styles back out of the DOM, we can
|
||||
* create a renderless JS animation and sample it twice to calculate
|
||||
* its current value, "previous" value, and therefore allow
|
||||
* Motion to calculate velocity for any subsequent animation.
|
||||
*/
|
||||
updateMotionValue(value) {
|
||||
const { motionValue, onUpdate, onComplete, element, ...options } = this.options;
|
||||
if (!motionValue)
|
||||
return;
|
||||
if (value !== undefined) {
|
||||
motionValue.set(value);
|
||||
return;
|
||||
}
|
||||
const sampleAnimation = new JSAnimation({
|
||||
...options,
|
||||
autoplay: false,
|
||||
});
|
||||
/**
|
||||
* Use wall-clock elapsed time for sampling.
|
||||
* Under CPU load, WAAPI's currentTime may not reflect actual
|
||||
* elapsed time, causing incorrect sampling and visual jumps.
|
||||
*/
|
||||
const sampleTime = Math.max(sampleDelta, time.now() - this.startTime);
|
||||
const delta = clamp(0, sampleDelta, sampleTime - sampleDelta);
|
||||
const current = sampleAnimation.sample(sampleTime).value;
|
||||
/**
|
||||
* Write the estimated value to inline style so it persists
|
||||
* after cancel(), covering the async gap before the next
|
||||
* animation starts.
|
||||
*/
|
||||
const { name } = this.options;
|
||||
if (element && name)
|
||||
setStyle(element, name, current);
|
||||
motionValue.setWithVelocity(sampleAnimation.sample(Math.max(0, sampleTime - delta)).value, current, delta);
|
||||
sampleAnimation.stop();
|
||||
}
|
||||
}
|
||||
|
||||
export { NativeAnimationExtended };
|
||||
//# sourceMappingURL=NativeAnimationExtended.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+15
@@ -0,0 +1,15 @@
|
||||
import { NativeAnimation } from './NativeAnimation.mjs';
|
||||
|
||||
class NativeAnimationWrapper extends NativeAnimation {
|
||||
constructor(animation) {
|
||||
super();
|
||||
this.animation = animation;
|
||||
animation.onfinish = () => {
|
||||
this.finishedTime = this.time;
|
||||
this.notifyFinished();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { NativeAnimationWrapper };
|
||||
//# sourceMappingURL=NativeAnimationWrapper.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"NativeAnimationWrapper.mjs","sources":["../../../src/animation/NativeAnimationWrapper.ts"],"sourcesContent":["import { NativeAnimation } from \"./NativeAnimation\"\nimport { AnyResolvedKeyframe } from \"./types\"\n\nexport class NativeAnimationWrapper<\n T extends AnyResolvedKeyframe\n> extends NativeAnimation<T> {\n constructor(animation: Animation) {\n super()\n\n this.animation = animation\n animation.onfinish = () => {\n this.finishedTime = this.time\n this.notifyFinished()\n }\n }\n}\n"],"names":[],"mappings":";;AAGM,MAAO,sBAEX,SAAQ,eAAkB,CAAA;AACxB,IAAA,WAAA,CAAY,SAAoB,EAAA;AAC5B,QAAA,KAAK,EAAE;AAEP,QAAA,IAAI,CAAC,SAAS,GAAG,SAAS;AAC1B,QAAA,SAAS,CAAC,QAAQ,GAAG,MAAK;AACtB,YAAA,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI;YAC7B,IAAI,CAAC,cAAc,EAAE;AACzB,QAAA,CAAC;IACL;AACH;;;;"}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { animateMotionValue } from '../interfaces/motion-value.mjs';
|
||||
import { motionValue } from '../../value/index.mjs';
|
||||
import { isMotionValue } from '../../value/utils/is-motion-value.mjs';
|
||||
|
||||
function animateSingleValue(value, keyframes, options) {
|
||||
const motionValue$1 = isMotionValue(value) ? value : motionValue(value);
|
||||
motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options));
|
||||
return motionValue$1.animation;
|
||||
}
|
||||
|
||||
export { animateSingleValue };
|
||||
//# sourceMappingURL=single-value.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"single-value.mjs","sources":["../../../../src/animation/animate/single-value.ts"],"sourcesContent":["import { animateMotionValue } from \"../interfaces/motion-value\"\nimport type {\n AnimationPlaybackControlsWithThen,\n AnyResolvedKeyframe,\n UnresolvedValueKeyframe,\n ValueAnimationTransition,\n} from \"../types\"\nimport {\n motionValue as createMotionValue,\n MotionValue,\n} from \"../../value\"\nimport { isMotionValue } from \"../../value/utils/is-motion-value\"\n\nexport function animateSingleValue<V extends AnyResolvedKeyframe>(\n value: MotionValue<V> | V,\n keyframes: V | UnresolvedValueKeyframe<V>[],\n options?: ValueAnimationTransition\n): AnimationPlaybackControlsWithThen {\n const motionValue = isMotionValue(value) ? value : createMotionValue(value)\n\n motionValue.start(animateMotionValue(\"\", motionValue, keyframes, options))\n\n return motionValue.animation!\n}\n"],"names":["motionValue","createMotionValue"],"mappings":";;;;SAagB,kBAAkB,CAC9B,KAAyB,EACzB,SAA2C,EAC3C,OAAkC,EAAA;AAElC,IAAA,MAAMA,aAAW,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,KAAK,GAAGC,WAAiB,CAAC,KAAK,CAAC;AAE3E,IAAAD,aAAW,CAAC,KAAK,CAAC,kBAAkB,CAAC,EAAE,EAAEA,aAAW,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAE1E,OAAOA,aAAW,CAAC,SAAU;AACjC;;;;"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { time } from '../../frameloop/sync-time.mjs';
|
||||
import { frameData, cancelFrame, frame } from '../../frameloop/frame.mjs';
|
||||
|
||||
const frameloopDriver = (update) => {
|
||||
const passTimestamp = ({ timestamp }) => update(timestamp);
|
||||
return {
|
||||
start: (keepAlive = true) => frame.update(passTimestamp, keepAlive),
|
||||
stop: () => cancelFrame(passTimestamp),
|
||||
/**
|
||||
* If we're processing this frame we can use the
|
||||
* framelocked timestamp to keep things in sync.
|
||||
*/
|
||||
now: () => (frameData.isProcessing ? frameData.timestamp : time.now()),
|
||||
};
|
||||
};
|
||||
|
||||
export { frameloopDriver };
|
||||
//# sourceMappingURL=frame.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"frame.mjs","sources":["../../../../src/animation/drivers/frame.ts"],"sourcesContent":["import { cancelFrame, frame, frameData } from \"../../frameloop\"\nimport { time } from \"../../frameloop/sync-time\"\nimport { FrameData } from \"../../frameloop/types\"\nimport { Driver } from \"./types\"\n\nexport const frameloopDriver: Driver = (update) => {\n const passTimestamp = ({ timestamp }: FrameData) => update(timestamp)\n\n return {\n start: (keepAlive = true) => frame.update(passTimestamp, keepAlive),\n stop: () => cancelFrame(passTimestamp),\n /**\n * If we're processing this frame we can use the\n * framelocked timestamp to keep things in sync.\n */\n now: () => (frameData.isProcessing ? frameData.timestamp : time.now()),\n }\n}\n"],"names":[],"mappings":";;;AAKO,MAAM,eAAe,GAAW,CAAC,MAAM,KAAI;AAC9C,IAAA,MAAM,aAAa,GAAG,CAAC,EAAE,SAAS,EAAa,KAAK,MAAM,CAAC,SAAS,CAAC;IAErE,OAAO;AACH,QAAA,KAAK,EAAE,CAAC,SAAS,GAAG,IAAI,KAAK,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC;AACnE,QAAA,IAAI,EAAE,MAAM,WAAW,CAAC,aAAa,CAAC;AACtC;;;AAGG;QACH,GAAG,EAAE,OAAO,SAAS,CAAC,YAAY,GAAG,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;KACzE;AACL;;;;"}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import { spring } from './spring.mjs';
|
||||
import { getGeneratorVelocity } from './utils/velocity.mjs';
|
||||
|
||||
function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
|
||||
const origin = keyframes[0];
|
||||
const state = {
|
||||
done: false,
|
||||
value: origin,
|
||||
};
|
||||
const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
|
||||
const nearestBoundary = (v) => {
|
||||
if (min === undefined)
|
||||
return max;
|
||||
if (max === undefined)
|
||||
return min;
|
||||
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
||||
};
|
||||
let amplitude = power * velocity;
|
||||
const ideal = origin + amplitude;
|
||||
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
||||
/**
|
||||
* If the target has changed we need to re-calculate the amplitude, otherwise
|
||||
* the animation will start from the wrong position.
|
||||
*/
|
||||
if (target !== ideal)
|
||||
amplitude = target - origin;
|
||||
const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
|
||||
const calcLatest = (t) => target + calcDelta(t);
|
||||
const applyFriction = (t) => {
|
||||
const delta = calcDelta(t);
|
||||
const latest = calcLatest(t);
|
||||
state.done = Math.abs(delta) <= restDelta;
|
||||
state.value = state.done ? target : latest;
|
||||
};
|
||||
/**
|
||||
* Ideally this would resolve for t in a stateless way, we could
|
||||
* do that by always precalculating the animation but as we know
|
||||
* this will be done anyway we can assume that spring will
|
||||
* be discovered during that.
|
||||
*/
|
||||
let timeReachedBoundary;
|
||||
let spring$1;
|
||||
const checkCatchBoundary = (t) => {
|
||||
if (!isOutOfBounds(state.value))
|
||||
return;
|
||||
timeReachedBoundary = t;
|
||||
spring$1 = spring({
|
||||
keyframes: [state.value, nearestBoundary(state.value)],
|
||||
velocity: getGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000
|
||||
damping: bounceDamping,
|
||||
stiffness: bounceStiffness,
|
||||
restDelta,
|
||||
restSpeed,
|
||||
});
|
||||
};
|
||||
checkCatchBoundary(0);
|
||||
return {
|
||||
calculatedDuration: null,
|
||||
next: (t) => {
|
||||
/**
|
||||
* We need to resolve the friction to figure out if we need a
|
||||
* spring but we don't want to do this twice per frame. So here
|
||||
* we flag if we updated for this frame and later if we did
|
||||
* we can skip doing it again.
|
||||
*/
|
||||
let hasUpdatedFrame = false;
|
||||
if (!spring$1 && timeReachedBoundary === undefined) {
|
||||
hasUpdatedFrame = true;
|
||||
applyFriction(t);
|
||||
checkCatchBoundary(t);
|
||||
}
|
||||
/**
|
||||
* If we have a spring and the provided t is beyond the moment the friction
|
||||
* animation crossed the min/max boundary, use the spring.
|
||||
*/
|
||||
if (timeReachedBoundary !== undefined && t >= timeReachedBoundary) {
|
||||
return spring$1.next(t - timeReachedBoundary);
|
||||
}
|
||||
else {
|
||||
!hasUpdatedFrame && applyFriction(t);
|
||||
return state;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { inertia };
|
||||
//# sourceMappingURL=inertia.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+50
@@ -0,0 +1,50 @@
|
||||
import { easeInOut, isEasingArray, easingDefinitionToFunction } from 'motion-utils';
|
||||
import { interpolate } from '../../utils/interpolate.mjs';
|
||||
import { defaultOffset } from '../keyframes/offsets/default.mjs';
|
||||
import { convertOffsetToTimes } from '../keyframes/offsets/time.mjs';
|
||||
|
||||
function defaultEasing(values, easing) {
|
||||
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
|
||||
}
|
||||
function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
|
||||
/**
|
||||
* Easing functions can be externally defined as strings. Here we convert them
|
||||
* into actual functions.
|
||||
*/
|
||||
const easingFunctions = isEasingArray(ease)
|
||||
? ease.map(easingDefinitionToFunction)
|
||||
: easingDefinitionToFunction(ease);
|
||||
/**
|
||||
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
||||
* to reduce GC during animation.
|
||||
*/
|
||||
const state = {
|
||||
done: false,
|
||||
value: keyframeValues[0],
|
||||
};
|
||||
/**
|
||||
* Create a times array based on the provided 0-1 offsets
|
||||
*/
|
||||
const absoluteTimes = convertOffsetToTimes(
|
||||
// Only use the provided offsets if they're the correct length
|
||||
// TODO Maybe we should warn here if there's a length mismatch
|
||||
times && times.length === keyframeValues.length
|
||||
? times
|
||||
: defaultOffset(keyframeValues), duration);
|
||||
const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
|
||||
ease: Array.isArray(easingFunctions)
|
||||
? easingFunctions
|
||||
: defaultEasing(keyframeValues, easingFunctions),
|
||||
});
|
||||
return {
|
||||
calculatedDuration: duration,
|
||||
next: (t) => {
|
||||
state.value = mapTimeToKeyframe(t);
|
||||
state.done = t >= duration;
|
||||
return state;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { defaultEasing, keyframes };
|
||||
//# sourceMappingURL=keyframes.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"keyframes.mjs","sources":["../../../../src/animation/generators/keyframes.ts"],"sourcesContent":["import {\n easeInOut,\n easingDefinitionToFunction,\n EasingFunction,\n isEasingArray,\n} from \"motion-utils\"\nimport { interpolate } from \"../../utils/interpolate\"\nimport { defaultOffset } from \"../keyframes/offsets/default\"\nimport { convertOffsetToTimes } from \"../keyframes/offsets/time\"\nimport {\n AnimationState,\n AnyResolvedKeyframe,\n KeyframeGenerator,\n ValueAnimationOptions,\n} from \"../types\"\n\nexport function defaultEasing(\n values: any[],\n easing?: EasingFunction\n): EasingFunction[] {\n return values.map(() => easing || easeInOut).splice(0, values.length - 1)\n}\n\nexport function keyframes<T extends AnyResolvedKeyframe>({\n duration = 300,\n keyframes: keyframeValues,\n times,\n ease = \"easeInOut\",\n}: ValueAnimationOptions<T>): KeyframeGenerator<T> {\n /**\n * Easing functions can be externally defined as strings. Here we convert them\n * into actual functions.\n */\n const easingFunctions = isEasingArray(ease)\n ? ease.map(easingDefinitionToFunction)\n : easingDefinitionToFunction(ease)\n\n /**\n * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator\n * to reduce GC during animation.\n */\n const state: AnimationState<T> = {\n done: false,\n value: keyframeValues[0],\n }\n\n /**\n * Create a times array based on the provided 0-1 offsets\n */\n const absoluteTimes = convertOffsetToTimes(\n // Only use the provided offsets if they're the correct length\n // TODO Maybe we should warn here if there's a length mismatch\n times && times.length === keyframeValues.length\n ? times\n : defaultOffset(keyframeValues),\n duration\n )\n\n const mapTimeToKeyframe = interpolate<T>(absoluteTimes, keyframeValues, {\n ease: Array.isArray(easingFunctions)\n ? easingFunctions\n : defaultEasing(keyframeValues, easingFunctions),\n })\n\n return {\n calculatedDuration: duration,\n next: (t: number) => {\n state.value = mapTimeToKeyframe(t)\n state.done = t >= duration\n return state\n },\n }\n}\n"],"names":[],"mappings":";;;;;AAgBM,SAAU,aAAa,CACzB,MAAa,EACb,MAAuB,EAAA;IAEvB,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,MAAM,IAAI,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AAC7E;SAEgB,SAAS,CAAgC,EACrD,QAAQ,GAAG,GAAG,EACd,SAAS,EAAE,cAAc,EACzB,KAAK,EACL,IAAI,GAAG,WAAW,GACK,EAAA;AACvB;;;AAGG;AACH,IAAA,MAAM,eAAe,GAAG,aAAa,CAAC,IAAI;AACtC,UAAE,IAAI,CAAC,GAAG,CAAC,0BAA0B;AACrC,UAAE,0BAA0B,CAAC,IAAI,CAAC;AAEtC;;;AAGG;AACH,IAAA,MAAM,KAAK,GAAsB;AAC7B,QAAA,IAAI,EAAE,KAAK;AACX,QAAA,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;KAC3B;AAED;;AAEG;IACH,MAAM,aAAa,GAAG,oBAAoB;;;AAGtC,IAAA,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,cAAc,CAAC;AACrC,UAAE;UACA,aAAa,CAAC,cAAc,CAAC,EACnC,QAAQ,CACX;AAED,IAAA,MAAM,iBAAiB,GAAG,WAAW,CAAI,aAAa,EAAE,cAAc,EAAE;AACpE,QAAA,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,eAAe;AAC/B,cAAE;AACF,cAAE,aAAa,CAAC,cAAc,EAAE,eAAe,CAAC;AACvD,KAAA,CAAC;IAEF,OAAO;AACH,QAAA,kBAAkB,EAAE,QAAQ;AAC5B,QAAA,IAAI,EAAE,CAAC,CAAS,KAAI;AAChB,YAAA,KAAK,CAAC,KAAK,GAAG,iBAAiB,CAAC,CAAC,CAAC;AAClC,YAAA,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,QAAQ;AAC1B,YAAA,OAAO,KAAK;QAChB,CAAC;KACJ;AACL;;;;"}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
import { millisecondsToSeconds, clamp, secondsToMilliseconds, warning } from 'motion-utils';
|
||||
import { generateLinearEasing } from '../waapi/utils/linear.mjs';
|
||||
import { calcGeneratorDuration, maxGeneratorDuration } from './utils/calc-duration.mjs';
|
||||
import { createGeneratorEasing } from './utils/create-generator-easing.mjs';
|
||||
|
||||
const springDefaults = {
|
||||
// Default spring physics
|
||||
stiffness: 100,
|
||||
damping: 10,
|
||||
mass: 1.0,
|
||||
velocity: 0.0,
|
||||
// Default duration/bounce-based options
|
||||
duration: 800, // in ms
|
||||
bounce: 0.3,
|
||||
visualDuration: 0.3, // in seconds
|
||||
// Rest thresholds
|
||||
restSpeed: {
|
||||
granular: 0.01,
|
||||
default: 2,
|
||||
},
|
||||
restDelta: {
|
||||
granular: 0.005,
|
||||
default: 0.5,
|
||||
},
|
||||
// Limits
|
||||
minDuration: 0.01, // in seconds
|
||||
maxDuration: 10.0, // in seconds
|
||||
minDamping: 0.05,
|
||||
maxDamping: 1,
|
||||
};
|
||||
function calcAngularFreq(undampedFreq, dampingRatio) {
|
||||
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
||||
}
|
||||
const rootIterations = 12;
|
||||
function approximateRoot(envelope, derivative, initialGuess) {
|
||||
let result = initialGuess;
|
||||
for (let i = 1; i < rootIterations; i++) {
|
||||
result = result - envelope(result) / derivative(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* This is ported from the Framer implementation of duration-based spring resolution.
|
||||
*/
|
||||
const safeMin = 0.001;
|
||||
function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
|
||||
let envelope;
|
||||
let derivative;
|
||||
warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
|
||||
let dampingRatio = 1 - bounce;
|
||||
/**
|
||||
* Restrict dampingRatio and duration to within acceptable ranges.
|
||||
*/
|
||||
dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
|
||||
duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
|
||||
if (dampingRatio < 1) {
|
||||
/**
|
||||
* Underdamped spring
|
||||
*/
|
||||
envelope = (undampedFreq) => {
|
||||
const exponentialDecay = undampedFreq * dampingRatio;
|
||||
const delta = exponentialDecay * duration;
|
||||
const a = exponentialDecay - velocity;
|
||||
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
||||
const c = Math.exp(-delta);
|
||||
return safeMin - (a / b) * c;
|
||||
};
|
||||
derivative = (undampedFreq) => {
|
||||
const exponentialDecay = undampedFreq * dampingRatio;
|
||||
const delta = exponentialDecay * duration;
|
||||
const d = delta * velocity + velocity;
|
||||
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
||||
const f = Math.exp(-delta);
|
||||
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
||||
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
||||
return (factor * ((d - e) * f)) / g;
|
||||
};
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Critically-damped spring
|
||||
*/
|
||||
envelope = (undampedFreq) => {
|
||||
const a = Math.exp(-undampedFreq * duration);
|
||||
const b = (undampedFreq - velocity) * duration + 1;
|
||||
return -safeMin + a * b;
|
||||
};
|
||||
derivative = (undampedFreq) => {
|
||||
const a = Math.exp(-undampedFreq * duration);
|
||||
const b = (velocity - undampedFreq) * (duration * duration);
|
||||
return a * b;
|
||||
};
|
||||
}
|
||||
const initialGuess = 5 / duration;
|
||||
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
||||
duration = secondsToMilliseconds(duration);
|
||||
if (isNaN(undampedFreq)) {
|
||||
return {
|
||||
stiffness: springDefaults.stiffness,
|
||||
damping: springDefaults.damping,
|
||||
duration,
|
||||
};
|
||||
}
|
||||
else {
|
||||
const stiffness = Math.pow(undampedFreq, 2) * mass;
|
||||
return {
|
||||
stiffness,
|
||||
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
||||
duration,
|
||||
};
|
||||
}
|
||||
}
|
||||
const durationKeys = ["duration", "bounce"];
|
||||
const physicsKeys = ["stiffness", "damping", "mass"];
|
||||
function isSpringType(options, keys) {
|
||||
return keys.some((key) => options[key] !== undefined);
|
||||
}
|
||||
function getSpringOptions(options) {
|
||||
let springOptions = {
|
||||
velocity: springDefaults.velocity,
|
||||
stiffness: springDefaults.stiffness,
|
||||
damping: springDefaults.damping,
|
||||
mass: springDefaults.mass,
|
||||
isResolvedFromDuration: false,
|
||||
...options,
|
||||
};
|
||||
// stiffness/damping/mass overrides duration/bounce
|
||||
if (!isSpringType(options, physicsKeys) &&
|
||||
isSpringType(options, durationKeys)) {
|
||||
// Time-defined springs should ignore inherited velocity.
|
||||
// Velocity from interrupted animations can cause findSpring()
|
||||
// to compute wildly different spring parameters, leading to
|
||||
// massive oscillation on small-range animations.
|
||||
springOptions.velocity = 0;
|
||||
if (options.visualDuration) {
|
||||
const visualDuration = options.visualDuration;
|
||||
const root = (2 * Math.PI) / (visualDuration * 1.2);
|
||||
const stiffness = root * root;
|
||||
const damping = 2 *
|
||||
clamp(0.05, 1, 1 - (options.bounce || 0)) *
|
||||
Math.sqrt(stiffness);
|
||||
springOptions = {
|
||||
...springOptions,
|
||||
mass: springDefaults.mass,
|
||||
stiffness,
|
||||
damping,
|
||||
};
|
||||
}
|
||||
else {
|
||||
const derived = findSpring({ ...options, velocity: 0 });
|
||||
springOptions = {
|
||||
...springOptions,
|
||||
...derived,
|
||||
mass: springDefaults.mass,
|
||||
};
|
||||
springOptions.isResolvedFromDuration = true;
|
||||
}
|
||||
}
|
||||
return springOptions;
|
||||
}
|
||||
function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
|
||||
const options = typeof optionsOrVisualDuration !== "object"
|
||||
? {
|
||||
visualDuration: optionsOrVisualDuration,
|
||||
keyframes: [0, 1],
|
||||
bounce,
|
||||
}
|
||||
: optionsOrVisualDuration;
|
||||
let { restSpeed, restDelta } = options;
|
||||
const origin = options.keyframes[0];
|
||||
const target = options.keyframes[options.keyframes.length - 1];
|
||||
/**
|
||||
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
||||
* to reduce GC during animation.
|
||||
*/
|
||||
const state = { done: false, value: origin };
|
||||
const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
|
||||
...options,
|
||||
velocity: -millisecondsToSeconds(options.velocity || 0),
|
||||
});
|
||||
const initialVelocity = velocity || 0.0;
|
||||
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
||||
const initialDelta = target - origin;
|
||||
const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
|
||||
/**
|
||||
* If we're working on a granular scale, use smaller defaults for determining
|
||||
* when the spring is finished.
|
||||
*
|
||||
* These defaults have been selected emprically based on what strikes a good
|
||||
* ratio between feeling good and finishing as soon as changes are imperceptible.
|
||||
*/
|
||||
const isGranularScale = Math.abs(initialDelta) < 5;
|
||||
restSpeed || (restSpeed = isGranularScale
|
||||
? springDefaults.restSpeed.granular
|
||||
: springDefaults.restSpeed.default);
|
||||
restDelta || (restDelta = isGranularScale
|
||||
? springDefaults.restDelta.granular
|
||||
: springDefaults.restDelta.default);
|
||||
let resolveSpring;
|
||||
let resolveVelocity;
|
||||
// Underdamped coefficients, hoisted for use in the inlined next() hot path
|
||||
let angularFreq;
|
||||
let A;
|
||||
let sinCoeff;
|
||||
let cosCoeff;
|
||||
if (dampingRatio < 1) {
|
||||
angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
||||
A =
|
||||
(initialVelocity +
|
||||
dampingRatio * undampedAngularFreq * initialDelta) /
|
||||
angularFreq;
|
||||
// Underdamped spring
|
||||
resolveSpring = (t) => {
|
||||
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
||||
return (target -
|
||||
envelope *
|
||||
(A * Math.sin(angularFreq * t) +
|
||||
initialDelta * Math.cos(angularFreq * t)));
|
||||
};
|
||||
// Analytical derivative of underdamped spring (px/ms)
|
||||
sinCoeff =
|
||||
dampingRatio * undampedAngularFreq * A + initialDelta * angularFreq;
|
||||
cosCoeff =
|
||||
dampingRatio * undampedAngularFreq * initialDelta - A * angularFreq;
|
||||
resolveVelocity = (t) => {
|
||||
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
||||
return envelope *
|
||||
(sinCoeff * Math.sin(angularFreq * t) +
|
||||
cosCoeff * Math.cos(angularFreq * t));
|
||||
};
|
||||
}
|
||||
else if (dampingRatio === 1) {
|
||||
// Critically damped spring
|
||||
resolveSpring = (t) => target -
|
||||
Math.exp(-undampedAngularFreq * t) *
|
||||
(initialDelta +
|
||||
(initialVelocity + undampedAngularFreq * initialDelta) * t);
|
||||
// Analytical derivative of critically damped spring (px/ms)
|
||||
const C = initialVelocity + undampedAngularFreq * initialDelta;
|
||||
resolveVelocity = (t) => Math.exp(-undampedAngularFreq * t) *
|
||||
(undampedAngularFreq * C * t - initialVelocity);
|
||||
}
|
||||
else {
|
||||
// Overdamped spring
|
||||
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
||||
resolveSpring = (t) => {
|
||||
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
||||
// When performing sinh or cosh values can hit Infinity so we cap them here
|
||||
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
||||
return (target -
|
||||
(envelope *
|
||||
((initialVelocity +
|
||||
dampingRatio * undampedAngularFreq * initialDelta) *
|
||||
Math.sinh(freqForT) +
|
||||
dampedAngularFreq *
|
||||
initialDelta *
|
||||
Math.cosh(freqForT))) /
|
||||
dampedAngularFreq);
|
||||
};
|
||||
// Analytical derivative of overdamped spring (px/ms)
|
||||
const P = (initialVelocity +
|
||||
dampingRatio * undampedAngularFreq * initialDelta) /
|
||||
dampedAngularFreq;
|
||||
const sinhCoeff = dampingRatio * undampedAngularFreq * P - initialDelta * dampedAngularFreq;
|
||||
const coshCoeff = dampingRatio * undampedAngularFreq * initialDelta - P * dampedAngularFreq;
|
||||
resolveVelocity = (t) => {
|
||||
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
||||
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
||||
return envelope *
|
||||
(sinhCoeff * Math.sinh(freqForT) +
|
||||
coshCoeff * Math.cosh(freqForT));
|
||||
};
|
||||
}
|
||||
const generator = {
|
||||
calculatedDuration: isResolvedFromDuration ? duration || null : null,
|
||||
velocity: (t) => secondsToMilliseconds(resolveVelocity(t)),
|
||||
next: (t) => {
|
||||
/**
|
||||
* For underdamped physics springs we need both position and
|
||||
* velocity each tick. Compute shared trig values once to avoid
|
||||
* duplicate Math.exp/sin/cos calls on the hot path.
|
||||
*/
|
||||
if (!isResolvedFromDuration && dampingRatio < 1) {
|
||||
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
||||
const sin = Math.sin(angularFreq * t);
|
||||
const cos = Math.cos(angularFreq * t);
|
||||
const current = target -
|
||||
envelope *
|
||||
(A * sin + initialDelta * cos);
|
||||
const currentVelocity = secondsToMilliseconds(envelope *
|
||||
(sinCoeff * sin + cosCoeff * cos));
|
||||
state.done =
|
||||
Math.abs(currentVelocity) <= restSpeed &&
|
||||
Math.abs(target - current) <= restDelta;
|
||||
state.value = state.done ? target : current;
|
||||
return state;
|
||||
}
|
||||
const current = resolveSpring(t);
|
||||
if (!isResolvedFromDuration) {
|
||||
const currentVelocity = secondsToMilliseconds(resolveVelocity(t));
|
||||
state.done =
|
||||
Math.abs(currentVelocity) <= restSpeed &&
|
||||
Math.abs(target - current) <= restDelta;
|
||||
}
|
||||
else {
|
||||
state.done = t >= duration;
|
||||
}
|
||||
state.value = state.done ? target : current;
|
||||
return state;
|
||||
},
|
||||
toString: () => {
|
||||
const calculatedDuration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
|
||||
const easing = generateLinearEasing((progress) => generator.next(calculatedDuration * progress).value, calculatedDuration, 30);
|
||||
return calculatedDuration + "ms " + easing;
|
||||
},
|
||||
toTransition: () => { },
|
||||
};
|
||||
return generator;
|
||||
}
|
||||
spring.applyToOptions = (options) => {
|
||||
const generatorOptions = createGeneratorEasing(options, 100, spring);
|
||||
options.ease = generatorOptions.ease;
|
||||
options.duration = secondsToMilliseconds(generatorOptions.duration);
|
||||
options.type = "keyframes";
|
||||
return options;
|
||||
};
|
||||
|
||||
export { spring };
|
||||
//# sourceMappingURL=spring.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Implement a practical max duration for keyframe generation
|
||||
* to prevent infinite loops
|
||||
*/
|
||||
const maxGeneratorDuration = 20000;
|
||||
function calcGeneratorDuration(generator) {
|
||||
let duration = 0;
|
||||
const timeStep = 50;
|
||||
let state = generator.next(duration);
|
||||
while (!state.done && duration < maxGeneratorDuration) {
|
||||
duration += timeStep;
|
||||
state = generator.next(duration);
|
||||
}
|
||||
return duration >= maxGeneratorDuration ? Infinity : duration;
|
||||
}
|
||||
|
||||
export { calcGeneratorDuration, maxGeneratorDuration };
|
||||
//# sourceMappingURL=calc-duration.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"calc-duration.mjs","sources":["../../../../../src/animation/generators/utils/calc-duration.ts"],"sourcesContent":["import { KeyframeGenerator } from \"../../types\"\n\n/**\n * Implement a practical max duration for keyframe generation\n * to prevent infinite loops\n */\nexport const maxGeneratorDuration = 20_000\nexport function calcGeneratorDuration(\n generator: KeyframeGenerator<unknown>\n): number {\n let duration = 0\n const timeStep = 50\n let state = generator.next(duration)\n while (!state.done && duration < maxGeneratorDuration) {\n duration += timeStep\n state = generator.next(duration)\n }\n\n return duration >= maxGeneratorDuration ? Infinity : duration\n}\n"],"names":[],"mappings":"AAEA;;;AAGG;AACI,MAAM,oBAAoB,GAAG;AAC9B,SAAU,qBAAqB,CACjC,SAAqC,EAAA;IAErC,IAAI,QAAQ,GAAG,CAAC;IAChB,MAAM,QAAQ,GAAG,EAAE;IACnB,IAAI,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC;IACpC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,QAAQ,GAAG,oBAAoB,EAAE;QACnD,QAAQ,IAAI,QAAQ;AACpB,QAAA,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC;IACpC;IAEA,OAAO,QAAQ,IAAI,oBAAoB,GAAG,QAAQ,GAAG,QAAQ;AACjE;;;;"}
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
import { millisecondsToSeconds } from 'motion-utils';
|
||||
import { calcGeneratorDuration, maxGeneratorDuration } from './calc-duration.mjs';
|
||||
|
||||
/**
|
||||
* Create a progress => progress easing function from a generator.
|
||||
*/
|
||||
function createGeneratorEasing(options, scale = 100, createGenerator) {
|
||||
const generator = createGenerator({ ...options, keyframes: [0, scale] });
|
||||
const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
|
||||
return {
|
||||
type: "keyframes",
|
||||
ease: (progress) => {
|
||||
return generator.next(duration * progress).value / scale;
|
||||
},
|
||||
duration: millisecondsToSeconds(duration),
|
||||
};
|
||||
}
|
||||
|
||||
export { createGeneratorEasing };
|
||||
//# sourceMappingURL=create-generator-easing.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"create-generator-easing.mjs","sources":["../../../../../src/animation/generators/utils/create-generator-easing.ts"],"sourcesContent":["import { millisecondsToSeconds } from \"motion-utils\"\nimport { GeneratorFactory, Transition } from \"../../types\"\nimport { calcGeneratorDuration, maxGeneratorDuration } from \"./calc-duration\"\n\n/**\n * Create a progress => progress easing function from a generator.\n */\nexport function createGeneratorEasing(\n options: Transition,\n scale = 100,\n createGenerator: GeneratorFactory\n) {\n const generator = createGenerator({ ...options, keyframes: [0, scale] })\n const duration = Math.min(\n calcGeneratorDuration(generator),\n maxGeneratorDuration\n )\n\n return {\n type: \"keyframes\",\n ease: (progress: number) => {\n return generator.next(duration * progress).value / scale\n },\n duration: millisecondsToSeconds(duration),\n }\n}\n"],"names":[],"mappings":";;;AAIA;;AAEG;AACG,SAAU,qBAAqB,CACjC,OAAmB,EACnB,KAAK,GAAG,GAAG,EACX,eAAiC,EAAA;AAEjC,IAAA,MAAM,SAAS,GAAG,eAAe,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;AACxE,IAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CACrB,qBAAqB,CAAC,SAAS,CAAC,EAChC,oBAAoB,CACvB;IAED,OAAO;AACH,QAAA,IAAI,EAAE,WAAW;AACjB,QAAA,IAAI,EAAE,CAAC,QAAgB,KAAI;AACvB,YAAA,OAAO,SAAS,CAAC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC,KAAK,GAAG,KAAK;QAC5D,CAAC;AACD,QAAA,QAAQ,EAAE,qBAAqB,CAAC,QAAQ,CAAC;KAC5C;AACL;;;;"}
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
function isGenerator(type) {
|
||||
return typeof type === "function" && "applyToOptions" in type;
|
||||
}
|
||||
|
||||
export { isGenerator };
|
||||
//# sourceMappingURL=is-generator.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-generator.mjs","sources":["../../../../../src/animation/generators/utils/is-generator.ts"],"sourcesContent":["import { AnimationGeneratorType, GeneratorFactory } from \"../../types\"\n\nexport function isGenerator(\n type?: AnimationGeneratorType\n): type is GeneratorFactory {\n return typeof type === \"function\" && \"applyToOptions\" in type\n}\n"],"names":[],"mappings":"AAEM,SAAU,WAAW,CACvB,IAA6B,EAAA;IAE7B,OAAO,OAAO,IAAI,KAAK,UAAU,IAAI,gBAAgB,IAAI,IAAI;AACjE;;;;"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { velocityPerSecond } from 'motion-utils';
|
||||
|
||||
const velocitySampleDuration = 5; // ms
|
||||
function getGeneratorVelocity(resolveValue, t, current) {
|
||||
const prevT = Math.max(t - velocitySampleDuration, 0);
|
||||
return velocityPerSecond(current - resolveValue(prevT), t - prevT);
|
||||
}
|
||||
|
||||
export { getGeneratorVelocity };
|
||||
//# sourceMappingURL=velocity.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"velocity.mjs","sources":["../../../../../src/animation/generators/utils/velocity.ts"],"sourcesContent":["import { velocityPerSecond } from \"motion-utils\"\n\nconst velocitySampleDuration = 5 // ms\n\nexport function getGeneratorVelocity(\n resolveValue: (v: number) => number,\n t: number,\n current: number\n) {\n const prevT = Math.max(t - velocitySampleDuration, 0)\n return velocityPerSecond(current - resolveValue(prevT), t - prevT)\n}\n"],"names":[],"mappings":";;AAEA,MAAM,sBAAsB,GAAG,CAAC,CAAA;SAEhB,oBAAoB,CAChC,YAAmC,EACnC,CAAS,EACT,OAAe,EAAA;AAEf,IAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,sBAAsB,EAAE,CAAC,CAAC;AACrD,IAAA,OAAO,iBAAiB,CAAC,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;AACtE;;;;"}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
import { secondsToMilliseconds, MotionGlobalConfig } from 'motion-utils';
|
||||
import { AsyncMotionValueAnimation } from '../AsyncMotionValueAnimation.mjs';
|
||||
import { JSAnimation } from '../JSAnimation.mjs';
|
||||
import { getValueTransition } from '../utils/get-value-transition.mjs';
|
||||
import { makeAnimationInstant } from '../utils/make-animation-instant.mjs';
|
||||
import { getDefaultTransition } from '../utils/default-transitions.mjs';
|
||||
import { getFinalKeyframe } from '../keyframes/get-final.mjs';
|
||||
import { isTransitionDefined } from '../utils/is-transition-defined.mjs';
|
||||
import { frame } from '../../frameloop/frame.mjs';
|
||||
|
||||
const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
|
||||
const valueTransition = getValueTransition(transition, name) || {};
|
||||
/**
|
||||
* Most transition values are currently completely overwritten by value-specific
|
||||
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
||||
* delay actually does inherit from the root transition if not value-specific.
|
||||
*/
|
||||
const delay = valueTransition.delay || transition.delay || 0;
|
||||
/**
|
||||
* Elapsed isn't a public transition option but can be passed through from
|
||||
* optimized appear effects in milliseconds.
|
||||
*/
|
||||
let { elapsed = 0 } = transition;
|
||||
elapsed = elapsed - secondsToMilliseconds(delay);
|
||||
const options = {
|
||||
keyframes: Array.isArray(target) ? target : [null, target],
|
||||
ease: "easeOut",
|
||||
velocity: value.getVelocity(),
|
||||
...valueTransition,
|
||||
delay: -elapsed,
|
||||
onUpdate: (v) => {
|
||||
value.set(v);
|
||||
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
||||
},
|
||||
onComplete: () => {
|
||||
onComplete();
|
||||
valueTransition.onComplete && valueTransition.onComplete();
|
||||
},
|
||||
name,
|
||||
motionValue: value,
|
||||
element: isHandoff ? undefined : element,
|
||||
};
|
||||
/**
|
||||
* If there's no transition defined for this value, we can generate
|
||||
* unique transition settings for this value.
|
||||
*/
|
||||
if (!isTransitionDefined(valueTransition)) {
|
||||
Object.assign(options, getDefaultTransition(name, options));
|
||||
}
|
||||
/**
|
||||
* Both WAAPI and our internal animation functions use durations
|
||||
* as defined by milliseconds, while our external API defines them
|
||||
* as seconds.
|
||||
*/
|
||||
options.duration && (options.duration = secondsToMilliseconds(options.duration));
|
||||
options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay));
|
||||
/**
|
||||
* Support deprecated way to set initial value. Prefer keyframe syntax.
|
||||
*/
|
||||
if (options.from !== undefined) {
|
||||
options.keyframes[0] = options.from;
|
||||
}
|
||||
let shouldSkip = false;
|
||||
if (options.type === false ||
|
||||
(options.duration === 0 && !options.repeatDelay)) {
|
||||
makeAnimationInstant(options);
|
||||
if (options.delay === 0) {
|
||||
shouldSkip = true;
|
||||
}
|
||||
}
|
||||
if (MotionGlobalConfig.instantAnimations ||
|
||||
MotionGlobalConfig.skipAnimations ||
|
||||
element?.shouldSkipAnimations ||
|
||||
valueTransition.skipAnimations) {
|
||||
shouldSkip = true;
|
||||
makeAnimationInstant(options);
|
||||
options.delay = 0;
|
||||
}
|
||||
/**
|
||||
* If the transition type or easing has been explicitly set by the user
|
||||
* then we don't want to allow flattening the animation.
|
||||
*/
|
||||
options.allowFlatten = !valueTransition.type && !valueTransition.ease;
|
||||
/**
|
||||
* If we can or must skip creating the animation, and apply only
|
||||
* the final keyframe, do so. We also check once keyframes are resolved but
|
||||
* this early check prevents the need to create an animation at all.
|
||||
*/
|
||||
if (shouldSkip && !isHandoff && value.get() !== undefined) {
|
||||
const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
|
||||
if (finalKeyframe !== undefined) {
|
||||
frame.update(() => {
|
||||
options.onUpdate(finalKeyframe);
|
||||
options.onComplete();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
return valueTransition.isSync
|
||||
? new JSAnimation(options)
|
||||
: new AsyncMotionValueAnimation(options);
|
||||
};
|
||||
|
||||
export { animateMotionValue };
|
||||
//# sourceMappingURL=motion-value.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+108
@@ -0,0 +1,108 @@
|
||||
import { getValueTransition } from '../utils/get-value-transition.mjs';
|
||||
import { resolveTransition } from '../utils/resolve-transition.mjs';
|
||||
import { positionalKeys } from '../../render/utils/keys-position.mjs';
|
||||
import { setTarget } from '../../render/utils/setters.mjs';
|
||||
import { addValueToWillChange } from '../../value/will-change/add-will-change.mjs';
|
||||
import { getOptimisedAppearId } from '../optimized-appear/get-appear-id.mjs';
|
||||
import { animateMotionValue } from './motion-value.mjs';
|
||||
import { frame } from '../../frameloop/frame.mjs';
|
||||
|
||||
/**
|
||||
* Decide whether we should block this animation. Previously, we achieved this
|
||||
* just by checking whether the key was listed in protectedKeys, but this
|
||||
* posed problems if an animation was triggered by afterChildren and protectedKeys
|
||||
* had been set to true in the meantime.
|
||||
*/
|
||||
function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
|
||||
const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
|
||||
needsAnimating[key] = false;
|
||||
return shouldBlock;
|
||||
}
|
||||
function animateTarget(visualElement, targetAndTransition, { delay = 0, transitionOverride, type } = {}) {
|
||||
let { transition, transitionEnd, ...target } = targetAndTransition;
|
||||
const defaultTransition = visualElement.getDefaultTransition();
|
||||
transition = transition
|
||||
? resolveTransition(transition, defaultTransition)
|
||||
: defaultTransition;
|
||||
const reduceMotion = transition?.reduceMotion;
|
||||
const skipAnimations = transition?.skipAnimations;
|
||||
if (transitionOverride)
|
||||
transition = transitionOverride;
|
||||
const animations = [];
|
||||
const animationTypeState = type &&
|
||||
visualElement.animationState &&
|
||||
visualElement.animationState.getState()[type];
|
||||
const path = transition?.path;
|
||||
if (path) {
|
||||
// path mutates `target` to claim x/y; loop below skips them.
|
||||
path.animateVisualElement(visualElement, target, transition, delay, animations);
|
||||
}
|
||||
for (const key in target) {
|
||||
const value = visualElement.getValue(key, visualElement.latestValues[key] ?? null);
|
||||
const valueTarget = target[key];
|
||||
if (valueTarget === undefined ||
|
||||
(animationTypeState &&
|
||||
shouldBlockAnimation(animationTypeState, key))) {
|
||||
continue;
|
||||
}
|
||||
const valueTransition = {
|
||||
delay,
|
||||
...getValueTransition(transition || {}, key),
|
||||
};
|
||||
if (skipAnimations)
|
||||
valueTransition.skipAnimations = true;
|
||||
/**
|
||||
* If the value is already at the defined target, skip the animation.
|
||||
* We still re-assert the value via frame.update to take precedence
|
||||
* over any stale transitionEnd callbacks from previous animations.
|
||||
*/
|
||||
const currentValue = value.get();
|
||||
if (currentValue !== undefined &&
|
||||
!value.isAnimating() &&
|
||||
!Array.isArray(valueTarget) &&
|
||||
valueTarget === currentValue &&
|
||||
!valueTransition.velocity) {
|
||||
frame.update(() => value.set(valueTarget));
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* If this is the first time a value is being animated, check
|
||||
* to see if we're handling off from an existing animation.
|
||||
*/
|
||||
let isHandoff = false;
|
||||
if (window.MotionHandoffAnimation) {
|
||||
const appearId = getOptimisedAppearId(visualElement);
|
||||
if (appearId) {
|
||||
const startTime = window.MotionHandoffAnimation(appearId, key, frame);
|
||||
if (startTime !== null) {
|
||||
valueTransition.startTime = startTime;
|
||||
isHandoff = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
addValueToWillChange(visualElement, key);
|
||||
const shouldReduceMotion = reduceMotion ?? visualElement.shouldReduceMotion;
|
||||
value.start(animateMotionValue(key, value, valueTarget, shouldReduceMotion && positionalKeys.has(key)
|
||||
? { type: false }
|
||||
: valueTransition, visualElement, isHandoff));
|
||||
const animation = value.animation;
|
||||
if (animation) {
|
||||
animations.push(animation);
|
||||
}
|
||||
}
|
||||
if (transitionEnd) {
|
||||
const applyTransitionEnd = () => frame.update(() => {
|
||||
transitionEnd && setTarget(visualElement, transitionEnd);
|
||||
});
|
||||
if (animations.length) {
|
||||
Promise.all(animations).then(applyTransitionEnd);
|
||||
}
|
||||
else {
|
||||
applyTransitionEnd();
|
||||
}
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
export { animateTarget };
|
||||
//# sourceMappingURL=visual-element-target.mjs.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
import { resolveVariant } from '../../render/utils/resolve-dynamic-variants.mjs';
|
||||
import { calcChildStagger } from '../utils/calc-child-stagger.mjs';
|
||||
import { animateTarget } from './visual-element-target.mjs';
|
||||
|
||||
function animateVariant(visualElement, variant, options = {}) {
|
||||
const resolved = resolveVariant(visualElement, variant, options.type === "exit"
|
||||
? visualElement.presenceContext?.custom
|
||||
: undefined);
|
||||
let { transition = visualElement.getDefaultTransition() || {} } = resolved || {};
|
||||
if (options.transitionOverride) {
|
||||
transition = options.transitionOverride;
|
||||
}
|
||||
/**
|
||||
* If we have a variant, create a callback that runs it as an animation.
|
||||
* Otherwise, we resolve a Promise immediately for a composable no-op.
|
||||
*/
|
||||
const getAnimation = resolved
|
||||
? () => Promise.all(animateTarget(visualElement, resolved, options))
|
||||
: () => Promise.resolve();
|
||||
/**
|
||||
* If we have children, create a callback that runs all their animations.
|
||||
* Otherwise, we resolve a Promise immediately for a composable no-op.
|
||||
*/
|
||||
const getChildAnimations = visualElement.variantChildren && visualElement.variantChildren.size
|
||||
? (forwardDelay = 0) => {
|
||||
const { delayChildren = 0, staggerChildren, staggerDirection, } = transition;
|
||||
return animateChildren(visualElement, variant, forwardDelay, delayChildren, staggerChildren, staggerDirection, options);
|
||||
}
|
||||
: () => Promise.resolve();
|
||||
/**
|
||||
* If the transition explicitly defines a "when" option, we need to resolve either
|
||||
* this animation or all children animations before playing the other.
|
||||
*/
|
||||
const { when } = transition;
|
||||
if (when) {
|
||||
const [first, last] = when === "beforeChildren"
|
||||
? [getAnimation, getChildAnimations]
|
||||
: [getChildAnimations, getAnimation];
|
||||
return first().then(() => last());
|
||||
}
|
||||
else {
|
||||
return Promise.all([getAnimation(), getChildAnimations(options.delay)]);
|
||||
}
|
||||
}
|
||||
function animateChildren(visualElement, variant, delay = 0, delayChildren = 0, staggerChildren = 0, staggerDirection = 1, options) {
|
||||
const animations = [];
|
||||
for (const child of visualElement.variantChildren) {
|
||||
child.notify("AnimationStart", variant);
|
||||
animations.push(animateVariant(child, variant, {
|
||||
...options,
|
||||
delay: delay +
|
||||
(typeof delayChildren === "function" ? 0 : delayChildren) +
|
||||
calcChildStagger(visualElement.variantChildren, child, delayChildren, staggerChildren, staggerDirection),
|
||||
}).then(() => child.notify("AnimationComplete", variant)));
|
||||
}
|
||||
return Promise.all(animations);
|
||||
}
|
||||
|
||||
export { animateVariant };
|
||||
//# sourceMappingURL=visual-element-variant.mjs.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
+27
@@ -0,0 +1,27 @@
|
||||
import { resolveVariant } from '../../render/utils/resolve-dynamic-variants.mjs';
|
||||
import { animateTarget } from './visual-element-target.mjs';
|
||||
import { animateVariant } from './visual-element-variant.mjs';
|
||||
|
||||
function animateVisualElement(visualElement, definition, options = {}) {
|
||||
visualElement.notify("AnimationStart", definition);
|
||||
let animation;
|
||||
if (Array.isArray(definition)) {
|
||||
const animations = definition.map((variant) => animateVariant(visualElement, variant, options));
|
||||
animation = Promise.all(animations);
|
||||
}
|
||||
else if (typeof definition === "string") {
|
||||
animation = animateVariant(visualElement, definition, options);
|
||||
}
|
||||
else {
|
||||
const resolvedDefinition = typeof definition === "function"
|
||||
? resolveVariant(visualElement, definition, options.custom)
|
||||
: definition;
|
||||
animation = Promise.all(animateTarget(visualElement, resolvedDefinition, options));
|
||||
}
|
||||
return animation.then(() => {
|
||||
visualElement.notify("AnimationComplete", definition);
|
||||
});
|
||||
}
|
||||
|
||||
export { animateVisualElement };
|
||||
//# sourceMappingURL=visual-element.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"visual-element.mjs","sources":["../../../../src/animation/interfaces/visual-element.ts"],"sourcesContent":["import { resolveVariant } from \"../../render/utils/resolve-dynamic-variants\"\nimport type { AnimationDefinition } from \"../../node/types\"\nimport type { VisualElement } from \"../../render/VisualElement\"\nimport type { VisualElementAnimationOptions } from \"./types\"\nimport { animateTarget } from \"./visual-element-target\"\nimport { animateVariant } from \"./visual-element-variant\"\n\nexport function animateVisualElement(\n visualElement: VisualElement,\n definition: AnimationDefinition,\n options: VisualElementAnimationOptions = {}\n) {\n visualElement.notify(\"AnimationStart\", definition)\n let animation: Promise<any>\n\n if (Array.isArray(definition)) {\n const animations = definition.map((variant) =>\n animateVariant(visualElement, variant, options)\n )\n animation = Promise.all(animations)\n } else if (typeof definition === \"string\") {\n animation = animateVariant(visualElement, definition, options)\n } else {\n const resolvedDefinition =\n typeof definition === \"function\"\n ? resolveVariant(visualElement, definition, options.custom)\n : definition\n\n animation = Promise.all(\n animateTarget(visualElement, resolvedDefinition, options)\n )\n }\n\n return animation.then(() => {\n visualElement.notify(\"AnimationComplete\", definition)\n })\n}\n"],"names":[],"mappings":";;;;AAOM,SAAU,oBAAoB,CAChC,aAA4B,EAC5B,UAA+B,EAC/B,UAAyC,EAAE,EAAA;AAE3C,IAAA,aAAa,CAAC,MAAM,CAAC,gBAAgB,EAAE,UAAU,CAAC;AAClD,IAAA,IAAI,SAAuB;AAE3B,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;QAC3B,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,OAAO,KACtC,cAAc,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,CAAC,CAClD;AACD,QAAA,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACvC;AAAO,SAAA,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE;QACvC,SAAS,GAAG,cAAc,CAAC,aAAa,EAAE,UAAU,EAAE,OAAO,CAAC;IAClE;SAAO;AACH,QAAA,MAAM,kBAAkB,GACpB,OAAO,UAAU,KAAK;cAChB,cAAc,CAAC,aAAa,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM;cACxD,UAAU;AAEpB,QAAA,SAAS,GAAG,OAAO,CAAC,GAAG,CACnB,aAAa,CAAC,aAAa,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAC5D;IACL;AAEA,IAAA,OAAO,SAAS,CAAC,IAAI,CAAC,MAAK;AACvB,QAAA,aAAa,CAAC,MAAM,CAAC,mBAAmB,EAAE,UAAU,CAAC;AACzD,IAAA,CAAC,CAAC;AACN;;;;"}
|
||||
Generated
Vendored
+142
@@ -0,0 +1,142 @@
|
||||
import { positionalKeys } from '../../render/utils/keys-position.mjs';
|
||||
import { findDimensionValueType } from '../../value/types/dimensions.mjs';
|
||||
import { getVariableValue } from '../utils/css-variables-conversion.mjs';
|
||||
import { isCSSVariableToken, containsCSSVariable } from '../utils/is-css-variable.mjs';
|
||||
import { KeyframeResolver } from './KeyframesResolver.mjs';
|
||||
import { isNone } from './utils/is-none.mjs';
|
||||
import { makeNoneKeyframesAnimatable } from './utils/make-none-animatable.mjs';
|
||||
import { positionalValues, isNumOrPxType } from './utils/unit-conversion.mjs';
|
||||
|
||||
class DOMKeyframesResolver extends KeyframeResolver {
|
||||
constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
|
||||
super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
|
||||
}
|
||||
readKeyframes() {
|
||||
const { unresolvedKeyframes, element, name } = this;
|
||||
if (!element || !element.current)
|
||||
return;
|
||||
super.readKeyframes();
|
||||
/**
|
||||
* If any keyframe is a CSS variable, we need to find its value by sampling the element
|
||||
*/
|
||||
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
||||
let keyframe = unresolvedKeyframes[i];
|
||||
if (typeof keyframe === "string") {
|
||||
keyframe = keyframe.trim();
|
||||
if (isCSSVariableToken(keyframe)) {
|
||||
const resolved = getVariableValue(keyframe, element.current);
|
||||
if (resolved !== undefined) {
|
||||
unresolvedKeyframes[i] = resolved;
|
||||
}
|
||||
if (i === unresolvedKeyframes.length - 1) {
|
||||
this.finalKeyframe = keyframe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
|
||||
* This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
|
||||
* have a far bigger performance impact.
|
||||
*/
|
||||
this.resolveNoneKeyframes();
|
||||
/**
|
||||
* Check to see if unit type has changed. If so schedule jobs that will
|
||||
* temporarily set styles to the destination keyframes.
|
||||
* Skip if we have more than two keyframes or this isn't a positional value.
|
||||
* TODO: We can throw if there are multiple keyframes and the value type changes.
|
||||
*/
|
||||
if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
|
||||
return;
|
||||
}
|
||||
const [origin, target] = unresolvedKeyframes;
|
||||
const originType = findDimensionValueType(origin);
|
||||
const targetType = findDimensionValueType(target);
|
||||
/**
|
||||
* If one keyframe contains embedded CSS variables (e.g. in calc()) and the other
|
||||
* doesn't, we need to measure to convert to pixels. This handles GitHub issue #3410.
|
||||
*/
|
||||
const originHasVar = containsCSSVariable(origin);
|
||||
const targetHasVar = containsCSSVariable(target);
|
||||
if (originHasVar !== targetHasVar && positionalValues[name]) {
|
||||
this.needsMeasurement = true;
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Either we don't recognise these value types or we can animate between them.
|
||||
*/
|
||||
if (originType === targetType)
|
||||
return;
|
||||
/**
|
||||
* If both values are numbers or pixels, we can animate between them by
|
||||
* converting them to numbers.
|
||||
*/
|
||||
if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
|
||||
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
||||
const value = unresolvedKeyframes[i];
|
||||
if (typeof value === "string") {
|
||||
unresolvedKeyframes[i] = parseFloat(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (positionalValues[name]) {
|
||||
/**
|
||||
* Else, the only way to resolve this is by measuring the element.
|
||||
*/
|
||||
this.needsMeasurement = true;
|
||||
}
|
||||
}
|
||||
resolveNoneKeyframes() {
|
||||
const { unresolvedKeyframes, name } = this;
|
||||
const noneKeyframeIndexes = [];
|
||||
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
||||
if (unresolvedKeyframes[i] === null ||
|
||||
isNone(unresolvedKeyframes[i])) {
|
||||
noneKeyframeIndexes.push(i);
|
||||
}
|
||||
}
|
||||
if (noneKeyframeIndexes.length) {
|
||||
makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
|
||||
}
|
||||
}
|
||||
measureInitialState() {
|
||||
const { element, unresolvedKeyframes, name } = this;
|
||||
if (!element || !element.current)
|
||||
return;
|
||||
if (name === "height") {
|
||||
this.suspendedScrollY = window.pageYOffset;
|
||||
}
|
||||
this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
|
||||
unresolvedKeyframes[0] = this.measuredOrigin;
|
||||
// Set final key frame to measure after next render
|
||||
const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
|
||||
if (measureKeyframe !== undefined) {
|
||||
element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
|
||||
}
|
||||
}
|
||||
measureEndState() {
|
||||
const { element, name, unresolvedKeyframes } = this;
|
||||
if (!element || !element.current)
|
||||
return;
|
||||
const value = element.getValue(name);
|
||||
value && value.jump(this.measuredOrigin, false);
|
||||
const finalKeyframeIndex = unresolvedKeyframes.length - 1;
|
||||
const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
|
||||
unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
|
||||
if (finalKeyframe !== null && this.finalKeyframe === undefined) {
|
||||
this.finalKeyframe = finalKeyframe;
|
||||
}
|
||||
// If we removed transform values, reapply them before the next render
|
||||
if (this.removedTransforms?.length) {
|
||||
this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
|
||||
element
|
||||
.getValue(unsetTransformName)
|
||||
.set(unsetTransformValue);
|
||||
});
|
||||
}
|
||||
this.resolveNoneKeyframes();
|
||||
}
|
||||
}
|
||||
|
||||
export { DOMKeyframesResolver };
|
||||
//# sourceMappingURL=DOMKeyframesResolver.mjs.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
+148
@@ -0,0 +1,148 @@
|
||||
import { fillWildcards } from './utils/fill-wildcards.mjs';
|
||||
import { removeNonTranslationalTransform } from './utils/unit-conversion.mjs';
|
||||
import { frame } from '../../frameloop/frame.mjs';
|
||||
|
||||
const toResolve = new Set();
|
||||
let isScheduled = false;
|
||||
let anyNeedsMeasurement = false;
|
||||
let isForced = false;
|
||||
function measureAllKeyframes() {
|
||||
if (anyNeedsMeasurement) {
|
||||
const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
|
||||
const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
|
||||
const transformsToRestore = new Map();
|
||||
/**
|
||||
* Write pass
|
||||
* If we're measuring elements we want to remove bounding box-changing transforms.
|
||||
*/
|
||||
elementsToMeasure.forEach((element) => {
|
||||
const removedTransforms = removeNonTranslationalTransform(element);
|
||||
if (!removedTransforms.length)
|
||||
return;
|
||||
transformsToRestore.set(element, removedTransforms);
|
||||
element.render();
|
||||
});
|
||||
// Read
|
||||
resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
|
||||
// Write
|
||||
elementsToMeasure.forEach((element) => {
|
||||
element.render();
|
||||
const restore = transformsToRestore.get(element);
|
||||
if (restore) {
|
||||
restore.forEach(([key, value]) => {
|
||||
element.getValue(key)?.set(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
// Read
|
||||
resolversToMeasure.forEach((resolver) => resolver.measureEndState());
|
||||
// Write
|
||||
resolversToMeasure.forEach((resolver) => {
|
||||
if (resolver.suspendedScrollY !== undefined) {
|
||||
window.scrollTo(0, resolver.suspendedScrollY);
|
||||
}
|
||||
});
|
||||
}
|
||||
anyNeedsMeasurement = false;
|
||||
isScheduled = false;
|
||||
toResolve.forEach((resolver) => resolver.complete(isForced));
|
||||
toResolve.clear();
|
||||
}
|
||||
function readAllKeyframes() {
|
||||
toResolve.forEach((resolver) => {
|
||||
resolver.readKeyframes();
|
||||
if (resolver.needsMeasurement) {
|
||||
anyNeedsMeasurement = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
function flushKeyframeResolvers() {
|
||||
isForced = true;
|
||||
readAllKeyframes();
|
||||
measureAllKeyframes();
|
||||
isForced = false;
|
||||
}
|
||||
class KeyframeResolver {
|
||||
constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
|
||||
this.state = "pending";
|
||||
/**
|
||||
* Track whether this resolver is async. If it is, it'll be added to the
|
||||
* resolver queue and flushed in the next frame. Resolvers that aren't going
|
||||
* to trigger read/write thrashing don't need to be async.
|
||||
*/
|
||||
this.isAsync = false;
|
||||
/**
|
||||
* Track whether this resolver needs to perform a measurement
|
||||
* to resolve its keyframes.
|
||||
*/
|
||||
this.needsMeasurement = false;
|
||||
this.unresolvedKeyframes = [...unresolvedKeyframes];
|
||||
this.onComplete = onComplete;
|
||||
this.name = name;
|
||||
this.motionValue = motionValue;
|
||||
this.element = element;
|
||||
this.isAsync = isAsync;
|
||||
}
|
||||
scheduleResolve() {
|
||||
this.state = "scheduled";
|
||||
if (this.isAsync) {
|
||||
toResolve.add(this);
|
||||
if (!isScheduled) {
|
||||
isScheduled = true;
|
||||
frame.read(readAllKeyframes);
|
||||
frame.resolveKeyframes(measureAllKeyframes);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.readKeyframes();
|
||||
this.complete();
|
||||
}
|
||||
}
|
||||
readKeyframes() {
|
||||
const { unresolvedKeyframes, name, element, motionValue } = this;
|
||||
// If initial keyframe is null we need to read it from the DOM
|
||||
if (unresolvedKeyframes[0] === null) {
|
||||
const currentValue = motionValue?.get();
|
||||
// TODO: This doesn't work if the final keyframe is a wildcard
|
||||
const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
|
||||
if (currentValue !== undefined) {
|
||||
unresolvedKeyframes[0] = currentValue;
|
||||
}
|
||||
else if (element && name) {
|
||||
const valueAsRead = element.readValue(name, finalKeyframe);
|
||||
if (valueAsRead !== undefined && valueAsRead !== null) {
|
||||
unresolvedKeyframes[0] = valueAsRead;
|
||||
}
|
||||
}
|
||||
if (unresolvedKeyframes[0] === undefined) {
|
||||
unresolvedKeyframes[0] = finalKeyframe;
|
||||
}
|
||||
if (motionValue && currentValue === undefined) {
|
||||
motionValue.set(unresolvedKeyframes[0]);
|
||||
}
|
||||
}
|
||||
fillWildcards(unresolvedKeyframes);
|
||||
}
|
||||
setFinalKeyframe() { }
|
||||
measureInitialState() { }
|
||||
renderEndStyles() { }
|
||||
measureEndState() { }
|
||||
complete(isForcedComplete = false) {
|
||||
this.state = "complete";
|
||||
this.onComplete(this.unresolvedKeyframes, this.finalKeyframe, isForcedComplete);
|
||||
toResolve.delete(this);
|
||||
}
|
||||
cancel() {
|
||||
if (this.state === "scheduled") {
|
||||
toResolve.delete(this);
|
||||
this.state = "pending";
|
||||
}
|
||||
}
|
||||
resume() {
|
||||
if (this.state === "pending")
|
||||
this.scheduleResolve();
|
||||
}
|
||||
}
|
||||
|
||||
export { KeyframeResolver, flushKeyframeResolvers };
|
||||
//# sourceMappingURL=KeyframesResolver.mjs.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
+12
@@ -0,0 +1,12 @@
|
||||
const isNotNull = (value) => value !== null;
|
||||
function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
|
||||
const resolvedKeyframes = keyframes.filter(isNotNull);
|
||||
const useFirstKeyframe = speed < 0 || (repeat && repeatType !== "loop" && repeat % 2 === 1);
|
||||
const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1;
|
||||
return !index || finalKeyframe === undefined
|
||||
? resolvedKeyframes[index]
|
||||
: finalKeyframe;
|
||||
}
|
||||
|
||||
export { getFinalKeyframe };
|
||||
//# sourceMappingURL=get-final.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"get-final.mjs","sources":["../../../../src/animation/keyframes/get-final.ts"],"sourcesContent":["import { AnimationPlaybackOptions } from \"../types\"\n\nconst isNotNull = (value: unknown) => value !== null\n\nexport function getFinalKeyframe<T>(\n keyframes: T[],\n { repeat, repeatType = \"loop\" }: AnimationPlaybackOptions,\n finalKeyframe?: T,\n speed: number = 1\n): T {\n const resolvedKeyframes = keyframes.filter(isNotNull)\n const useFirstKeyframe =\n speed < 0 || (repeat && repeatType !== \"loop\" && repeat % 2 === 1)\n const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1\n\n return !index || finalKeyframe === undefined\n ? resolvedKeyframes[index]\n : finalKeyframe\n}\n"],"names":[],"mappings":"AAEA,MAAM,SAAS,GAAG,CAAC,KAAc,KAAK,KAAK,KAAK,IAAI;SAEpC,gBAAgB,CAC5B,SAAc,EACd,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,EAA4B,EACzD,aAAiB,EACjB,QAAgB,CAAC,EAAA;IAEjB,MAAM,iBAAiB,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC;AACrD,IAAA,MAAM,gBAAgB,GAClB,KAAK,GAAG,CAAC,KAAK,MAAM,IAAI,UAAU,KAAK,MAAM,IAAI,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC;AACtE,IAAA,MAAM,KAAK,GAAG,gBAAgB,GAAG,CAAC,GAAG,iBAAiB,CAAC,MAAM,GAAG,CAAC;AAEjE,IAAA,OAAO,CAAC,KAAK,IAAI,aAAa,KAAK;AAC/B,UAAE,iBAAiB,CAAC,KAAK;UACvB,aAAa;AACvB;;;;"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { fillOffset } from './fill.mjs';
|
||||
|
||||
function defaultOffset(arr) {
|
||||
const offset = [0];
|
||||
fillOffset(offset, arr.length - 1);
|
||||
return offset;
|
||||
}
|
||||
|
||||
export { defaultOffset };
|
||||
//# sourceMappingURL=default.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"default.mjs","sources":["../../../../../src/animation/keyframes/offsets/default.ts"],"sourcesContent":["import { fillOffset } from \"./fill\"\n\nexport function defaultOffset(arr: any[]): number[] {\n const offset = [0]\n fillOffset(offset, arr.length - 1)\n return offset\n}\n"],"names":[],"mappings":";;AAEM,SAAU,aAAa,CAAC,GAAU,EAAA;AACpC,IAAA,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC;IAClB,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;AAClC,IAAA,OAAO,MAAM;AACjB;;;;"}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { progress } from 'motion-utils';
|
||||
import { mixNumber } from '../../../utils/mix/number.mjs';
|
||||
|
||||
function fillOffset(offset, remaining) {
|
||||
const min = offset[offset.length - 1];
|
||||
for (let i = 1; i <= remaining; i++) {
|
||||
const offsetProgress = progress(0, remaining, i);
|
||||
offset.push(mixNumber(min, 1, offsetProgress));
|
||||
}
|
||||
}
|
||||
|
||||
export { fillOffset };
|
||||
//# sourceMappingURL=fill.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"fill.mjs","sources":["../../../../../src/animation/keyframes/offsets/fill.ts"],"sourcesContent":["import { progress } from \"motion-utils\"\nimport { mixNumber } from \"../../../utils/mix/number\"\n\nexport function fillOffset(offset: number[], remaining: number): void {\n const min = offset[offset.length - 1]\n for (let i = 1; i <= remaining; i++) {\n const offsetProgress = progress(0, remaining, i)\n offset.push(mixNumber(min, 1, offsetProgress))\n }\n}\n"],"names":[],"mappings":";;;AAGM,SAAU,UAAU,CAAC,MAAgB,EAAE,SAAiB,EAAA;IAC1D,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AACrC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;AAChD,QAAA,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;IAClD;AACJ;;;;"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
function convertOffsetToTimes(offset, duration) {
|
||||
return offset.map((o) => o * duration);
|
||||
}
|
||||
|
||||
export { convertOffsetToTimes };
|
||||
//# sourceMappingURL=time.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"time.mjs","sources":["../../../../../src/animation/keyframes/offsets/time.ts"],"sourcesContent":["export function convertOffsetToTimes(offset: number[], duration: number) {\n return offset.map((o) => o * duration)\n}\n"],"names":[],"mappings":"AAAM,SAAU,oBAAoB,CAAC,MAAgB,EAAE,QAAgB,EAAA;AACnE,IAAA,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC;AAC1C;;;;"}
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import { pxValues } from '../../waapi/utils/px-values.mjs';
|
||||
|
||||
function applyPxDefaults(keyframes, name) {
|
||||
for (let i = 0; i < keyframes.length; i++) {
|
||||
if (typeof keyframes[i] === "number" && pxValues.has(name)) {
|
||||
keyframes[i] = keyframes[i] + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { applyPxDefaults };
|
||||
//# sourceMappingURL=apply-px-defaults.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"apply-px-defaults.mjs","sources":["../../../../../src/animation/keyframes/utils/apply-px-defaults.ts"],"sourcesContent":["import { UnresolvedValueKeyframe, ValueKeyframe } from \"../../types\"\nimport { pxValues } from \"../../waapi/utils/px-values\"\n\nexport function applyPxDefaults(\n keyframes: ValueKeyframe[] | UnresolvedValueKeyframe[],\n name: string\n) {\n for (let i = 0; i < keyframes.length; i++) {\n if (typeof keyframes[i] === \"number\" && pxValues.has(name)) {\n keyframes[i] = keyframes[i] + \"px\"\n }\n }\n}\n"],"names":[],"mappings":";;AAGM,SAAU,eAAe,CAC3B,SAAsD,EACtD,IAAY,EAAA;AAEZ,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACvC,QAAA,IAAI,OAAO,SAAS,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACxD,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI;QACtC;IACJ;AACJ;;;;"}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
function fillWildcards(keyframes) {
|
||||
for (let i = 1; i < keyframes.length; i++) {
|
||||
keyframes[i] ?? (keyframes[i] = keyframes[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
export { fillWildcards };
|
||||
//# sourceMappingURL=fill-wildcards.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"fill-wildcards.mjs","sources":["../../../../../src/animation/keyframes/utils/fill-wildcards.ts"],"sourcesContent":["import { UnresolvedValueKeyframe, ValueKeyframe } from \"../../types\"\n\nexport function fillWildcards(\n keyframes: ValueKeyframe[] | UnresolvedValueKeyframe[]\n) {\n for (let i = 1; i < keyframes.length; i++) {\n keyframes[i] ??= keyframes[i - 1]\n }\n}\n"],"names":[],"mappings":"AAEM,SAAU,aAAa,CACzB,SAAsD,EAAA;AAEtD,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACvC,QAAA,SAAS,CAAC,CAAC,CAAA,KAAX,SAAS,CAAC,CAAC,CAAA,GAAM,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACrC;AACJ;;;;"}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { isZeroValueString } from 'motion-utils';
|
||||
|
||||
function isNone(value) {
|
||||
if (typeof value === "number") {
|
||||
return value === 0;
|
||||
}
|
||||
else if (value !== null) {
|
||||
return value === "none" || value === "0" || isZeroValueString(value);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export { isNone };
|
||||
//# sourceMappingURL=is-none.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-none.mjs","sources":["../../../../../src/animation/keyframes/utils/is-none.ts"],"sourcesContent":["import { isZeroValueString } from \"motion-utils\"\nimport { AnyResolvedKeyframe } from \"../../types\"\n\nexport function isNone(value: AnyResolvedKeyframe | null) {\n if (typeof value === \"number\") {\n return value === 0\n } else if (value !== null) {\n return value === \"none\" || value === \"0\" || isZeroValueString(value)\n } else {\n return true\n }\n}\n"],"names":[],"mappings":";;AAGM,SAAU,MAAM,CAAC,KAAiC,EAAA;AACpD,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QAC3B,OAAO,KAAK,KAAK,CAAC;IACtB;AAAO,SAAA,IAAI,KAAK,KAAK,IAAI,EAAE;AACvB,QAAA,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG,IAAI,iBAAiB,CAAC,KAAK,CAAC;IACxE;SAAO;AACH,QAAA,OAAO,IAAI;IACf;AACJ;;;;"}
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
import { analyseComplexValue } from '../../../value/types/complex/index.mjs';
|
||||
import { getAnimatableNone } from '../../../value/types/utils/animatable-none.mjs';
|
||||
|
||||
/**
|
||||
* If we encounter keyframes like "none" or "0" and we also have keyframes like
|
||||
* "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
|
||||
* the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
|
||||
* zero equivalents, i.e. "#fff0" or "0px 0px".
|
||||
*/
|
||||
const invalidTemplates = new Set(["auto", "none", "0"]);
|
||||
function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
|
||||
let i = 0;
|
||||
let animatableTemplate = undefined;
|
||||
while (i < unresolvedKeyframes.length && !animatableTemplate) {
|
||||
const keyframe = unresolvedKeyframes[i];
|
||||
if (typeof keyframe === "string" &&
|
||||
!invalidTemplates.has(keyframe) &&
|
||||
analyseComplexValue(keyframe).values.length) {
|
||||
animatableTemplate = unresolvedKeyframes[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (animatableTemplate && name) {
|
||||
for (const noneIndex of noneKeyframeIndexes) {
|
||||
unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { makeNoneKeyframesAnimatable };
|
||||
//# sourceMappingURL=make-none-animatable.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"make-none-animatable.mjs","sources":["../../../../../src/animation/keyframes/utils/make-none-animatable.ts"],"sourcesContent":["import { analyseComplexValue } from \"../../../value/types/complex\"\nimport { getAnimatableNone } from \"../../../value/types/utils/animatable-none\"\nimport { AnyResolvedKeyframe } from \"../../types\"\nimport { UnresolvedKeyframes } from \"../KeyframesResolver\"\n\n/**\n * If we encounter keyframes like \"none\" or \"0\" and we also have keyframes like\n * \"#fff\" or \"200px 200px\" we want to find a keyframe to serve as a template for\n * the \"none\" keyframes. In this case \"#fff\" or \"200px 200px\" - then these get turned into\n * zero equivalents, i.e. \"#fff0\" or \"0px 0px\".\n */\nconst invalidTemplates = new Set([\"auto\", \"none\", \"0\"])\n\nexport function makeNoneKeyframesAnimatable(\n unresolvedKeyframes: UnresolvedKeyframes<AnyResolvedKeyframe>,\n noneKeyframeIndexes: number[],\n name?: string\n) {\n let i = 0\n let animatableTemplate: string | undefined = undefined\n while (i < unresolvedKeyframes.length && !animatableTemplate) {\n const keyframe = unresolvedKeyframes[i]\n if (\n typeof keyframe === \"string\" &&\n !invalidTemplates.has(keyframe) &&\n analyseComplexValue(keyframe).values.length\n ) {\n animatableTemplate = unresolvedKeyframes[i] as string\n }\n i++\n }\n\n if (animatableTemplate && name) {\n for (const noneIndex of noneKeyframeIndexes) {\n unresolvedKeyframes[noneIndex] = getAnimatableNone(\n name,\n animatableTemplate\n )\n }\n }\n}\n"],"names":[],"mappings":";;;AAKA;;;;;AAKG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;SAEvC,2BAA2B,CACvC,mBAA6D,EAC7D,mBAA6B,EAC7B,IAAa,EAAA;IAEb,IAAI,CAAC,GAAG,CAAC;IACT,IAAI,kBAAkB,GAAuB,SAAS;IACtD,OAAO,CAAC,GAAG,mBAAmB,CAAC,MAAM,IAAI,CAAC,kBAAkB,EAAE;AAC1D,QAAA,MAAM,QAAQ,GAAG,mBAAmB,CAAC,CAAC,CAAC;QACvC,IACI,OAAO,QAAQ,KAAK,QAAQ;AAC5B,YAAA,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC/B,mBAAmB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,EAC7C;AACE,YAAA,kBAAkB,GAAG,mBAAmB,CAAC,CAAC,CAAW;QACzD;AACA,QAAA,CAAC,EAAE;IACP;AAEA,IAAA,IAAI,kBAAkB,IAAI,IAAI,EAAE;AAC5B,QAAA,KAAK,MAAM,SAAS,IAAI,mBAAmB,EAAE;YACzC,mBAAmB,CAAC,SAAS,CAAC,GAAG,iBAAiB,CAC9C,IAAI,EACJ,kBAAkB,CACrB;QACL;IACJ;AACJ;;;;"}
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
import { parseValueFromTransform } from '../../../render/dom/parse-transform.mjs';
|
||||
import { transformPropOrder } from '../../../render/utils/keys-transform.mjs';
|
||||
import { number } from '../../../value/types/numbers/index.mjs';
|
||||
import { px } from '../../../value/types/numbers/units.mjs';
|
||||
|
||||
const isNumOrPxType = (v) => v === number || v === px;
|
||||
const transformKeys = new Set(["x", "y", "z"]);
|
||||
const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
|
||||
function removeNonTranslationalTransform(visualElement) {
|
||||
const removedTransforms = [];
|
||||
nonTranslationalTransformKeys.forEach((key) => {
|
||||
const value = visualElement.getValue(key);
|
||||
if (value !== undefined) {
|
||||
removedTransforms.push([key, value.get()]);
|
||||
value.set(key.startsWith("scale") ? 1 : 0);
|
||||
}
|
||||
});
|
||||
return removedTransforms;
|
||||
}
|
||||
const positionalValues = {
|
||||
// Dimensions
|
||||
width: ({ x }, { paddingLeft = "0", paddingRight = "0", boxSizing }) => {
|
||||
const width = x.max - x.min;
|
||||
return boxSizing === "border-box"
|
||||
? width
|
||||
: width - parseFloat(paddingLeft) - parseFloat(paddingRight);
|
||||
},
|
||||
height: ({ y }, { paddingTop = "0", paddingBottom = "0", boxSizing }) => {
|
||||
const height = y.max - y.min;
|
||||
return boxSizing === "border-box"
|
||||
? height
|
||||
: height - parseFloat(paddingTop) - parseFloat(paddingBottom);
|
||||
},
|
||||
top: (_bbox, { top }) => parseFloat(top),
|
||||
left: (_bbox, { left }) => parseFloat(left),
|
||||
bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
|
||||
right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min),
|
||||
// Transform
|
||||
x: (_bbox, { transform }) => parseValueFromTransform(transform, "x"),
|
||||
y: (_bbox, { transform }) => parseValueFromTransform(transform, "y"),
|
||||
};
|
||||
// Alias translate longform names
|
||||
positionalValues.translateX = positionalValues.x;
|
||||
positionalValues.translateY = positionalValues.y;
|
||||
|
||||
export { isNumOrPxType, positionalValues, removeNonTranslationalTransform };
|
||||
//# sourceMappingURL=unit-conversion.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"unit-conversion.mjs","sources":["../../../../../src/animation/keyframes/utils/unit-conversion.ts"],"sourcesContent":["import type { Box } from \"motion-utils\"\nimport { parseValueFromTransform } from \"../../../render/dom/parse-transform\"\nimport { transformPropOrder } from \"../../../render/utils/keys-transform\"\nimport { MotionValue } from \"../../../value\"\nimport { number } from \"../../../value/types/numbers\"\nimport { px } from \"../../../value/types/numbers/units\"\nimport { ValueType } from \"../../../value/types/types\"\nimport { AnyResolvedKeyframe } from \"../../types\"\nimport { WithRender } from \"../types\"\n\nexport const isNumOrPxType = (v?: ValueType): v is ValueType =>\n v === number || v === px\n\ntype GetActualMeasurementInPixels = (\n bbox: Box,\n computedStyle: Partial<CSSStyleDeclaration>\n) => number\n\nconst transformKeys = new Set([\"x\", \"y\", \"z\"])\nconst nonTranslationalTransformKeys = transformPropOrder.filter(\n (key) => !transformKeys.has(key)\n)\n\ntype RemovedTransforms = [string, AnyResolvedKeyframe][]\nexport function removeNonTranslationalTransform(visualElement: WithRender) {\n const removedTransforms: RemovedTransforms = []\n\n nonTranslationalTransformKeys.forEach((key) => {\n const value: MotionValue<AnyResolvedKeyframe> | undefined =\n visualElement.getValue(key)\n if (value !== undefined) {\n removedTransforms.push([key, value.get()])\n value.set(key.startsWith(\"scale\") ? 1 : 0)\n }\n })\n\n return removedTransforms\n}\n\nexport const positionalValues: { [key: string]: GetActualMeasurementInPixels } =\n {\n // Dimensions\n width: (\n { x },\n { paddingLeft = \"0\", paddingRight = \"0\", boxSizing }\n ) => {\n const width = x.max - x.min\n return boxSizing === \"border-box\"\n ? width\n : width - parseFloat(paddingLeft) - parseFloat(paddingRight)\n },\n height: (\n { y },\n { paddingTop = \"0\", paddingBottom = \"0\", boxSizing }\n ) => {\n const height = y.max - y.min\n return boxSizing === \"border-box\"\n ? height\n : height - parseFloat(paddingTop) - parseFloat(paddingBottom)\n },\n\n top: (_bbox, { top }) => parseFloat(top as string),\n left: (_bbox, { left }) => parseFloat(left as string),\n bottom: ({ y }, { top }) => parseFloat(top as string) + (y.max - y.min),\n right: ({ x }, { left }) =>\n parseFloat(left as string) + (x.max - x.min),\n\n // Transform\n x: (_bbox, { transform }) => parseValueFromTransform(transform, \"x\"),\n y: (_bbox, { transform }) => parseValueFromTransform(transform, \"y\"),\n }\n\n// Alias translate longform names\npositionalValues.translateX = positionalValues.x\npositionalValues.translateY = positionalValues.y\n"],"names":[],"mappings":";;;;;AAUO,MAAM,aAAa,GAAG,CAAC,CAAa,KACvC,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK;AAO1B,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC9C,MAAM,6BAA6B,GAAG,kBAAkB,CAAC,MAAM,CAC3D,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CACnC;AAGK,SAAU,+BAA+B,CAAC,aAAyB,EAAA;IACrE,MAAM,iBAAiB,GAAsB,EAAE;AAE/C,IAAA,6BAA6B,CAAC,OAAO,CAAC,CAAC,GAAG,KAAI;QAC1C,MAAM,KAAK,GACP,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;AAC/B,QAAA,IAAI,KAAK,KAAK,SAAS,EAAE;AACrB,YAAA,iBAAiB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;AAC1C,YAAA,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9C;AACJ,IAAA,CAAC,CAAC;AAEF,IAAA,OAAO,iBAAiB;AAC5B;AAEO,MAAM,gBAAgB,GACzB;;AAEI,IAAA,KAAK,EAAE,CACH,EAAE,CAAC,EAAE,EACL,EAAE,WAAW,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,EAAE,SAAS,EAAE,KACpD;QACA,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG;QAC3B,OAAO,SAAS,KAAK;AACjB,cAAE;AACF,cAAE,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,UAAU,CAAC,YAAY,CAAC;IACpE,CAAC;AACD,IAAA,MAAM,EAAE,CACJ,EAAE,CAAC,EAAE,EACL,EAAE,UAAU,GAAG,GAAG,EAAE,aAAa,GAAG,GAAG,EAAE,SAAS,EAAE,KACpD;QACA,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG;QAC5B,OAAO,SAAS,KAAK;AACjB,cAAE;AACF,cAAE,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC,aAAa,CAAC;IACrE,CAAC;AAED,IAAA,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,UAAU,CAAC,GAAa,CAAC;AAClD,IAAA,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,UAAU,CAAC,IAAc,CAAC;IACrD,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,UAAU,CAAC,GAAa,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;IACvE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KACnB,UAAU,CAAC,IAAc,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;;AAGhD,IAAA,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,uBAAuB,CAAC,SAAS,EAAE,GAAG,CAAC;AACpE,IAAA,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,uBAAuB,CAAC,SAAS,EAAE,GAAG,CAAC;;AAG5E;AACA,gBAAgB,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC;AAChD,gBAAgB,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC;;;;"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { camelToDash } from '../../render/dom/utils/camel-to-dash.mjs';
|
||||
|
||||
const optimizedAppearDataId = "framerAppearId";
|
||||
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
||||
|
||||
export { optimizedAppearDataAttribute, optimizedAppearDataId };
|
||||
//# sourceMappingURL=data-id.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"data-id.mjs","sources":["../../../../src/animation/optimized-appear/data-id.ts"],"sourcesContent":["import { camelToDash } from \"../../render/dom/utils/camel-to-dash\"\n\nexport const optimizedAppearDataId = \"framerAppearId\"\n\nexport const optimizedAppearDataAttribute =\n \"data-\" + camelToDash(optimizedAppearDataId) as \"data-framer-appear-id\"\n"],"names":[],"mappings":";;AAEO,MAAM,qBAAqB,GAAG;AAE9B,MAAM,4BAA4B,GACrC,OAAO,GAAG,WAAW,CAAC,qBAAqB;;;;"}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
import { optimizedAppearDataAttribute } from './data-id.mjs';
|
||||
|
||||
function getOptimisedAppearId(visualElement) {
|
||||
return visualElement.props[optimizedAppearDataAttribute];
|
||||
}
|
||||
|
||||
export { getOptimisedAppearId };
|
||||
//# sourceMappingURL=get-appear-id.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"get-appear-id.mjs","sources":["../../../../src/animation/optimized-appear/get-appear-id.ts"],"sourcesContent":["import { optimizedAppearDataAttribute } from \"./data-id\"\nimport type { WithAppearProps } from \"./types\"\n\nexport function getOptimisedAppearId(\n visualElement: WithAppearProps\n): string | undefined {\n return visualElement.props[optimizedAppearDataAttribute]\n}\n"],"names":[],"mappings":";;AAGM,SAAU,oBAAoB,CAChC,aAA8B,EAAA;AAE9B,IAAA,OAAO,aAAa,CAAC,KAAK,CAAC,4BAA4B,CAAC;AAC5D;;;;"}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
class WithPromise {
|
||||
constructor() {
|
||||
this.updateFinished();
|
||||
}
|
||||
get finished() {
|
||||
return this._finished;
|
||||
}
|
||||
updateFinished() {
|
||||
this._finished = new Promise((resolve) => {
|
||||
this.resolve = resolve;
|
||||
});
|
||||
}
|
||||
notifyFinished() {
|
||||
this.resolve();
|
||||
}
|
||||
/**
|
||||
* Allows the animation to be awaited.
|
||||
*
|
||||
* @deprecated Use `finished` instead.
|
||||
*/
|
||||
then(onResolve, onReject) {
|
||||
return this.finished.then(onResolve, onReject);
|
||||
}
|
||||
}
|
||||
|
||||
export { WithPromise };
|
||||
//# sourceMappingURL=WithPromise.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"WithPromise.mjs","sources":["../../../../src/animation/utils/WithPromise.ts"],"sourcesContent":["export class WithPromise {\n protected _finished: Promise<void>\n\n resolve: VoidFunction\n\n constructor() {\n this.updateFinished()\n }\n\n get finished() {\n return this._finished\n }\n\n protected updateFinished() {\n this._finished = new Promise<void>((resolve) => {\n this.resolve = resolve\n })\n }\n\n protected notifyFinished() {\n this.resolve()\n }\n\n /**\n * Allows the animation to be awaited.\n *\n * @deprecated Use `finished` instead.\n */\n then(onResolve: VoidFunction, onReject?: VoidFunction) {\n return this.finished.then(onResolve, onReject)\n }\n}\n"],"names":[],"mappings":"MAAa,WAAW,CAAA;AAKpB,IAAA,WAAA,GAAA;QACI,IAAI,CAAC,cAAc,EAAE;IACzB;AAEA,IAAA,IAAI,QAAQ,GAAA;QACR,OAAO,IAAI,CAAC,SAAS;IACzB;IAEU,cAAc,GAAA;QACpB,IAAI,CAAC,SAAS,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,KAAI;AAC3C,YAAA,IAAI,CAAC,OAAO,GAAG,OAAO;AAC1B,QAAA,CAAC,CAAC;IACN;IAEU,cAAc,GAAA;QACpB,IAAI,CAAC,OAAO,EAAE;IAClB;AAEA;;;;AAIG;IACH,IAAI,CAAC,SAAuB,EAAE,QAAuB,EAAA;QACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC;IAClD;AACH;;;;"}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
const animationMaps = new WeakMap();
|
||||
const animationMapKey = (name, pseudoElement = "") => `${name}:${pseudoElement}`;
|
||||
function getAnimationMap(element) {
|
||||
let map = animationMaps.get(element);
|
||||
if (!map) {
|
||||
map = new Map();
|
||||
animationMaps.set(element, map);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export { animationMapKey, getAnimationMap };
|
||||
//# sourceMappingURL=active-animations.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"active-animations.mjs","sources":["../../../../src/animation/utils/active-animations.ts"],"sourcesContent":["import { NativeAnimation } from \"../NativeAnimation\"\nimport { AnyResolvedKeyframe } from \"../types\"\n\nconst animationMaps = new WeakMap<\n Element,\n Map<string, NativeAnimation<AnyResolvedKeyframe>>\n>()\nexport const animationMapKey = (name: string, pseudoElement: string = \"\") =>\n `${name}:${pseudoElement}`\n\nexport function getAnimationMap(element: Element) {\n let map = animationMaps.get(element)\n if (!map) {\n map = new Map()\n animationMaps.set(element, map)\n }\n return map\n}\n"],"names":[],"mappings":"AAGA,MAAM,aAAa,GAAG,IAAI,OAAO,EAG9B;AACI,MAAM,eAAe,GAAG,CAAC,IAAY,EAAE,aAAA,GAAwB,EAAE,KACpE,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,aAAa;AAEtB,SAAU,eAAe,CAAC,OAAgB,EAAA;IAC5C,IAAI,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE;AACN,QAAA,GAAG,GAAG,IAAI,GAAG,EAAE;AACf,QAAA,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC;IACnC;AACA,IAAA,OAAO,GAAG;AACd;;;;"}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
import { wrap } from 'motion-utils';
|
||||
import { motionValue } from '../../value/index.mjs';
|
||||
import { animateMotionValue } from '../interfaces/motion-value.mjs';
|
||||
import { getValueTransition } from './get-value-transition.mjs';
|
||||
|
||||
const MIN_LAYOUT_DISTANCE = 20;
|
||||
function bezierPoint(t, origin, control, target) {
|
||||
const inv = 1 - t;
|
||||
return inv * inv * origin + 2 * inv * t * control + t * t * target;
|
||||
}
|
||||
function bezierTangentAngle(t, originX, controlX, targetX, originY, controlY, targetY) {
|
||||
const dx = 2 * (1 - t) * (controlX - originX) + 2 * t * (targetX - controlX);
|
||||
const dy = 2 * (1 - t) * (controlY - originY) + 2 * t * (targetY - controlY);
|
||||
return Math.atan2(dy, dx) * (180 / Math.PI);
|
||||
}
|
||||
function computeArcControlPoint(fromX, fromY, toX, toY, strength, peak) {
|
||||
const deltaX = toX - fromX;
|
||||
const deltaY = toY - fromY;
|
||||
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
if (distance > 0) {
|
||||
const normalPerpX = -deltaY / distance;
|
||||
const normalPerpY = deltaX / distance;
|
||||
const desiredHeight = strength * distance;
|
||||
return {
|
||||
x: fromX + deltaX * peak + normalPerpX * desiredHeight,
|
||||
y: fromY + deltaY * peak + normalPerpY * desiredHeight,
|
||||
};
|
||||
}
|
||||
return { x: fromX, y: fromY };
|
||||
}
|
||||
/**
|
||||
* The pure sampling factory: `(from, to) => (t) => point`. Internal —
|
||||
* used by {@link arc} and the unit tests. Not part of the public surface.
|
||||
*/
|
||||
function createArcPath({ strength = 0.5, peak = 0.5, direction, rotate = false, } = {}) {
|
||||
const rotationScale = rotate === true ? 1 : typeof rotate === "number" ? rotate : 0;
|
||||
// Auto-direction only: persists across calls to flip the bulge back
|
||||
// onto the same screen side when the dominant axis changes between
|
||||
// calls. Reuse the factory (module scope / useMemo) to keep this alive.
|
||||
let prevBulgeSign;
|
||||
const createInterpolator = (from, to) => {
|
||||
const dx = to.x - from.x;
|
||||
const dy = to.y - from.y;
|
||||
let signed;
|
||||
if (direction === "cw") {
|
||||
signed = -strength;
|
||||
}
|
||||
else if (direction === "ccw") {
|
||||
signed = strength;
|
||||
}
|
||||
else {
|
||||
const dom = Math.abs(dx) >= Math.abs(dy) ? dx : dy;
|
||||
signed = dom < 0 ? -strength : strength;
|
||||
}
|
||||
let control = computeArcControlPoint(from.x, from.y, to.x, to.y, signed, peak);
|
||||
if (direction === undefined) {
|
||||
const isVertical = Math.abs(dx) < Math.abs(dy);
|
||||
const midX = from.x + dx * peak;
|
||||
const midY = from.y + dy * peak;
|
||||
const bulgeSign = isVertical
|
||||
? Math.sign(control.x - midX)
|
||||
: Math.sign(control.y - midY);
|
||||
if (prevBulgeSign !== undefined &&
|
||||
bulgeSign !== 0 &&
|
||||
bulgeSign !== prevBulgeSign) {
|
||||
signed = -signed;
|
||||
control = computeArcControlPoint(from.x, from.y, to.x, to.y, signed, peak);
|
||||
}
|
||||
else if (bulgeSign !== 0) {
|
||||
prevBulgeSign = bulgeSign;
|
||||
}
|
||||
}
|
||||
const tangent0 = rotationScale
|
||||
? bezierTangentAngle(0, from.x, control.x, to.x, from.y, control.y, to.y)
|
||||
: 0;
|
||||
const tangent1 = rotationScale
|
||||
? bezierTangentAngle(1, from.x, control.x, to.x, from.y, control.y, to.y)
|
||||
: 0;
|
||||
const tangentDelta = rotationScale
|
||||
? wrap(-180, 180, tangent1 - tangent0)
|
||||
: 0;
|
||||
return (t) => {
|
||||
const out = {
|
||||
x: bezierPoint(t, from.x, control.x, to.x),
|
||||
y: bezierPoint(t, from.y, control.y, to.y),
|
||||
};
|
||||
if (rotationScale) {
|
||||
const raw = bezierTangentAngle(t, from.x, control.x, to.x, from.y, control.y, to.y);
|
||||
const baseline = tangent0 + tangentDelta * t;
|
||||
out.rotate = wrap(-180, 180, raw - baseline) * rotationScale;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
};
|
||||
return createInterpolator;
|
||||
}
|
||||
/**
|
||||
* Creates a curved path for `transition.path`:
|
||||
*
|
||||
* ```ts
|
||||
* <motion.div animate={{ x: 200, y: 100 }} transition={{ path: arc() }} />
|
||||
* ```
|
||||
*
|
||||
* Reuse the returned value (module scope / useMemo / useRef) so its
|
||||
* continuity closure survives re-renders — a fresh `arc()` has no memory.
|
||||
*/
|
||||
function arc(options = {}) {
|
||||
const sample = createArcPath(options);
|
||||
const path = {
|
||||
interpolateProjection(delta) {
|
||||
// `from` is the current translate offset (carries any in-flight
|
||||
// displacement when interrupted); `to` is the new layout origin.
|
||||
// The distance floor avoids visible wobble on tiny shifts.
|
||||
const tx = delta.x.translate;
|
||||
const ty = delta.y.translate;
|
||||
if (Math.sqrt(tx * tx + ty * ty) < MIN_LAYOUT_DISTANCE) {
|
||||
return undefined;
|
||||
}
|
||||
return sample({ x: tx, y: ty }, { x: 0, y: 0 });
|
||||
},
|
||||
animateVisualElement(visualElement, target, transition, delay, animations) {
|
||||
if (!("x" in target || "y" in target))
|
||||
return;
|
||||
const xValue = visualElement.getValue("x", visualElement.latestValues["x"] ?? 0);
|
||||
const yValue = visualElement.getValue("y", visualElement.latestValues["y"] ?? 0);
|
||||
const xRaw = target.x;
|
||||
const yRaw = target.y;
|
||||
const xFrom = (Array.isArray(xRaw) && xRaw[0] != null
|
||||
? xRaw[0]
|
||||
: xValue?.get()) ?? 0;
|
||||
const yFrom = (Array.isArray(yRaw) && yRaw[0] != null
|
||||
? yRaw[0]
|
||||
: yValue?.get()) ?? 0;
|
||||
const xTo = (Array.isArray(xRaw)
|
||||
? xRaw[xRaw.length - 1]
|
||||
: xRaw ?? xFrom);
|
||||
const yTo = (Array.isArray(yRaw)
|
||||
? yRaw[yRaw.length - 1]
|
||||
: yRaw ?? yFrom);
|
||||
// Interruption needs no flag: x/y already hold the displaced
|
||||
// mid-arc position, so xFrom/yFrom carry the continuity geometry.
|
||||
const interpolate = sample({ x: xFrom, y: yFrom }, { x: xTo, y: yTo });
|
||||
// Drive a dedicated `pathRotation` value (composed onto `rotate`
|
||||
// at the build sites) rather than `rotate` itself, so a
|
||||
// concurrent rotate animation composes and nothing accumulates
|
||||
// on interrupt.
|
||||
const pathRotationValue = interpolate(0).rotate !== undefined
|
||||
? visualElement.getValue("pathRotation", 0)
|
||||
: undefined;
|
||||
const pathTransition = {
|
||||
delay,
|
||||
...getValueTransition(transition || {}, "x"),
|
||||
};
|
||||
delete pathTransition.path;
|
||||
const progress = motionValue(0);
|
||||
progress.start(animateMotionValue("", progress, [0, 1000], {
|
||||
...pathTransition,
|
||||
isSync: true,
|
||||
velocity: 0,
|
||||
onUpdate: (latest) => {
|
||||
const point = interpolate(latest / 1000);
|
||||
xValue?.set(point.x);
|
||||
yValue?.set(point.y);
|
||||
if (pathRotationValue && point.rotate !== undefined) {
|
||||
pathRotationValue.set(point.rotate);
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
xValue?.set(xTo);
|
||||
yValue?.set(yTo);
|
||||
pathRotationValue?.set(0);
|
||||
},
|
||||
// Interrupt/cancel must clear our additive contribution
|
||||
// so it can't linger on top of the user's `rotate`.
|
||||
onStop: () => pathRotationValue?.set(0),
|
||||
onCancel: () => pathRotationValue?.set(0),
|
||||
}));
|
||||
if (progress.animation)
|
||||
animations.push(progress.animation);
|
||||
delete target.x;
|
||||
delete target.y;
|
||||
},
|
||||
};
|
||||
return path;
|
||||
}
|
||||
|
||||
export { arc, createArcPath };
|
||||
//# sourceMappingURL=arc.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+16
@@ -0,0 +1,16 @@
|
||||
function calcChildStagger(children, child, delayChildren, staggerChildren = 0, staggerDirection = 1) {
|
||||
const index = Array.from(children)
|
||||
.sort((a, b) => a.sortNodePosition(b))
|
||||
.indexOf(child);
|
||||
const numChildren = children.size;
|
||||
const maxStaggerDuration = (numChildren - 1) * staggerChildren;
|
||||
const delayIsFunction = typeof delayChildren === "function";
|
||||
return delayIsFunction
|
||||
? delayChildren(index, numChildren)
|
||||
: staggerDirection === 1
|
||||
? index * staggerChildren
|
||||
: maxStaggerDuration - index * staggerChildren;
|
||||
}
|
||||
|
||||
export { calcChildStagger };
|
||||
//# sourceMappingURL=calc-child-stagger.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"calc-child-stagger.mjs","sources":["../../../../src/animation/utils/calc-child-stagger.ts"],"sourcesContent":["import type { DynamicOption } from \"../types\"\nimport type { VisualElement } from \"../../render/VisualElement\"\n\nexport function calcChildStagger(\n children: Set<VisualElement>,\n child: VisualElement,\n delayChildren?: number | DynamicOption<number>,\n staggerChildren: number = 0,\n staggerDirection: number = 1\n): number {\n const index = Array.from(children)\n .sort((a, b) => a.sortNodePosition(b))\n .indexOf(child)\n const numChildren = children.size\n const maxStaggerDuration = (numChildren - 1) * staggerChildren\n const delayIsFunction = typeof delayChildren === \"function\"\n\n return delayIsFunction\n ? delayChildren(index, numChildren)\n : staggerDirection === 1\n ? index * staggerChildren\n : maxStaggerDuration - index * staggerChildren\n}\n"],"names":[],"mappings":"AAGM,SAAU,gBAAgB,CAC5B,QAA4B,EAC5B,KAAoB,EACpB,aAA8C,EAC9C,eAAA,GAA0B,CAAC,EAC3B,mBAA2B,CAAC,EAAA;AAE5B,IAAA,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ;AAC5B,SAAA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;SACpC,OAAO,CAAC,KAAK,CAAC;AACnB,IAAA,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI;IACjC,MAAM,kBAAkB,GAAG,CAAC,WAAW,GAAG,CAAC,IAAI,eAAe;AAC9D,IAAA,MAAM,eAAe,GAAG,OAAO,aAAa,KAAK,UAAU;AAE3D,IAAA,OAAO;AACH,UAAE,aAAa,CAAC,KAAK,EAAE,WAAW;UAChC,gBAAgB,KAAK;cACrB,KAAK,GAAG;AACV,cAAE,kBAAkB,GAAG,KAAK,GAAG,eAAe;AACtD;;;;"}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { warning } from 'motion-utils';
|
||||
import { isGenerator } from '../generators/utils/is-generator.mjs';
|
||||
import { isAnimatable } from './is-animatable.mjs';
|
||||
|
||||
function hasKeyframesChanged(keyframes) {
|
||||
const current = keyframes[0];
|
||||
if (keyframes.length === 1)
|
||||
return true;
|
||||
for (let i = 0; i < keyframes.length; i++) {
|
||||
if (keyframes[i] !== current)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function canAnimate(keyframes, name, type, velocity) {
|
||||
/**
|
||||
* Check if we're able to animate between the start and end keyframes,
|
||||
* and throw a warning if we're attempting to animate between one that's
|
||||
* animatable and another that isn't.
|
||||
*/
|
||||
const originKeyframe = keyframes[0];
|
||||
if (originKeyframe === null) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* These aren't traditionally animatable but we do support them.
|
||||
* In future we could look into making this more generic or replacing
|
||||
* this function with mix() === mixImmediate
|
||||
*/
|
||||
if (name === "display" || name === "visibility")
|
||||
return true;
|
||||
const targetKeyframe = keyframes[keyframes.length - 1];
|
||||
const isOriginAnimatable = isAnimatable(originKeyframe, name);
|
||||
const isTargetAnimatable = isAnimatable(targetKeyframe, name);
|
||||
warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${name} from "${originKeyframe}" to "${targetKeyframe}". "${isOriginAnimatable ? targetKeyframe : originKeyframe}" is not an animatable value.`, "value-not-animatable");
|
||||
// Always skip if any of these are true
|
||||
if (!isOriginAnimatable || !isTargetAnimatable) {
|
||||
return false;
|
||||
}
|
||||
return (hasKeyframesChanged(keyframes) ||
|
||||
((type === "spring" || isGenerator(type)) && velocity));
|
||||
}
|
||||
|
||||
export { canAnimate };
|
||||
//# sourceMappingURL=can-animate.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"can-animate.mjs","sources":["../../../../src/animation/utils/can-animate.ts"],"sourcesContent":["import { warning } from \"motion-utils\"\nimport { isGenerator } from \"../generators/utils/is-generator\"\nimport { ResolvedKeyframes } from \"../keyframes/KeyframesResolver\"\nimport { AnimationGeneratorType } from \"../types\"\nimport { isAnimatable } from \"./is-animatable\"\n\nfunction hasKeyframesChanged(keyframes: ResolvedKeyframes<any>) {\n const current = keyframes[0]\n if (keyframes.length === 1) return true\n for (let i = 0; i < keyframes.length; i++) {\n if (keyframes[i] !== current) return true\n }\n}\n\nexport function canAnimate(\n keyframes: ResolvedKeyframes<any>,\n name?: string,\n type?: AnimationGeneratorType,\n velocity?: number\n) {\n /**\n * Check if we're able to animate between the start and end keyframes,\n * and throw a warning if we're attempting to animate between one that's\n * animatable and another that isn't.\n */\n const originKeyframe = keyframes[0]\n if (originKeyframe === null) {\n return false\n }\n\n /**\n * These aren't traditionally animatable but we do support them.\n * In future we could look into making this more generic or replacing\n * this function with mix() === mixImmediate\n */\n if (name === \"display\" || name === \"visibility\") return true\n\n const targetKeyframe = keyframes[keyframes.length - 1]\n const isOriginAnimatable = isAnimatable(originKeyframe, name)\n const isTargetAnimatable = isAnimatable(targetKeyframe, name)\n\n warning(\n isOriginAnimatable === isTargetAnimatable,\n `You are trying to animate ${name} from \"${originKeyframe}\" to \"${targetKeyframe}\". \"${\n isOriginAnimatable ? targetKeyframe : originKeyframe\n }\" is not an animatable value.`,\n \"value-not-animatable\"\n )\n\n // Always skip if any of these are true\n if (!isOriginAnimatable || !isTargetAnimatable) {\n return false\n }\n\n return (\n hasKeyframesChanged(keyframes) ||\n ((type === \"spring\" || isGenerator(type)) && velocity)\n )\n}\n"],"names":[],"mappings":";;;;AAMA,SAAS,mBAAmB,CAAC,SAAiC,EAAA;AAC1D,IAAA,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC;AAC5B,IAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,IAAI;AACvC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACvC,QAAA,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,OAAO;AAAE,YAAA,OAAO,IAAI;IAC7C;AACJ;AAEM,SAAU,UAAU,CACtB,SAAiC,EACjC,IAAa,EACb,IAA6B,EAC7B,QAAiB,EAAA;AAEjB;;;;AAIG;AACH,IAAA,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,CAAC;AACnC,IAAA,IAAI,cAAc,KAAK,IAAI,EAAE;AACzB,QAAA,OAAO,KAAK;IAChB;AAEA;;;;AAIG;AACH,IAAA,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,YAAY;AAAE,QAAA,OAAO,IAAI;IAE5D,MAAM,cAAc,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IACtD,MAAM,kBAAkB,GAAG,YAAY,CAAC,cAAc,EAAE,IAAI,CAAC;IAC7D,MAAM,kBAAkB,GAAG,YAAY,CAAC,cAAc,EAAE,IAAI,CAAC;IAE7D,OAAO,CACH,kBAAkB,KAAK,kBAAkB,EACzC,6BAA6B,IAAI,CAAA,OAAA,EAAU,cAAc,CAAA,MAAA,EAAS,cAAc,CAAA,IAAA,EAC5E,kBAAkB,GAAG,cAAc,GAAG,cAC1C,CAAA,6BAAA,CAA+B,EAC/B,sBAAsB,CACzB;;AAGD,IAAA,IAAI,CAAC,kBAAkB,IAAI,CAAC,kBAAkB,EAAE;AAC5C,QAAA,OAAO,KAAK;IAChB;AAEA,IAAA,QACI,mBAAmB,CAAC,SAAS,CAAC;AAC9B,SAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC;AAE9D;;;;"}
|
||||
Generated
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
import { invariant, isNumericalString } from 'motion-utils';
|
||||
import { isCSSVariableToken } from './is-css-variable.mjs';
|
||||
|
||||
/**
|
||||
* Parse Framer's special CSS variable format into a CSS token and a fallback.
|
||||
*
|
||||
* ```
|
||||
* `var(--foo, #fff)` => [`--foo`, '#fff']
|
||||
* ```
|
||||
*
|
||||
* @param current
|
||||
*/
|
||||
const splitCSSVariableRegex =
|
||||
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
|
||||
/^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
|
||||
function parseCSSVariable(current) {
|
||||
const match = splitCSSVariableRegex.exec(current);
|
||||
if (!match)
|
||||
return [,];
|
||||
const [, token1, token2, fallback] = match;
|
||||
return [`--${token1 ?? token2}`, fallback];
|
||||
}
|
||||
const maxDepth = 4;
|
||||
function getVariableValue(current, element, depth = 1) {
|
||||
invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`, "max-css-var-depth");
|
||||
const [token, fallback] = parseCSSVariable(current);
|
||||
// No CSS variable detected
|
||||
if (!token)
|
||||
return;
|
||||
// Attempt to read this CSS variable off the element
|
||||
const resolved = window.getComputedStyle(element).getPropertyValue(token);
|
||||
if (resolved) {
|
||||
const trimmed = resolved.trim();
|
||||
return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
|
||||
}
|
||||
return isCSSVariableToken(fallback)
|
||||
? getVariableValue(fallback, element, depth + 1)
|
||||
: fallback;
|
||||
}
|
||||
|
||||
export { getVariableValue, parseCSSVariable };
|
||||
//# sourceMappingURL=css-variables-conversion.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"css-variables-conversion.mjs","sources":["../../../../src/animation/utils/css-variables-conversion.ts"],"sourcesContent":["import { invariant, isNumericalString } from \"motion-utils\"\nimport { AnyResolvedKeyframe } from \"../types\"\nimport { CSSVariableToken, isCSSVariableToken } from \"./is-css-variable\"\n\n/**\n * Parse Framer's special CSS variable format into a CSS token and a fallback.\n *\n * ```\n * `var(--foo, #fff)` => [`--foo`, '#fff']\n * ```\n *\n * @param current\n */\n\nconst splitCSSVariableRegex =\n // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words\n /^var\\(--(?:([\\w-]+)|([\\w-]+), ?([a-zA-Z\\d ()%#.,-]+))\\)/u\nexport function parseCSSVariable(current: string) {\n const match = splitCSSVariableRegex.exec(current)\n if (!match) return [,]\n\n const [, token1, token2, fallback] = match\n return [`--${token1 ?? token2}`, fallback]\n}\n\nconst maxDepth = 4\nexport function getVariableValue(\n current: CSSVariableToken,\n element: Element,\n depth = 1\n): AnyResolvedKeyframe | undefined {\n invariant(\n depth <= maxDepth,\n `Max CSS variable fallback depth detected in property \"${current}\". This may indicate a circular fallback dependency.`,\n \"max-css-var-depth\"\n )\n\n const [token, fallback] = parseCSSVariable(current)\n\n // No CSS variable detected\n if (!token) return\n\n // Attempt to read this CSS variable off the element\n const resolved = window.getComputedStyle(element).getPropertyValue(token)\n\n if (resolved) {\n const trimmed = resolved.trim()\n return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed\n }\n\n return isCSSVariableToken(fallback)\n ? getVariableValue(fallback, element, depth + 1)\n : fallback\n}\n"],"names":[],"mappings":";;;AAIA;;;;;;;;AAQG;AAEH,MAAM,qBAAqB;AACvB;AACA,0DAA0D;AACxD,SAAU,gBAAgB,CAAC,OAAe,EAAA;IAC5C,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC;AACjD,IAAA,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG;IAEtB,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,KAAK;IAC1C,OAAO,CAAC,KAAK,MAAM,IAAI,MAAM,CAAA,CAAE,EAAE,QAAQ,CAAC;AAC9C;AAEA,MAAM,QAAQ,GAAG,CAAC;AACZ,SAAU,gBAAgB,CAC5B,OAAyB,EACzB,OAAgB,EAChB,KAAK,GAAG,CAAC,EAAA;IAET,SAAS,CACL,KAAK,IAAI,QAAQ,EACjB,CAAA,sDAAA,EAAyD,OAAO,CAAA,oDAAA,CAAsD,EACtH,mBAAmB,CACtB;IAED,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC;;AAGnD,IAAA,IAAI,CAAC,KAAK;QAAE;;AAGZ,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC;IAEzE,IAAI,QAAQ,EAAE;AACV,QAAA,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE;AAC/B,QAAA,OAAO,iBAAiB,CAAC,OAAO,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,OAAO;IACrE;IAEA,OAAO,kBAAkB,CAAC,QAAQ;UAC5B,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,GAAG,CAAC;UAC7C,QAAQ;AAClB;;;;"}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { transformProps } from '../../render/utils/keys-transform.mjs';
|
||||
|
||||
const underDampedSpring = {
|
||||
type: "spring",
|
||||
stiffness: 500,
|
||||
damping: 25,
|
||||
restSpeed: 10,
|
||||
};
|
||||
const criticallyDampedSpring = (target) => ({
|
||||
type: "spring",
|
||||
stiffness: 550,
|
||||
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
||||
restSpeed: 10,
|
||||
});
|
||||
const keyframesTransition = {
|
||||
type: "keyframes",
|
||||
duration: 0.8,
|
||||
};
|
||||
/**
|
||||
* Default easing curve is a slightly shallower version of
|
||||
* the default browser easing curve.
|
||||
*/
|
||||
const ease = {
|
||||
type: "keyframes",
|
||||
ease: [0.25, 0.1, 0.35, 1],
|
||||
duration: 0.3,
|
||||
};
|
||||
const getDefaultTransition = (valueKey, { keyframes }) => {
|
||||
if (keyframes.length > 2) {
|
||||
return keyframesTransition;
|
||||
}
|
||||
else if (transformProps.has(valueKey)) {
|
||||
return valueKey.startsWith("scale")
|
||||
? criticallyDampedSpring(keyframes[1])
|
||||
: underDampedSpring;
|
||||
}
|
||||
return ease;
|
||||
};
|
||||
|
||||
export { getDefaultTransition };
|
||||
//# sourceMappingURL=default-transitions.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"default-transitions.mjs","sources":["../../../../src/animation/utils/default-transitions.ts"],"sourcesContent":["import { transformProps } from \"../../render/utils/keys-transform\"\nimport type { ValueAnimationOptions } from \"../types\"\n\nconst underDampedSpring: Partial<ValueAnimationOptions> = {\n type: \"spring\",\n stiffness: 500,\n damping: 25,\n restSpeed: 10,\n}\n\nconst criticallyDampedSpring = (\n target: unknown\n): Partial<ValueAnimationOptions> => ({\n type: \"spring\",\n stiffness: 550,\n damping: target === 0 ? 2 * Math.sqrt(550) : 30,\n restSpeed: 10,\n})\n\nconst keyframesTransition: Partial<ValueAnimationOptions> = {\n type: \"keyframes\",\n duration: 0.8,\n}\n\n/**\n * Default easing curve is a slightly shallower version of\n * the default browser easing curve.\n */\nconst ease: Partial<ValueAnimationOptions> = {\n type: \"keyframes\",\n ease: [0.25, 0.1, 0.35, 1],\n duration: 0.3,\n}\n\nexport const getDefaultTransition = (\n valueKey: string,\n { keyframes }: ValueAnimationOptions\n): Partial<ValueAnimationOptions> => {\n if (keyframes.length > 2) {\n return keyframesTransition\n } else if (transformProps.has(valueKey)) {\n return valueKey.startsWith(\"scale\")\n ? criticallyDampedSpring(keyframes[1])\n : underDampedSpring\n }\n\n return ease\n}\n"],"names":[],"mappings":";;AAGA,MAAM,iBAAiB,GAAmC;AACtD,IAAA,IAAI,EAAE,QAAQ;AACd,IAAA,SAAS,EAAE,GAAG;AACd,IAAA,OAAO,EAAE,EAAE;AACX,IAAA,SAAS,EAAE,EAAE;CAChB;AAED,MAAM,sBAAsB,GAAG,CAC3B,MAAe,MACmB;AAClC,IAAA,IAAI,EAAE,QAAQ;AACd,IAAA,SAAS,EAAE,GAAG;AACd,IAAA,OAAO,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;AAC/C,IAAA,SAAS,EAAE,EAAE;AAChB,CAAA,CAAC;AAEF,MAAM,mBAAmB,GAAmC;AACxD,IAAA,IAAI,EAAE,WAAW;AACjB,IAAA,QAAQ,EAAE,GAAG;CAChB;AAED;;;AAGG;AACH,MAAM,IAAI,GAAmC;AACzC,IAAA,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1B,IAAA,QAAQ,EAAE,GAAG;CAChB;AAEM,MAAM,oBAAoB,GAAG,CAChC,QAAgB,EAChB,EAAE,SAAS,EAAyB,KACJ;AAChC,IAAA,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE;AACtB,QAAA,OAAO,mBAAmB;IAC9B;AAAO,SAAA,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;AACrC,QAAA,OAAO,QAAQ,CAAC,UAAU,CAAC,OAAO;AAC9B,cAAE,sBAAsB,CAAC,SAAS,CAAC,CAAC,CAAC;cACnC,iBAAiB;IAC3B;AAEA,IAAA,OAAO,IAAI;AACf;;;;"}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { resolveTransition } from './resolve-transition.mjs';
|
||||
|
||||
function getValueTransition(transition, key) {
|
||||
const valueTransition = transition?.[key] ??
|
||||
transition?.["default"] ??
|
||||
transition;
|
||||
if (valueTransition !== transition) {
|
||||
return resolveTransition(valueTransition, transition);
|
||||
}
|
||||
return valueTransition;
|
||||
}
|
||||
|
||||
export { getValueTransition };
|
||||
//# sourceMappingURL=get-value-transition.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"get-value-transition.mjs","sources":["../../../../src/animation/utils/get-value-transition.ts"],"sourcesContent":["import { resolveTransition } from \"./resolve-transition\"\n\nexport function getValueTransition(transition: any, key: string) {\n const valueTransition =\n transition?.[key as keyof typeof transition] ??\n transition?.[\"default\"] ??\n transition\n\n if (valueTransition !== transition) {\n return resolveTransition(valueTransition, transition)\n }\n\n return valueTransition\n}\n"],"names":[],"mappings":";;AAEM,SAAU,kBAAkB,CAAC,UAAe,EAAE,GAAW,EAAA;AAC3D,IAAA,MAAM,eAAe,GACjB,UAAU,GAAG,GAA8B,CAAC;QAC5C,UAAU,GAAG,SAAS,CAAC;AACvB,QAAA,UAAU;AAEd,IAAA,IAAI,eAAe,KAAK,UAAU,EAAE;AAChC,QAAA,OAAO,iBAAiB,CAAC,eAAe,EAAE,UAAU,CAAC;IACzD;AAEA,IAAA,OAAO,eAAe;AAC1B;;;;"}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { complex } from '../../value/types/complex/index.mjs';
|
||||
|
||||
/**
|
||||
* Check if a value is animatable. Examples:
|
||||
*
|
||||
* ✅: 100, "100px", "#fff"
|
||||
* ❌: "block", "url(2.jpg)"
|
||||
* @param value
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const isAnimatable = (value, name) => {
|
||||
// If the list of keys that might be non-animatable grows, replace with Set
|
||||
if (name === "zIndex")
|
||||
return false;
|
||||
// If it's a number or a keyframes array, we can animate it. We might at some point
|
||||
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
|
||||
// but for now lets leave it like this for performance reasons
|
||||
if (typeof value === "number" || Array.isArray(value))
|
||||
return true;
|
||||
if (typeof value === "string" && // It's animatable if we have a string
|
||||
(complex.test(value) || value === "0") && // And it contains numbers and/or colors
|
||||
!value.startsWith("url(") // Unless it starts with "url("
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export { isAnimatable };
|
||||
//# sourceMappingURL=is-animatable.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-animatable.mjs","sources":["../../../../src/animation/utils/is-animatable.ts"],"sourcesContent":["import { complex } from \"../../value/types/complex\"\nimport { ValueKeyframesDefinition } from \"../types\"\n\n/**\n * Check if a value is animatable. Examples:\n *\n * ✅: 100, \"100px\", \"#fff\"\n * ❌: \"block\", \"url(2.jpg)\"\n * @param value\n *\n * @internal\n */\nexport const isAnimatable = (\n value: ValueKeyframesDefinition,\n name?: string\n) => {\n // If the list of keys that might be non-animatable grows, replace with Set\n if (name === \"zIndex\") return false\n\n // If it's a number or a keyframes array, we can animate it. We might at some point\n // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,\n // but for now lets leave it like this for performance reasons\n if (typeof value === \"number\" || Array.isArray(value)) return true\n\n if (\n typeof value === \"string\" && // It's animatable if we have a string\n (complex.test(value) || value === \"0\") && // And it contains numbers and/or colors\n !value.startsWith(\"url(\") // Unless it starts with \"url(\"\n ) {\n return true\n }\n\n return false\n}\n"],"names":[],"mappings":";;AAGA;;;;;;;;AAQG;MACU,YAAY,GAAG,CACxB,KAA+B,EAC/B,IAAa,KACb;;IAEA,IAAI,IAAI,KAAK,QAAQ;AAAE,QAAA,OAAO,KAAK;;;;IAKnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,IAAI;AAElE,IAAA,IACI,OAAO,KAAK,KAAK,QAAQ;AACzB,SAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,GAAG,CAAC;AACtC,QAAA,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;MAC3B;AACE,QAAA,OAAO,IAAI;IACf;AAEA,IAAA,OAAO,KAAK;AAChB;;;;"}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
|
||||
const isCSSVariableName =
|
||||
/*@__PURE__*/ checkStringStartsWith("--");
|
||||
const startsAsVariableToken =
|
||||
/*@__PURE__*/ checkStringStartsWith("var(--");
|
||||
const isCSSVariableToken = (value) => {
|
||||
const startsWithToken = startsAsVariableToken(value);
|
||||
if (!startsWithToken)
|
||||
return false;
|
||||
// Ensure any comments are stripped from the value as this can harm performance of the regex.
|
||||
return singleCssVariableRegex.test(value.split("/*")[0].trim());
|
||||
};
|
||||
const singleCssVariableRegex = /var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu;
|
||||
/**
|
||||
* Check if a value contains a CSS variable anywhere (e.g. inside calc()).
|
||||
* Unlike isCSSVariableToken which checks if the value IS a var() token,
|
||||
* this checks if the value CONTAINS var() somewhere in the string.
|
||||
*/
|
||||
function containsCSSVariable(value) {
|
||||
if (typeof value !== "string")
|
||||
return false;
|
||||
// Strip comments to avoid false positives
|
||||
return value.split("/*")[0].includes("var(--");
|
||||
}
|
||||
|
||||
export { containsCSSVariable, isCSSVariableName, isCSSVariableToken };
|
||||
//# sourceMappingURL=is-css-variable.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-css-variable.mjs","sources":["../../../../src/animation/utils/is-css-variable.ts"],"sourcesContent":["import { AnyResolvedKeyframe } from \"../types\"\n\nexport type CSSVariableName = `--${string}`\n\nexport type CSSVariableToken = `var(${CSSVariableName})`\n\nconst checkStringStartsWith =\n <T extends string>(token: string) =>\n (key?: AnyResolvedKeyframe | null): key is T =>\n typeof key === \"string\" && key.startsWith(token)\n\nexport const isCSSVariableName =\n /*@__PURE__*/ checkStringStartsWith<CSSVariableName>(\"--\")\n\nconst startsAsVariableToken =\n /*@__PURE__*/ checkStringStartsWith<CSSVariableToken>(\"var(--\")\nexport const isCSSVariableToken = (\n value?: string\n): value is CSSVariableToken => {\n const startsWithToken = startsAsVariableToken(value)\n\n if (!startsWithToken) return false\n\n // Ensure any comments are stripped from the value as this can harm performance of the regex.\n return singleCssVariableRegex.test(value.split(\"/*\")[0].trim())\n}\n\nconst singleCssVariableRegex =\n /var\\(--(?:[\\w-]+\\s*|[\\w-]+\\s*,(?:\\s*[^)(\\s]|\\s*\\((?:[^)(]|\\([^)(]*\\))*\\))+\\s*)\\)$/iu\n\n/**\n * Check if a value contains a CSS variable anywhere (e.g. inside calc()).\n * Unlike isCSSVariableToken which checks if the value IS a var() token,\n * this checks if the value CONTAINS var() somewhere in the string.\n */\nexport function containsCSSVariable(\n value?: AnyResolvedKeyframe | null\n): boolean {\n if (typeof value !== \"string\") return false\n // Strip comments to avoid false positives\n return value.split(\"/*\")[0].includes(\"var(--\")\n}\n"],"names":[],"mappings":"AAMA,MAAM,qBAAqB,GACvB,CAAmB,KAAa,KAChC,CAAC,GAAgC,KAC7B,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC;MAE3C,iBAAiB;AAC1B,cAAc,qBAAqB,CAAkB,IAAI;AAE7D,MAAM,qBAAqB;AACvB,cAAc,qBAAqB,CAAmB,QAAQ,CAAC;AAC5D,MAAM,kBAAkB,GAAG,CAC9B,KAAc,KACa;AAC3B,IAAA,MAAM,eAAe,GAAG,qBAAqB,CAAC,KAAK,CAAC;AAEpD,IAAA,IAAI,CAAC,eAAe;AAAE,QAAA,OAAO,KAAK;;AAGlC,IAAA,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACnE;AAEA,MAAM,sBAAsB,GACxB,qFAAqF;AAEzF;;;;AAIG;AACG,SAAU,mBAAmB,CAC/B,KAAkC,EAAA;IAElC,IAAI,OAAO,KAAK,KAAK,QAAQ;AAAE,QAAA,OAAO,KAAK;;AAE3C,IAAA,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;AAClD;;;;"}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
const orchestrationKeys = new Set([
|
||||
"when",
|
||||
"delay",
|
||||
"delayChildren",
|
||||
"staggerChildren",
|
||||
"staggerDirection",
|
||||
"repeat",
|
||||
"repeatType",
|
||||
"repeatDelay",
|
||||
"from",
|
||||
"elapsed",
|
||||
]);
|
||||
/**
|
||||
* Decide whether a transition is defined on a given Transition.
|
||||
* This filters out orchestration options and returns true
|
||||
* if any options are left.
|
||||
*/
|
||||
function isTransitionDefined(transition) {
|
||||
for (const key in transition) {
|
||||
if (!orchestrationKeys.has(key))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export { isTransitionDefined };
|
||||
//# sourceMappingURL=is-transition-defined.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-transition-defined.mjs","sources":["../../../../src/animation/utils/is-transition-defined.ts"],"sourcesContent":["import type { AnyResolvedKeyframe } from \"../types\"\nimport type { Transition } from \"../types\"\n\nconst orchestrationKeys = new Set([\n \"when\",\n \"delay\",\n \"delayChildren\",\n \"staggerChildren\",\n \"staggerDirection\",\n \"repeat\",\n \"repeatType\",\n \"repeatDelay\",\n \"from\",\n \"elapsed\",\n])\n\n/**\n * Decide whether a transition is defined on a given Transition.\n * This filters out orchestration options and returns true\n * if any options are left.\n */\nexport function isTransitionDefined(\n transition: Transition & { elapsed?: number; from?: AnyResolvedKeyframe }\n) {\n for (const key in transition) {\n if (!orchestrationKeys.has(key)) return true\n }\n return false\n}\n"],"names":[],"mappings":"AAGA,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAC9B,MAAM;IACN,OAAO;IACP,eAAe;IACf,iBAAiB;IACjB,kBAAkB;IAClB,QAAQ;IACR,YAAY;IACZ,aAAa;IACb,MAAM;IACN,SAAS;AACZ,CAAA,CAAC;AAEF;;;;AAIG;AACG,SAAU,mBAAmB,CAC/B,UAAyE,EAAA;AAEzE,IAAA,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE;AAC1B,QAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC;AAAE,YAAA,OAAO,IAAI;IAChD;AACA,IAAA,OAAO,KAAK;AAChB;;;;"}
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
function makeAnimationInstant(options) {
|
||||
options.duration = 0;
|
||||
options.type = "keyframes";
|
||||
}
|
||||
|
||||
export { makeAnimationInstant };
|
||||
//# sourceMappingURL=make-animation-instant.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"make-animation-instant.mjs","sources":["../../../../src/animation/utils/make-animation-instant.ts"],"sourcesContent":["import { ValueAnimationOptions } from \"../types\"\n\nexport function makeAnimationInstant(\n options: Partial<{\n duration: ValueAnimationOptions[\"duration\"]\n type: ValueAnimationOptions[\"type\"]\n }>\n): void {\n options.duration = 0\n options.type = \"keyframes\"\n}\n"],"names":[],"mappings":"AAEM,SAAU,oBAAoB,CAChC,OAGE,EAAA;AAEF,IAAA,OAAO,CAAC,QAAQ,GAAG,CAAC;AACpB,IAAA,OAAO,CAAC,IAAI,GAAG,WAAW;AAC9B;;;;"}
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
import { inertia } from '../generators/inertia.mjs';
|
||||
import { keyframes } from '../generators/keyframes.mjs';
|
||||
import { spring } from '../generators/spring.mjs';
|
||||
|
||||
const transitionTypeMap = {
|
||||
decay: inertia,
|
||||
inertia,
|
||||
tween: keyframes,
|
||||
keyframes: keyframes,
|
||||
spring,
|
||||
};
|
||||
function replaceTransitionType(transition) {
|
||||
if (typeof transition.type === "string") {
|
||||
transition.type = transitionTypeMap[transition.type];
|
||||
}
|
||||
}
|
||||
|
||||
export { replaceTransitionType };
|
||||
//# sourceMappingURL=replace-transition-type.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"replace-transition-type.mjs","sources":["../../../../src/animation/utils/replace-transition-type.ts"],"sourcesContent":["import { inertia } from \"../generators/inertia\"\nimport { keyframes } from \"../generators/keyframes\"\nimport { spring } from \"../generators/spring\"\nimport { GeneratorFactory, ValueAnimationTransition } from \"../types\"\n\nconst transitionTypeMap: { [key: string]: GeneratorFactory } = {\n decay: inertia,\n inertia,\n tween: keyframes,\n keyframes: keyframes,\n spring,\n}\n\nexport function replaceTransitionType(transition: ValueAnimationTransition) {\n if (typeof transition.type === \"string\") {\n transition.type = transitionTypeMap[transition.type]\n }\n}\n"],"names":[],"mappings":";;;;AAKA,MAAM,iBAAiB,GAAwC;AAC3D,IAAA,KAAK,EAAE,OAAO;IACd,OAAO;AACP,IAAA,KAAK,EAAE,SAAS;AAChB,IAAA,SAAS,EAAE,SAAS;IACpB,MAAM;CACT;AAEK,SAAU,qBAAqB,CAAC,UAAoC,EAAA;AACtE,IAAA,IAAI,OAAO,UAAU,CAAC,IAAI,KAAK,QAAQ,EAAE;QACrC,UAAU,CAAC,IAAI,GAAG,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC;IACxD;AACJ;;;;"}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* If `transition` has `inherit: true`, shallow-merge it with
|
||||
* `parentTransition` (child keys win) and strip the `inherit` key.
|
||||
* Otherwise return `transition` unchanged.
|
||||
*/
|
||||
function resolveTransition(transition, parentTransition) {
|
||||
if (transition?.inherit && parentTransition) {
|
||||
const { inherit: _, ...rest } = transition;
|
||||
return { ...parentTransition, ...rest };
|
||||
}
|
||||
return transition;
|
||||
}
|
||||
|
||||
export { resolveTransition };
|
||||
//# sourceMappingURL=resolve-transition.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"resolve-transition.mjs","sources":["../../../../src/animation/utils/resolve-transition.ts"],"sourcesContent":["/**\n * If `transition` has `inherit: true`, shallow-merge it with\n * `parentTransition` (child keys win) and strip the `inherit` key.\n * Otherwise return `transition` unchanged.\n */\nexport function resolveTransition(\n transition: any,\n parentTransition?: any\n) {\n if (transition?.inherit && parentTransition) {\n const { inherit: _, ...rest } = transition\n return { ...parentTransition, ...rest }\n }\n\n return transition\n}\n"],"names":[],"mappings":"AAAA;;;;AAIG;AACG,SAAU,iBAAiB,CAC7B,UAAe,EACf,gBAAsB,EAAA;AAEtB,IAAA,IAAI,UAAU,EAAE,OAAO,IAAI,gBAAgB,EAAE;QACzC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,UAAU;AAC1C,QAAA,OAAO,EAAE,GAAG,gBAAgB,EAAE,GAAG,IAAI,EAAE;IAC3C;AAEA,IAAA,OAAO,UAAU;AACrB;;;;"}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
|
||||
|
||||
export { cubicBezierAsString };
|
||||
//# sourceMappingURL=cubic-bezier.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cubic-bezier.mjs","sources":["../../../../../src/animation/waapi/easing/cubic-bezier.ts"],"sourcesContent":["import { BezierDefinition } from \"motion-utils\"\n\nexport const cubicBezierAsString = ([a, b, c, d]: BezierDefinition) =>\n `cubic-bezier(${a}, ${b}, ${c}, ${d})`\n"],"names":[],"mappings":"AAEO,MAAM,mBAAmB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAmB,KAC9D,CAAA,aAAA,EAAgB,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,CAAA;;;;"}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { isBezierDefinition } from 'motion-utils';
|
||||
import { supportsLinearEasing } from '../../../utils/supports/linear-easing.mjs';
|
||||
import { supportedWaapiEasing } from './supported.mjs';
|
||||
|
||||
function isWaapiSupportedEasing(easing) {
|
||||
return Boolean((typeof easing === "function" && supportsLinearEasing()) ||
|
||||
!easing ||
|
||||
(typeof easing === "string" &&
|
||||
(easing in supportedWaapiEasing || supportsLinearEasing())) ||
|
||||
isBezierDefinition(easing) ||
|
||||
(Array.isArray(easing) && easing.every(isWaapiSupportedEasing)));
|
||||
}
|
||||
|
||||
export { isWaapiSupportedEasing };
|
||||
//# sourceMappingURL=is-supported.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-supported.mjs","sources":["../../../../../src/animation/waapi/easing/is-supported.ts"],"sourcesContent":["import { Easing, isBezierDefinition } from \"motion-utils\"\nimport { supportsLinearEasing } from \"../../../utils/supports/linear-easing\"\nimport { supportedWaapiEasing } from \"./supported\"\n\nexport function isWaapiSupportedEasing(easing?: Easing | Easing[]): boolean {\n return Boolean(\n (typeof easing === \"function\" && supportsLinearEasing()) ||\n !easing ||\n (typeof easing === \"string\" &&\n (easing in supportedWaapiEasing || supportsLinearEasing())) ||\n isBezierDefinition(easing) ||\n (Array.isArray(easing) && easing.every(isWaapiSupportedEasing))\n )\n}\n"],"names":[],"mappings":";;;;AAIM,SAAU,sBAAsB,CAAC,MAA0B,EAAA;IAC7D,OAAO,OAAO,CACV,CAAC,OAAO,MAAM,KAAK,UAAU,IAAI,oBAAoB,EAAE;AACnD,QAAA,CAAC,MAAM;SACN,OAAO,MAAM,KAAK,QAAQ;AACvB,aAAC,MAAM,IAAI,oBAAoB,IAAI,oBAAoB,EAAE,CAAC,CAAC;QAC/D,kBAAkB,CAAC,MAAM,CAAC;AAC1B,SAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,CACtE;AACL;;;;"}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { isBezierDefinition } from 'motion-utils';
|
||||
import { supportsLinearEasing } from '../../../utils/supports/linear-easing.mjs';
|
||||
import { generateLinearEasing } from '../utils/linear.mjs';
|
||||
import { cubicBezierAsString } from './cubic-bezier.mjs';
|
||||
import { supportedWaapiEasing } from './supported.mjs';
|
||||
|
||||
function mapEasingToNativeEasing(easing, duration) {
|
||||
if (!easing) {
|
||||
return undefined;
|
||||
}
|
||||
else if (typeof easing === "function") {
|
||||
return supportsLinearEasing()
|
||||
? generateLinearEasing(easing, duration)
|
||||
: "ease-out";
|
||||
}
|
||||
else if (isBezierDefinition(easing)) {
|
||||
return cubicBezierAsString(easing);
|
||||
}
|
||||
else if (Array.isArray(easing)) {
|
||||
return easing.map((segmentEasing) => mapEasingToNativeEasing(segmentEasing, duration) ||
|
||||
supportedWaapiEasing.easeOut);
|
||||
}
|
||||
else {
|
||||
return supportedWaapiEasing[easing];
|
||||
}
|
||||
}
|
||||
|
||||
export { mapEasingToNativeEasing };
|
||||
//# sourceMappingURL=map-easing.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"map-easing.mjs","sources":["../../../../../src/animation/waapi/easing/map-easing.ts"],"sourcesContent":["import { Easing, isBezierDefinition } from \"motion-utils\"\nimport { supportsLinearEasing } from \"../../../utils/supports/linear-easing\"\nimport { generateLinearEasing } from \"../utils/linear\"\nimport { cubicBezierAsString } from \"./cubic-bezier\"\nimport { supportedWaapiEasing } from \"./supported\"\n\nexport function mapEasingToNativeEasing(\n easing: Easing | Easing[] | undefined,\n duration: number\n): undefined | string | string[] {\n if (!easing) {\n return undefined\n } else if (typeof easing === \"function\") {\n return supportsLinearEasing()\n ? generateLinearEasing(easing, duration)\n : \"ease-out\"\n } else if (isBezierDefinition(easing)) {\n return cubicBezierAsString(easing)\n } else if (Array.isArray(easing)) {\n return easing.map(\n (segmentEasing) =>\n (mapEasingToNativeEasing(segmentEasing, duration) as string) ||\n supportedWaapiEasing.easeOut\n )\n } else {\n return supportedWaapiEasing[easing as keyof typeof supportedWaapiEasing]\n }\n}\n"],"names":[],"mappings":";;;;;;AAMM,SAAU,uBAAuB,CACnC,MAAqC,EACrC,QAAgB,EAAA;IAEhB,IAAI,CAAC,MAAM,EAAE;AACT,QAAA,OAAO,SAAS;IACpB;AAAO,SAAA,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE;AACrC,QAAA,OAAO,oBAAoB;AACvB,cAAE,oBAAoB,CAAC,MAAM,EAAE,QAAQ;cACrC,UAAU;IACpB;AAAO,SAAA,IAAI,kBAAkB,CAAC,MAAM,CAAC,EAAE;AACnC,QAAA,OAAO,mBAAmB,CAAC,MAAM,CAAC;IACtC;AAAO,SAAA,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;AAC9B,QAAA,OAAO,MAAM,CAAC,GAAG,CACb,CAAC,aAAa,KACT,uBAAuB,CAAC,aAAa,EAAE,QAAQ,CAAY;YAC5D,oBAAoB,CAAC,OAAO,CACnC;IACL;SAAO;AACH,QAAA,OAAO,oBAAoB,CAAC,MAA2C,CAAC;IAC5E;AACJ;;;;"}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { cubicBezierAsString } from './cubic-bezier.mjs';
|
||||
|
||||
const supportedWaapiEasing = {
|
||||
linear: "linear",
|
||||
ease: "ease",
|
||||
easeIn: "ease-in",
|
||||
easeOut: "ease-out",
|
||||
easeInOut: "ease-in-out",
|
||||
circIn: /*@__PURE__*/ cubicBezierAsString([0, 0.65, 0.55, 1]),
|
||||
circOut: /*@__PURE__*/ cubicBezierAsString([0.55, 0, 1, 0.45]),
|
||||
backIn: /*@__PURE__*/ cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
|
||||
backOut: /*@__PURE__*/ cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
|
||||
};
|
||||
|
||||
export { supportedWaapiEasing };
|
||||
//# sourceMappingURL=supported.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"supported.mjs","sources":["../../../../../src/animation/waapi/easing/supported.ts"],"sourcesContent":["import { cubicBezierAsString } from \"./cubic-bezier\"\n\nexport const supportedWaapiEasing = {\n linear: \"linear\",\n ease: \"ease\",\n easeIn: \"ease-in\",\n easeOut: \"ease-out\",\n easeInOut: \"ease-in-out\",\n circIn: /*@__PURE__*/ cubicBezierAsString([0, 0.65, 0.55, 1]),\n circOut: /*@__PURE__*/ cubicBezierAsString([0.55, 0, 1, 0.45]),\n backIn: /*@__PURE__*/ cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),\n backOut: /*@__PURE__*/ cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),\n}\n"],"names":[],"mappings":";;AAEO,MAAM,oBAAoB,GAAG;AAChC,IAAA,MAAM,EAAE,QAAQ;AAChB,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,MAAM,EAAE,SAAS;AACjB,IAAA,OAAO,EAAE,UAAU;AACnB,IAAA,SAAS,EAAE,aAAa;AACxB,IAAA,MAAM,gBAAgB,mBAAmB,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7D,IAAA,OAAO,gBAAgB,mBAAmB,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;AAC9D,IAAA,MAAM,gBAAgB,mBAAmB,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AACpE,IAAA,OAAO,gBAAgB,mBAAmB,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;;;;;"}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { mapEasingToNativeEasing } from './easing/map-easing.mjs';
|
||||
|
||||
function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duration = 300, repeat = 0, repeatType = "loop", ease = "easeOut", times, } = {}, pseudoElement = undefined) {
|
||||
const keyframeOptions = {
|
||||
[valueName]: keyframes,
|
||||
};
|
||||
if (times)
|
||||
keyframeOptions.offset = times;
|
||||
const easing = mapEasingToNativeEasing(ease, duration);
|
||||
/**
|
||||
* If this is an easing array, apply to keyframes, not animation as a whole
|
||||
*/
|
||||
if (Array.isArray(easing))
|
||||
keyframeOptions.easing = easing;
|
||||
const options = {
|
||||
delay,
|
||||
duration,
|
||||
easing: !Array.isArray(easing) ? easing : "linear",
|
||||
fill: "both",
|
||||
iterations: repeat + 1,
|
||||
direction: repeatType === "reverse" ? "alternate" : "normal",
|
||||
};
|
||||
if (pseudoElement)
|
||||
options.pseudoElement = pseudoElement;
|
||||
return element.animate(keyframeOptions, options);
|
||||
}
|
||||
|
||||
export { startWaapiAnimation };
|
||||
//# sourceMappingURL=start-waapi-animation.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"start-waapi-animation.mjs","sources":["../../../../src/animation/waapi/start-waapi-animation.ts"],"sourcesContent":["import { ValueKeyframesDefinition, ValueTransition } from \"../types\"\nimport { mapEasingToNativeEasing } from \"./easing/map-easing\"\n\nexport function startWaapiAnimation(\n element: Element,\n valueName: string,\n keyframes: ValueKeyframesDefinition,\n {\n delay = 0,\n duration = 300,\n repeat = 0,\n repeatType = \"loop\",\n ease = \"easeOut\",\n times,\n }: ValueTransition = {},\n pseudoElement: string | undefined = undefined\n) {\n const keyframeOptions: PropertyIndexedKeyframes = {\n [valueName]: keyframes as string[],\n }\n if (times) keyframeOptions.offset = times\n\n const easing = mapEasingToNativeEasing(ease, duration)\n\n /**\n * If this is an easing array, apply to keyframes, not animation as a whole\n */\n if (Array.isArray(easing)) keyframeOptions.easing = easing\n\n const options: KeyframeAnimationOptions = {\n delay,\n duration,\n easing: !Array.isArray(easing) ? easing : \"linear\",\n fill: \"both\",\n iterations: repeat + 1,\n direction: repeatType === \"reverse\" ? \"alternate\" : \"normal\",\n }\n\n if (pseudoElement) options.pseudoElement = pseudoElement\n\n return element.animate(keyframeOptions, options)\n}\n"],"names":[],"mappings":";;AAGM,SAAU,mBAAmB,CAC/B,OAAgB,EAChB,SAAiB,EACjB,SAAmC,EACnC,EACI,KAAK,GAAG,CAAC,EACT,QAAQ,GAAG,GAAG,EACd,MAAM,GAAG,CAAC,EACV,UAAU,GAAG,MAAM,EACnB,IAAI,GAAG,SAAS,EAChB,KAAK,GAAA,GACY,EAAE,EACvB,gBAAoC,SAAS,EAAA;AAE7C,IAAA,MAAM,eAAe,GAA6B;QAC9C,CAAC,SAAS,GAAG,SAAqB;KACrC;AACD,IAAA,IAAI,KAAK;AAAE,QAAA,eAAe,CAAC,MAAM,GAAG,KAAK;IAEzC,MAAM,MAAM,GAAG,uBAAuB,CAAC,IAAI,EAAE,QAAQ,CAAC;AAEtD;;AAEG;AACH,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;AAAE,QAAA,eAAe,CAAC,MAAM,GAAG,MAAM;AAE1D,IAAA,MAAM,OAAO,GAA6B;QACtC,KAAK;QACL,QAAQ;AACR,QAAA,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,QAAQ;AAClD,QAAA,IAAI,EAAE,MAAM;QACZ,UAAU,EAAE,MAAM,GAAG,CAAC;QACtB,SAAS,EAAE,UAAU,KAAK,SAAS,GAAG,WAAW,GAAG,QAAQ;KAC/D;AAED,IAAA,IAAI,aAAa;AAAE,QAAA,OAAO,CAAC,aAAa,GAAG,aAAa;IAExD,OAAO,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC;AACpD;;;;"}
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
import { memo } from 'motion-utils';
|
||||
|
||||
const supportsPartialKeyframes = /*@__PURE__*/ memo(() => {
|
||||
try {
|
||||
document.createElement("div").animate({ opacity: [1] });
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
export { supportsPartialKeyframes };
|
||||
//# sourceMappingURL=partial-keyframes.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"partial-keyframes.mjs","sources":["../../../../../src/animation/waapi/supports/partial-keyframes.ts"],"sourcesContent":["import { memo } from \"motion-utils\"\n\nexport const supportsPartialKeyframes = /*@__PURE__*/ memo(() => {\n try {\n document.createElement(\"div\").animate({ opacity: [1] })\n } catch (e) {\n return false\n }\n return true\n})\n"],"names":[],"mappings":";;MAEa,wBAAwB,iBAAiB,IAAI,CAAC,MAAK;AAC5D,IAAA,IAAI;AACA,QAAA,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D;IAAE,OAAO,CAAC,EAAE;AACR,QAAA,OAAO,KAAK;IAChB;AACA,IAAA,OAAO,IAAI;AACf,CAAC;;;;"}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { memo } from 'motion-utils';
|
||||
import { acceleratedValues } from '../utils/accelerated-values.mjs';
|
||||
import { hasBrowserOnlyColors } from '../utils/is-browser-color.mjs';
|
||||
|
||||
const colorProperties = new Set([
|
||||
"color",
|
||||
"backgroundColor",
|
||||
"outlineColor",
|
||||
"fill",
|
||||
"stroke",
|
||||
"borderColor",
|
||||
"borderTopColor",
|
||||
"borderRightColor",
|
||||
"borderBottomColor",
|
||||
"borderLeftColor",
|
||||
]);
|
||||
const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
|
||||
function supportsBrowserAnimation(options) {
|
||||
const { motionValue, name, repeatDelay, repeatType, damping, type, keyframes, } = options;
|
||||
const subject = motionValue?.owner?.current;
|
||||
/**
|
||||
* We use this check instead of isHTMLElement() because we explicitly
|
||||
* **don't** want elements in different timing contexts (i.e. popups)
|
||||
* to be accelerated, as it's not possible to sync these animations
|
||||
* properly with those driven from the main window frameloop.
|
||||
*/
|
||||
if (!(subject instanceof HTMLElement)) {
|
||||
return false;
|
||||
}
|
||||
const { onUpdate, transformTemplate } = motionValue.owner.getProps();
|
||||
return (supportsWaapi() &&
|
||||
name &&
|
||||
/**
|
||||
* Force WAAPI for color properties with browser-only color formats
|
||||
* (oklch, oklab, lab, lch, etc.) that the JS animation path can't parse.
|
||||
*/
|
||||
(acceleratedValues.has(name) ||
|
||||
(colorProperties.has(name) &&
|
||||
hasBrowserOnlyColors(keyframes))) &&
|
||||
(name !== "transform" || !transformTemplate) &&
|
||||
/**
|
||||
* If we're outputting values to onUpdate then we can't use WAAPI as there's
|
||||
* no way to read the value from WAAPI every frame.
|
||||
*/
|
||||
!onUpdate &&
|
||||
!repeatDelay &&
|
||||
repeatType !== "mirror" &&
|
||||
damping !== 0 &&
|
||||
type !== "inertia");
|
||||
}
|
||||
|
||||
export { supportsBrowserAnimation };
|
||||
//# sourceMappingURL=waapi.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"waapi.mjs","sources":["../../../../../src/animation/waapi/supports/waapi.ts"],"sourcesContent":["import { memo } from \"motion-utils\"\nimport {\n AnyResolvedKeyframe,\n ValueAnimationOptionsWithRenderContext,\n} from \"../../types\"\nimport { acceleratedValues } from \"../utils/accelerated-values\"\nimport { hasBrowserOnlyColors } from \"../utils/is-browser-color\"\n\nconst colorProperties = new Set([\n \"color\",\n \"backgroundColor\",\n \"outlineColor\",\n \"fill\",\n \"stroke\",\n \"borderColor\",\n \"borderTopColor\",\n \"borderRightColor\",\n \"borderBottomColor\",\n \"borderLeftColor\",\n])\n\nconst supportsWaapi = /*@__PURE__*/ memo(() =>\n Object.hasOwnProperty.call(Element.prototype, \"animate\")\n)\n\nexport function supportsBrowserAnimation<T extends AnyResolvedKeyframe>(\n options: ValueAnimationOptionsWithRenderContext<T>\n) {\n const {\n motionValue,\n name,\n repeatDelay,\n repeatType,\n damping,\n type,\n keyframes,\n } = options\n\n const subject = motionValue?.owner?.current\n\n /**\n * We use this check instead of isHTMLElement() because we explicitly\n * **don't** want elements in different timing contexts (i.e. popups)\n * to be accelerated, as it's not possible to sync these animations\n * properly with those driven from the main window frameloop.\n */\n if (!(subject instanceof HTMLElement)) {\n return false\n }\n\n const { onUpdate, transformTemplate } = motionValue!.owner!.getProps()\n\n return (\n supportsWaapi() &&\n name &&\n /**\n * Force WAAPI for color properties with browser-only color formats\n * (oklch, oklab, lab, lch, etc.) that the JS animation path can't parse.\n */\n (acceleratedValues.has(name) ||\n (colorProperties.has(name) &&\n hasBrowserOnlyColors(keyframes))) &&\n (name !== \"transform\" || !transformTemplate) &&\n /**\n * If we're outputting values to onUpdate then we can't use WAAPI as there's\n * no way to read the value from WAAPI every frame.\n */\n !onUpdate &&\n !repeatDelay &&\n repeatType !== \"mirror\" &&\n damping !== 0 &&\n type !== \"inertia\"\n )\n}\n"],"names":[],"mappings":";;;;AAQA,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC5B,OAAO;IACP,iBAAiB;IACjB,cAAc;IACd,MAAM;IACN,QAAQ;IACR,aAAa;IACb,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;IACnB,iBAAiB;AACpB,CAAA,CAAC;AAEF,MAAM,aAAa,iBAAiB,IAAI,CAAC,MACrC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAC3D;AAEK,SAAU,wBAAwB,CACpC,OAAkD,EAAA;AAElD,IAAA,MAAM,EACF,WAAW,EACX,IAAI,EACJ,WAAW,EACX,UAAU,EACV,OAAO,EACP,IAAI,EACJ,SAAS,GACZ,GAAG,OAAO;AAEX,IAAA,MAAM,OAAO,GAAG,WAAW,EAAE,KAAK,EAAE,OAAO;AAE3C;;;;;AAKG;AACH,IAAA,IAAI,EAAE,OAAO,YAAY,WAAW,CAAC,EAAE;AACnC,QAAA,OAAO,KAAK;IAChB;AAEA,IAAA,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,WAAY,CAAC,KAAM,CAAC,QAAQ,EAAE;IAEtE,QACI,aAAa,EAAE;QACf,IAAI;AACJ;;;AAGG;AACH,SAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;AACxB,aAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;AACtB,gBAAA,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC;AACzC,SAAC,IAAI,KAAK,WAAW,IAAI,CAAC,iBAAiB,CAAC;AAC5C;;;AAGG;AACH,QAAA,CAAC,QAAQ;AACT,QAAA,CAAC,WAAW;AACZ,QAAA,UAAU,KAAK,QAAQ;AACvB,QAAA,OAAO,KAAK,CAAC;QACb,IAAI,KAAK,SAAS;AAE1B;;;;"}
|
||||
Generated
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* A list of values that can be hardware-accelerated.
|
||||
*/
|
||||
const acceleratedValues = new Set([
|
||||
"opacity",
|
||||
"clipPath",
|
||||
"filter",
|
||||
"transform",
|
||||
// TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
|
||||
// or until we implement support for linear() easing.
|
||||
// "background-color"
|
||||
]);
|
||||
|
||||
export { acceleratedValues };
|
||||
//# sourceMappingURL=accelerated-values.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"accelerated-values.mjs","sources":["../../../../../src/animation/waapi/utils/accelerated-values.ts"],"sourcesContent":["/**\n * A list of values that can be hardware-accelerated.\n */\nexport const acceleratedValues = new Set<string>([\n \"opacity\",\n \"clipPath\",\n \"filter\",\n \"transform\",\n // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved\n // or until we implement support for linear() easing.\n // \"background-color\"\n])\n"],"names":[],"mappings":"AAAA;;AAEG;AACI,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS;IAC7C,SAAS;IACT,UAAU;IACV,QAAQ;IACR,WAAW;;;;AAId,CAAA;;;;"}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { supportsLinearEasing } from '../../../utils/supports/linear-easing.mjs';
|
||||
import { isGenerator } from '../../generators/utils/is-generator.mjs';
|
||||
|
||||
function applyGeneratorOptions({ type, ...options }) {
|
||||
if (isGenerator(type) && supportsLinearEasing()) {
|
||||
return type.applyToOptions(options);
|
||||
}
|
||||
else {
|
||||
options.duration ?? (options.duration = 300);
|
||||
options.ease ?? (options.ease = "easeOut");
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
export { applyGeneratorOptions };
|
||||
//# sourceMappingURL=apply-generator.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"apply-generator.mjs","sources":["../../../../../src/animation/waapi/utils/apply-generator.ts"],"sourcesContent":["import { ValueTransition } from \"../../../animation/types\"\nimport { supportsLinearEasing } from \"../../../utils/supports/linear-easing\"\nimport { isGenerator } from \"../../generators/utils/is-generator\"\n\nexport function applyGeneratorOptions({\n type,\n ...options\n}: ValueTransition): ValueTransition {\n if (isGenerator(type) && supportsLinearEasing()) {\n return type.applyToOptions!(options)\n } else {\n options.duration ??= 300\n options.ease ??= \"easeOut\"\n }\n\n return options\n}\n"],"names":[],"mappings":";;;AAIM,SAAU,qBAAqB,CAAC,EAClC,IAAI,EACJ,GAAG,OAAO,EACI,EAAA;IACd,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,oBAAoB,EAAE,EAAE;AAC7C,QAAA,OAAO,IAAI,CAAC,cAAe,CAAC,OAAO,CAAC;IACxC;SAAO;QACH,OAAO,CAAC,QAAQ,KAAhB,OAAO,CAAC,QAAQ,GAAK,GAAG,CAAA;QACxB,OAAO,CAAC,IAAI,KAAZ,OAAO,CAAC,IAAI,GAAK,SAAS,CAAA;IAC9B;AAEA,IAAA,OAAO,OAAO;AAClB;;;;"}
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
const browserColorFunctions = /^(?:oklch|oklab|lab|lch|color|color-mix|light-dark)\(/;
|
||||
function hasBrowserOnlyColors(keyframes) {
|
||||
for (let i = 0; i < keyframes.length; i++) {
|
||||
if (typeof keyframes[i] === "string" &&
|
||||
browserColorFunctions.test(keyframes[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export { hasBrowserOnlyColors };
|
||||
//# sourceMappingURL=is-browser-color.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-browser-color.mjs","sources":["../../../../../src/animation/waapi/utils/is-browser-color.ts"],"sourcesContent":["const browserColorFunctions =\n /^(?:oklch|oklab|lab|lch|color|color-mix|light-dark)\\(/\n\nexport function hasBrowserOnlyColors(keyframes: any[]): boolean {\n for (let i = 0; i < keyframes.length; i++) {\n if (\n typeof keyframes[i] === \"string\" &&\n browserColorFunctions.test(keyframes[i])\n ) {\n return true\n }\n }\n return false\n}\n"],"names":[],"mappings":"AAAA,MAAM,qBAAqB,GACvB,uDAAuD;AAErD,SAAU,oBAAoB,CAAC,SAAgB,EAAA;AACjD,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACvC,QAAA,IACI,OAAO,SAAS,CAAC,CAAC,CAAC,KAAK,QAAQ;YAChC,qBAAqB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC1C;AACE,YAAA,OAAO,IAAI;QACf;IACJ;AACA,IAAA,OAAO,KAAK;AAChB;;;;"}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
const generateLinearEasing = (easing, duration, // as milliseconds
|
||||
resolution = 10 // as milliseconds
|
||||
) => {
|
||||
let points = "";
|
||||
const numPoints = Math.max(Math.round(duration / resolution), 2);
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
points += Math.round(easing(i / (numPoints - 1)) * 10000) / 10000 + ", ";
|
||||
}
|
||||
return `linear(${points.substring(0, points.length - 2)})`;
|
||||
};
|
||||
|
||||
export { generateLinearEasing };
|
||||
//# sourceMappingURL=linear.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"linear.mjs","sources":["../../../../../src/animation/waapi/utils/linear.ts"],"sourcesContent":["import { EasingFunction } from \"motion-utils\"\n\nexport const generateLinearEasing = (\n easing: EasingFunction,\n duration: number, // as milliseconds\n resolution: number = 10 // as milliseconds\n): string => {\n let points = \"\"\n const numPoints = Math.max(Math.round(duration / resolution), 2)\n\n for (let i = 0; i < numPoints; i++) {\n points += Math.round(easing(i / (numPoints - 1)) * 10000) / 10000 + \", \"\n }\n\n return `linear(${points.substring(0, points.length - 2)})`\n}\n"],"names":[],"mappings":"MAEa,oBAAoB,GAAG,CAChC,MAAsB,EACtB,QAAgB;AAChB,UAAA,GAAqB,EAAE;KACf;IACR,IAAI,MAAM,GAAG,EAAE;AACf,IAAA,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;AAEhE,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;QAChC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI;IAC5E;AAEA,IAAA,OAAO,CAAA,OAAA,EAAU,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG;AAC9D;;;;"}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { cornerRadiusProps } from '../../../utils/border-radius.mjs';
|
||||
|
||||
const pxValues = new Set([
|
||||
// Border props
|
||||
"borderWidth",
|
||||
"borderTopWidth",
|
||||
"borderRightWidth",
|
||||
"borderBottomWidth",
|
||||
"borderLeftWidth",
|
||||
"borderRadius",
|
||||
...cornerRadiusProps,
|
||||
// Positioning props
|
||||
"width",
|
||||
"maxWidth",
|
||||
"height",
|
||||
"maxHeight",
|
||||
"top",
|
||||
"right",
|
||||
"bottom",
|
||||
"left",
|
||||
"inset",
|
||||
"insetBlock",
|
||||
"insetBlockStart",
|
||||
"insetBlockEnd",
|
||||
"insetInline",
|
||||
"insetInlineStart",
|
||||
"insetInlineEnd",
|
||||
// Spacing props
|
||||
"padding",
|
||||
"paddingTop",
|
||||
"paddingRight",
|
||||
"paddingBottom",
|
||||
"paddingLeft",
|
||||
"paddingBlock",
|
||||
"paddingBlockStart",
|
||||
"paddingBlockEnd",
|
||||
"paddingInline",
|
||||
"paddingInlineStart",
|
||||
"paddingInlineEnd",
|
||||
"margin",
|
||||
"marginTop",
|
||||
"marginRight",
|
||||
"marginBottom",
|
||||
"marginLeft",
|
||||
"marginBlock",
|
||||
"marginBlockStart",
|
||||
"marginBlockEnd",
|
||||
"marginInline",
|
||||
"marginInlineStart",
|
||||
"marginInlineEnd",
|
||||
// Typography
|
||||
"fontSize",
|
||||
// Misc
|
||||
"backgroundPositionX",
|
||||
"backgroundPositionY",
|
||||
]);
|
||||
|
||||
export { pxValues };
|
||||
//# sourceMappingURL=px-values.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"px-values.mjs","sources":["../../../../../src/animation/waapi/utils/px-values.ts"],"sourcesContent":["import { cornerRadiusProps } from \"../../../utils/border-radius\"\n\nexport const pxValues = new Set([\n // Border props\n \"borderWidth\",\n \"borderTopWidth\",\n \"borderRightWidth\",\n \"borderBottomWidth\",\n \"borderLeftWidth\",\n \"borderRadius\",\n ...cornerRadiusProps,\n // Positioning props\n \"width\",\n \"maxWidth\",\n \"height\",\n \"maxHeight\",\n \"top\",\n \"right\",\n \"bottom\",\n \"left\",\n \"inset\",\n \"insetBlock\",\n \"insetBlockStart\",\n \"insetBlockEnd\",\n \"insetInline\",\n \"insetInlineStart\",\n \"insetInlineEnd\",\n // Spacing props\n \"padding\",\n \"paddingTop\",\n \"paddingRight\",\n \"paddingBottom\",\n \"paddingLeft\",\n \"paddingBlock\",\n \"paddingBlockStart\",\n \"paddingBlockEnd\",\n \"paddingInline\",\n \"paddingInlineStart\",\n \"paddingInlineEnd\",\n \"margin\",\n \"marginTop\",\n \"marginRight\",\n \"marginBottom\",\n \"marginLeft\",\n \"marginBlock\",\n \"marginBlockStart\",\n \"marginBlockEnd\",\n \"marginInline\",\n \"marginInlineStart\",\n \"marginInlineEnd\",\n // Typography\n \"fontSize\",\n // Misc\n \"backgroundPositionX\",\n \"backgroundPositionY\",\n])\n"],"names":[],"mappings":";;AAEO,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC;;IAE5B,aAAa;IACb,gBAAgB;IAChB,kBAAkB;IAClB,mBAAmB;IACnB,iBAAiB;IACjB,cAAc;AACd,IAAA,GAAG,iBAAiB;;IAEpB,OAAO;IACP,UAAU;IACV,QAAQ;IACR,WAAW;IACX,KAAK;IACL,OAAO;IACP,QAAQ;IACR,MAAM;IACN,OAAO;IACP,YAAY;IACZ,iBAAiB;IACjB,eAAe;IACf,aAAa;IACb,kBAAkB;IAClB,gBAAgB;;IAEhB,SAAS;IACT,YAAY;IACZ,cAAc;IACd,eAAe;IACf,aAAa;IACb,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,eAAe;IACf,oBAAoB;IACpB,kBAAkB;IAClB,QAAQ;IACR,WAAW;IACX,aAAa;IACb,cAAc;IACd,YAAY;IACZ,aAAa;IACb,kBAAkB;IAClB,gBAAgB;IAChB,cAAc;IACd,mBAAmB;IACnB,iBAAiB;;IAEjB,UAAU;;IAEV,qBAAqB;IACrB,qBAAqB;AACxB,CAAA;;;;"}
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
import { circInOut, backInOut, anticipate } from 'motion-utils';
|
||||
|
||||
const unsupportedEasingFunctions = {
|
||||
anticipate,
|
||||
backInOut,
|
||||
circInOut,
|
||||
};
|
||||
function isUnsupportedEase(key) {
|
||||
return key in unsupportedEasingFunctions;
|
||||
}
|
||||
function replaceStringEasing(transition) {
|
||||
if (typeof transition.ease === "string" &&
|
||||
isUnsupportedEase(transition.ease)) {
|
||||
transition.ease = unsupportedEasingFunctions[transition.ease];
|
||||
}
|
||||
}
|
||||
|
||||
export { replaceStringEasing };
|
||||
//# sourceMappingURL=unsupported-easing.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"unsupported-easing.mjs","sources":["../../../../../src/animation/waapi/utils/unsupported-easing.ts"],"sourcesContent":["import { anticipate, backInOut, circInOut } from \"motion-utils\"\nimport { ValueAnimationTransition } from \"../../types\"\n\nconst unsupportedEasingFunctions = {\n anticipate,\n backInOut,\n circInOut,\n}\n\nfunction isUnsupportedEase(\n key: string\n): key is keyof typeof unsupportedEasingFunctions {\n return key in unsupportedEasingFunctions\n}\n\nexport function replaceStringEasing(transition: ValueAnimationTransition) {\n if (\n typeof transition.ease === \"string\" &&\n isUnsupportedEase(transition.ease)\n ) {\n transition.ease = unsupportedEasingFunctions[transition.ease]\n }\n}\n"],"names":[],"mappings":";;AAGA,MAAM,0BAA0B,GAAG;IAC/B,UAAU;IACV,SAAS;IACT,SAAS;CACZ;AAED,SAAS,iBAAiB,CACtB,GAAW,EAAA;IAEX,OAAO,GAAG,IAAI,0BAA0B;AAC5C;AAEM,SAAU,mBAAmB,CAAC,UAAoC,EAAA;AACpE,IAAA,IACI,OAAO,UAAU,CAAC,IAAI,KAAK,QAAQ;AACnC,QAAA,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,EACpC;QACE,UAAU,CAAC,IAAI,GAAG,0BAA0B,CAAC,UAAU,CAAC,IAAI,CAAC;IACjE;AACJ;;;;"}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { frame, cancelFrame } from '../frameloop/frame.mjs';
|
||||
import { numberValueTypes } from '../value/types/maps/number.mjs';
|
||||
import { getValueAsType } from '../value/types/utils/get-as-type.mjs';
|
||||
|
||||
class MotionValueState {
|
||||
constructor() {
|
||||
this.latest = {};
|
||||
this.values = new Map();
|
||||
}
|
||||
set(name, value, render, computed, useDefaultValueType = true) {
|
||||
const existingValue = this.values.get(name);
|
||||
if (existingValue) {
|
||||
existingValue.onRemove();
|
||||
}
|
||||
const onChange = () => {
|
||||
const v = value.get();
|
||||
if (useDefaultValueType) {
|
||||
this.latest[name] = getValueAsType(v, numberValueTypes[name]);
|
||||
}
|
||||
else {
|
||||
this.latest[name] = v;
|
||||
}
|
||||
render && frame.render(render);
|
||||
};
|
||||
onChange();
|
||||
const cancelOnChange = value.on("change", onChange);
|
||||
computed && value.addDependent(computed);
|
||||
const remove = () => {
|
||||
cancelOnChange();
|
||||
render && cancelFrame(render);
|
||||
this.values.delete(name);
|
||||
computed && value.removeDependent(computed);
|
||||
};
|
||||
this.values.set(name, { value, onRemove: remove });
|
||||
return remove;
|
||||
}
|
||||
get(name) {
|
||||
return this.values.get(name)?.value;
|
||||
}
|
||||
}
|
||||
|
||||
export { MotionValueState };
|
||||
//# sourceMappingURL=MotionValueState.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"MotionValueState.mjs","sources":["../../../src/effects/MotionValueState.ts"],"sourcesContent":["import { AnyResolvedKeyframe } from \"../animation/types\"\nimport { cancelFrame, frame } from \"../frameloop/frame\"\nimport { MotionValue } from \"../value\"\nimport { numberValueTypes } from \"../value/types/maps/number\"\nimport { getValueAsType } from \"../value/types/utils/get-as-type\"\n\nexport class MotionValueState {\n latest: { [name: string]: AnyResolvedKeyframe } = {}\n\n private values = new Map<\n string,\n { value: MotionValue; onRemove: VoidFunction }\n >()\n\n set(\n name: string,\n value: MotionValue,\n render?: VoidFunction,\n computed?: MotionValue,\n useDefaultValueType = true\n ) {\n const existingValue = this.values.get(name)\n\n if (existingValue) {\n existingValue.onRemove()\n }\n\n const onChange = () => {\n const v = value.get()\n\n if (useDefaultValueType) {\n this.latest[name] = getValueAsType(v, numberValueTypes[name])\n } else {\n this.latest[name] = v\n }\n\n render && frame.render(render)\n }\n\n onChange()\n\n const cancelOnChange = value.on(\"change\", onChange)\n\n computed && value.addDependent(computed)\n\n const remove = () => {\n cancelOnChange()\n render && cancelFrame(render)\n this.values.delete(name)\n computed && value.removeDependent(computed)\n }\n\n this.values.set(name, { value, onRemove: remove })\n\n return remove\n }\n\n get(name: string): MotionValue | undefined {\n return this.values.get(name)?.value\n }\n}\n"],"names":[],"mappings":";;;;MAMa,gBAAgB,CAAA;AAA7B,IAAA,WAAA,GAAA;QACI,IAAA,CAAA,MAAM,GAA4C,EAAE;AAE5C,QAAA,IAAA,CAAA,MAAM,GAAG,IAAI,GAAG,EAGrB;IAgDP;IA9CI,GAAG,CACC,IAAY,EACZ,KAAkB,EAClB,MAAqB,EACrB,QAAsB,EACtB,mBAAmB,GAAG,IAAI,EAAA;QAE1B,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QAE3C,IAAI,aAAa,EAAE;YACf,aAAa,CAAC,QAAQ,EAAE;QAC5B;QAEA,MAAM,QAAQ,GAAG,MAAK;AAClB,YAAA,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,EAAE;YAErB,IAAI,mBAAmB,EAAE;AACrB,gBAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACjE;iBAAO;AACH,gBAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;YACzB;AAEA,YAAA,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;AAClC,QAAA,CAAC;AAED,QAAA,QAAQ,EAAE;QAEV,MAAM,cAAc,GAAG,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;AAEnD,QAAA,QAAQ,IAAI,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC;QAExC,MAAM,MAAM,GAAG,MAAK;AAChB,YAAA,cAAc,EAAE;AAChB,YAAA,MAAM,IAAI,WAAW,CAAC,MAAM,CAAC;AAC7B,YAAA,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;AACxB,YAAA,QAAQ,IAAI,KAAK,CAAC,eAAe,CAAC,QAAQ,CAAC;AAC/C,QAAA,CAAC;AAED,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAElD,QAAA,OAAO,MAAM;IACjB;AAEA,IAAA,GAAG,CAAC,IAAY,EAAA;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK;IACvC;AACH;;;;"}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { camelToDash } from '../../render/dom/utils/camel-to-dash.mjs';
|
||||
import { createSelectorEffect } from '../utils/create-dom-effect.mjs';
|
||||
import { createEffect } from '../utils/create-effect.mjs';
|
||||
|
||||
function canSetAsProperty(element, name) {
|
||||
if (!(name in element))
|
||||
return false;
|
||||
const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), name) ||
|
||||
Object.getOwnPropertyDescriptor(element, name);
|
||||
// Check if it has a setter
|
||||
return descriptor && typeof descriptor.set === "function";
|
||||
}
|
||||
const addAttrValue = (element, state, key, value) => {
|
||||
const isProp = canSetAsProperty(element, key);
|
||||
const name = isProp
|
||||
? key
|
||||
: key.startsWith("data") || key.startsWith("aria")
|
||||
? camelToDash(key)
|
||||
: key;
|
||||
/**
|
||||
* Set attribute directly via property if available
|
||||
*/
|
||||
const render = isProp
|
||||
? () => {
|
||||
element[name] = state.latest[key];
|
||||
}
|
||||
: () => {
|
||||
const v = state.latest[key];
|
||||
if (v === null || v === undefined) {
|
||||
element.removeAttribute(name);
|
||||
}
|
||||
else {
|
||||
element.setAttribute(name, String(v));
|
||||
}
|
||||
};
|
||||
return state.set(key, value, render);
|
||||
};
|
||||
const attrEffect = /*@__PURE__*/ createSelectorEffect(
|
||||
/*@__PURE__*/ createEffect(addAttrValue));
|
||||
|
||||
export { addAttrValue, attrEffect };
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.mjs","sources":["../../../../src/effects/attr/index.ts"],"sourcesContent":["import { camelToDash } from \"../../render/dom/utils/camel-to-dash\"\nimport { MotionValue } from \"../../value\"\nimport { MotionValueState } from \"../MotionValueState\"\nimport { createSelectorEffect } from \"../utils/create-dom-effect\"\nimport { createEffect } from \"../utils/create-effect\"\n\nfunction canSetAsProperty(element: HTMLElement | SVGElement, name: string) {\n if (!(name in element)) return false\n\n const descriptor =\n Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), name) ||\n Object.getOwnPropertyDescriptor(element, name)\n\n // Check if it has a setter\n return descriptor && typeof descriptor.set === \"function\"\n}\n\nexport const addAttrValue = (\n element: HTMLElement | SVGElement,\n state: MotionValueState,\n key: string,\n value: MotionValue\n) => {\n const isProp = canSetAsProperty(element, key)\n const name = isProp\n ? key\n : key.startsWith(\"data\") || key.startsWith(\"aria\")\n ? camelToDash(key)\n : key\n\n /**\n * Set attribute directly via property if available\n */\n const render = isProp\n ? () => {\n ;(element as any)[name] = state.latest[key]\n }\n : () => {\n const v = state.latest[key]\n if (v === null || v === undefined) {\n element.removeAttribute(name)\n } else {\n element.setAttribute(name, String(v))\n }\n }\n\n return state.set(key, value, render)\n}\n\nexport const attrEffect = /*@__PURE__*/ createSelectorEffect(\n /*@__PURE__*/ createEffect(addAttrValue)\n)\n"],"names":[],"mappings":";;;;AAMA,SAAS,gBAAgB,CAAC,OAAiC,EAAE,IAAY,EAAA;AACrE,IAAA,IAAI,EAAE,IAAI,IAAI,OAAO,CAAC;AAAE,QAAA,OAAO,KAAK;AAEpC,IAAA,MAAM,UAAU,GACZ,MAAM,CAAC,wBAAwB,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC;AACrE,QAAA,MAAM,CAAC,wBAAwB,CAAC,OAAO,EAAE,IAAI,CAAC;;IAGlD,OAAO,UAAU,IAAI,OAAO,UAAU,CAAC,GAAG,KAAK,UAAU;AAC7D;AAEO,MAAM,YAAY,GAAG,CACxB,OAAiC,EACjC,KAAuB,EACvB,GAAW,EACX,KAAkB,KAClB;IACA,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC;IAC7C,MAAM,IAAI,GAAG;AACT,UAAE;AACF,UAAE,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM;AACjD,cAAE,WAAW,CAAC,GAAG;cACf,GAAG;AAET;;AAEG;IACH,MAAM,MAAM,GAAG;UACT,MAAK;YACC,OAAe,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;QAC/C;UACA,MAAK;YACD,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;YAC3B,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,EAAE;AAC/B,gBAAA,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC;YACjC;iBAAO;gBACH,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;YACzC;AACJ,QAAA,CAAC;IAEP,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC;AACxC;AAEO,MAAM,UAAU,iBAAiB,oBAAoB;AACxD,cAAc,YAAY,CAAC,YAAY,CAAC;;;;"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { createEffect } from '../utils/create-effect.mjs';
|
||||
|
||||
const propEffect = /*@__PURE__*/ createEffect((subject, state, key, value) => {
|
||||
return state.set(key, value, () => {
|
||||
subject[key] = state.latest[key];
|
||||
}, undefined, false);
|
||||
});
|
||||
|
||||
export { propEffect };
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.mjs","sources":["../../../../src/effects/prop/index.ts"],"sourcesContent":["import { MotionValue } from \"../../value\"\nimport { MotionValueState } from \"../MotionValueState\"\nimport { createEffect } from \"../utils/create-effect\"\n\nexport const propEffect = /*@__PURE__*/ createEffect(\n (\n subject: { [key: string]: any },\n state: MotionValueState,\n key: string,\n value: MotionValue\n ) => {\n return state.set(\n key,\n value,\n () => {\n subject[key] = state.latest[key]\n },\n undefined,\n false\n )\n }\n)\n"],"names":[],"mappings":";;AAIO,MAAM,UAAU,iBAAiB,YAAY,CAChD,CACI,OAA+B,EAC/B,KAAuB,EACvB,GAAW,EACX,KAAkB,KAClB;IACA,OAAO,KAAK,CAAC,GAAG,CACZ,GAAG,EACH,KAAK,EACL,MAAK;QACD,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;AACpC,IAAA,CAAC,EACD,SAAS,EACT,KAAK,CACR;AACL,CAAC;;;;"}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { isCSSVar } from '../../render/dom/is-css-var.mjs';
|
||||
import { transformProps } from '../../render/utils/keys-transform.mjs';
|
||||
import { isHTMLElement } from '../../utils/is-html-element.mjs';
|
||||
import { MotionValue } from '../../value/index.mjs';
|
||||
import { createSelectorEffect } from '../utils/create-dom-effect.mjs';
|
||||
import { createEffect } from '../utils/create-effect.mjs';
|
||||
import { buildTransform } from './transform.mjs';
|
||||
|
||||
const originProps = new Set(["originX", "originY", "originZ"]);
|
||||
const addStyleValue = (element, state, key, value) => {
|
||||
let render = undefined;
|
||||
let computed = undefined;
|
||||
if (transformProps.has(key)) {
|
||||
if (!state.get("transform")) {
|
||||
// If this is an HTML element, we need to set the transform-box to fill-box
|
||||
// to normalise the transform relative to the element's bounding box
|
||||
if (!isHTMLElement(element) && !state.get("transformBox")) {
|
||||
addStyleValue(element, state, "transformBox", new MotionValue("fill-box"));
|
||||
}
|
||||
state.set("transform", new MotionValue("none"), () => {
|
||||
element.style.transform = buildTransform(state);
|
||||
});
|
||||
}
|
||||
computed = state.get("transform");
|
||||
}
|
||||
else if (originProps.has(key)) {
|
||||
if (!state.get("transformOrigin")) {
|
||||
state.set("transformOrigin", new MotionValue(""), () => {
|
||||
const originX = state.latest.originX ?? "50%";
|
||||
const originY = state.latest.originY ?? "50%";
|
||||
const originZ = state.latest.originZ ?? 0;
|
||||
element.style.transformOrigin = `${originX} ${originY} ${originZ}`;
|
||||
});
|
||||
}
|
||||
computed = state.get("transformOrigin");
|
||||
}
|
||||
else if (isCSSVar(key)) {
|
||||
render = () => {
|
||||
element.style.setProperty(key, state.latest[key]);
|
||||
};
|
||||
}
|
||||
else {
|
||||
render = () => {
|
||||
element.style[key] = state.latest[key];
|
||||
};
|
||||
}
|
||||
return state.set(key, value, render, computed);
|
||||
};
|
||||
const styleEffect = /*@__PURE__*/ createSelectorEffect(
|
||||
/*@__PURE__*/ createEffect(addStyleValue));
|
||||
|
||||
export { addStyleValue, styleEffect };
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.mjs","sources":["../../../../src/effects/style/index.ts"],"sourcesContent":["import { isCSSVar } from \"../../render/dom/is-css-var\"\nimport { transformProps } from \"../../render/utils/keys-transform\"\nimport { isHTMLElement } from \"../../utils/is-html-element\"\nimport { MotionValue } from \"../../value\"\nimport { MotionValueState } from \"../MotionValueState\"\nimport { createSelectorEffect } from \"../utils/create-dom-effect\"\nimport { createEffect } from \"../utils/create-effect\"\nimport { buildTransform } from \"./transform\"\n\nconst originProps = new Set([\"originX\", \"originY\", \"originZ\"])\n\nexport const addStyleValue = (\n element: HTMLElement | SVGElement,\n state: MotionValueState,\n key: string,\n value: MotionValue\n) => {\n let render: VoidFunction | undefined = undefined\n let computed: MotionValue | undefined = undefined\n\n if (transformProps.has(key)) {\n if (!state.get(\"transform\")) {\n // If this is an HTML element, we need to set the transform-box to fill-box\n // to normalise the transform relative to the element's bounding box\n if (!isHTMLElement(element) && !state.get(\"transformBox\")) {\n addStyleValue(\n element,\n state,\n \"transformBox\",\n new MotionValue(\"fill-box\")\n )\n }\n\n state.set(\"transform\", new MotionValue(\"none\"), () => {\n element.style.transform = buildTransform(state)\n })\n }\n\n computed = state.get(\"transform\")\n } else if (originProps.has(key)) {\n if (!state.get(\"transformOrigin\")) {\n state.set(\"transformOrigin\", new MotionValue(\"\"), () => {\n const originX = state.latest.originX ?? \"50%\"\n const originY = state.latest.originY ?? \"50%\"\n const originZ = state.latest.originZ ?? 0\n element.style.transformOrigin = `${originX} ${originY} ${originZ}`\n })\n }\n\n computed = state.get(\"transformOrigin\")\n } else if (isCSSVar(key)) {\n render = () => {\n element.style.setProperty(key, state.latest[key] as string)\n }\n } else {\n render = () => {\n element.style[key as any] = state.latest[key] as string\n }\n }\n\n return state.set(key, value, render, computed)\n}\n\nexport const styleEffect = /*@__PURE__*/ createSelectorEffect(\n /*@__PURE__*/ createEffect(addStyleValue)\n)\n"],"names":[],"mappings":";;;;;;;;AASA,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AAEvD,MAAM,aAAa,GAAG,CACzB,OAAiC,EACjC,KAAuB,EACvB,GAAW,EACX,KAAkB,KAClB;IACA,IAAI,MAAM,GAA6B,SAAS;IAChD,IAAI,QAAQ,GAA4B,SAAS;AAEjD,IAAA,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QACzB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE;;;AAGzB,YAAA,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE;AACvD,gBAAA,aAAa,CACT,OAAO,EACP,KAAK,EACL,cAAc,EACd,IAAI,WAAW,CAAC,UAAU,CAAC,CAC9B;YACL;AAEA,YAAA,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,WAAW,CAAC,MAAM,CAAC,EAAE,MAAK;gBACjD,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC;AACnD,YAAA,CAAC,CAAC;QACN;AAEA,QAAA,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC;IACrC;AAAO,SAAA,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC7B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE;AAC/B,YAAA,KAAK,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,WAAW,CAAC,EAAE,CAAC,EAAE,MAAK;gBACnD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,IAAI,KAAK;gBAC7C,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,IAAI,KAAK;gBAC7C,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC;AACzC,gBAAA,OAAO,CAAC,KAAK,CAAC,eAAe,GAAG,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE;AACtE,YAAA,CAAC,CAAC;QACN;AAEA,QAAA,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC3C;AAAO,SAAA,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE;QACtB,MAAM,GAAG,MAAK;AACV,YAAA,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAW,CAAC;AAC/D,QAAA,CAAC;IACL;SAAO;QACH,MAAM,GAAG,MAAK;AACV,YAAA,OAAO,CAAC,KAAK,CAAC,GAAU,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAW;AAC3D,QAAA,CAAC;IACL;AAEA,IAAA,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC;AAClD;AAEO,MAAM,WAAW,iBAAiB,oBAAoB;AACzD,cAAc,YAAY,CAAC,aAAa,CAAC;;;;"}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { transformPropOrder } from '../../render/utils/keys-transform.mjs';
|
||||
|
||||
const translateAlias = {
|
||||
x: "translateX",
|
||||
y: "translateY",
|
||||
z: "translateZ",
|
||||
transformPerspective: "perspective",
|
||||
};
|
||||
function buildTransform(state) {
|
||||
let transform = "";
|
||||
let transformIsDefault = true;
|
||||
/**
|
||||
* Loop over all possible transforms in order, adding the ones that
|
||||
* are present to the transform string.
|
||||
*/
|
||||
for (let i = 0; i < transformPropOrder.length; i++) {
|
||||
const key = transformPropOrder[i];
|
||||
const value = state.latest[key];
|
||||
if (value === undefined)
|
||||
continue;
|
||||
let valueIsDefault = true;
|
||||
if (typeof value === "number") {
|
||||
valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
|
||||
}
|
||||
else {
|
||||
const parsed = parseFloat(value);
|
||||
valueIsDefault = key.startsWith("scale") ? parsed === 1 : parsed === 0;
|
||||
}
|
||||
if (!valueIsDefault) {
|
||||
transformIsDefault = false;
|
||||
const transformName = translateAlias[key] || key;
|
||||
transform += `${transformName}(${value}) `;
|
||||
}
|
||||
}
|
||||
// See build-transform.ts: additive `rotate()` so user `rotate` isn't
|
||||
// clobbered. Not a `transformPropOrder` slot.
|
||||
const pathRotation = state.latest.pathRotation;
|
||||
if (pathRotation) {
|
||||
transformIsDefault = false;
|
||||
transform += `rotate(${typeof pathRotation === "number"
|
||||
? `${pathRotation}deg`
|
||||
: pathRotation}) `;
|
||||
}
|
||||
return transformIsDefault ? "none" : transform.trim();
|
||||
}
|
||||
|
||||
export { buildTransform };
|
||||
//# sourceMappingURL=transform.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"transform.mjs","sources":["../../../../src/effects/style/transform.ts"],"sourcesContent":["import { transformPropOrder } from \"../../render/utils/keys-transform\"\nimport { MotionValueState } from \"../MotionValueState\"\n\nconst translateAlias = {\n x: \"translateX\",\n y: \"translateY\",\n z: \"translateZ\",\n transformPerspective: \"perspective\",\n}\n\nexport function buildTransform(state: MotionValueState) {\n let transform = \"\"\n let transformIsDefault = true\n\n /**\n * Loop over all possible transforms in order, adding the ones that\n * are present to the transform string.\n */\n for (let i = 0; i < transformPropOrder.length; i++) {\n const key = transformPropOrder[i] as keyof typeof translateAlias\n const value = state.latest[key]\n\n if (value === undefined) continue\n\n let valueIsDefault = true\n if (typeof value === \"number\") {\n valueIsDefault = value === (key.startsWith(\"scale\") ? 1 : 0)\n } else {\n const parsed = parseFloat(value)\n valueIsDefault = key.startsWith(\"scale\") ? parsed === 1 : parsed === 0\n }\n\n if (!valueIsDefault) {\n transformIsDefault = false\n const transformName = translateAlias[key] || key\n transform += `${transformName}(${value}) `\n }\n }\n\n // See build-transform.ts: additive `rotate()` so user `rotate` isn't\n // clobbered. Not a `transformPropOrder` slot.\n const pathRotation = state.latest.pathRotation\n if (pathRotation) {\n transformIsDefault = false\n transform += `rotate(${\n typeof pathRotation === \"number\"\n ? `${pathRotation}deg`\n : pathRotation\n }) `\n }\n\n return transformIsDefault ? \"none\" : transform.trim()\n}\n"],"names":[],"mappings":";;AAGA,MAAM,cAAc,GAAG;AACnB,IAAA,CAAC,EAAE,YAAY;AACf,IAAA,CAAC,EAAE,YAAY;AACf,IAAA,CAAC,EAAE,YAAY;AACf,IAAA,oBAAoB,EAAE,aAAa;CACtC;AAEK,SAAU,cAAc,CAAC,KAAuB,EAAA;IAClD,IAAI,SAAS,GAAG,EAAE;IAClB,IAAI,kBAAkB,GAAG,IAAI;AAE7B;;;AAGG;AACH,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,kBAAkB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AAChD,QAAA,MAAM,GAAG,GAAG,kBAAkB,CAAC,CAAC,CAAgC;QAChE,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;QAE/B,IAAI,KAAK,KAAK,SAAS;YAAE;QAEzB,IAAI,cAAc,GAAG,IAAI;AACzB,QAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC3B,YAAA,cAAc,GAAG,KAAK,MAAM,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChE;aAAO;AACH,YAAA,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC;AAChC,YAAA,cAAc,GAAG,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,KAAK,CAAC;QAC1E;QAEA,IAAI,CAAC,cAAc,EAAE;YACjB,kBAAkB,GAAG,KAAK;YAC1B,MAAM,aAAa,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,GAAG;AAChD,YAAA,SAAS,IAAI,CAAA,EAAG,aAAa,CAAA,CAAA,EAAI,KAAK,IAAI;QAC9C;IACJ;;;AAIA,IAAA,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,YAAY;IAC9C,IAAI,YAAY,EAAE;QACd,kBAAkB,GAAG,KAAK;AAC1B,QAAA,SAAS,IAAI,CAAA,OAAA,EACT,OAAO,YAAY,KAAK;cAClB,CAAA,EAAG,YAAY,CAAA,GAAA;cACf,YACV,CAAA,EAAA,CAAI;IACR;AAEA,IAAA,OAAO,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE;AACzD;;;;"}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { MotionValue } from '../../value/index.mjs';
|
||||
import { addAttrValue } from '../attr/index.mjs';
|
||||
import { addStyleValue } from '../style/index.mjs';
|
||||
import { createSelectorEffect } from '../utils/create-dom-effect.mjs';
|
||||
import { createEffect } from '../utils/create-effect.mjs';
|
||||
import { frame } from '../../frameloop/frame.mjs';
|
||||
|
||||
function addSVGPathValue(element, state, key, value) {
|
||||
frame.render(() => element.setAttribute("pathLength", "1"));
|
||||
if (key === "pathOffset") {
|
||||
return state.set(key, value, () => {
|
||||
// Use unitless value to avoid Safari zoom bug
|
||||
const offset = state.latest[key];
|
||||
element.setAttribute("stroke-dashoffset", `${-offset}`);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (!state.get("stroke-dasharray")) {
|
||||
state.set("stroke-dasharray", new MotionValue("1 1"), () => {
|
||||
const { pathLength = 1, pathSpacing } = state.latest;
|
||||
// Use unitless values to avoid Safari zoom bug
|
||||
element.setAttribute("stroke-dasharray", `${pathLength} ${pathSpacing ?? 1 - Number(pathLength)}`);
|
||||
});
|
||||
}
|
||||
return state.set(key, value, undefined, state.get("stroke-dasharray"));
|
||||
}
|
||||
}
|
||||
const addSVGValue = (element, state, key, value) => {
|
||||
if (key.startsWith("path")) {
|
||||
return addSVGPathValue(element, state, key, value);
|
||||
}
|
||||
else if (key.startsWith("attr")) {
|
||||
return addAttrValue(element, state, convertAttrKey(key), value);
|
||||
}
|
||||
const handler = key in element.style ? addStyleValue : addAttrValue;
|
||||
return handler(element, state, key, value);
|
||||
};
|
||||
const svgEffect = /*@__PURE__*/ createSelectorEffect(
|
||||
/*@__PURE__*/ createEffect(addSVGValue));
|
||||
function convertAttrKey(key) {
|
||||
return key.replace(/^attr([A-Z])/, (_, firstChar) => firstChar.toLowerCase());
|
||||
}
|
||||
|
||||
export { svgEffect };
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.mjs","sources":["../../../../src/effects/svg/index.ts"],"sourcesContent":["import { frame } from \"../../frameloop\"\nimport { MotionValue } from \"../../value\"\nimport { addAttrValue } from \"../attr\"\nimport { MotionValueState } from \"../MotionValueState\"\nimport { addStyleValue } from \"../style\"\nimport { createSelectorEffect } from \"../utils/create-dom-effect\"\nimport { createEffect } from \"../utils/create-effect\"\n\nfunction addSVGPathValue(\n element: SVGElement,\n state: MotionValueState,\n key: string,\n value: MotionValue\n) {\n frame.render(() => element.setAttribute(\"pathLength\", \"1\"))\n\n if (key === \"pathOffset\") {\n return state.set(key, value, () => {\n // Use unitless value to avoid Safari zoom bug\n const offset = state.latest[key]\n element.setAttribute(\"stroke-dashoffset\", `${-offset}`)\n })\n } else {\n if (!state.get(\"stroke-dasharray\")) {\n state.set(\"stroke-dasharray\", new MotionValue(\"1 1\"), () => {\n const { pathLength = 1, pathSpacing } = state.latest\n\n // Use unitless values to avoid Safari zoom bug\n element.setAttribute(\n \"stroke-dasharray\",\n `${pathLength} ${pathSpacing ?? 1 - Number(pathLength)}`\n )\n })\n }\n\n return state.set(key, value, undefined, state.get(\"stroke-dasharray\"))\n }\n}\n\nconst addSVGValue = (\n element: SVGElement,\n state: MotionValueState,\n key: string,\n value: MotionValue\n) => {\n if (key.startsWith(\"path\")) {\n return addSVGPathValue(element, state, key, value)\n } else if (key.startsWith(\"attr\")) {\n return addAttrValue(element, state, convertAttrKey(key), value)\n }\n\n const handler = key in element.style ? addStyleValue : addAttrValue\n return handler(element, state, key, value)\n}\n\nexport const svgEffect = /*@__PURE__*/ createSelectorEffect(\n /*@__PURE__*/ createEffect(addSVGValue)\n)\n\nfunction convertAttrKey(key: string) {\n return key.replace(/^attr([A-Z])/, (_, firstChar) =>\n firstChar.toLowerCase()\n )\n}\n"],"names":[],"mappings":";;;;;;;AAQA,SAAS,eAAe,CACpB,OAAmB,EACnB,KAAuB,EACvB,GAAW,EACX,KAAkB,EAAA;AAElB,IAAA,KAAK,CAAC,MAAM,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;AAE3D,IAAA,IAAI,GAAG,KAAK,YAAY,EAAE;QACtB,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,MAAK;;YAE9B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;YAChC,OAAO,CAAC,YAAY,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAA,CAAE,CAAC;AAC3D,QAAA,CAAC,CAAC;IACN;SAAO;QACH,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE;AAChC,YAAA,KAAK,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,MAAK;gBACvD,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC,MAAM;;AAGpD,gBAAA,OAAO,CAAC,YAAY,CAChB,kBAAkB,EAClB,CAAA,EAAG,UAAU,CAAA,CAAA,EAAI,WAAW,IAAI,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA,CAAE,CAC3D;AACL,YAAA,CAAC,CAAC;QACN;AAEA,QAAA,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC1E;AACJ;AAEA,MAAM,WAAW,GAAG,CAChB,OAAmB,EACnB,KAAuB,EACvB,GAAW,EACX,KAAkB,KAClB;AACA,IAAA,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,OAAO,eAAe,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC;IACtD;AAAO,SAAA,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;AAC/B,QAAA,OAAO,YAAY,CAAC,OAAO,EAAE,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC;IACnE;AAEA,IAAA,MAAM,OAAO,GAAG,GAAG,IAAI,OAAO,CAAC,KAAK,GAAG,aAAa,GAAG,YAAY;IACnE,OAAO,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC;AAC9C,CAAC;AAEM,MAAM,SAAS,iBAAiB,oBAAoB;AACvD,cAAc,YAAY,CAAC,WAAW,CAAC;AAG3C,SAAS,cAAc,CAAC,GAAW,EAAA;AAC/B,IAAA,OAAO,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,SAAS,KAC5C,SAAS,CAAC,WAAW,EAAE,CAC1B;AACL;;;;"}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { resolveElements } from '../../utils/resolve-elements.mjs';
|
||||
|
||||
function createSelectorEffect(subjectEffect) {
|
||||
return (subject, values) => {
|
||||
const elements = resolveElements(subject);
|
||||
const subscriptions = [];
|
||||
for (const element of elements) {
|
||||
const remove = subjectEffect(element, values);
|
||||
subscriptions.push(remove);
|
||||
}
|
||||
return () => {
|
||||
for (const remove of subscriptions)
|
||||
remove();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export { createSelectorEffect };
|
||||
//# sourceMappingURL=create-dom-effect.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"create-dom-effect.mjs","sources":["../../../../src/effects/utils/create-dom-effect.ts"],"sourcesContent":["import {\n ElementOrSelector,\n resolveElements,\n} from \"../../utils/resolve-elements\"\nimport { MotionValue } from \"../../value\"\n\nexport function createSelectorEffect<T>(\n subjectEffect: (\n subject: T,\n values: Record<string, MotionValue>\n ) => VoidFunction\n) {\n return (\n subject: ElementOrSelector,\n values: Record<string, MotionValue>\n ) => {\n const elements = resolveElements(subject)\n const subscriptions: VoidFunction[] = []\n\n for (const element of elements) {\n const remove = subjectEffect(element as T, values)\n subscriptions.push(remove)\n }\n\n return () => {\n for (const remove of subscriptions) remove()\n }\n }\n}\n"],"names":[],"mappings":";;AAMM,SAAU,oBAAoB,CAChC,aAGiB,EAAA;AAEjB,IAAA,OAAO,CACH,OAA0B,EAC1B,MAAmC,KACnC;AACA,QAAA,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC;QACzC,MAAM,aAAa,GAAmB,EAAE;AAExC,QAAA,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;YAC5B,MAAM,MAAM,GAAG,aAAa,CAAC,OAAY,EAAE,MAAM,CAAC;AAClD,YAAA,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;QAC9B;AAEA,QAAA,OAAO,MAAK;YACR,KAAK,MAAM,MAAM,IAAI,aAAa;AAAE,gBAAA,MAAM,EAAE;AAChD,QAAA,CAAC;AACL,IAAA,CAAC;AACL;;;;"}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { MotionValueState } from '../MotionValueState.mjs';
|
||||
|
||||
function createEffect(addValue) {
|
||||
const stateCache = new WeakMap();
|
||||
return (subject, values) => {
|
||||
const state = stateCache.get(subject) ?? new MotionValueState();
|
||||
stateCache.set(subject, state);
|
||||
const subscriptions = [];
|
||||
for (const key in values) {
|
||||
const value = values[key];
|
||||
const remove = addValue(subject, state, key, value);
|
||||
subscriptions.push(remove);
|
||||
}
|
||||
return () => {
|
||||
for (const cancel of subscriptions)
|
||||
cancel();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export { createEffect };
|
||||
//# sourceMappingURL=create-effect.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"create-effect.mjs","sources":["../../../../src/effects/utils/create-effect.ts"],"sourcesContent":["import { MotionValue } from \"../../value\"\nimport { MotionValueState } from \"../MotionValueState\"\n\nexport function createEffect<Subject extends object>(\n addValue: (\n subject: Subject,\n state: MotionValueState,\n key: string,\n value: MotionValue\n ) => VoidFunction\n) {\n const stateCache = new WeakMap<Subject, MotionValueState>()\n\n return (\n subject: Subject,\n values: Record<string, MotionValue>\n ): VoidFunction => {\n const state = stateCache.get(subject) ?? new MotionValueState()\n\n stateCache.set(subject, state)\n\n const subscriptions: VoidFunction[] = []\n\n for (const key in values) {\n const value = values[key]\n const remove = addValue(subject, state, key, value)\n subscriptions.push(remove)\n }\n\n return () => {\n for (const cancel of subscriptions) cancel()\n }\n }\n}\n"],"names":[],"mappings":";;AAGM,SAAU,YAAY,CACxB,QAKiB,EAAA;AAEjB,IAAA,MAAM,UAAU,GAAG,IAAI,OAAO,EAA6B;AAE3D,IAAA,OAAO,CACH,OAAgB,EAChB,MAAmC,KACrB;AACd,QAAA,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,gBAAgB,EAAE;AAE/D,QAAA,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC;QAE9B,MAAM,aAAa,GAAmB,EAAE;AAExC,QAAA,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE;AACtB,YAAA,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC;AACzB,YAAA,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC;AACnD,YAAA,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;QAC9B;AAEA,QAAA,OAAO,MAAK;YACR,KAAK,MAAM,MAAM,IAAI,aAAa;AAAE,gBAAA,MAAM,EAAE;AAChD,QAAA,CAAC;AACL,IAAA,CAAC;AACL;;;;"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
function addDomEvent(target, eventName, handler, options = { passive: true }) {
|
||||
target.addEventListener(eventName, handler, options);
|
||||
return () => target.removeEventListener(eventName, handler, options);
|
||||
}
|
||||
|
||||
export { addDomEvent };
|
||||
//# sourceMappingURL=add-dom-event.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"add-dom-event.mjs","sources":["../../../src/events/add-dom-event.ts"],"sourcesContent":["export function addDomEvent(\n target: EventTarget,\n eventName: string,\n handler: EventListener,\n options: AddEventListenerOptions = { passive: true }\n) {\n target.addEventListener(eventName, handler, options)\n\n return () => target.removeEventListener(eventName, handler, options)\n}\n"],"names":[],"mappings":"AAAM,SAAU,WAAW,CACvB,MAAmB,EACnB,SAAiB,EACjB,OAAsB,EACtB,OAAA,GAAmC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAA;IAEpD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC;AAEpD,IAAA,OAAO,MAAM,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC;AACxE;;;;"}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import { MotionGlobalConfig } from 'motion-utils';
|
||||
import { stepsOrder } from './order.mjs';
|
||||
import { createRenderStep } from './render-step.mjs';
|
||||
|
||||
const maxElapsed = 40;
|
||||
function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
|
||||
let runNextFrame = false;
|
||||
let useDefaultElapsed = true;
|
||||
const state = {
|
||||
delta: 0.0,
|
||||
timestamp: 0.0,
|
||||
isProcessing: false,
|
||||
};
|
||||
const flagRunNextFrame = () => (runNextFrame = true);
|
||||
const steps = stepsOrder.reduce((acc, key) => {
|
||||
acc[key] = createRenderStep(flagRunNextFrame);
|
||||
return acc;
|
||||
}, {});
|
||||
const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
|
||||
const processBatch = () => {
|
||||
const useManualTiming = MotionGlobalConfig.useManualTiming;
|
||||
const timestamp = useManualTiming
|
||||
? state.timestamp
|
||||
: performance.now();
|
||||
runNextFrame = false;
|
||||
if (!useManualTiming) {
|
||||
state.delta = useDefaultElapsed
|
||||
? 1000 / 60
|
||||
: Math.max(Math.min(timestamp - state.timestamp, maxElapsed), 1);
|
||||
}
|
||||
state.timestamp = timestamp;
|
||||
state.isProcessing = true;
|
||||
// Unrolled render loop for better per-frame performance
|
||||
setup.process(state);
|
||||
read.process(state);
|
||||
resolveKeyframes.process(state);
|
||||
preUpdate.process(state);
|
||||
update.process(state);
|
||||
preRender.process(state);
|
||||
render.process(state);
|
||||
postRender.process(state);
|
||||
state.isProcessing = false;
|
||||
if (runNextFrame && allowKeepAlive) {
|
||||
useDefaultElapsed = false;
|
||||
scheduleNextBatch(processBatch);
|
||||
}
|
||||
};
|
||||
const wake = () => {
|
||||
runNextFrame = true;
|
||||
useDefaultElapsed = true;
|
||||
if (!state.isProcessing) {
|
||||
scheduleNextBatch(processBatch);
|
||||
}
|
||||
};
|
||||
const schedule = stepsOrder.reduce((acc, key) => {
|
||||
const step = steps[key];
|
||||
acc[key] = (process, keepAlive = false, immediate = false) => {
|
||||
if (!runNextFrame)
|
||||
wake();
|
||||
return step.schedule(process, keepAlive, immediate);
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
const cancel = (process) => {
|
||||
for (let i = 0; i < stepsOrder.length; i++) {
|
||||
steps[stepsOrder[i]].cancel(process);
|
||||
}
|
||||
};
|
||||
return { schedule, cancel, state, steps };
|
||||
}
|
||||
|
||||
export { createRenderBatcher };
|
||||
//# sourceMappingURL=batcher.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+7
@@ -0,0 +1,7 @@
|
||||
import { noop } from 'motion-utils';
|
||||
import { createRenderBatcher } from './batcher.mjs';
|
||||
|
||||
const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
|
||||
|
||||
export { cancelFrame, frame, frameData, frameSteps };
|
||||
//# sourceMappingURL=frame.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"frame.mjs","sources":["../../../src/frameloop/frame.ts"],"sourcesContent":["import { noop } from \"motion-utils\"\nimport { createRenderBatcher } from \"./batcher\"\n\nexport const {\n schedule: frame,\n cancel: cancelFrame,\n state: frameData,\n steps: frameSteps,\n} = /* @__PURE__ */ createRenderBatcher(\n typeof requestAnimationFrame !== \"undefined\" ? requestAnimationFrame : noop,\n true\n)\n"],"names":[],"mappings":";;;AAGO,MAAM,EACT,QAAQ,EAAE,KAAK,EACf,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,UAAU,GACpB,mBAAmB,mBAAmB,CACnC,OAAO,qBAAqB,KAAK,WAAW,GAAG,qBAAqB,GAAG,IAAI,EAC3E,IAAI;;;;"}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { stepsOrder } from './order.mjs';
|
||||
import { frame, cancelFrame } from './frame.mjs';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Import as `frame` instead.
|
||||
*/
|
||||
const sync = frame;
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Use cancelFrame(callback) instead.
|
||||
*/
|
||||
const cancelSync = stepsOrder.reduce((acc, key) => {
|
||||
acc[key] = (process) => cancelFrame(process);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
export { cancelSync, sync };
|
||||
//# sourceMappingURL=index-legacy.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index-legacy.mjs","sources":["../../../src/frameloop/index-legacy.ts"],"sourcesContent":["import { cancelFrame, frame } from \".\"\nimport { stepsOrder } from \"./order\"\nimport { Process } from \"./types\"\n\n/**\n * @deprecated\n *\n * Import as `frame` instead.\n */\nexport const sync = frame\n\n/**\n * @deprecated\n *\n * Use cancelFrame(callback) instead.\n */\nexport const cancelSync = stepsOrder.reduce((acc, key) => {\n acc[key] = (process: Process) => cancelFrame(process)\n return acc\n}, {} as Record<string, (process: Process) => void>)\n"],"names":[],"mappings":";;;AAIA;;;;AAIG;AACI,MAAM,IAAI,GAAG;AAEpB;;;;AAIG;AACI,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,KAAI;AACrD,IAAA,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,OAAgB,KAAK,WAAW,CAAC,OAAO,CAAC;AACrD,IAAA,OAAO,GAAG;AACd,CAAC,EAAE,EAAgD;;;;"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { createRenderBatcher } from './batcher.mjs';
|
||||
|
||||
const { schedule: microtask, cancel: cancelMicrotask } =
|
||||
/* @__PURE__ */ createRenderBatcher(queueMicrotask, false);
|
||||
|
||||
export { cancelMicrotask, microtask };
|
||||
//# sourceMappingURL=microtask.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"microtask.mjs","sources":["../../../src/frameloop/microtask.ts"],"sourcesContent":["import { createRenderBatcher } from \"./batcher\"\n\nexport const { schedule: microtask, cancel: cancelMicrotask } =\n /* @__PURE__ */ createRenderBatcher(queueMicrotask, false)\n"],"names":[],"mappings":";;AAEO,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE;AACzD,gBAAgB,mBAAmB,CAAC,cAAc,EAAE,KAAK;;;;"}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
const stepsOrder = [
|
||||
"setup", // Compute
|
||||
"read", // Read
|
||||
"resolveKeyframes", // Write/Read/Write/Read
|
||||
"preUpdate", // Compute
|
||||
"update", // Compute
|
||||
"preRender", // Compute
|
||||
"render", // Write
|
||||
"postRender", // Compute
|
||||
];
|
||||
|
||||
export { stepsOrder };
|
||||
//# sourceMappingURL=order.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"order.mjs","sources":["../../../src/frameloop/order.ts"],"sourcesContent":["import { StepId } from \"./types\"\n\nexport const stepsOrder: StepId[] = [\n \"setup\", // Compute\n \"read\", // Read\n \"resolveKeyframes\", // Write/Read/Write/Read\n \"preUpdate\", // Compute\n \"update\", // Compute\n \"preRender\", // Compute\n \"render\", // Write\n \"postRender\", // Compute\n] as const\n\nexport type StepNames = (typeof stepsOrder)[number]\n"],"names":[],"mappings":"AAEO,MAAM,UAAU,GAAa;AAChC,IAAA,OAAO;AACP,IAAA,MAAM;AACN,IAAA,kBAAkB;AAClB,IAAA,WAAW;AACX,IAAA,QAAQ;AACR,IAAA,WAAW;AACX,IAAA,QAAQ;AACR,IAAA,YAAY;;;;;"}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
function createRenderStep(runNextFrame) {
|
||||
/**
|
||||
* We create and reuse two queues, one to queue jobs for the current frame
|
||||
* and one for the next. We reuse to avoid triggering GC after x frames.
|
||||
*/
|
||||
let thisFrame = new Set();
|
||||
let nextFrame = new Set();
|
||||
/**
|
||||
* Track whether we're currently processing jobs in this step. This way
|
||||
* we can decide whether to schedule new jobs for this frame or next.
|
||||
*/
|
||||
let isProcessing = false;
|
||||
let flushNextFrame = false;
|
||||
/**
|
||||
* A set of processes which were marked keepAlive when scheduled.
|
||||
*/
|
||||
const toKeepAlive = new WeakSet();
|
||||
let latestFrameData = {
|
||||
delta: 0.0,
|
||||
timestamp: 0.0,
|
||||
isProcessing: false,
|
||||
};
|
||||
function triggerCallback(callback) {
|
||||
if (toKeepAlive.has(callback)) {
|
||||
step.schedule(callback);
|
||||
runNextFrame();
|
||||
}
|
||||
callback(latestFrameData);
|
||||
}
|
||||
const step = {
|
||||
/**
|
||||
* Schedule a process to run on the next frame.
|
||||
*/
|
||||
schedule: (callback, keepAlive = false, immediate = false) => {
|
||||
const addToCurrentFrame = immediate && isProcessing;
|
||||
const queue = addToCurrentFrame ? thisFrame : nextFrame;
|
||||
if (keepAlive)
|
||||
toKeepAlive.add(callback);
|
||||
queue.add(callback);
|
||||
return callback;
|
||||
},
|
||||
/**
|
||||
* Cancel the provided callback from running on the next frame.
|
||||
*/
|
||||
cancel: (callback) => {
|
||||
nextFrame.delete(callback);
|
||||
toKeepAlive.delete(callback);
|
||||
},
|
||||
/**
|
||||
* Execute all schedule callbacks.
|
||||
*/
|
||||
process: (frameData) => {
|
||||
latestFrameData = frameData;
|
||||
/**
|
||||
* If we're already processing we've probably been triggered by a flushSync
|
||||
* inside an existing process. Instead of executing, mark flushNextFrame
|
||||
* as true and ensure we flush the following frame at the end of this one.
|
||||
*/
|
||||
if (isProcessing) {
|
||||
flushNextFrame = true;
|
||||
return;
|
||||
}
|
||||
isProcessing = true;
|
||||
// Swap this frame and the next to avoid GC
|
||||
const prevFrame = thisFrame;
|
||||
thisFrame = nextFrame;
|
||||
nextFrame = prevFrame;
|
||||
// Execute this frame
|
||||
thisFrame.forEach(triggerCallback);
|
||||
// Clear the frame so no callbacks remain. This is to avoid
|
||||
// memory leaks should this render step not run for a while.
|
||||
thisFrame.clear();
|
||||
isProcessing = false;
|
||||
if (flushNextFrame) {
|
||||
flushNextFrame = false;
|
||||
step.process(frameData);
|
||||
}
|
||||
},
|
||||
};
|
||||
return step;
|
||||
}
|
||||
|
||||
export { createRenderStep };
|
||||
//# sourceMappingURL=render-step.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"render-step.mjs","sources":["../../../src/frameloop/render-step.ts"],"sourcesContent":["import { FrameData, Process, Step } from \"./types\"\n\nexport function createRenderStep(runNextFrame: () => void): Step {\n /**\n * We create and reuse two queues, one to queue jobs for the current frame\n * and one for the next. We reuse to avoid triggering GC after x frames.\n */\n let thisFrame = new Set<Process>()\n let nextFrame = new Set<Process>()\n\n /**\n * Track whether we're currently processing jobs in this step. This way\n * we can decide whether to schedule new jobs for this frame or next.\n */\n let isProcessing = false\n\n let flushNextFrame = false\n\n /**\n * A set of processes which were marked keepAlive when scheduled.\n */\n const toKeepAlive = new WeakSet<Process>()\n\n let latestFrameData: FrameData = {\n delta: 0.0,\n timestamp: 0.0,\n isProcessing: false,\n }\n\n function triggerCallback(callback: Process) {\n if (toKeepAlive.has(callback)) {\n step.schedule(callback)\n runNextFrame()\n }\n\n callback(latestFrameData)\n }\n\n const step: Step = {\n /**\n * Schedule a process to run on the next frame.\n */\n schedule: (callback, keepAlive = false, immediate = false) => {\n const addToCurrentFrame = immediate && isProcessing\n const queue = addToCurrentFrame ? thisFrame : nextFrame\n\n if (keepAlive) toKeepAlive.add(callback)\n\n queue.add(callback)\n\n return callback\n },\n\n /**\n * Cancel the provided callback from running on the next frame.\n */\n cancel: (callback) => {\n nextFrame.delete(callback)\n toKeepAlive.delete(callback)\n },\n\n /**\n * Execute all schedule callbacks.\n */\n process: (frameData) => {\n latestFrameData = frameData\n\n /**\n * If we're already processing we've probably been triggered by a flushSync\n * inside an existing process. Instead of executing, mark flushNextFrame\n * as true and ensure we flush the following frame at the end of this one.\n */\n if (isProcessing) {\n flushNextFrame = true\n return\n }\n\n isProcessing = true\n\n // Swap this frame and the next to avoid GC\n const prevFrame = thisFrame\n thisFrame = nextFrame\n nextFrame = prevFrame\n\n // Execute this frame\n thisFrame.forEach(triggerCallback)\n\n // Clear the frame so no callbacks remain. This is to avoid\n // memory leaks should this render step not run for a while.\n thisFrame.clear()\n\n isProcessing = false\n\n if (flushNextFrame) {\n flushNextFrame = false\n step.process(frameData)\n }\n },\n }\n\n return step\n}\n"],"names":[],"mappings":"AAEM,SAAU,gBAAgB,CAAC,YAAwB,EAAA;AACrD;;;AAGG;AACH,IAAA,IAAI,SAAS,GAAG,IAAI,GAAG,EAAW;AAClC,IAAA,IAAI,SAAS,GAAG,IAAI,GAAG,EAAW;AAElC;;;AAGG;IACH,IAAI,YAAY,GAAG,KAAK;IAExB,IAAI,cAAc,GAAG,KAAK;AAE1B;;AAEG;AACH,IAAA,MAAM,WAAW,GAAG,IAAI,OAAO,EAAW;AAE1C,IAAA,IAAI,eAAe,GAAc;AAC7B,QAAA,KAAK,EAAE,GAAG;AACV,QAAA,SAAS,EAAE,GAAG;AACd,QAAA,YAAY,EAAE,KAAK;KACtB;IAED,SAAS,eAAe,CAAC,QAAiB,EAAA;AACtC,QAAA,IAAI,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;AAC3B,YAAA,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;AACvB,YAAA,YAAY,EAAE;QAClB;QAEA,QAAQ,CAAC,eAAe,CAAC;IAC7B;AAEA,IAAA,MAAM,IAAI,GAAS;AACf;;AAEG;AACH,QAAA,QAAQ,EAAE,CAAC,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,KAAI;AACzD,YAAA,MAAM,iBAAiB,GAAG,SAAS,IAAI,YAAY;YACnD,MAAM,KAAK,GAAG,iBAAiB,GAAG,SAAS,GAAG,SAAS;AAEvD,YAAA,IAAI,SAAS;AAAE,gBAAA,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC;AAExC,YAAA,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC;AAEnB,YAAA,OAAO,QAAQ;QACnB,CAAC;AAED;;AAEG;AACH,QAAA,MAAM,EAAE,CAAC,QAAQ,KAAI;AACjB,YAAA,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;AAC1B,YAAA,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC;QAChC,CAAC;AAED;;AAEG;AACH,QAAA,OAAO,EAAE,CAAC,SAAS,KAAI;YACnB,eAAe,GAAG,SAAS;AAE3B;;;;AAIG;YACH,IAAI,YAAY,EAAE;gBACd,cAAc,GAAG,IAAI;gBACrB;YACJ;YAEA,YAAY,GAAG,IAAI;;YAGnB,MAAM,SAAS,GAAG,SAAS;YAC3B,SAAS,GAAG,SAAS;YACrB,SAAS,GAAG,SAAS;;AAGrB,YAAA,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC;;;YAIlC,SAAS,CAAC,KAAK,EAAE;YAEjB,YAAY,GAAG,KAAK;YAEpB,IAAI,cAAc,EAAE;gBAChB,cAAc,GAAG,KAAK;AACtB,gBAAA,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;YAC3B;QACJ,CAAC;KACJ;AAED,IAAA,OAAO,IAAI;AACf;;;;"}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { MotionGlobalConfig } from 'motion-utils';
|
||||
import { frameData } from './frame.mjs';
|
||||
|
||||
let now;
|
||||
function clearTime() {
|
||||
now = undefined;
|
||||
}
|
||||
/**
|
||||
* An eventloop-synchronous alternative to performance.now().
|
||||
*
|
||||
* Ensures that time measurements remain consistent within a synchronous context.
|
||||
* Usually calling performance.now() twice within the same synchronous context
|
||||
* will return different values which isn't useful for animations when we're usually
|
||||
* trying to sync animations to the same frame.
|
||||
*/
|
||||
const time = {
|
||||
now: () => {
|
||||
if (now === undefined) {
|
||||
time.set(frameData.isProcessing || MotionGlobalConfig.useManualTiming
|
||||
? frameData.timestamp
|
||||
: performance.now());
|
||||
}
|
||||
return now;
|
||||
},
|
||||
set: (newTime) => {
|
||||
now = newTime;
|
||||
queueMicrotask(clearTime);
|
||||
},
|
||||
};
|
||||
|
||||
export { time };
|
||||
//# sourceMappingURL=sync-time.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sync-time.mjs","sources":["../../../src/frameloop/sync-time.ts"],"sourcesContent":["import { MotionGlobalConfig } from \"motion-utils\"\nimport { frameData } from \"./frame\"\n\nlet now: number | undefined\n\nfunction clearTime() {\n now = undefined\n}\n\n/**\n * An eventloop-synchronous alternative to performance.now().\n *\n * Ensures that time measurements remain consistent within a synchronous context.\n * Usually calling performance.now() twice within the same synchronous context\n * will return different values which isn't useful for animations when we're usually\n * trying to sync animations to the same frame.\n */\nexport const time = {\n now: (): number => {\n if (now === undefined) {\n time.set(\n frameData.isProcessing || MotionGlobalConfig.useManualTiming\n ? frameData.timestamp\n : performance.now()\n )\n }\n\n return now!\n },\n set: (newTime: number) => {\n now = newTime\n queueMicrotask(clearTime)\n },\n}\n"],"names":[],"mappings":";;;AAGA,IAAI,GAAuB;AAE3B,SAAS,SAAS,GAAA;IACd,GAAG,GAAG,SAAS;AACnB;AAEA;;;;;;;AAOG;AACI,MAAM,IAAI,GAAG;IAChB,GAAG,EAAE,MAAa;AACd,QAAA,IAAI,GAAG,KAAK,SAAS,EAAE;YACnB,IAAI,CAAC,GAAG,CACJ,SAAS,CAAC,YAAY,IAAI,kBAAkB,CAAC;kBACvC,SAAS,CAAC;AACZ,kBAAE,WAAW,CAAC,GAAG,EAAE,CAC1B;QACL;AAEA,QAAA,OAAO,GAAI;IACf,CAAC;AACD,IAAA,GAAG,EAAE,CAAC,OAAe,KAAI;QACrB,GAAG,GAAG,OAAO;QACb,cAAc,CAAC,SAAS,CAAC;IAC7B,CAAC;;;;;"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
const isDragging = {
|
||||
x: false,
|
||||
y: false,
|
||||
};
|
||||
function isDragActive() {
|
||||
return isDragging.x || isDragging.y;
|
||||
}
|
||||
|
||||
export { isDragActive, isDragging };
|
||||
//# sourceMappingURL=is-active.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-active.mjs","sources":["../../../../../src/gestures/drag/state/is-active.ts"],"sourcesContent":["export const isDragging = {\n x: false,\n y: false,\n}\n\nexport function isDragActive() {\n return isDragging.x || isDragging.y\n}\n"],"names":[],"mappings":"AAAO,MAAM,UAAU,GAAG;AACtB,IAAA,CAAC,EAAE,KAAK;AACR,IAAA,CAAC,EAAE,KAAK;;SAGI,YAAY,GAAA;AACxB,IAAA,OAAO,UAAU,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC;AACvC;;;;"}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { isDragging } from './is-active.mjs';
|
||||
|
||||
function setDragLock(axis) {
|
||||
if (axis === "x" || axis === "y") {
|
||||
if (isDragging[axis]) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
isDragging[axis] = true;
|
||||
return () => {
|
||||
isDragging[axis] = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isDragging.x || isDragging.y) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
isDragging.x = isDragging.y = true;
|
||||
return () => {
|
||||
isDragging.x = isDragging.y = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { setDragLock };
|
||||
//# sourceMappingURL=set-active.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"set-active.mjs","sources":["../../../../../src/gestures/drag/state/set-active.ts"],"sourcesContent":["import { isDragging } from \"./is-active\"\n\nexport function setDragLock(axis: boolean | \"x\" | \"y\" | \"lockDirection\") {\n if (axis === \"x\" || axis === \"y\") {\n if (isDragging[axis]) {\n return null\n } else {\n isDragging[axis] = true\n return () => {\n isDragging[axis] = false\n }\n }\n } else {\n if (isDragging.x || isDragging.y) {\n return null\n } else {\n isDragging.x = isDragging.y = true\n return () => {\n isDragging.x = isDragging.y = false\n }\n }\n }\n}\n"],"names":[],"mappings":";;AAEM,SAAU,WAAW,CAAC,IAA2C,EAAA;IACnE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE;AAC9B,QAAA,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE;AAClB,YAAA,OAAO,IAAI;QACf;aAAO;AACH,YAAA,UAAU,CAAC,IAAI,CAAC,GAAG,IAAI;AACvB,YAAA,OAAO,MAAK;AACR,gBAAA,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK;AAC5B,YAAA,CAAC;QACL;IACJ;SAAO;QACH,IAAI,UAAU,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE;AAC9B,YAAA,OAAO,IAAI;QACf;aAAO;YACH,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG,IAAI;AAClC,YAAA,OAAO,MAAK;gBACR,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG,KAAK;AACvC,YAAA,CAAC;QACL;IACJ;AACJ;;;;"}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { isDragActive } from './drag/state/is-active.mjs';
|
||||
import { setupGesture } from './utils/setup.mjs';
|
||||
|
||||
function isValidHover(event) {
|
||||
return !(event.pointerType === "touch" || isDragActive());
|
||||
}
|
||||
/**
|
||||
* Create a hover gesture. hover() is different to .addEventListener("pointerenter")
|
||||
* in that it has an easier syntax, filters out polyfilled touch events, interoperates
|
||||
* with drag gestures, and automatically removes the "pointerennd" event listener when the hover ends.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function hover(elementOrSelector, onHoverStart, options = {}) {
|
||||
const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options);
|
||||
elements.forEach((element) => {
|
||||
let isPressed = false;
|
||||
let deferredHoverEnd = false;
|
||||
let hoverEndCallback;
|
||||
const removePointerLeave = () => {
|
||||
element.removeEventListener("pointerleave", onPointerLeave);
|
||||
};
|
||||
const endHover = (event) => {
|
||||
if (hoverEndCallback) {
|
||||
hoverEndCallback(event);
|
||||
hoverEndCallback = undefined;
|
||||
}
|
||||
removePointerLeave();
|
||||
};
|
||||
const onPointerUp = (event) => {
|
||||
isPressed = false;
|
||||
window.removeEventListener("pointerup", onPointerUp);
|
||||
window.removeEventListener("pointercancel", onPointerUp);
|
||||
if (deferredHoverEnd) {
|
||||
deferredHoverEnd = false;
|
||||
endHover(event);
|
||||
}
|
||||
};
|
||||
const onPointerDown = () => {
|
||||
isPressed = true;
|
||||
window.addEventListener("pointerup", onPointerUp, eventOptions);
|
||||
window.addEventListener("pointercancel", onPointerUp, eventOptions);
|
||||
};
|
||||
const onPointerLeave = (leaveEvent) => {
|
||||
if (leaveEvent.pointerType === "touch")
|
||||
return;
|
||||
if (isPressed) {
|
||||
deferredHoverEnd = true;
|
||||
return;
|
||||
}
|
||||
endHover(leaveEvent);
|
||||
};
|
||||
const onPointerEnter = (enterEvent) => {
|
||||
if (!isValidHover(enterEvent))
|
||||
return;
|
||||
deferredHoverEnd = false;
|
||||
const onHoverEnd = onHoverStart(element, enterEvent);
|
||||
if (typeof onHoverEnd !== "function")
|
||||
return;
|
||||
hoverEndCallback = onHoverEnd;
|
||||
element.addEventListener("pointerleave", onPointerLeave, eventOptions);
|
||||
};
|
||||
element.addEventListener("pointerenter", onPointerEnter, eventOptions);
|
||||
element.addEventListener("pointerdown", onPointerDown, eventOptions);
|
||||
});
|
||||
return cancel;
|
||||
}
|
||||
|
||||
export { hover };
|
||||
//# sourceMappingURL=hover.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+97
@@ -0,0 +1,97 @@
|
||||
import { isHTMLElement } from '../../utils/is-html-element.mjs';
|
||||
import { isDragActive } from '../drag/state/is-active.mjs';
|
||||
import { isNodeOrChild } from '../utils/is-node-or-child.mjs';
|
||||
import { isPrimaryPointer } from '../utils/is-primary-pointer.mjs';
|
||||
import { setupGesture } from '../utils/setup.mjs';
|
||||
import { isElementKeyboardAccessible } from './utils/is-keyboard-accessible.mjs';
|
||||
import { enableKeyboardPress } from './utils/keyboard.mjs';
|
||||
import { isPressing } from './utils/state.mjs';
|
||||
|
||||
/**
|
||||
* Filter out events that are not primary pointer events, or are triggering
|
||||
* while a Motion gesture is active.
|
||||
*/
|
||||
function isValidPressEvent(event) {
|
||||
return isPrimaryPointer(event) && !isDragActive();
|
||||
}
|
||||
const claimedPointerDownEvents = new WeakSet();
|
||||
/**
|
||||
* Create a press gesture.
|
||||
*
|
||||
* Press is different to `"pointerdown"`, `"pointerup"` in that it
|
||||
* automatically filters out secondary pointer events like right
|
||||
* click and multitouch.
|
||||
*
|
||||
* It also adds accessibility support for keyboards, where
|
||||
* an element with a press gesture will receive focus and
|
||||
* trigger on Enter `"keydown"` and `"keyup"` events.
|
||||
*
|
||||
* This is different to a browser's `"click"` event, which does
|
||||
* respond to keyboards but only for the `"click"` itself, rather
|
||||
* than the press start and end/cancel. The element also needs
|
||||
* to be focusable for this to work, whereas a press gesture will
|
||||
* make an element focusable by default.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function press(targetOrSelector, onPressStart, options = {}) {
|
||||
const [targets, eventOptions, cancelEvents] = setupGesture(targetOrSelector, options);
|
||||
const startPress = (startEvent) => {
|
||||
const target = startEvent.currentTarget;
|
||||
if (!isValidPressEvent(startEvent))
|
||||
return;
|
||||
if (claimedPointerDownEvents.has(startEvent))
|
||||
return;
|
||||
isPressing.add(target);
|
||||
if (options.stopPropagation) {
|
||||
claimedPointerDownEvents.add(startEvent);
|
||||
}
|
||||
const onPressEnd = onPressStart(target, startEvent);
|
||||
/**
|
||||
* End listeners run in the capture phase so a descendant calling
|
||||
* stopPropagation() in its own pointerup handler can't prevent the
|
||||
* press gesture from ending. This also keeps the gesture-end
|
||||
* ordering consistent with the drag gesture. See #2794.
|
||||
*/
|
||||
const endEventOptions = { ...eventOptions, capture: true };
|
||||
const onPointerEnd = (endEvent, success) => {
|
||||
window.removeEventListener("pointerup", onPointerUp, endEventOptions);
|
||||
window.removeEventListener("pointercancel", onPointerCancel, endEventOptions);
|
||||
if (isPressing.has(target)) {
|
||||
isPressing.delete(target);
|
||||
}
|
||||
if (!isValidPressEvent(endEvent)) {
|
||||
return;
|
||||
}
|
||||
if (typeof onPressEnd === "function") {
|
||||
onPressEnd(endEvent, { success });
|
||||
}
|
||||
};
|
||||
const onPointerUp = (upEvent) => {
|
||||
onPointerEnd(upEvent, target === window ||
|
||||
target === document ||
|
||||
options.useGlobalTarget ||
|
||||
isNodeOrChild(target, upEvent.target));
|
||||
};
|
||||
const onPointerCancel = (cancelEvent) => {
|
||||
onPointerEnd(cancelEvent, false);
|
||||
};
|
||||
window.addEventListener("pointerup", onPointerUp, endEventOptions);
|
||||
window.addEventListener("pointercancel", onPointerCancel, endEventOptions);
|
||||
};
|
||||
targets.forEach((target) => {
|
||||
const pointerDownTarget = options.useGlobalTarget ? window : target;
|
||||
pointerDownTarget.addEventListener("pointerdown", startPress, eventOptions);
|
||||
if (isHTMLElement(target)) {
|
||||
target.addEventListener("focus", (event) => enableKeyboardPress(event, eventOptions));
|
||||
if (!isElementKeyboardAccessible(target) &&
|
||||
!target.hasAttribute("tabindex")) {
|
||||
target.tabIndex = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
return cancelEvents;
|
||||
}
|
||||
|
||||
export { press };
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
const keyboardAccessibleElements = new Set([
|
||||
"BUTTON",
|
||||
"INPUT",
|
||||
"SELECT",
|
||||
"TEXTAREA",
|
||||
"A",
|
||||
]);
|
||||
/**
|
||||
* Checks if an element is natively keyboard accessible (focusable).
|
||||
* Used by the press gesture to determine if we need to add tabIndex.
|
||||
*/
|
||||
function isElementKeyboardAccessible(element) {
|
||||
return (keyboardAccessibleElements.has(element.tagName) ||
|
||||
element.isContentEditable === true);
|
||||
}
|
||||
const textInputElements = new Set(["INPUT", "SELECT", "TEXTAREA"]);
|
||||
/**
|
||||
* Checks if an element has text selection or direct interaction behavior
|
||||
* that should block drag gestures from starting.
|
||||
*
|
||||
* This specifically targets form controls where the user might want to select
|
||||
* text or interact with the control (e.g., sliders, dropdowns).
|
||||
*
|
||||
* Buttons and links are NOT included because they don't have click-and-move
|
||||
* actions of their own - they only respond to click events, so dragging
|
||||
* should still work when initiated from these elements.
|
||||
*/
|
||||
function isElementTextInput(element) {
|
||||
return (textInputElements.has(element.tagName) ||
|
||||
element.isContentEditable === true);
|
||||
}
|
||||
|
||||
export { isElementKeyboardAccessible, isElementTextInput };
|
||||
//# sourceMappingURL=is-keyboard-accessible.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-keyboard-accessible.mjs","sources":["../../../../../src/gestures/press/utils/is-keyboard-accessible.ts"],"sourcesContent":["const keyboardAccessibleElements = new Set([\n \"BUTTON\",\n \"INPUT\",\n \"SELECT\",\n \"TEXTAREA\",\n \"A\",\n])\n\n/**\n * Checks if an element is natively keyboard accessible (focusable).\n * Used by the press gesture to determine if we need to add tabIndex.\n */\nexport function isElementKeyboardAccessible(element: Element) {\n return (\n keyboardAccessibleElements.has(element.tagName) ||\n (element as HTMLElement).isContentEditable === true\n )\n}\n\nconst textInputElements = new Set([\"INPUT\", \"SELECT\", \"TEXTAREA\"])\n\n/**\n * Checks if an element has text selection or direct interaction behavior\n * that should block drag gestures from starting.\n *\n * This specifically targets form controls where the user might want to select\n * text or interact with the control (e.g., sliders, dropdowns).\n *\n * Buttons and links are NOT included because they don't have click-and-move\n * actions of their own - they only respond to click events, so dragging\n * should still work when initiated from these elements.\n */\nexport function isElementTextInput(element: Element) {\n return (\n textInputElements.has(element.tagName) ||\n (element as HTMLElement).isContentEditable === true\n )\n}\n"],"names":[],"mappings":"AAAA,MAAM,0BAA0B,GAAG,IAAI,GAAG,CAAC;IACvC,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,UAAU;IACV,GAAG;AACN,CAAA,CAAC;AAEF;;;AAGG;AACG,SAAU,2BAA2B,CAAC,OAAgB,EAAA;IACxD,QACI,0BAA0B,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;AAC9C,QAAA,OAAuB,CAAC,iBAAiB,KAAK,IAAI;AAE3D;AAEA,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;AAElE;;;;;;;;;;AAUG;AACG,SAAU,kBAAkB,CAAC,OAAgB,EAAA;IAC/C,QACI,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;AACrC,QAAA,OAAuB,CAAC,iBAAiB,KAAK,IAAI;AAE3D;;;;"}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { isPressing } from './state.mjs';
|
||||
|
||||
/**
|
||||
* Filter out events that are not "Enter" keys.
|
||||
*/
|
||||
function filterEvents(callback) {
|
||||
return (event) => {
|
||||
if (event.key !== "Enter")
|
||||
return;
|
||||
callback(event);
|
||||
};
|
||||
}
|
||||
function firePointerEvent(target, type) {
|
||||
target.dispatchEvent(new PointerEvent("pointer" + type, { isPrimary: true, bubbles: true }));
|
||||
}
|
||||
const enableKeyboardPress = (focusEvent, eventOptions) => {
|
||||
const element = focusEvent.currentTarget;
|
||||
if (!element)
|
||||
return;
|
||||
const handleKeydown = filterEvents(() => {
|
||||
if (isPressing.has(element))
|
||||
return;
|
||||
firePointerEvent(element, "down");
|
||||
const handleKeyup = filterEvents(() => {
|
||||
firePointerEvent(element, "up");
|
||||
});
|
||||
const handleBlur = () => firePointerEvent(element, "cancel");
|
||||
element.addEventListener("keyup", handleKeyup, eventOptions);
|
||||
element.addEventListener("blur", handleBlur, eventOptions);
|
||||
});
|
||||
element.addEventListener("keydown", handleKeydown, eventOptions);
|
||||
/**
|
||||
* Add an event listener that fires on blur to remove the keydown events.
|
||||
*/
|
||||
element.addEventListener("blur", () => element.removeEventListener("keydown", handleKeydown), eventOptions);
|
||||
};
|
||||
|
||||
export { enableKeyboardPress };
|
||||
//# sourceMappingURL=keyboard.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"keyboard.mjs","sources":["../../../../../src/gestures/press/utils/keyboard.ts"],"sourcesContent":["import { isPressing } from \"./state\"\n\n/**\n * Filter out events that are not \"Enter\" keys.\n */\nfunction filterEvents(callback: (event: KeyboardEvent) => void) {\n return (event: KeyboardEvent) => {\n if (event.key !== \"Enter\") return\n callback(event)\n }\n}\n\nfunction firePointerEvent(target: EventTarget, type: \"down\" | \"up\" | \"cancel\") {\n target.dispatchEvent(\n new PointerEvent(\"pointer\" + type, { isPrimary: true, bubbles: true })\n )\n}\n\nexport const enableKeyboardPress = (\n focusEvent: FocusEvent,\n eventOptions: AddEventListenerOptions\n) => {\n const element = focusEvent.currentTarget as HTMLElement\n if (!element) return\n\n const handleKeydown = filterEvents(() => {\n if (isPressing.has(element)) return\n\n firePointerEvent(element, \"down\")\n\n const handleKeyup = filterEvents(() => {\n firePointerEvent(element, \"up\")\n })\n\n const handleBlur = () => firePointerEvent(element, \"cancel\")\n\n element.addEventListener(\"keyup\", handleKeyup, eventOptions)\n element.addEventListener(\"blur\", handleBlur, eventOptions)\n })\n\n element.addEventListener(\"keydown\", handleKeydown, eventOptions)\n\n /**\n * Add an event listener that fires on blur to remove the keydown events.\n */\n element.addEventListener(\n \"blur\",\n () => element.removeEventListener(\"keydown\", handleKeydown),\n eventOptions\n )\n}\n"],"names":[],"mappings":";;AAEA;;AAEG;AACH,SAAS,YAAY,CAAC,QAAwC,EAAA;IAC1D,OAAO,CAAC,KAAoB,KAAI;AAC5B,QAAA,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO;YAAE;QAC3B,QAAQ,CAAC,KAAK,CAAC;AACnB,IAAA,CAAC;AACL;AAEA,SAAS,gBAAgB,CAAC,MAAmB,EAAE,IAA8B,EAAA;IACzE,MAAM,CAAC,aAAa,CAChB,IAAI,YAAY,CAAC,SAAS,GAAG,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CACzE;AACL;MAEa,mBAAmB,GAAG,CAC/B,UAAsB,EACtB,YAAqC,KACrC;AACA,IAAA,MAAM,OAAO,GAAG,UAAU,CAAC,aAA4B;AACvD,IAAA,IAAI,CAAC,OAAO;QAAE;AAEd,IAAA,MAAM,aAAa,GAAG,YAAY,CAAC,MAAK;AACpC,QAAA,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE;AAE7B,QAAA,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC;AAEjC,QAAA,MAAM,WAAW,GAAG,YAAY,CAAC,MAAK;AAClC,YAAA,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC;AACnC,QAAA,CAAC,CAAC;QAEF,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC;QAE5D,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,CAAC;QAC5D,OAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC;AAC9D,IAAA,CAAC,CAAC;IAEF,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,EAAE,YAAY,CAAC;AAEhE;;AAEG;AACH,IAAA,OAAO,CAAC,gBAAgB,CACpB,MAAM,EACN,MAAM,OAAO,CAAC,mBAAmB,CAAC,SAAS,EAAE,aAAa,CAAC,EAC3D,YAAY,CACf;AACL;;;;"}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
const isPressing = new WeakSet();
|
||||
|
||||
export { isPressing };
|
||||
//# sourceMappingURL=state.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"state.mjs","sources":["../../../../../src/gestures/press/utils/state.ts"],"sourcesContent":["export const isPressing = new WeakSet<EventTarget>()\n"],"names":[],"mappings":"AAAO,MAAM,UAAU,GAAG,IAAI,OAAO;;;;"}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Recursively traverse up the tree to check whether the provided child node
|
||||
* is the parent or a descendant of it.
|
||||
*
|
||||
* @param parent - Element to find
|
||||
* @param child - Element to test against parent
|
||||
*/
|
||||
const isNodeOrChild = (parent, child) => {
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
else if (parent === child) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return isNodeOrChild(parent, child.parentElement);
|
||||
}
|
||||
};
|
||||
|
||||
export { isNodeOrChild };
|
||||
//# sourceMappingURL=is-node-or-child.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-node-or-child.mjs","sources":["../../../../src/gestures/utils/is-node-or-child.ts"],"sourcesContent":["/**\n * Recursively traverse up the tree to check whether the provided child node\n * is the parent or a descendant of it.\n *\n * @param parent - Element to find\n * @param child - Element to test against parent\n */\nexport const isNodeOrChild = (\n parent: Element | null,\n child?: Element | null\n): boolean => {\n if (!child) {\n return false\n } else if (parent === child) {\n return true\n } else {\n return isNodeOrChild(parent, child.parentElement)\n }\n}\n"],"names":[],"mappings":"AAAA;;;;;;AAMG;MACU,aAAa,GAAG,CACzB,MAAsB,EACtB,KAAsB,KACb;IACT,IAAI,CAAC,KAAK,EAAE;AACR,QAAA,OAAO,KAAK;IAChB;AAAO,SAAA,IAAI,MAAM,KAAK,KAAK,EAAE;AACzB,QAAA,OAAO,IAAI;IACf;SAAO;QACH,OAAO,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC;IACrD;AACJ;;;;"}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
const isPrimaryPointer = (event) => {
|
||||
if (event.pointerType === "mouse") {
|
||||
return typeof event.button !== "number" || event.button <= 0;
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* isPrimary is true for all mice buttons, whereas every touch point
|
||||
* is regarded as its own input. So subsequent concurrent touch points
|
||||
* will be false.
|
||||
*
|
||||
* Specifically match against false here as incomplete versions of
|
||||
* PointerEvents in very old browser might have it set as undefined.
|
||||
*/
|
||||
return event.isPrimary !== false;
|
||||
}
|
||||
};
|
||||
|
||||
export { isPrimaryPointer };
|
||||
//# sourceMappingURL=is-primary-pointer.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"is-primary-pointer.mjs","sources":["../../../../src/gestures/utils/is-primary-pointer.ts"],"sourcesContent":["export const isPrimaryPointer = (event: PointerEvent) => {\n if (event.pointerType === \"mouse\") {\n return typeof event.button !== \"number\" || event.button <= 0\n } else {\n /**\n * isPrimary is true for all mice buttons, whereas every touch point\n * is regarded as its own input. So subsequent concurrent touch points\n * will be false.\n *\n * Specifically match against false here as incomplete versions of\n * PointerEvents in very old browser might have it set as undefined.\n */\n return event.isPrimary !== false\n }\n}\n"],"names":[],"mappings":"AAAO,MAAM,gBAAgB,GAAG,CAAC,KAAmB,KAAI;AACpD,IAAA,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,EAAE;AAC/B,QAAA,OAAO,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;IAChE;SAAO;AACH;;;;;;;AAOG;AACH,QAAA,OAAO,KAAK,CAAC,SAAS,KAAK,KAAK;IACpC;AACJ;;;;"}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { resolveElements } from '../../utils/resolve-elements.mjs';
|
||||
|
||||
function setupGesture(elementOrSelector, options) {
|
||||
const elements = resolveElements(elementOrSelector);
|
||||
const gestureAbortController = new AbortController();
|
||||
const eventOptions = {
|
||||
passive: true,
|
||||
...options,
|
||||
signal: gestureAbortController.signal,
|
||||
};
|
||||
const cancel = () => gestureAbortController.abort();
|
||||
return [elements, eventOptions, cancel];
|
||||
}
|
||||
|
||||
export { setupGesture };
|
||||
//# sourceMappingURL=setup.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"setup.mjs","sources":["../../../../src/gestures/utils/setup.ts"],"sourcesContent":["import {\n ElementOrSelector,\n resolveElements,\n} from \"../../utils/resolve-elements\"\nimport { EventOptions } from \"../types\"\n\nexport function setupGesture(\n elementOrSelector: ElementOrSelector,\n options: EventOptions\n): [Element[], AddEventListenerOptions, VoidFunction] {\n const elements = resolveElements(elementOrSelector)\n\n const gestureAbortController = new AbortController()\n\n const eventOptions = {\n passive: true,\n ...options,\n signal: gestureAbortController.signal,\n }\n\n const cancel = () => gestureAbortController.abort()\n\n return [elements, eventOptions, cancel]\n}\n"],"names":[],"mappings":";;AAMM,SAAU,YAAY,CACxB,iBAAoC,EACpC,OAAqB,EAAA;AAErB,IAAA,MAAM,QAAQ,GAAG,eAAe,CAAC,iBAAiB,CAAC;AAEnD,IAAA,MAAM,sBAAsB,GAAG,IAAI,eAAe,EAAE;AAEpD,IAAA,MAAM,YAAY,GAAG;AACjB,QAAA,OAAO,EAAE,IAAI;AACb,QAAA,GAAG,OAAO;QACV,MAAM,EAAE,sBAAsB,CAAC,MAAM;KACxC;IAED,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,KAAK,EAAE;AAEnD,IAAA,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,CAAC;AAC3C;;;;"}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
export { AsyncMotionValueAnimation } from './animation/AsyncMotionValueAnimation.mjs';
|
||||
export { GroupAnimation } from './animation/GroupAnimation.mjs';
|
||||
export { GroupAnimationWithThen } from './animation/GroupAnimationWithThen.mjs';
|
||||
export { JSAnimation, animateValue } from './animation/JSAnimation.mjs';
|
||||
export { NativeAnimation } from './animation/NativeAnimation.mjs';
|
||||
export { NativeAnimationExtended } from './animation/NativeAnimationExtended.mjs';
|
||||
export { NativeAnimationWrapper } from './animation/NativeAnimationWrapper.mjs';
|
||||
export { animationMapKey, getAnimationMap } from './animation/utils/active-animations.mjs';
|
||||
export { calcChildStagger } from './animation/utils/calc-child-stagger.mjs';
|
||||
export { arc } from './animation/utils/arc.mjs';
|
||||
export { getVariableValue, parseCSSVariable } from './animation/utils/css-variables-conversion.mjs';
|
||||
export { getDefaultTransition } from './animation/utils/default-transitions.mjs';
|
||||
export { getFinalKeyframe } from './animation/keyframes/get-final.mjs';
|
||||
export { getValueTransition } from './animation/utils/get-value-transition.mjs';
|
||||
export { resolveTransition } from './animation/utils/resolve-transition.mjs';
|
||||
export { containsCSSVariable, isCSSVariableName, isCSSVariableToken } from './animation/utils/is-css-variable.mjs';
|
||||
export { isTransitionDefined } from './animation/utils/is-transition-defined.mjs';
|
||||
export { makeAnimationInstant } from './animation/utils/make-animation-instant.mjs';
|
||||
export { animateMotionValue } from './animation/interfaces/motion-value.mjs';
|
||||
export { animateVisualElement } from './animation/interfaces/visual-element.mjs';
|
||||
export { animateTarget } from './animation/interfaces/visual-element-target.mjs';
|
||||
export { animateVariant } from './animation/interfaces/visual-element-variant.mjs';
|
||||
export { optimizedAppearDataAttribute, optimizedAppearDataId } from './animation/optimized-appear/data-id.mjs';
|
||||
export { getOptimisedAppearId } from './animation/optimized-appear/get-appear-id.mjs';
|
||||
export { inertia } from './animation/generators/inertia.mjs';
|
||||
export { defaultEasing, keyframes } from './animation/generators/keyframes.mjs';
|
||||
export { spring } from './animation/generators/spring.mjs';
|
||||
export { calcGeneratorDuration, maxGeneratorDuration } from './animation/generators/utils/calc-duration.mjs';
|
||||
export { createGeneratorEasing } from './animation/generators/utils/create-generator-easing.mjs';
|
||||
export { isGenerator } from './animation/generators/utils/is-generator.mjs';
|
||||
export { DOMKeyframesResolver } from './animation/keyframes/DOMKeyframesResolver.mjs';
|
||||
export { KeyframeResolver, flushKeyframeResolvers } from './animation/keyframes/KeyframesResolver.mjs';
|
||||
export { defaultOffset } from './animation/keyframes/offsets/default.mjs';
|
||||
export { fillOffset } from './animation/keyframes/offsets/fill.mjs';
|
||||
export { convertOffsetToTimes } from './animation/keyframes/offsets/time.mjs';
|
||||
export { applyPxDefaults } from './animation/keyframes/utils/apply-px-defaults.mjs';
|
||||
export { fillWildcards } from './animation/keyframes/utils/fill-wildcards.mjs';
|
||||
export { cubicBezierAsString } from './animation/waapi/easing/cubic-bezier.mjs';
|
||||
export { isWaapiSupportedEasing } from './animation/waapi/easing/is-supported.mjs';
|
||||
export { mapEasingToNativeEasing } from './animation/waapi/easing/map-easing.mjs';
|
||||
export { supportedWaapiEasing } from './animation/waapi/easing/supported.mjs';
|
||||
export { startWaapiAnimation } from './animation/waapi/start-waapi-animation.mjs';
|
||||
export { supportsPartialKeyframes } from './animation/waapi/supports/partial-keyframes.mjs';
|
||||
export { supportsBrowserAnimation } from './animation/waapi/supports/waapi.mjs';
|
||||
export { acceleratedValues } from './animation/waapi/utils/accelerated-values.mjs';
|
||||
export { applyGeneratorOptions } from './animation/waapi/utils/apply-generator.mjs';
|
||||
export { generateLinearEasing } from './animation/waapi/utils/linear.mjs';
|
||||
export { addAttrValue, attrEffect } from './effects/attr/index.mjs';
|
||||
export { propEffect } from './effects/prop/index.mjs';
|
||||
export { addStyleValue, styleEffect } from './effects/style/index.mjs';
|
||||
export { svgEffect } from './effects/svg/index.mjs';
|
||||
export { createRenderBatcher } from './frameloop/batcher.mjs';
|
||||
export { cancelMicrotask, microtask } from './frameloop/microtask.mjs';
|
||||
export { time } from './frameloop/sync-time.mjs';
|
||||
export { isDragActive, isDragging } from './gestures/drag/state/is-active.mjs';
|
||||
export { setDragLock } from './gestures/drag/state/set-active.mjs';
|
||||
export { hover } from './gestures/hover.mjs';
|
||||
export { press } from './gestures/press/index.mjs';
|
||||
export { isElementKeyboardAccessible, isElementTextInput } from './gestures/press/utils/is-keyboard-accessible.mjs';
|
||||
export { isNodeOrChild } from './gestures/utils/is-node-or-child.mjs';
|
||||
export { isPrimaryPointer } from './gestures/utils/is-primary-pointer.mjs';
|
||||
export { defaultTransformValue, parseValueFromTransform, readTransformValue } from './render/dom/parse-transform.mjs';
|
||||
export { getComputedStyle } from './render/dom/style-computed.mjs';
|
||||
export { setStyle } from './render/dom/style-set.mjs';
|
||||
export { isKeyframesTarget } from './render/utils/is-keyframes-target.mjs';
|
||||
export { positionalKeys } from './render/utils/keys-position.mjs';
|
||||
export { transformPropOrder, transformProps } from './render/utils/keys-transform.mjs';
|
||||
export { resize } from './resize/index.mjs';
|
||||
export { observeTimeline } from './scroll/observe.mjs';
|
||||
export { recordStats } from './stats/index.mjs';
|
||||
export { statsBuffer } from './stats/buffer.mjs';
|
||||
export { interpolate } from './utils/interpolate.mjs';
|
||||
export { isHTMLElement } from './utils/is-html-element.mjs';
|
||||
export { isSVGElement } from './utils/is-svg-element.mjs';
|
||||
export { isSVGSVGElement } from './utils/is-svg-svg-element.mjs';
|
||||
export { mix } from './utils/mix/index.mjs';
|
||||
export { mixColor, mixLinearColor } from './utils/mix/color.mjs';
|
||||
export { getMixer, mixArray, mixComplex, mixObject } from './utils/mix/complex.mjs';
|
||||
export { mixImmediate } from './utils/mix/immediate.mjs';
|
||||
export { mixNumber } from './utils/mix/number.mjs';
|
||||
export { invisibleValues, mixVisibility } from './utils/mix/visibility.mjs';
|
||||
export { resolveElements } from './utils/resolve-elements.mjs';
|
||||
export { getOriginIndex, stagger } from './utils/stagger.mjs';
|
||||
export { supportsFlags } from './utils/supports/flags.mjs';
|
||||
export { supportsLinearEasing } from './utils/supports/linear-easing.mjs';
|
||||
export { supportsScrollTimeline, supportsViewTimeline } from './utils/supports/scroll-timeline.mjs';
|
||||
export { transform } from './utils/transform.mjs';
|
||||
export { MotionValue, collectMotionValues, motionValue } from './value/index.mjs';
|
||||
export { attachFollow, followValue } from './value/follow-value.mjs';
|
||||
export { mapValue } from './value/map-value.mjs';
|
||||
export { attachSpring, springValue } from './value/spring-value.mjs';
|
||||
export { transformValue } from './value/transform-value.mjs';
|
||||
export { color } from './value/types/color/index.mjs';
|
||||
export { hex } from './value/types/color/hex.mjs';
|
||||
export { hsla } from './value/types/color/hsla.mjs';
|
||||
export { hslaToRgba } from './value/types/color/hsla-to-rgba.mjs';
|
||||
export { rgbUnit, rgba } from './value/types/color/rgba.mjs';
|
||||
export { analyseComplexValue, complex } from './value/types/complex/index.mjs';
|
||||
export { dimensionValueTypes, findDimensionValueType } from './value/types/dimensions.mjs';
|
||||
export { defaultValueTypes, getDefaultValueType } from './value/types/maps/defaults.mjs';
|
||||
export { numberValueTypes } from './value/types/maps/number.mjs';
|
||||
export { transformValueTypes } from './value/types/maps/transform.mjs';
|
||||
export { alpha, number, scale } from './value/types/numbers/index.mjs';
|
||||
export { degrees, percent, progressPercentage, px, vh, vw } from './value/types/numbers/units.mjs';
|
||||
export { testValueType } from './value/types/test.mjs';
|
||||
export { getAnimatableNone } from './value/types/utils/animatable-none.mjs';
|
||||
export { findValueType } from './value/types/utils/find.mjs';
|
||||
export { getValueAsType } from './value/types/utils/get-as-type.mjs';
|
||||
export { isMotionValue } from './value/utils/is-motion-value.mjs';
|
||||
export { addValueToWillChange } from './value/will-change/add-will-change.mjs';
|
||||
export { isWillChangeMotionValue } from './value/will-change/is.mjs';
|
||||
export { ViewTransitionBuilder, animateView } from './view/index.mjs';
|
||||
export { getViewAnimationLayerInfo } from './view/utils/get-layer-info.mjs';
|
||||
export { getViewAnimations } from './view/utils/get-view-animations.mjs';
|
||||
export { DOMVisualElement } from './render/dom/DOMVisualElement.mjs';
|
||||
export { Feature } from './render/Feature.mjs';
|
||||
export { HTMLVisualElement } from './render/html/HTMLVisualElement.mjs';
|
||||
export { ObjectVisualElement } from './render/object/ObjectVisualElement.mjs';
|
||||
export { visualElementStore } from './render/store.mjs';
|
||||
export { SVGVisualElement } from './render/svg/SVGVisualElement.mjs';
|
||||
export { VisualElement, getFeatureDefinitions, setFeatureDefinitions } from './render/VisualElement.mjs';
|
||||
export { checkVariantsDidChange, createAnimationState } from './render/utils/animation-state.mjs';
|
||||
export { getVariantContext } from './render/utils/get-variant-context.mjs';
|
||||
export { isAnimationControls } from './render/utils/is-animation-controls.mjs';
|
||||
export { isControllingVariants, isVariantNode } from './render/utils/is-controlling-variants.mjs';
|
||||
export { isForcedMotionValue } from './render/utils/is-forced-motion-value.mjs';
|
||||
export { isVariantLabel } from './render/utils/is-variant-label.mjs';
|
||||
export { updateMotionValuesFromProps } from './render/utils/motion-values.mjs';
|
||||
export { resolveVariant } from './render/utils/resolve-dynamic-variants.mjs';
|
||||
export { resolveVariantFromProps } from './render/utils/resolve-variants.mjs';
|
||||
export { setTarget } from './render/utils/setters.mjs';
|
||||
export { variantPriorityOrder, variantProps } from './render/utils/variant-props.mjs';
|
||||
export { initPrefersReducedMotion } from './render/utils/reduced-motion/index.mjs';
|
||||
export { convertBoundingBoxToBox, convertBoxToBoundingBox, transformBoxPoints } from './projection/geometry/conversion.mjs';
|
||||
export { copyAxisDeltaInto, copyAxisInto, copyBoxInto } from './projection/geometry/copy.mjs';
|
||||
export { applyAxisDelta, applyBoxDelta, applyPointDelta, applyTreeDeltas, scalePoint, transformAxis, transformBox, translateAxis } from './projection/geometry/delta-apply.mjs';
|
||||
export { calcAxisDelta, calcBoxDelta, calcLength, calcRelativeAxis, calcRelativeAxisPosition, calcRelativeBox, calcRelativePosition, isNear } from './projection/geometry/delta-calc.mjs';
|
||||
export { removeAxisDelta, removeAxisTransforms, removeBoxTransforms, removePointDelta } from './projection/geometry/delta-remove.mjs';
|
||||
export { createAxis, createAxisDelta, createBox, createDelta } from './projection/geometry/models.mjs';
|
||||
export { aspectRatio, axisDeltaEquals, axisEquals, axisEqualsRounded, boxEquals, boxEqualsRounded, isDeltaZero } from './projection/geometry/utils.mjs';
|
||||
export { eachAxis } from './projection/utils/each-axis.mjs';
|
||||
export { has2DTranslate, hasScale, hasTransform } from './projection/utils/has-transform.mjs';
|
||||
export { measurePageBox, measureViewportBox } from './projection/utils/measure.mjs';
|
||||
export { correctBorderRadius, pixelsToPercent } from './projection/styles/scale-border-radius.mjs';
|
||||
export { correctBoxShadow } from './projection/styles/scale-box-shadow.mjs';
|
||||
export { buildProjectionTransform } from './projection/styles/transform.mjs';
|
||||
export { mixValues } from './projection/animation/mix-values.mjs';
|
||||
export { animateSingleValue } from './animation/animate/single-value.mjs';
|
||||
export { addDomEvent } from './events/add-dom-event.mjs';
|
||||
export { compareByDepth } from './projection/utils/compare-by-depth.mjs';
|
||||
export { FlatTree } from './projection/utils/flat-tree.mjs';
|
||||
export { delay, delayInSeconds } from './utils/delay.mjs';
|
||||
export { resolveMotionValue } from './value/utils/resolve-motion-value.mjs';
|
||||
export { cleanDirtyNodes, createProjectionNode, propagateDirtyNodes } from './projection/node/create-projection-node.mjs';
|
||||
export { DocumentProjectionNode } from './projection/node/DocumentProjectionNode.mjs';
|
||||
export { nodeGroup } from './projection/node/group.mjs';
|
||||
export { HTMLProjectionNode, rootProjectionNode } from './projection/node/HTMLProjectionNode.mjs';
|
||||
export { globalProjectionState } from './projection/node/state.mjs';
|
||||
export { NodeStack } from './projection/shared/stack.mjs';
|
||||
export { camelToDash } from './render/dom/utils/camel-to-dash.mjs';
|
||||
export { buildHTMLStyles } from './render/html/utils/build-styles.mjs';
|
||||
export { buildTransform } from './render/html/utils/build-transform.mjs';
|
||||
export { renderHTML } from './render/html/utils/render.mjs';
|
||||
export { scrapeMotionValuesFromProps as scrapeHTMLMotionValuesFromProps } from './render/html/utils/scrape-motion-values.mjs';
|
||||
export { buildSVGAttrs } from './render/svg/utils/build-attrs.mjs';
|
||||
export { camelCaseAttributes } from './render/svg/utils/camel-case-attrs.mjs';
|
||||
export { isSVGTag } from './render/svg/utils/is-svg-tag.mjs';
|
||||
export { buildSVGPath } from './render/svg/utils/path.mjs';
|
||||
export { renderSVG } from './render/svg/utils/render.mjs';
|
||||
export { scrapeMotionValuesFromProps as scrapeSVGMotionValuesFromProps } from './render/svg/utils/scrape-motion-values.mjs';
|
||||
export { LayoutAnimationBuilder, parseAnimateLayoutArgs } from './layout/LayoutAnimationBuilder.mjs';
|
||||
export { cancelSync, sync } from './frameloop/index-legacy.mjs';
|
||||
export { addScaleCorrector, scaleCorrectors } from './projection/styles/scale-correction.mjs';
|
||||
export { cancelFrame, frame, frameData, frameSteps } from './frameloop/frame.mjs';
|
||||
export { hasReducedMotionListener, prefersReducedMotion } from './render/utils/reduced-motion/state.mjs';
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
import { clamp } from 'motion-utils';
|
||||
import { GroupAnimation } from '../animation/GroupAnimation.mjs';
|
||||
import { microtask } from '../frameloop/microtask.mjs';
|
||||
import { time } from '../frameloop/sync-time.mjs';
|
||||
import { HTMLProjectionNode } from '../projection/node/HTMLProjectionNode.mjs';
|
||||
import { HTMLVisualElement } from '../render/html/HTMLVisualElement.mjs';
|
||||
import { visualElementStore } from '../render/store.mjs';
|
||||
import { hasTransform } from '../projection/utils/has-transform.mjs';
|
||||
import { resolveElements } from '../utils/resolve-elements.mjs';
|
||||
import { frameData, frameSteps } from '../frameloop/frame.mjs';
|
||||
|
||||
const layoutSelector = "[data-layout],[data-layout-id]";
|
||||
/**
|
||||
* All imperatively-created projection nodes live in one persistent tree,
|
||||
* shared across animateLayout() calls (and with any React-created nodes,
|
||||
* via the singleton document root). Keyed by element for reuse.
|
||||
*/
|
||||
const layoutNodes = new WeakMap();
|
||||
/**
|
||||
* Builders created within the same synchronous tick are flushed together
|
||||
* as a single "commit": every node is snapshotted before any updateDom
|
||||
* runs, mirroring React batching renders from different parts of the tree.
|
||||
*/
|
||||
let pendingBuilders;
|
||||
function collectLayoutElements(scope) {
|
||||
const elements = [];
|
||||
if (scope instanceof HTMLElement && scope.matches(layoutSelector)) {
|
||||
elements.push(scope);
|
||||
}
|
||||
scope.querySelectorAll(layoutSelector).forEach((element) => {
|
||||
if (element instanceof HTMLElement)
|
||||
elements.push(element);
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
/**
|
||||
* Process any work scheduled on the frameloop now. A previous animation
|
||||
* may have been seeked while paused (controls.time = x) without a frame
|
||||
* having rendered it - we must materialise that state into the DOM
|
||||
* before taking snapshots.
|
||||
*/
|
||||
function flushPendingFrame() {
|
||||
if (frameData.isProcessing)
|
||||
return;
|
||||
const now = time.now();
|
||||
frameData.delta = clamp(0, 1000 / 60, now - frameData.timestamp);
|
||||
frameData.timestamp = now;
|
||||
frameData.isProcessing = true;
|
||||
frameSteps.update.process(frameData);
|
||||
frameSteps.preRender.process(frameData);
|
||||
frameSteps.render.process(frameData);
|
||||
frameData.isProcessing = false;
|
||||
}
|
||||
function getProjectionParent(element) {
|
||||
let ancestor = element.parentElement;
|
||||
while (ancestor) {
|
||||
const node = layoutNodes.get(ancestor);
|
||||
if (node && node.instance)
|
||||
return node;
|
||||
ancestor = ancestor.parentElement;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function createVisualElement() {
|
||||
return new HTMLVisualElement({
|
||||
props: {},
|
||||
presenceContext: null,
|
||||
visualState: {
|
||||
latestValues: {},
|
||||
renderState: {
|
||||
transform: {},
|
||||
transformOrigin: {},
|
||||
style: {},
|
||||
vars: {},
|
||||
},
|
||||
},
|
||||
}, { allowProjection: true });
|
||||
}
|
||||
function readNodeOptions(element, transition) {
|
||||
const layoutAttr = element.getAttribute("data-layout");
|
||||
const layoutId = element.getAttribute("data-layout-id") ?? undefined;
|
||||
return {
|
||||
layoutId,
|
||||
layout: layoutAttr !== null ? true : undefined,
|
||||
animationType: (!layoutAttr || layoutAttr === "true"
|
||||
? "both"
|
||||
: layoutAttr),
|
||||
transition,
|
||||
};
|
||||
}
|
||||
function prepareNode(element, transition) {
|
||||
let node = layoutNodes.get(element);
|
||||
if (!node) {
|
||||
let visualElement = visualElementStore.get(element);
|
||||
if (!visualElement)
|
||||
visualElement = createVisualElement();
|
||||
/**
|
||||
* A first-time element may carry a projection transform in its
|
||||
* inline style (e.g. it was cloned from an element mid-animation).
|
||||
* That transform isn't tracked in latestValues so the engine can't
|
||||
* reset it before measuring - clear it now so the first layout
|
||||
* measurement isn't inflated.
|
||||
*/
|
||||
if (element.style.transform &&
|
||||
!hasTransform(visualElement.latestValues)) {
|
||||
element.style.transform = "";
|
||||
}
|
||||
node = new HTMLProjectionNode(visualElement.latestValues, getProjectionParent(element));
|
||||
visualElement.projection = node;
|
||||
node.setOptions({
|
||||
...readNodeOptions(element, transition),
|
||||
visualElement,
|
||||
});
|
||||
node.mount(element);
|
||||
layoutNodes.set(element, node);
|
||||
}
|
||||
else {
|
||||
node.setOptions(readNodeOptions(element, transition));
|
||||
}
|
||||
node.isPresent = true;
|
||||
if (node.options.onExitComplete) {
|
||||
node.setOptions({ onExitComplete: undefined });
|
||||
}
|
||||
return node;
|
||||
}
|
||||
function sortDocumentOrder(elements) {
|
||||
return [...elements].sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
|
||||
}
|
||||
function dropNode(element, node) {
|
||||
node.setOptions({ onExitComplete: undefined });
|
||||
/**
|
||||
* Stop any lingering animation so it can't leak into future updates.
|
||||
* A follow node can share its currentAnimation with a surviving lead
|
||||
* (via resumingFrom), in which case it isn't ours to stop.
|
||||
*/
|
||||
const stack = node.getStack();
|
||||
if (!stack || node.isLead())
|
||||
node.currentAnimation?.stop();
|
||||
node.unmount();
|
||||
layoutNodes.delete(element);
|
||||
}
|
||||
function flushPendingBuilders() {
|
||||
const builders = pendingBuilders;
|
||||
pendingBuilders = undefined;
|
||||
flushPendingFrame();
|
||||
/**
|
||||
* Discover and mount every node across all builders before snapshotting
|
||||
* any of them. Mounting during an active update flags isLayoutDirty,
|
||||
* which would make that node's own willUpdate skip its snapshot.
|
||||
* Document order guarantees ancestors mount before descendants, even
|
||||
* when they're discovered by different builders.
|
||||
*/
|
||||
const targets = new Map();
|
||||
for (const builder of builders) {
|
||||
for (const element of builder.collectTargets()) {
|
||||
const owners = targets.get(element);
|
||||
owners ? owners.push(builder) : targets.set(element, [builder]);
|
||||
}
|
||||
}
|
||||
const union = new Map();
|
||||
for (const element of sortDocumentOrder(targets.keys())) {
|
||||
const owners = targets.get(element);
|
||||
const node = prepareNode(element, owners[owners.length - 1].transitionFor(element));
|
||||
for (const owner of owners)
|
||||
owner.adopt(element, node);
|
||||
union.set(element, node);
|
||||
}
|
||||
union.forEach((node) => {
|
||||
node.isLayoutDirty = false;
|
||||
node.willUpdate();
|
||||
});
|
||||
const updatePromises = [];
|
||||
for (const builder of builders) {
|
||||
const result = builder.runUpdate();
|
||||
if (result)
|
||||
updatePromises.push(result);
|
||||
}
|
||||
const commit = () => {
|
||||
/**
|
||||
* Process all additions before any removals so that, even across
|
||||
* builders, a removed member knows whether a replacement with the
|
||||
* same layoutId was added in this commit.
|
||||
*/
|
||||
const newMemberIds = new Set();
|
||||
for (const builder of builders) {
|
||||
builder.reconcileAdditions(newMemberIds);
|
||||
}
|
||||
for (const builder of builders) {
|
||||
builder.reconcileRemovals(newMemberIds);
|
||||
}
|
||||
let root;
|
||||
union.forEach((node) => (root || (root = node.root)));
|
||||
for (const builder of builders)
|
||||
root || (root = builder.getRoot());
|
||||
root?.didUpdate();
|
||||
/**
|
||||
* The root flushes the update on a microtask, synchronously
|
||||
* processing the frame that creates the layout animations. Collect
|
||||
* them in a later microtask step of the same pass.
|
||||
*/
|
||||
microtask.render(() => {
|
||||
for (const builder of builders)
|
||||
builder.finalize();
|
||||
});
|
||||
};
|
||||
updatePromises.length ? Promise.all(updatePromises).then(commit) : commit();
|
||||
}
|
||||
class LayoutAnimationBuilder {
|
||||
constructor(scope, updateDom, defaultOptions) {
|
||||
this.scope = scope;
|
||||
this.updateDom = updateDom;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.sharedTransitions = new Map();
|
||||
this.notifyReady = () => { };
|
||||
this.rejectReady = () => { };
|
||||
this.tracked = new Map();
|
||||
this.restorePoints = new Map();
|
||||
this.readyPromise = new Promise((resolve, reject) => {
|
||||
this.notifyReady = resolve;
|
||||
this.rejectReady = reject;
|
||||
});
|
||||
if (!pendingBuilders) {
|
||||
pendingBuilders = [];
|
||||
queueMicrotask(flushPendingBuilders);
|
||||
}
|
||||
pendingBuilders.push(this);
|
||||
}
|
||||
shared(id, transition) {
|
||||
this.sharedTransitions.set(id, transition);
|
||||
return this;
|
||||
}
|
||||
then(resolve, reject) {
|
||||
return this.readyPromise.then(resolve, reject);
|
||||
}
|
||||
transitionFor(element) {
|
||||
const layoutId = element.getAttribute("data-layout-id");
|
||||
return ((layoutId && this.sharedTransitions.get(layoutId)) ||
|
||||
this.defaultOptions);
|
||||
}
|
||||
adopt(element, node) {
|
||||
this.tracked.set(element, node);
|
||||
this.restorePoints.set(element, {
|
||||
parent: element.parentElement,
|
||||
next: element.nextSibling,
|
||||
});
|
||||
}
|
||||
collectTargets() {
|
||||
return collectLayoutElements(this.scope);
|
||||
}
|
||||
runUpdate() {
|
||||
try {
|
||||
const result = this.updateDom();
|
||||
if (result && typeof result.then === "function") {
|
||||
return result.then(undefined, (error) => {
|
||||
this.updateError = error;
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.updateError = error;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
reconcileAdditions(newMemberIds) {
|
||||
for (const element of collectLayoutElements(this.scope)) {
|
||||
if (this.tracked.has(element))
|
||||
continue;
|
||||
const node = prepareNode(element, this.transitionFor(element));
|
||||
this.adopt(element, node);
|
||||
node.options.layoutId && newMemberIds.add(node.options.layoutId);
|
||||
}
|
||||
}
|
||||
reconcileRemovals(newMemberIds) {
|
||||
this.tracked.forEach((node, element) => {
|
||||
if (element.isConnected)
|
||||
return;
|
||||
const restore = this.restorePoints.get(element);
|
||||
this.restorePoints.delete(element);
|
||||
const { layoutId } = node.options;
|
||||
const stack = node.getStack();
|
||||
const hasSurvivor = stack &&
|
||||
stack.members.some((member) => member !== node &&
|
||||
member.instance
|
||||
?.isConnected);
|
||||
/**
|
||||
* A removed lead with a surviving stack member - and no
|
||||
* replacement member added this commit - runs an exit
|
||||
* crossfade: restore the element to its old position in the
|
||||
* DOM, relegate it and let the survivor take over. It's
|
||||
* removed again once the animation completes.
|
||||
*/
|
||||
if (layoutId &&
|
||||
node.isLead() &&
|
||||
hasSurvivor &&
|
||||
!newMemberIds.has(layoutId)) {
|
||||
if (restore && restore.parent.isConnected) {
|
||||
restore.parent.insertBefore(element, restore.next && restore.next.parentNode === restore.parent
|
||||
? restore.next
|
||||
: null);
|
||||
node.isPresent = false;
|
||||
node.setOptions({
|
||||
onExitComplete: () => {
|
||||
element.remove();
|
||||
dropNode(element, node);
|
||||
},
|
||||
});
|
||||
if (node.relegate())
|
||||
return;
|
||||
element.remove();
|
||||
}
|
||||
}
|
||||
dropNode(element, node);
|
||||
this.tracked.delete(element);
|
||||
});
|
||||
}
|
||||
getRoot() {
|
||||
let root;
|
||||
this.tracked.forEach((node) => (root || (root = node.root)));
|
||||
return root;
|
||||
}
|
||||
finalize() {
|
||||
if (this.updateError) {
|
||||
this.rejectReady(this.updateError);
|
||||
return;
|
||||
}
|
||||
const animations = new Set();
|
||||
this.tracked.forEach((node) => {
|
||||
if (node.instance && node.currentAnimation) {
|
||||
animations.add(node.currentAnimation);
|
||||
}
|
||||
});
|
||||
this.notifyReady(new GroupAnimation([...animations]));
|
||||
}
|
||||
}
|
||||
function parseAnimateLayoutArgs(scopeOrUpdateDom, updateDomOrOptions, options) {
|
||||
if (typeof scopeOrUpdateDom === "function") {
|
||||
return {
|
||||
scope: document,
|
||||
updateDom: scopeOrUpdateDom,
|
||||
defaultOptions: updateDomOrOptions,
|
||||
};
|
||||
}
|
||||
const scope = scopeOrUpdateDom instanceof Document
|
||||
? scopeOrUpdateDom
|
||||
: resolveElements(scopeOrUpdateDom)[0] ?? document;
|
||||
return {
|
||||
scope,
|
||||
updateDom: updateDomOrOptions,
|
||||
defaultOptions: options,
|
||||
};
|
||||
}
|
||||
|
||||
export { LayoutAnimationBuilder, parseAnimateLayoutArgs };
|
||||
//# sourceMappingURL=LayoutAnimationBuilder.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+67
@@ -0,0 +1,67 @@
|
||||
import { mixNumber } from '../../utils/mix/number.mjs';
|
||||
import { percent, px } from '../../value/types/numbers/units.mjs';
|
||||
import { progress, circOut, noop } from 'motion-utils';
|
||||
import { cornerRadiusProps } from '../../utils/border-radius.mjs';
|
||||
|
||||
const numBorders = cornerRadiusProps.length;
|
||||
const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
|
||||
const isPx = (value) => typeof value === "number" || px.test(value);
|
||||
function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
|
||||
if (shouldCrossfadeOpacity) {
|
||||
target.opacity = mixNumber(0, lead.opacity ?? 1, easeCrossfadeIn(progress));
|
||||
target.opacityExit = mixNumber(follow.opacity ?? 1, 0, easeCrossfadeOut(progress));
|
||||
}
|
||||
else if (isOnlyMember) {
|
||||
target.opacity = mixNumber(follow.opacity ?? 1, lead.opacity ?? 1, progress);
|
||||
}
|
||||
/**
|
||||
* Mix border radius
|
||||
*/
|
||||
for (let i = 0; i < numBorders; i++) {
|
||||
const borderLabel = cornerRadiusProps[i];
|
||||
let followRadius = getRadius(follow, borderLabel);
|
||||
let leadRadius = getRadius(lead, borderLabel);
|
||||
if (followRadius === undefined && leadRadius === undefined)
|
||||
continue;
|
||||
followRadius || (followRadius = 0);
|
||||
leadRadius || (leadRadius = 0);
|
||||
const canMix = followRadius === 0 ||
|
||||
leadRadius === 0 ||
|
||||
isPx(followRadius) === isPx(leadRadius);
|
||||
if (canMix) {
|
||||
target[borderLabel] = Math.max(mixNumber(asNumber(followRadius), asNumber(leadRadius), progress), 0);
|
||||
if (percent.test(leadRadius) || percent.test(followRadius)) {
|
||||
target[borderLabel] += "%";
|
||||
}
|
||||
}
|
||||
else {
|
||||
target[borderLabel] = leadRadius;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Mix rotation
|
||||
*/
|
||||
if (follow.rotate || lead.rotate) {
|
||||
target.rotate = mixNumber(follow.rotate || 0, lead.rotate || 0, progress);
|
||||
}
|
||||
}
|
||||
function getRadius(values, radiusName) {
|
||||
return values[radiusName] !== undefined
|
||||
? values[radiusName]
|
||||
: values.borderRadius;
|
||||
}
|
||||
const easeCrossfadeIn = /*@__PURE__*/ compress(0, 0.5, circOut);
|
||||
const easeCrossfadeOut = /*@__PURE__*/ compress(0.5, 0.95, noop);
|
||||
function compress(min, max, easing) {
|
||||
return (p) => {
|
||||
// Could replace ifs with clamp
|
||||
if (p < min)
|
||||
return 0;
|
||||
if (p > max)
|
||||
return 1;
|
||||
return easing(progress(min, max, p));
|
||||
};
|
||||
}
|
||||
|
||||
export { mixValues };
|
||||
//# sourceMappingURL=mix-values.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Bounding boxes tend to be defined as top, left, right, bottom. For various operations
|
||||
* it's easier to consider each axis individually. This function returns a bounding box
|
||||
* as a map of single-axis min/max values.
|
||||
*/
|
||||
function convertBoundingBoxToBox({ top, left, right, bottom, }) {
|
||||
return {
|
||||
x: { min: left, max: right },
|
||||
y: { min: top, max: bottom },
|
||||
};
|
||||
}
|
||||
function convertBoxToBoundingBox({ x, y }) {
|
||||
return { top: y.min, right: x.max, bottom: y.max, left: x.min };
|
||||
}
|
||||
/**
|
||||
* Applies a TransformPoint function to a bounding box. TransformPoint is usually a function
|
||||
* provided by Framer to allow measured points to be corrected for device scaling. This is used
|
||||
* when measuring DOM elements and DOM event points.
|
||||
*/
|
||||
function transformBoxPoints(point, transformPoint) {
|
||||
if (!transformPoint)
|
||||
return point;
|
||||
const topLeft = transformPoint({ x: point.left, y: point.top });
|
||||
const bottomRight = transformPoint({ x: point.right, y: point.bottom });
|
||||
return {
|
||||
top: topLeft.y,
|
||||
left: topLeft.x,
|
||||
bottom: bottomRight.y,
|
||||
right: bottomRight.x,
|
||||
};
|
||||
}
|
||||
|
||||
export { convertBoundingBoxToBox, convertBoxToBoundingBox, transformBoxPoints };
|
||||
//# sourceMappingURL=conversion.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"conversion.mjs","sources":["../../../../src/projection/geometry/conversion.ts"],"sourcesContent":["import { BoundingBox, Box, TransformPoint } from \"motion-utils\"\n\n/**\n * Bounding boxes tend to be defined as top, left, right, bottom. For various operations\n * it's easier to consider each axis individually. This function returns a bounding box\n * as a map of single-axis min/max values.\n */\nexport function convertBoundingBoxToBox({\n top,\n left,\n right,\n bottom,\n}: BoundingBox): Box {\n return {\n x: { min: left, max: right },\n y: { min: top, max: bottom },\n }\n}\n\nexport function convertBoxToBoundingBox({ x, y }: Box): BoundingBox {\n return { top: y.min, right: x.max, bottom: y.max, left: x.min }\n}\n\n/**\n * Applies a TransformPoint function to a bounding box. TransformPoint is usually a function\n * provided by Framer to allow measured points to be corrected for device scaling. This is used\n * when measuring DOM elements and DOM event points.\n */\nexport function transformBoxPoints(\n point: BoundingBox,\n transformPoint?: TransformPoint\n) {\n if (!transformPoint) return point\n const topLeft = transformPoint({ x: point.left, y: point.top })\n const bottomRight = transformPoint({ x: point.right, y: point.bottom })\n\n return {\n top: topLeft.y,\n left: topLeft.x,\n bottom: bottomRight.y,\n right: bottomRight.x,\n }\n}\n"],"names":[],"mappings":"AAEA;;;;AAIG;AACG,SAAU,uBAAuB,CAAC,EACpC,GAAG,EACH,IAAI,EACJ,KAAK,EACL,MAAM,GACI,EAAA;IACV,OAAO;QACH,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE;QAC5B,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE;KAC/B;AACL;SAEgB,uBAAuB,CAAC,EAAE,CAAC,EAAE,CAAC,EAAO,EAAA;IACjD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE;AACnE;AAEA;;;;AAIG;AACG,SAAU,kBAAkB,CAC9B,KAAkB,EAClB,cAA+B,EAAA;AAE/B,IAAA,IAAI,CAAC,cAAc;AAAE,QAAA,OAAO,KAAK;AACjC,IAAA,MAAM,OAAO,GAAG,cAAc,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;AAC/D,IAAA,MAAM,WAAW,GAAG,cAAc,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAEvE,OAAO;QACH,GAAG,EAAE,OAAO,CAAC,CAAC;QACd,IAAI,EAAE,OAAO,CAAC,CAAC;QACf,MAAM,EAAE,WAAW,CAAC,CAAC;QACrB,KAAK,EAAE,WAAW,CAAC,CAAC;KACvB;AACL;;;;"}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Reset an axis to the provided origin box.
|
||||
*
|
||||
* This is a mutative operation.
|
||||
*/
|
||||
function copyAxisInto(axis, originAxis) {
|
||||
axis.min = originAxis.min;
|
||||
axis.max = originAxis.max;
|
||||
}
|
||||
/**
|
||||
* Reset a box to the provided origin box.
|
||||
*
|
||||
* This is a mutative operation.
|
||||
*/
|
||||
function copyBoxInto(box, originBox) {
|
||||
copyAxisInto(box.x, originBox.x);
|
||||
copyAxisInto(box.y, originBox.y);
|
||||
}
|
||||
/**
|
||||
* Reset a delta to the provided origin box.
|
||||
*
|
||||
* This is a mutative operation.
|
||||
*/
|
||||
function copyAxisDeltaInto(delta, originDelta) {
|
||||
delta.translate = originDelta.translate;
|
||||
delta.scale = originDelta.scale;
|
||||
delta.originPoint = originDelta.originPoint;
|
||||
delta.origin = originDelta.origin;
|
||||
}
|
||||
|
||||
export { copyAxisDeltaInto, copyAxisInto, copyBoxInto };
|
||||
//# sourceMappingURL=copy.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"copy.mjs","sources":["../../../../src/projection/geometry/copy.ts"],"sourcesContent":["import { Axis, AxisDelta, Box } from \"motion-utils\"\n\n/**\n * Reset an axis to the provided origin box.\n *\n * This is a mutative operation.\n */\nexport function copyAxisInto(axis: Axis, originAxis: Axis) {\n axis.min = originAxis.min\n axis.max = originAxis.max\n}\n\n/**\n * Reset a box to the provided origin box.\n *\n * This is a mutative operation.\n */\nexport function copyBoxInto(box: Box, originBox: Box) {\n copyAxisInto(box.x, originBox.x)\n copyAxisInto(box.y, originBox.y)\n}\n\n/**\n * Reset a delta to the provided origin box.\n *\n * This is a mutative operation.\n */\nexport function copyAxisDeltaInto(delta: AxisDelta, originDelta: AxisDelta) {\n delta.translate = originDelta.translate\n delta.scale = originDelta.scale\n delta.originPoint = originDelta.originPoint\n delta.origin = originDelta.origin\n}\n"],"names":[],"mappings":"AAEA;;;;AAIG;AACG,SAAU,YAAY,CAAC,IAAU,EAAE,UAAgB,EAAA;AACrD,IAAA,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,GAAG;AACzB,IAAA,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,GAAG;AAC7B;AAEA;;;;AAIG;AACG,SAAU,WAAW,CAAC,GAAQ,EAAE,SAAc,EAAA;IAChD,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IAChC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;AACpC;AAEA;;;;AAIG;AACG,SAAU,iBAAiB,CAAC,KAAgB,EAAE,WAAsB,EAAA;AACtE,IAAA,KAAK,CAAC,SAAS,GAAG,WAAW,CAAC,SAAS;AACvC,IAAA,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC,KAAK;AAC/B,IAAA,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC,WAAW;AAC3C,IAAA,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM;AACrC;;;;"}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import { mixNumber } from '../../utils/mix/number.mjs';
|
||||
import { hasTransform } from '../utils/has-transform.mjs';
|
||||
|
||||
/**
|
||||
* Scales a point based on a factor and an originPoint
|
||||
*/
|
||||
function scalePoint(point, scale, originPoint) {
|
||||
const distanceFromOrigin = point - originPoint;
|
||||
const scaled = scale * distanceFromOrigin;
|
||||
return originPoint + scaled;
|
||||
}
|
||||
/**
|
||||
* Applies a translate/scale delta to a point
|
||||
*/
|
||||
function applyPointDelta(point, translate, scale, originPoint, boxScale) {
|
||||
if (boxScale !== undefined) {
|
||||
point = scalePoint(point, boxScale, originPoint);
|
||||
}
|
||||
return scalePoint(point, scale, originPoint) + translate;
|
||||
}
|
||||
/**
|
||||
* Applies a translate/scale delta to an axis
|
||||
*/
|
||||
function applyAxisDelta(axis, translate = 0, scale = 1, originPoint, boxScale) {
|
||||
axis.min = applyPointDelta(axis.min, translate, scale, originPoint, boxScale);
|
||||
axis.max = applyPointDelta(axis.max, translate, scale, originPoint, boxScale);
|
||||
}
|
||||
/**
|
||||
* Applies a translate/scale delta to a box
|
||||
*/
|
||||
function applyBoxDelta(box, { x, y }) {
|
||||
applyAxisDelta(box.x, x.translate, x.scale, x.originPoint);
|
||||
applyAxisDelta(box.y, y.translate, y.scale, y.originPoint);
|
||||
}
|
||||
const TREE_SCALE_SNAP_MIN = 0.999999999999;
|
||||
const TREE_SCALE_SNAP_MAX = 1.0000000000001;
|
||||
/**
|
||||
* Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms
|
||||
* in a tree upon our box before then calculating how to project it into our desired viewport-relative box
|
||||
*
|
||||
* This is the final nested loop within updateLayoutDelta for future refactoring
|
||||
*/
|
||||
function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) {
|
||||
const treeLength = treePath.length;
|
||||
if (!treeLength)
|
||||
return;
|
||||
// Reset the treeScale
|
||||
treeScale.x = treeScale.y = 1;
|
||||
let node;
|
||||
let delta;
|
||||
for (let i = 0; i < treeLength; i++) {
|
||||
node = treePath[i];
|
||||
delta = node.projectionDelta;
|
||||
/**
|
||||
* TODO: Prefer to remove this, but currently we have motion components with
|
||||
* display: contents in Framer.
|
||||
*/
|
||||
const { visualElement } = node.options;
|
||||
if (visualElement &&
|
||||
visualElement.props.style &&
|
||||
visualElement.props.style.display === "contents") {
|
||||
continue;
|
||||
}
|
||||
if (isSharedTransition &&
|
||||
node.options.layoutScroll &&
|
||||
node.scroll &&
|
||||
node !== node.root) {
|
||||
translateAxis(box.x, -node.scroll.offset.x);
|
||||
translateAxis(box.y, -node.scroll.offset.y);
|
||||
}
|
||||
if (delta) {
|
||||
// Incoporate each ancestor's scale into a cumulative treeScale for this component
|
||||
treeScale.x *= delta.x.scale;
|
||||
treeScale.y *= delta.y.scale;
|
||||
// Apply each ancestor's calculated delta into this component's recorded layout box
|
||||
applyBoxDelta(box, delta);
|
||||
}
|
||||
if (isSharedTransition && hasTransform(node.latestValues)) {
|
||||
transformBox(box, node.latestValues, node.layout?.layoutBox);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Snap tree scale back to 1 if it's within a non-perceivable threshold.
|
||||
* This will help reduce useless scales getting rendered.
|
||||
*/
|
||||
if (treeScale.x < TREE_SCALE_SNAP_MAX &&
|
||||
treeScale.x > TREE_SCALE_SNAP_MIN) {
|
||||
treeScale.x = 1.0;
|
||||
}
|
||||
if (treeScale.y < TREE_SCALE_SNAP_MAX &&
|
||||
treeScale.y > TREE_SCALE_SNAP_MIN) {
|
||||
treeScale.y = 1.0;
|
||||
}
|
||||
}
|
||||
function translateAxis(axis, distance) {
|
||||
axis.min += distance;
|
||||
axis.max += distance;
|
||||
}
|
||||
/**
|
||||
* Apply a transform to an axis from the latest resolved motion values.
|
||||
* This function basically acts as a bridge between a flat motion value map
|
||||
* and applyAxisDelta
|
||||
*/
|
||||
function transformAxis(axis, axisTranslate, axisScale, boxScale, axisOrigin = 0.5) {
|
||||
const originPoint = mixNumber(axis.min, axis.max, axisOrigin);
|
||||
// Apply the axis delta to the final axis
|
||||
applyAxisDelta(axis, axisTranslate, axisScale, originPoint, boxScale);
|
||||
}
|
||||
function resolveAxisTranslate(value, axis) {
|
||||
if (typeof value === "string") {
|
||||
return (parseFloat(value) / 100) * (axis.max - axis.min);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* Apply a transform to a box from the latest resolved motion values.
|
||||
*/
|
||||
function transformBox(box, transform, sourceBox) {
|
||||
const resolveBox = sourceBox ?? box;
|
||||
transformAxis(box.x, resolveAxisTranslate(transform.x, resolveBox.x), transform.scaleX, transform.scale, transform.originX);
|
||||
transformAxis(box.y, resolveAxisTranslate(transform.y, resolveBox.y), transform.scaleY, transform.scale, transform.originY);
|
||||
}
|
||||
|
||||
export { applyAxisDelta, applyBoxDelta, applyPointDelta, applyTreeDeltas, scalePoint, transformAxis, transformBox, translateAxis };
|
||||
//# sourceMappingURL=delta-apply.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+59
@@ -0,0 +1,59 @@
|
||||
import { mixNumber } from '../../utils/mix/number.mjs';
|
||||
|
||||
const SCALE_PRECISION = 0.0001;
|
||||
const SCALE_MIN = 1 - SCALE_PRECISION;
|
||||
const SCALE_MAX = 1 + SCALE_PRECISION;
|
||||
const TRANSLATE_PRECISION = 0.01;
|
||||
const TRANSLATE_MIN = 0 - TRANSLATE_PRECISION;
|
||||
const TRANSLATE_MAX = 0 + TRANSLATE_PRECISION;
|
||||
function calcLength(axis) {
|
||||
return axis.max - axis.min;
|
||||
}
|
||||
function isNear(value, target, maxDistance) {
|
||||
return Math.abs(value - target) <= maxDistance;
|
||||
}
|
||||
function calcAxisDelta(delta, source, target, origin = 0.5) {
|
||||
delta.origin = origin;
|
||||
delta.originPoint = mixNumber(source.min, source.max, delta.origin);
|
||||
delta.scale = calcLength(target) / calcLength(source);
|
||||
delta.translate =
|
||||
mixNumber(target.min, target.max, delta.origin) - delta.originPoint;
|
||||
if ((delta.scale >= SCALE_MIN && delta.scale <= SCALE_MAX) ||
|
||||
isNaN(delta.scale)) {
|
||||
delta.scale = 1.0;
|
||||
}
|
||||
if ((delta.translate >= TRANSLATE_MIN &&
|
||||
delta.translate <= TRANSLATE_MAX) ||
|
||||
isNaN(delta.translate)) {
|
||||
delta.translate = 0.0;
|
||||
}
|
||||
}
|
||||
function calcBoxDelta(delta, source, target, origin) {
|
||||
calcAxisDelta(delta.x, source.x, target.x, origin ? origin.originX : undefined);
|
||||
calcAxisDelta(delta.y, source.y, target.y, origin ? origin.originY : undefined);
|
||||
}
|
||||
function calcRelativeAxis(target, relative, parent, anchor = 0) {
|
||||
const anchorPoint = anchor
|
||||
? mixNumber(parent.min, parent.max, anchor)
|
||||
: parent.min;
|
||||
target.min = anchorPoint + relative.min;
|
||||
target.max = target.min + calcLength(relative);
|
||||
}
|
||||
function calcRelativeBox(target, relative, parent, anchor) {
|
||||
calcRelativeAxis(target.x, relative.x, parent.x, anchor?.x);
|
||||
calcRelativeAxis(target.y, relative.y, parent.y, anchor?.y);
|
||||
}
|
||||
function calcRelativeAxisPosition(target, layout, parent, anchor = 0) {
|
||||
const anchorPoint = anchor
|
||||
? mixNumber(parent.min, parent.max, anchor)
|
||||
: parent.min;
|
||||
target.min = layout.min - anchorPoint;
|
||||
target.max = target.min + calcLength(layout);
|
||||
}
|
||||
function calcRelativePosition(target, layout, parent, anchor) {
|
||||
calcRelativeAxisPosition(target.x, layout.x, parent.x, anchor?.x);
|
||||
calcRelativeAxisPosition(target.y, layout.y, parent.y, anchor?.y);
|
||||
}
|
||||
|
||||
export { calcAxisDelta, calcBoxDelta, calcLength, calcRelativeAxis, calcRelativeAxisPosition, calcRelativeBox, calcRelativePosition, isNear };
|
||||
//# sourceMappingURL=delta-calc.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+55
@@ -0,0 +1,55 @@
|
||||
import { mixNumber } from '../../utils/mix/number.mjs';
|
||||
import { percent } from '../../value/types/numbers/units.mjs';
|
||||
import { scalePoint } from './delta-apply.mjs';
|
||||
|
||||
/**
|
||||
* Remove a delta from a point. This is essentially the steps of applyPointDelta in reverse
|
||||
*/
|
||||
function removePointDelta(point, translate, scale, originPoint, boxScale) {
|
||||
point -= translate;
|
||||
point = scalePoint(point, 1 / scale, originPoint);
|
||||
if (boxScale !== undefined) {
|
||||
point = scalePoint(point, 1 / boxScale, originPoint);
|
||||
}
|
||||
return point;
|
||||
}
|
||||
/**
|
||||
* Remove a delta from an axis. This is essentially the steps of applyAxisDelta in reverse
|
||||
*/
|
||||
function removeAxisDelta(axis, translate = 0, scale = 1, origin = 0.5, boxScale, originAxis = axis, sourceAxis = axis) {
|
||||
if (percent.test(translate)) {
|
||||
translate = parseFloat(translate);
|
||||
const relativeProgress = mixNumber(sourceAxis.min, sourceAxis.max, translate / 100);
|
||||
translate = relativeProgress - sourceAxis.min;
|
||||
}
|
||||
if (typeof translate !== "number")
|
||||
return;
|
||||
let originPoint = mixNumber(originAxis.min, originAxis.max, origin);
|
||||
if (axis === originAxis)
|
||||
originPoint -= translate;
|
||||
axis.min = removePointDelta(axis.min, translate, scale, originPoint, boxScale);
|
||||
axis.max = removePointDelta(axis.max, translate, scale, originPoint, boxScale);
|
||||
}
|
||||
/**
|
||||
* Remove a transforms from an axis. This is essentially the steps of applyAxisTransforms in reverse
|
||||
* and acts as a bridge between motion values and removeAxisDelta
|
||||
*/
|
||||
function removeAxisTransforms(axis, transforms, [key, scaleKey, originKey], origin, sourceAxis) {
|
||||
removeAxisDelta(axis, transforms[key], transforms[scaleKey], transforms[originKey], transforms.scale, origin, sourceAxis);
|
||||
}
|
||||
/**
|
||||
* The names of the motion values we want to apply as translation, scale and origin.
|
||||
*/
|
||||
const xKeys = ["x", "scaleX", "originX"];
|
||||
const yKeys = ["y", "scaleY", "originY"];
|
||||
/**
|
||||
* Remove a transforms from an box. This is essentially the steps of applyAxisBox in reverse
|
||||
* and acts as a bridge between motion values and removeAxisDelta
|
||||
*/
|
||||
function removeBoxTransforms(box, transforms, originBox, sourceBox) {
|
||||
removeAxisTransforms(box.x, transforms, xKeys, originBox ? originBox.x : undefined, sourceBox ? sourceBox.x : undefined);
|
||||
removeAxisTransforms(box.y, transforms, yKeys, originBox ? originBox.y : undefined, sourceBox ? sourceBox.y : undefined);
|
||||
}
|
||||
|
||||
export { removeAxisDelta, removeAxisTransforms, removeBoxTransforms, removePointDelta };
|
||||
//# sourceMappingURL=delta-remove.mjs.map
|
||||
+1
File diff suppressed because one or more lines are too long
+18
@@ -0,0 +1,18 @@
|
||||
const createAxisDelta = () => ({
|
||||
translate: 0,
|
||||
scale: 1,
|
||||
origin: 0,
|
||||
originPoint: 0,
|
||||
});
|
||||
const createDelta = () => ({
|
||||
x: createAxisDelta(),
|
||||
y: createAxisDelta(),
|
||||
});
|
||||
const createAxis = () => ({ min: 0, max: 0 });
|
||||
const createBox = () => ({
|
||||
x: createAxis(),
|
||||
y: createAxis(),
|
||||
});
|
||||
|
||||
export { createAxis, createAxisDelta, createBox, createDelta };
|
||||
//# sourceMappingURL=models.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"models.mjs","sources":["../../../../src/projection/geometry/models.ts"],"sourcesContent":["import { Axis, AxisDelta, Box, Delta } from \"motion-utils\"\n\nexport const createAxisDelta = (): AxisDelta => ({\n translate: 0,\n scale: 1,\n origin: 0,\n originPoint: 0,\n})\n\nexport const createDelta = (): Delta => ({\n x: createAxisDelta(),\n y: createAxisDelta(),\n})\n\nexport const createAxis = (): Axis => ({ min: 0, max: 0 })\n\nexport const createBox = (): Box => ({\n x: createAxis(),\n y: createAxis(),\n})\n"],"names":[],"mappings":"AAEO,MAAM,eAAe,GAAG,OAAkB;AAC7C,IAAA,SAAS,EAAE,CAAC;AACZ,IAAA,KAAK,EAAE,CAAC;AACR,IAAA,MAAM,EAAE,CAAC;AACT,IAAA,WAAW,EAAE,CAAC;AACjB,CAAA;AAEM,MAAM,WAAW,GAAG,OAAc;IACrC,CAAC,EAAE,eAAe,EAAE;IACpB,CAAC,EAAE,eAAe,EAAE;AACvB,CAAA;AAEM,MAAM,UAAU,GAAG,OAAa,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;AAElD,MAAM,SAAS,GAAG,OAAY;IACjC,CAAC,EAAE,UAAU,EAAE;IACf,CAAC,EAAE,UAAU,EAAE;AAClB,CAAA;;;;"}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { calcLength } from './delta-calc.mjs';
|
||||
|
||||
function isAxisDeltaZero(delta) {
|
||||
return delta.translate === 0 && delta.scale === 1;
|
||||
}
|
||||
function isDeltaZero(delta) {
|
||||
return isAxisDeltaZero(delta.x) && isAxisDeltaZero(delta.y);
|
||||
}
|
||||
function axisEquals(a, b) {
|
||||
return a.min === b.min && a.max === b.max;
|
||||
}
|
||||
function boxEquals(a, b) {
|
||||
return axisEquals(a.x, b.x) && axisEquals(a.y, b.y);
|
||||
}
|
||||
function axisEqualsRounded(a, b) {
|
||||
return (Math.round(a.min) === Math.round(b.min) &&
|
||||
Math.round(a.max) === Math.round(b.max));
|
||||
}
|
||||
function boxEqualsRounded(a, b) {
|
||||
return axisEqualsRounded(a.x, b.x) && axisEqualsRounded(a.y, b.y);
|
||||
}
|
||||
function aspectRatio(box) {
|
||||
return calcLength(box.x) / calcLength(box.y);
|
||||
}
|
||||
function axisDeltaEquals(a, b) {
|
||||
return (a.translate === b.translate &&
|
||||
a.scale === b.scale &&
|
||||
a.originPoint === b.originPoint);
|
||||
}
|
||||
|
||||
export { aspectRatio, axisDeltaEquals, axisEquals, axisEqualsRounded, boxEquals, boxEqualsRounded, isDeltaZero };
|
||||
//# sourceMappingURL=utils.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"utils.mjs","sources":["../../../../src/projection/geometry/utils.ts"],"sourcesContent":["import { Axis, AxisDelta, Box, Delta } from \"motion-utils\"\nimport { calcLength } from \"./delta-calc\"\n\nfunction isAxisDeltaZero(delta: AxisDelta) {\n return delta.translate === 0 && delta.scale === 1\n}\n\nexport function isDeltaZero(delta: Delta) {\n return isAxisDeltaZero(delta.x) && isAxisDeltaZero(delta.y)\n}\n\nexport function axisEquals(a: Axis, b: Axis) {\n return a.min === b.min && a.max === b.max\n}\n\nexport function boxEquals(a: Box, b: Box) {\n return axisEquals(a.x, b.x) && axisEquals(a.y, b.y)\n}\n\nexport function axisEqualsRounded(a: Axis, b: Axis) {\n return (\n Math.round(a.min) === Math.round(b.min) &&\n Math.round(a.max) === Math.round(b.max)\n )\n}\n\nexport function boxEqualsRounded(a: Box, b: Box) {\n return axisEqualsRounded(a.x, b.x) && axisEqualsRounded(a.y, b.y)\n}\n\nexport function aspectRatio(box: Box): number {\n return calcLength(box.x) / calcLength(box.y)\n}\n\nexport function axisDeltaEquals(a: AxisDelta, b: AxisDelta) {\n return (\n a.translate === b.translate &&\n a.scale === b.scale &&\n a.originPoint === b.originPoint\n )\n}\n"],"names":[],"mappings":";;AAGA,SAAS,eAAe,CAAC,KAAgB,EAAA;IACrC,OAAO,KAAK,CAAC,SAAS,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC;AACrD;AAEM,SAAU,WAAW,CAAC,KAAY,EAAA;AACpC,IAAA,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/D;AAEM,SAAU,UAAU,CAAC,CAAO,EAAE,CAAO,EAAA;AACvC,IAAA,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;AAC7C;AAEM,SAAU,SAAS,CAAC,CAAM,EAAE,CAAM,EAAA;IACpC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACvD;AAEM,SAAU,iBAAiB,CAAC,CAAO,EAAE,CAAO,EAAA;AAC9C,IAAA,QACI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;AACvC,QAAA,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;AAE/C;AAEM,SAAU,gBAAgB,CAAC,CAAM,EAAE,CAAM,EAAA;IAC3C,OAAO,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACrE;AAEM,SAAU,WAAW,CAAC,GAAQ,EAAA;AAChC,IAAA,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAChD;AAEM,SAAU,eAAe,CAAC,CAAY,EAAE,CAAY,EAAA;AACtD,IAAA,QACI,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;AAC3B,QAAA,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;AACnB,QAAA,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW;AAEvC;;;;"}
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
import { addDomEvent } from '../../events/add-dom-event.mjs';
|
||||
import { createProjectionNode } from './create-projection-node.mjs';
|
||||
|
||||
const DocumentProjectionNode = createProjectionNode({
|
||||
attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
|
||||
measureScroll: () => ({
|
||||
x: document.documentElement.scrollLeft || document.body?.scrollLeft || 0,
|
||||
y: document.documentElement.scrollTop || document.body?.scrollTop || 0,
|
||||
}),
|
||||
checkIsScrollRoot: () => true,
|
||||
});
|
||||
|
||||
export { DocumentProjectionNode };
|
||||
//# sourceMappingURL=DocumentProjectionNode.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DocumentProjectionNode.mjs","sources":["../../../../src/projection/node/DocumentProjectionNode.ts"],"sourcesContent":["import { addDomEvent } from \"../../events/add-dom-event\"\nimport { createProjectionNode } from \"./create-projection-node\"\n\nexport const DocumentProjectionNode = createProjectionNode<Window>({\n attachResizeListener: (\n ref: Window | Element,\n notify: VoidFunction\n ): VoidFunction => addDomEvent(ref, \"resize\", notify),\n measureScroll: () => ({\n x: document.documentElement.scrollLeft || document.body?.scrollLeft || 0,\n y: document.documentElement.scrollTop || document.body?.scrollTop || 0,\n }),\n checkIsScrollRoot: () => true,\n})\n"],"names":[],"mappings":";;;AAGO,MAAM,sBAAsB,GAAG,oBAAoB,CAAS;AAC/D,IAAA,oBAAoB,EAAE,CAClB,GAAqB,EACrB,MAAoB,KACL,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC;AACrD,IAAA,aAAa,EAAE,OAAO;AAClB,QAAA,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,UAAU,IAAI,QAAQ,CAAC,IAAI,EAAE,UAAU,IAAI,CAAC;AACxE,QAAA,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC,SAAS,IAAI,QAAQ,CAAC,IAAI,EAAE,SAAS,IAAI,CAAC;KACzE,CAAC;AACF,IAAA,iBAAiB,EAAE,MAAM,IAAI;AAChC,CAAA;;;;"}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { createProjectionNode } from './create-projection-node.mjs';
|
||||
import { DocumentProjectionNode } from './DocumentProjectionNode.mjs';
|
||||
|
||||
const rootProjectionNode = {
|
||||
current: undefined,
|
||||
};
|
||||
const HTMLProjectionNode = createProjectionNode({
|
||||
measureScroll: (instance) => ({
|
||||
x: instance.scrollLeft,
|
||||
y: instance.scrollTop,
|
||||
}),
|
||||
defaultParent: () => {
|
||||
if (!rootProjectionNode.current) {
|
||||
const documentNode = new DocumentProjectionNode({});
|
||||
documentNode.mount(window);
|
||||
documentNode.setOptions({ layoutScroll: true });
|
||||
rootProjectionNode.current = documentNode;
|
||||
}
|
||||
return rootProjectionNode.current;
|
||||
},
|
||||
resetTransform: (instance, value) => {
|
||||
instance.style.transform = value !== undefined ? value : "none";
|
||||
},
|
||||
checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
|
||||
});
|
||||
|
||||
export { HTMLProjectionNode, rootProjectionNode };
|
||||
//# sourceMappingURL=HTMLProjectionNode.mjs.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"HTMLProjectionNode.mjs","sources":["../../../../src/projection/node/HTMLProjectionNode.ts"],"sourcesContent":["import { createProjectionNode } from \"./create-projection-node\"\nimport { DocumentProjectionNode } from \"./DocumentProjectionNode\"\nimport { IProjectionNode } from \"./types\"\n\nexport const rootProjectionNode: { current: IProjectionNode | undefined } = {\n current: undefined,\n}\n\nexport const HTMLProjectionNode = createProjectionNode<HTMLElement>({\n measureScroll: (instance) => ({\n x: instance.scrollLeft,\n y: instance.scrollTop,\n }),\n defaultParent: () => {\n if (!rootProjectionNode.current) {\n const documentNode = new DocumentProjectionNode({})\n documentNode.mount(window)\n documentNode.setOptions({ layoutScroll: true })\n rootProjectionNode.current = documentNode\n }\n return rootProjectionNode.current\n },\n resetTransform: (instance, value) => {\n instance.style.transform = value !== undefined ? value : \"none\"\n },\n checkIsScrollRoot: (instance) =>\n Boolean(window.getComputedStyle(instance).position === \"fixed\"),\n})\n"],"names":[],"mappings":";;;AAIO,MAAM,kBAAkB,GAA6C;AACxE,IAAA,OAAO,EAAE,SAAS;;AAGf,MAAM,kBAAkB,GAAG,oBAAoB,CAAc;AAChE,IAAA,aAAa,EAAE,CAAC,QAAQ,MAAM;QAC1B,CAAC,EAAE,QAAQ,CAAC,UAAU;QACtB,CAAC,EAAE,QAAQ,CAAC,SAAS;KACxB,CAAC;IACF,aAAa,EAAE,MAAK;AAChB,QAAA,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE;AAC7B,YAAA,MAAM,YAAY,GAAG,IAAI,sBAAsB,CAAC,EAAE,CAAC;AACnD,YAAA,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC;YAC1B,YAAY,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;AAC/C,YAAA,kBAAkB,CAAC,OAAO,GAAG,YAAY;QAC7C;QACA,OAAO,kBAAkB,CAAC,OAAO;IACrC,CAAC;AACD,IAAA,cAAc,EAAE,CAAC,QAAQ,EAAE,KAAK,KAAI;AAChC,QAAA,QAAQ,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,KAAK,SAAS,GAAG,KAAK,GAAG,MAAM;IACnE,CAAC;AACD,IAAA,iBAAiB,EAAE,CAAC,QAAQ,KACxB,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AACtE,CAAA;;;;"}
|
||||
Generated
Vendored
+1695
@@ -0,0 +1,1695 @@
|
||||
import { SubscriptionManager, clamp, noop } from 'motion-utils';
|
||||
import { animateSingleValue } from '../../animation/animate/single-value.mjs';
|
||||
import { getOptimisedAppearId } from '../../animation/optimized-appear/get-appear-id.mjs';
|
||||
import { getValueTransition } from '../../animation/utils/get-value-transition.mjs';
|
||||
import { microtask } from '../../frameloop/microtask.mjs';
|
||||
import { time } from '../../frameloop/sync-time.mjs';
|
||||
import { scaleCorrectors } from '../styles/scale-correction.mjs';
|
||||
import { statsBuffer } from '../../stats/buffer.mjs';
|
||||
import { delay } from '../../utils/delay.mjs';
|
||||
import { isSVGElement } from '../../utils/is-svg-element.mjs';
|
||||
import { isSVGSVGElement } from '../../utils/is-svg-svg-element.mjs';
|
||||
import { mixNumber } from '../../utils/mix/number.mjs';
|
||||
import { motionValue } from '../../value/index.mjs';
|
||||
import { resolveMotionValue } from '../../value/utils/resolve-motion-value.mjs';
|
||||
import { mixValues } from '../animation/mix-values.mjs';
|
||||
import { copyBoxInto, copyAxisDeltaInto, copyAxisInto } from '../geometry/copy.mjs';
|
||||
import { translateAxis, transformBox, applyBoxDelta, applyTreeDeltas } from '../geometry/delta-apply.mjs';
|
||||
import { calcLength, calcRelativeBox, calcRelativePosition, calcBoxDelta, isNear } from '../geometry/delta-calc.mjs';
|
||||
import { removeBoxTransforms } from '../geometry/delta-remove.mjs';
|
||||
import { createBox, createDelta } from '../geometry/models.mjs';
|
||||
import { boxEqualsRounded, isDeltaZero, axisDeltaEquals, boxEquals, aspectRatio } from '../geometry/utils.mjs';
|
||||
import { NodeStack } from '../shared/stack.mjs';
|
||||
import { buildProjectionTransform } from '../styles/transform.mjs';
|
||||
import { eachAxis } from '../utils/each-axis.mjs';
|
||||
import { FlatTree } from '../utils/flat-tree.mjs';
|
||||
import { hasTransform, hasScale, has2DTranslate } from '../utils/has-transform.mjs';
|
||||
import { globalProjectionState } from './state.mjs';
|
||||
import { frame, cancelFrame, frameData, frameSteps } from '../../frameloop/frame.mjs';
|
||||
|
||||
const metrics = {
|
||||
nodes: 0,
|
||||
calculatedTargetDeltas: 0,
|
||||
calculatedProjections: 0,
|
||||
};
|
||||
const transformAxes = ["", "X", "Y", "Z"];
|
||||
/**
|
||||
* We use 1000 as the animation target as 0-1000 maps better to pixels than 0-1
|
||||
* which has a noticeable difference in spring animations
|
||||
*/
|
||||
const animationTarget = 1000;
|
||||
let id = 0;
|
||||
function resetDistortingTransform(key, visualElement, values, sharedAnimationValues) {
|
||||
const { latestValues } = visualElement;
|
||||
// Record the distorting transform and then temporarily set it to 0
|
||||
if (latestValues[key]) {
|
||||
values[key] = latestValues[key];
|
||||
visualElement.setStaticValue(key, 0);
|
||||
if (sharedAnimationValues) {
|
||||
sharedAnimationValues[key] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
function cancelTreeOptimisedTransformAnimations(projectionNode) {
|
||||
projectionNode.hasCheckedOptimisedAppear = true;
|
||||
if (projectionNode.root === projectionNode)
|
||||
return;
|
||||
const { visualElement } = projectionNode.options;
|
||||
if (!visualElement)
|
||||
return;
|
||||
const appearId = getOptimisedAppearId(visualElement);
|
||||
if (window.MotionHasOptimisedAnimation(appearId, "transform")) {
|
||||
const { layout, layoutId } = projectionNode.options;
|
||||
window.MotionCancelOptimisedAnimation(appearId, "transform", frame, !(layout || layoutId));
|
||||
}
|
||||
const { parent } = projectionNode;
|
||||
if (parent && !parent.hasCheckedOptimisedAppear) {
|
||||
cancelTreeOptimisedTransformAnimations(parent);
|
||||
}
|
||||
}
|
||||
function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
|
||||
return class ProjectionNode {
|
||||
constructor(latestValues = {}, parent = defaultParent?.()) {
|
||||
/**
|
||||
* A unique ID generated for every projection node.
|
||||
*/
|
||||
this.id = id++;
|
||||
/**
|
||||
* An id that represents a unique session instigated by startUpdate.
|
||||
*/
|
||||
this.animationId = 0;
|
||||
this.animationCommitId = 0;
|
||||
/**
|
||||
* A Set containing all this component's children. This is used to iterate
|
||||
* through the children.
|
||||
*
|
||||
* TODO: This could be faster to iterate as a flat array stored on the root node.
|
||||
*/
|
||||
this.children = new Set();
|
||||
/**
|
||||
* Options for the node. We use this to configure what kind of layout animations
|
||||
* we should perform (if any).
|
||||
*/
|
||||
this.options = {};
|
||||
/**
|
||||
* We use this to detect when its safe to shut down part of a projection tree.
|
||||
* We have to keep projecting children for scale correction and relative projection
|
||||
* until all their parents stop performing layout animations.
|
||||
*/
|
||||
this.isTreeAnimating = false;
|
||||
this.isAnimationBlocked = false;
|
||||
/**
|
||||
* Flag to true if we think this layout has been changed. We can't always know this,
|
||||
* currently we set it to true every time a component renders, or if it has a layoutDependency
|
||||
* if that has changed between renders. Additionally, components can be grouped by LayoutGroup
|
||||
* and if one node is dirtied, they all are.
|
||||
*/
|
||||
this.isLayoutDirty = false;
|
||||
/**
|
||||
* Flag to true if we think the projection calculations for this node needs
|
||||
* recalculating as a result of an updated transform or layout animation.
|
||||
*/
|
||||
this.isProjectionDirty = false;
|
||||
/**
|
||||
* Flag to true if the layout *or* transform has changed. This then gets propagated
|
||||
* throughout the projection tree, forcing any element below to recalculate on the next frame.
|
||||
*/
|
||||
this.isSharedProjectionDirty = false;
|
||||
/**
|
||||
* Flag transform dirty. This gets propagated throughout the whole tree but is only
|
||||
* respected by shared nodes.
|
||||
*/
|
||||
this.isTransformDirty = false;
|
||||
/**
|
||||
* Block layout updates for instant layout transitions throughout the tree.
|
||||
*/
|
||||
this.updateManuallyBlocked = false;
|
||||
this.updateBlockedByResize = false;
|
||||
/**
|
||||
* Set to true between the start of the first `willUpdate` call and the end of the `didUpdate`
|
||||
* call.
|
||||
*/
|
||||
this.isUpdating = false;
|
||||
/**
|
||||
* If this is an SVG element we currently disable projection transforms
|
||||
*/
|
||||
this.isSVG = false;
|
||||
/**
|
||||
* Flag to true (during promotion) if a node doing an instant layout transition needs to reset
|
||||
* its projection styles.
|
||||
*/
|
||||
this.needsReset = false;
|
||||
/**
|
||||
* Flags whether this node should have its transform reset prior to measuring.
|
||||
*/
|
||||
this.shouldResetTransform = false;
|
||||
/**
|
||||
* Store whether this node has been checked for optimised appear animations. As
|
||||
* effects fire bottom-up, and we want to look up the tree for appear animations,
|
||||
* this makes sure we only check each path once, stopping at nodes that
|
||||
* have already been checked.
|
||||
*/
|
||||
this.hasCheckedOptimisedAppear = false;
|
||||
/**
|
||||
* An object representing the calculated contextual/accumulated/tree scale.
|
||||
* This will be used to scale calculcated projection transforms, as these are
|
||||
* calculated in screen-space but need to be scaled for elements to layoutly
|
||||
* make it to their calculated destinations.
|
||||
*
|
||||
* TODO: Lazy-init
|
||||
*/
|
||||
this.treeScale = { x: 1, y: 1 };
|
||||
/**
|
||||
*
|
||||
*/
|
||||
this.eventHandlers = new Map();
|
||||
this.hasTreeAnimated = false;
|
||||
this.layoutVersion = 0;
|
||||
// Note: Currently only running on root node
|
||||
this.updateScheduled = false;
|
||||
this.scheduleUpdate = () => this.update();
|
||||
this.projectionUpdateScheduled = false;
|
||||
this.checkUpdateFailed = () => {
|
||||
if (this.isUpdating) {
|
||||
this.isUpdating = false;
|
||||
this.clearAllSnapshots();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* This is a multi-step process as shared nodes might be of different depths. Nodes
|
||||
* are sorted by depth order, so we need to resolve the entire tree before moving to
|
||||
* the next step.
|
||||
*/
|
||||
this.updateProjection = () => {
|
||||
this.projectionUpdateScheduled = false;
|
||||
/**
|
||||
* Reset debug counts. Manually resetting rather than creating a new
|
||||
* object each frame.
|
||||
*/
|
||||
if (statsBuffer.value) {
|
||||
metrics.nodes =
|
||||
metrics.calculatedTargetDeltas =
|
||||
metrics.calculatedProjections =
|
||||
0;
|
||||
}
|
||||
this.nodes.forEach(propagateDirtyNodes);
|
||||
this.nodes.forEach(resolveTargetDelta);
|
||||
this.nodes.forEach(calcProjection);
|
||||
this.nodes.forEach(cleanDirtyNodes);
|
||||
if (statsBuffer.addProjectionMetrics) {
|
||||
statsBuffer.addProjectionMetrics(metrics);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Frame calculations
|
||||
*/
|
||||
this.resolvedRelativeTargetAt = 0.0;
|
||||
this.linkedParentVersion = 0;
|
||||
this.hasProjected = false;
|
||||
this.isVisible = true;
|
||||
this.animationProgress = 0;
|
||||
/**
|
||||
* Shared layout
|
||||
*/
|
||||
// TODO Only running on root node
|
||||
this.sharedNodes = new Map();
|
||||
this.latestValues = latestValues;
|
||||
this.root = parent ? parent.root || parent : this;
|
||||
this.path = parent ? [...parent.path, parent] : [];
|
||||
this.parent = parent;
|
||||
this.depth = parent ? parent.depth + 1 : 0;
|
||||
for (let i = 0; i < this.path.length; i++) {
|
||||
this.path[i].shouldResetTransform = true;
|
||||
}
|
||||
if (this.root === this)
|
||||
this.nodes = new FlatTree();
|
||||
}
|
||||
addEventListener(name, handler) {
|
||||
if (!this.eventHandlers.has(name)) {
|
||||
this.eventHandlers.set(name, new SubscriptionManager());
|
||||
}
|
||||
return this.eventHandlers.get(name).add(handler);
|
||||
}
|
||||
notifyListeners(name, ...args) {
|
||||
const subscriptionManager = this.eventHandlers.get(name);
|
||||
subscriptionManager && subscriptionManager.notify(...args);
|
||||
}
|
||||
hasListeners(name) {
|
||||
return this.eventHandlers.has(name);
|
||||
}
|
||||
/**
|
||||
* Lifecycles
|
||||
*/
|
||||
mount(instance) {
|
||||
if (this.instance)
|
||||
return;
|
||||
this.isSVG = isSVGElement(instance) && !isSVGSVGElement(instance);
|
||||
this.instance = instance;
|
||||
const { layoutId, layout, visualElement } = this.options;
|
||||
if (visualElement && !visualElement.current) {
|
||||
visualElement.mount(instance);
|
||||
}
|
||||
this.root.nodes.add(this);
|
||||
this.parent && this.parent.children.add(this);
|
||||
if (this.root.hasTreeAnimated && (layout || layoutId)) {
|
||||
this.isLayoutDirty = true;
|
||||
}
|
||||
if (attachResizeListener) {
|
||||
let cancelDelay;
|
||||
let innerWidth = 0;
|
||||
const resizeUnblockUpdate = () => (this.root.updateBlockedByResize = false);
|
||||
// Set initial innerWidth in a frame.read callback to batch the read
|
||||
frame.read(() => {
|
||||
innerWidth = window.innerWidth;
|
||||
});
|
||||
attachResizeListener(instance, () => {
|
||||
const newInnerWidth = window.innerWidth;
|
||||
if (newInnerWidth === innerWidth)
|
||||
return;
|
||||
innerWidth = newInnerWidth;
|
||||
this.root.updateBlockedByResize = true;
|
||||
cancelDelay && cancelDelay();
|
||||
cancelDelay = delay(resizeUnblockUpdate, 250);
|
||||
if (globalProjectionState.hasAnimatedSinceResize) {
|
||||
globalProjectionState.hasAnimatedSinceResize = false;
|
||||
this.nodes.forEach(finishAnimation);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (layoutId) {
|
||||
this.root.registerSharedNode(layoutId, this);
|
||||
}
|
||||
// Only register the handler if it requires layout animation
|
||||
if (this.options.animate !== false &&
|
||||
visualElement &&
|
||||
(layoutId || layout)) {
|
||||
this.addEventListener("didUpdate", ({ delta, hasLayoutChanged, hasRelativeLayoutChanged, layout: newLayout, }) => {
|
||||
if (this.isTreeAnimationBlocked()) {
|
||||
this.target = undefined;
|
||||
this.relativeTarget = undefined;
|
||||
return;
|
||||
}
|
||||
// TODO: Check here if an animation exists
|
||||
const layoutTransition = this.options.transition ||
|
||||
visualElement.getDefaultTransition() ||
|
||||
defaultLayoutTransition;
|
||||
const { onLayoutAnimationStart, onLayoutAnimationComplete, } = visualElement.getProps();
|
||||
/**
|
||||
* The target layout of the element might stay the same,
|
||||
* but its position relative to its parent has changed.
|
||||
*/
|
||||
const hasTargetChanged = !this.targetLayout ||
|
||||
!boxEqualsRounded(this.targetLayout, newLayout);
|
||||
/*
|
||||
* Note: Disabled to fix relative animations always triggering new
|
||||
* layout animations. If this causes further issues, we can try
|
||||
* a different approach to detecting relative target changes.
|
||||
*/
|
||||
// || hasRelativeLayoutChanged
|
||||
/**
|
||||
* If the layout hasn't seemed to have changed, it might be that the
|
||||
* element is visually in the same place in the document but its position
|
||||
* relative to its parent has indeed changed. So here we check for that.
|
||||
*/
|
||||
const hasOnlyRelativeTargetChanged = !hasLayoutChanged && hasRelativeLayoutChanged;
|
||||
if (this.options.layoutRoot ||
|
||||
this.resumeFrom ||
|
||||
hasOnlyRelativeTargetChanged ||
|
||||
(hasLayoutChanged &&
|
||||
(hasTargetChanged || !this.currentAnimation))) {
|
||||
if (this.resumeFrom) {
|
||||
this.resumingFrom = this.resumeFrom;
|
||||
this.resumingFrom.resumingFrom = undefined;
|
||||
}
|
||||
const animationOptions = {
|
||||
...getValueTransition(layoutTransition, "layout"),
|
||||
onPlay: onLayoutAnimationStart,
|
||||
onComplete: onLayoutAnimationComplete,
|
||||
};
|
||||
if (visualElement.shouldReduceMotion ||
|
||||
this.options.layoutRoot) {
|
||||
animationOptions.delay = 0;
|
||||
animationOptions.type = false;
|
||||
}
|
||||
this.startAnimation(animationOptions);
|
||||
/**
|
||||
* Set animation origin after starting animation to avoid layout jump
|
||||
* caused by stopping previous layout animation
|
||||
*/
|
||||
this.setAnimationOrigin(delta, hasOnlyRelativeTargetChanged, animationOptions
|
||||
.path);
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* If the layout hasn't changed and we have an animation that hasn't started yet,
|
||||
* finish it immediately. Otherwise it will be animating from a location
|
||||
* that was probably never committed to screen and look like a jumpy box.
|
||||
*/
|
||||
if (!hasLayoutChanged) {
|
||||
finishAnimation(this);
|
||||
}
|
||||
if (this.isLead() && this.options.onExitComplete) {
|
||||
this.options.onExitComplete();
|
||||
}
|
||||
}
|
||||
this.targetLayout = newLayout;
|
||||
});
|
||||
}
|
||||
}
|
||||
unmount() {
|
||||
this.options.layoutId && this.willUpdate();
|
||||
this.root.nodes.remove(this);
|
||||
const stack = this.getStack();
|
||||
stack && stack.remove(this);
|
||||
this.parent && this.parent.children.delete(this);
|
||||
this.instance = undefined;
|
||||
this.eventHandlers.clear();
|
||||
cancelFrame(this.updateProjection);
|
||||
}
|
||||
// only on the root
|
||||
blockUpdate() {
|
||||
this.updateManuallyBlocked = true;
|
||||
}
|
||||
unblockUpdate() {
|
||||
this.updateManuallyBlocked = false;
|
||||
}
|
||||
isUpdateBlocked() {
|
||||
return this.updateManuallyBlocked || this.updateBlockedByResize;
|
||||
}
|
||||
isTreeAnimationBlocked() {
|
||||
return (this.isAnimationBlocked ||
|
||||
(this.parent && this.parent.isTreeAnimationBlocked()) ||
|
||||
false);
|
||||
}
|
||||
// Note: currently only running on root node
|
||||
startUpdate() {
|
||||
if (this.isUpdateBlocked())
|
||||
return;
|
||||
this.isUpdating = true;
|
||||
this.nodes && this.nodes.forEach(resetSkewAndRotation);
|
||||
this.animationId++;
|
||||
}
|
||||
getTransformTemplate() {
|
||||
const { visualElement } = this.options;
|
||||
return visualElement && visualElement.getProps().transformTemplate;
|
||||
}
|
||||
willUpdate(shouldNotifyListeners = true) {
|
||||
this.root.hasTreeAnimated = true;
|
||||
if (this.root.isUpdateBlocked()) {
|
||||
this.options.onExitComplete && this.options.onExitComplete();
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* If we're running optimised appear animations then these must be
|
||||
* cancelled before measuring the DOM. This is so we can measure
|
||||
* the true layout of the element rather than the WAAPI animation
|
||||
* which will be unaffected by the resetSkewAndRotate step.
|
||||
*
|
||||
* Note: This is a DOM write. Worst case scenario is this is sandwiched
|
||||
* between other snapshot reads which will cause unnecessary style recalculations.
|
||||
* This has to happen here though, as we don't yet know which nodes will need
|
||||
* snapshots in startUpdate(), but we only want to cancel optimised animations
|
||||
* if a layout animation measurement is actually going to be affected by them.
|
||||
*/
|
||||
if (window.MotionCancelOptimisedAnimation &&
|
||||
!this.hasCheckedOptimisedAppear) {
|
||||
cancelTreeOptimisedTransformAnimations(this);
|
||||
}
|
||||
!this.root.isUpdating && this.root.startUpdate();
|
||||
if (this.isLayoutDirty)
|
||||
return;
|
||||
this.isLayoutDirty = true;
|
||||
for (let i = 0; i < this.path.length; i++) {
|
||||
const node = this.path[i];
|
||||
node.shouldResetTransform = true;
|
||||
/**
|
||||
* Percentage translates resolve against layoutBox dimensions,
|
||||
* so ancestors with them must be re-measured after transform reset.
|
||||
*/
|
||||
if (typeof node.latestValues.x === "string" ||
|
||||
typeof node.latestValues.y === "string") {
|
||||
node.isLayoutDirty = true;
|
||||
}
|
||||
node.updateScroll("snapshot");
|
||||
if (node.options.layoutRoot) {
|
||||
node.willUpdate(false);
|
||||
}
|
||||
}
|
||||
const { layoutId, layout } = this.options;
|
||||
if (layoutId === undefined && !layout)
|
||||
return;
|
||||
const transformTemplate = this.getTransformTemplate();
|
||||
this.prevTransformTemplateValue = transformTemplate
|
||||
? transformTemplate(this.latestValues, "")
|
||||
: undefined;
|
||||
this.updateSnapshot();
|
||||
shouldNotifyListeners && this.notifyListeners("willUpdate");
|
||||
}
|
||||
update() {
|
||||
this.updateScheduled = false;
|
||||
const updateWasBlocked = this.isUpdateBlocked();
|
||||
// When doing an instant transition, we skip the layout update,
|
||||
// but should still clean up the measurements so that the next
|
||||
// snapshot could be taken correctly.
|
||||
if (updateWasBlocked) {
|
||||
const wasBlockedByResize = this.updateBlockedByResize;
|
||||
this.unblockUpdate();
|
||||
this.updateBlockedByResize = false;
|
||||
this.clearAllSnapshots();
|
||||
/**
|
||||
* When blocked by resize, still measure layouts so
|
||||
* callbacks like onLayoutMeasure fire (e.g. Reorder).
|
||||
* Skip notifyLayoutUpdate to prevent animations.
|
||||
*/
|
||||
if (wasBlockedByResize) {
|
||||
this.nodes.forEach(forceLayoutMeasure);
|
||||
}
|
||||
this.nodes.forEach(clearMeasurements);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* If this is a repeat of didUpdate then ignore the animation.
|
||||
*/
|
||||
if (this.animationId <= this.animationCommitId) {
|
||||
this.nodes.forEach(clearIsLayoutDirty);
|
||||
return;
|
||||
}
|
||||
this.animationCommitId = this.animationId;
|
||||
if (!this.isUpdating) {
|
||||
this.nodes.forEach(clearIsLayoutDirty);
|
||||
}
|
||||
else {
|
||||
this.isUpdating = false;
|
||||
/**
|
||||
* Ensure animation-blocked nodes (e.g. during drag)
|
||||
* get measured even when memoized (willUpdate skipped).
|
||||
*/
|
||||
this.nodes.forEach(ensureDraggedNodesSnapshotted);
|
||||
/**
|
||||
* Write
|
||||
*/
|
||||
this.nodes.forEach(resetTransformStyle);
|
||||
/**
|
||||
* Read ==================
|
||||
*/
|
||||
// Update layout measurements of updated children
|
||||
this.nodes.forEach(updateLayout);
|
||||
/**
|
||||
* Write
|
||||
*/
|
||||
// Notify listeners that the layout is updated
|
||||
this.nodes.forEach(notifyLayoutUpdate);
|
||||
}
|
||||
this.clearAllSnapshots();
|
||||
/**
|
||||
* Manually flush any pending updates. Ideally
|
||||
* we could leave this to the following requestAnimationFrame but this seems
|
||||
* to leave a flash of incorrectly styled content.
|
||||
*/
|
||||
const now = time.now();
|
||||
frameData.delta = clamp(0, 1000 / 60, now - frameData.timestamp);
|
||||
frameData.timestamp = now;
|
||||
frameData.isProcessing = true;
|
||||
frameSteps.update.process(frameData);
|
||||
frameSteps.preRender.process(frameData);
|
||||
frameSteps.render.process(frameData);
|
||||
frameData.isProcessing = false;
|
||||
}
|
||||
didUpdate() {
|
||||
if (!this.updateScheduled) {
|
||||
this.updateScheduled = true;
|
||||
microtask.read(this.scheduleUpdate);
|
||||
}
|
||||
}
|
||||
clearAllSnapshots() {
|
||||
this.nodes.forEach(clearSnapshot);
|
||||
this.sharedNodes.forEach(removeLeadSnapshots);
|
||||
}
|
||||
scheduleUpdateProjection() {
|
||||
if (!this.projectionUpdateScheduled) {
|
||||
this.projectionUpdateScheduled = true;
|
||||
frame.preRender(this.updateProjection, false, true);
|
||||
}
|
||||
}
|
||||
scheduleCheckAfterUnmount() {
|
||||
/**
|
||||
* If the unmounting node is in a layoutGroup and did trigger a willUpdate,
|
||||
* we manually call didUpdate to give a chance to the siblings to animate.
|
||||
* Otherwise, cleanup all snapshots to prevents future nodes from reusing them.
|
||||
*/
|
||||
frame.postRender(() => {
|
||||
if (this.isLayoutDirty) {
|
||||
this.root.didUpdate();
|
||||
}
|
||||
else {
|
||||
this.root.checkUpdateFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Update measurements
|
||||
*/
|
||||
updateSnapshot() {
|
||||
if (this.snapshot || !this.instance)
|
||||
return;
|
||||
this.snapshot = this.measure();
|
||||
if (this.snapshot &&
|
||||
!calcLength(this.snapshot.measuredBox.x) &&
|
||||
!calcLength(this.snapshot.measuredBox.y)) {
|
||||
this.snapshot = undefined;
|
||||
}
|
||||
}
|
||||
updateLayout() {
|
||||
if (!this.instance)
|
||||
return;
|
||||
this.updateScroll();
|
||||
if (!(this.options.alwaysMeasureLayout && this.isLead()) &&
|
||||
!this.isLayoutDirty) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* When a node is mounted, it simply resumes from the prevLead's
|
||||
* snapshot instead of taking a new one, but the ancestors scroll
|
||||
* might have updated while the prevLead is unmounted. We need to
|
||||
* update the scroll again to make sure the layout we measure is
|
||||
* up to date.
|
||||
*/
|
||||
if (this.resumeFrom && !this.resumeFrom.instance) {
|
||||
for (let i = 0; i < this.path.length; i++) {
|
||||
const node = this.path[i];
|
||||
node.updateScroll();
|
||||
}
|
||||
}
|
||||
const prevLayout = this.layout;
|
||||
this.layout = this.measure(false);
|
||||
this.layoutVersion++;
|
||||
if (!this.layoutCorrected)
|
||||
this.layoutCorrected = createBox();
|
||||
this.isLayoutDirty = false;
|
||||
this.projectionDelta = undefined;
|
||||
this.notifyListeners("measure", this.layout.layoutBox);
|
||||
const { visualElement } = this.options;
|
||||
visualElement &&
|
||||
visualElement.notify("LayoutMeasure", this.layout.layoutBox, prevLayout ? prevLayout.layoutBox : undefined);
|
||||
}
|
||||
updateScroll(phase = "measure") {
|
||||
let needsMeasurement = Boolean(this.options.layoutScroll && this.instance);
|
||||
if (this.scroll &&
|
||||
this.scroll.animationId === this.root.animationId &&
|
||||
this.scroll.phase === phase) {
|
||||
needsMeasurement = false;
|
||||
}
|
||||
if (needsMeasurement && this.instance) {
|
||||
const isRoot = checkIsScrollRoot(this.instance);
|
||||
this.scroll = {
|
||||
animationId: this.root.animationId,
|
||||
phase,
|
||||
isRoot,
|
||||
offset: measureScroll(this.instance),
|
||||
wasRoot: this.scroll ? this.scroll.isRoot : isRoot,
|
||||
};
|
||||
}
|
||||
}
|
||||
resetTransform() {
|
||||
if (!resetTransform)
|
||||
return;
|
||||
const isResetRequested = this.isLayoutDirty ||
|
||||
this.shouldResetTransform ||
|
||||
this.options.alwaysMeasureLayout;
|
||||
const hasProjection = this.projectionDelta && !isDeltaZero(this.projectionDelta);
|
||||
const transformTemplate = this.getTransformTemplate();
|
||||
const transformTemplateValue = transformTemplate
|
||||
? transformTemplate(this.latestValues, "")
|
||||
: undefined;
|
||||
const transformTemplateHasChanged = transformTemplateValue !== this.prevTransformTemplateValue;
|
||||
if (isResetRequested &&
|
||||
this.instance &&
|
||||
(hasProjection ||
|
||||
hasTransform(this.latestValues) ||
|
||||
transformTemplateHasChanged)) {
|
||||
resetTransform(this.instance, transformTemplateValue);
|
||||
this.shouldResetTransform = false;
|
||||
this.scheduleRender();
|
||||
}
|
||||
}
|
||||
measure(removeTransform = true) {
|
||||
const pageBox = this.measurePageBox();
|
||||
let layoutBox = this.removeElementScroll(pageBox);
|
||||
/**
|
||||
* Measurements taken during the pre-render stage
|
||||
* still have transforms applied so we remove them
|
||||
* via calculation.
|
||||
*/
|
||||
if (removeTransform) {
|
||||
layoutBox = this.removeTransform(layoutBox);
|
||||
}
|
||||
roundBox(layoutBox);
|
||||
return {
|
||||
animationId: this.root.animationId,
|
||||
measuredBox: pageBox,
|
||||
layoutBox,
|
||||
latestValues: {},
|
||||
source: this.id,
|
||||
};
|
||||
}
|
||||
measurePageBox() {
|
||||
const { visualElement } = this.options;
|
||||
if (!visualElement)
|
||||
return createBox();
|
||||
const box = visualElement.measureViewportBox();
|
||||
const wasInScrollRoot = this.scroll?.wasRoot || this.path.some(checkNodeWasScrollRoot);
|
||||
if (!wasInScrollRoot) {
|
||||
// Remove viewport scroll to give page-relative coordinates
|
||||
const { scroll } = this.root;
|
||||
if (scroll) {
|
||||
translateAxis(box.x, scroll.offset.x);
|
||||
translateAxis(box.y, scroll.offset.y);
|
||||
}
|
||||
}
|
||||
return box;
|
||||
}
|
||||
removeElementScroll(box) {
|
||||
const boxWithoutScroll = createBox();
|
||||
copyBoxInto(boxWithoutScroll, box);
|
||||
if (this.scroll?.wasRoot) {
|
||||
return boxWithoutScroll;
|
||||
}
|
||||
/**
|
||||
* Performance TODO: Keep a cumulative scroll offset down the tree
|
||||
* rather than loop back up the path.
|
||||
*/
|
||||
for (let i = 0; i < this.path.length; i++) {
|
||||
const node = this.path[i];
|
||||
const { scroll, options } = node;
|
||||
if (node !== this.root && scroll && options.layoutScroll) {
|
||||
/**
|
||||
* If this is a new scroll root, we want to remove all previous scrolls
|
||||
* from the viewport box.
|
||||
*/
|
||||
if (scroll.wasRoot) {
|
||||
copyBoxInto(boxWithoutScroll, box);
|
||||
}
|
||||
translateAxis(boxWithoutScroll.x, scroll.offset.x);
|
||||
translateAxis(boxWithoutScroll.y, scroll.offset.y);
|
||||
}
|
||||
}
|
||||
return boxWithoutScroll;
|
||||
}
|
||||
applyTransform(box, transformOnly = false, output) {
|
||||
const withTransforms = output || createBox();
|
||||
copyBoxInto(withTransforms, box);
|
||||
for (let i = 0; i < this.path.length; i++) {
|
||||
const node = this.path[i];
|
||||
if (!transformOnly &&
|
||||
node.options.layoutScroll &&
|
||||
node.scroll &&
|
||||
node !== node.root) {
|
||||
translateAxis(withTransforms.x, -node.scroll.offset.x);
|
||||
translateAxis(withTransforms.y, -node.scroll.offset.y);
|
||||
}
|
||||
if (!hasTransform(node.latestValues))
|
||||
continue;
|
||||
transformBox(withTransforms, node.latestValues, node.layout?.layoutBox);
|
||||
}
|
||||
if (hasTransform(this.latestValues)) {
|
||||
transformBox(withTransforms, this.latestValues, this.layout?.layoutBox);
|
||||
}
|
||||
return withTransforms;
|
||||
}
|
||||
removeTransform(box) {
|
||||
const boxWithoutTransform = createBox();
|
||||
copyBoxInto(boxWithoutTransform, box);
|
||||
for (let i = 0; i < this.path.length; i++) {
|
||||
const node = this.path[i];
|
||||
if (!hasTransform(node.latestValues))
|
||||
continue;
|
||||
let sourceBox;
|
||||
if (node.instance) {
|
||||
hasScale(node.latestValues) && node.updateSnapshot();
|
||||
sourceBox = createBox();
|
||||
copyBoxInto(sourceBox, node.measurePageBox());
|
||||
}
|
||||
removeBoxTransforms(boxWithoutTransform, node.latestValues, node.snapshot?.layoutBox, sourceBox);
|
||||
}
|
||||
if (hasTransform(this.latestValues)) {
|
||||
removeBoxTransforms(boxWithoutTransform, this.latestValues);
|
||||
}
|
||||
return boxWithoutTransform;
|
||||
}
|
||||
setTargetDelta(delta) {
|
||||
this.targetDelta = delta;
|
||||
this.root.scheduleUpdateProjection();
|
||||
this.isProjectionDirty = true;
|
||||
}
|
||||
setOptions(options) {
|
||||
this.options = {
|
||||
...this.options,
|
||||
...options,
|
||||
crossfade: options.crossfade !== undefined ? options.crossfade : true,
|
||||
};
|
||||
}
|
||||
clearMeasurements() {
|
||||
this.scroll = undefined;
|
||||
this.layout = undefined;
|
||||
this.snapshot = undefined;
|
||||
this.prevTransformTemplateValue = undefined;
|
||||
this.targetDelta = undefined;
|
||||
this.target = undefined;
|
||||
this.isLayoutDirty = false;
|
||||
}
|
||||
forceRelativeParentToResolveTarget() {
|
||||
if (!this.relativeParent)
|
||||
return;
|
||||
/**
|
||||
* If the parent target isn't up-to-date, force it to update.
|
||||
* This is an unfortunate de-optimisation as it means any updating relative
|
||||
* projection will cause all the relative parents to recalculate back
|
||||
* up the tree.
|
||||
*/
|
||||
if (this.relativeParent.resolvedRelativeTargetAt !==
|
||||
frameData.timestamp) {
|
||||
this.relativeParent.resolveTargetDelta(true);
|
||||
}
|
||||
}
|
||||
resolveTargetDelta(forceRecalculation = false) {
|
||||
/**
|
||||
* Once the dirty status of nodes has been spread through the tree, we also
|
||||
* need to check if we have a shared node of a different depth that has itself
|
||||
* been dirtied.
|
||||
*/
|
||||
const lead = this.getLead();
|
||||
this.isProjectionDirty || (this.isProjectionDirty = lead.isProjectionDirty);
|
||||
this.isTransformDirty || (this.isTransformDirty = lead.isTransformDirty);
|
||||
this.isSharedProjectionDirty || (this.isSharedProjectionDirty = lead.isSharedProjectionDirty);
|
||||
const isShared = Boolean(this.resumingFrom) || this !== lead;
|
||||
/**
|
||||
* We don't use transform for this step of processing so we don't
|
||||
* need to check whether any nodes have changed transform.
|
||||
*/
|
||||
const canSkip = !(forceRecalculation ||
|
||||
(isShared && this.isSharedProjectionDirty) ||
|
||||
this.isProjectionDirty ||
|
||||
this.parent?.isProjectionDirty ||
|
||||
this.attemptToResolveRelativeTarget ||
|
||||
this.root.updateBlockedByResize);
|
||||
if (canSkip)
|
||||
return;
|
||||
const { layout, layoutId } = this.options;
|
||||
/**
|
||||
* If we have no layout, we can't perform projection, so early return
|
||||
*/
|
||||
if (!this.layout || !(layout || layoutId))
|
||||
return;
|
||||
this.resolvedRelativeTargetAt = frameData.timestamp;
|
||||
const relativeParent = this.getClosestProjectingParent();
|
||||
if (relativeParent &&
|
||||
this.linkedParentVersion !== relativeParent.layoutVersion &&
|
||||
!relativeParent.options.layoutRoot) {
|
||||
this.removeRelativeTarget();
|
||||
}
|
||||
/**
|
||||
* If we don't have a targetDelta but do have a layout, we can attempt to resolve
|
||||
* a relativeParent. This will allow a component to perform scale correction
|
||||
* even if no animation has started.
|
||||
*/
|
||||
if (!this.targetDelta && !this.relativeTarget) {
|
||||
if (this.options.layoutAnchor !== false &&
|
||||
relativeParent &&
|
||||
relativeParent.layout) {
|
||||
this.createRelativeTarget(relativeParent, this.layout.layoutBox, relativeParent.layout.layoutBox);
|
||||
}
|
||||
else {
|
||||
this.removeRelativeTarget();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If we have no relative target or no target delta our target isn't valid
|
||||
* for this frame.
|
||||
*/
|
||||
if (!this.relativeTarget && !this.targetDelta)
|
||||
return;
|
||||
/**
|
||||
* Lazy-init target data structure
|
||||
*/
|
||||
if (!this.target) {
|
||||
this.target = createBox();
|
||||
this.targetWithTransforms = createBox();
|
||||
}
|
||||
/**
|
||||
* If we've got a relative box for this component, resolve it into a target relative to the parent.
|
||||
*/
|
||||
if (this.relativeTarget &&
|
||||
this.relativeTargetOrigin &&
|
||||
this.relativeParent &&
|
||||
this.relativeParent.target) {
|
||||
this.forceRelativeParentToResolveTarget();
|
||||
calcRelativeBox(this.target, this.relativeTarget, this.relativeParent.target, this.options.layoutAnchor || undefined);
|
||||
/**
|
||||
* If we've only got a targetDelta, resolve it into a target
|
||||
*/
|
||||
}
|
||||
else if (this.targetDelta) {
|
||||
if (Boolean(this.resumingFrom)) {
|
||||
this.applyTransform(this.layout.layoutBox, false, this.target);
|
||||
}
|
||||
else {
|
||||
copyBoxInto(this.target, this.layout.layoutBox);
|
||||
}
|
||||
applyBoxDelta(this.target, this.targetDelta);
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* If no target, use own layout as target
|
||||
*/
|
||||
copyBoxInto(this.target, this.layout.layoutBox);
|
||||
}
|
||||
/**
|
||||
* If we've been told to attempt to resolve a relative target, do so.
|
||||
*/
|
||||
if (this.attemptToResolveRelativeTarget) {
|
||||
this.attemptToResolveRelativeTarget = false;
|
||||
if (this.options.layoutAnchor !== false &&
|
||||
relativeParent &&
|
||||
Boolean(relativeParent.resumingFrom) ===
|
||||
Boolean(this.resumingFrom) &&
|
||||
!relativeParent.options.layoutScroll &&
|
||||
relativeParent.target &&
|
||||
this.animationProgress !== 1) {
|
||||
this.createRelativeTarget(relativeParent, this.target, relativeParent.target);
|
||||
}
|
||||
else {
|
||||
this.relativeParent = this.relativeTarget = undefined;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Increase debug counter for resolved target deltas
|
||||
*/
|
||||
if (statsBuffer.value) {
|
||||
metrics.calculatedTargetDeltas++;
|
||||
}
|
||||
}
|
||||
getClosestProjectingParent() {
|
||||
if (!this.parent ||
|
||||
hasScale(this.parent.latestValues) ||
|
||||
has2DTranslate(this.parent.latestValues)) {
|
||||
return undefined;
|
||||
}
|
||||
if (this.parent.isProjecting()) {
|
||||
return this.parent;
|
||||
}
|
||||
else {
|
||||
return this.parent.getClosestProjectingParent();
|
||||
}
|
||||
}
|
||||
isProjecting() {
|
||||
return Boolean((this.relativeTarget ||
|
||||
this.targetDelta ||
|
||||
this.options.layoutRoot) &&
|
||||
this.layout);
|
||||
}
|
||||
createRelativeTarget(relativeParent, layout, parentLayout) {
|
||||
this.relativeParent = relativeParent;
|
||||
this.linkedParentVersion = relativeParent.layoutVersion;
|
||||
this.forceRelativeParentToResolveTarget();
|
||||
this.relativeTarget = createBox();
|
||||
this.relativeTargetOrigin = createBox();
|
||||
calcRelativePosition(this.relativeTargetOrigin, layout, parentLayout, this.options.layoutAnchor || undefined);
|
||||
copyBoxInto(this.relativeTarget, this.relativeTargetOrigin);
|
||||
}
|
||||
removeRelativeTarget() {
|
||||
this.relativeParent = this.relativeTarget = undefined;
|
||||
}
|
||||
calcProjection() {
|
||||
const lead = this.getLead();
|
||||
const isShared = Boolean(this.resumingFrom) || this !== lead;
|
||||
let canSkip = true;
|
||||
/**
|
||||
* If this is a normal layout animation and neither this node nor its nearest projecting
|
||||
* is dirty then we can't skip.
|
||||
*/
|
||||
if (this.isProjectionDirty || this.parent?.isProjectionDirty) {
|
||||
canSkip = false;
|
||||
}
|
||||
/**
|
||||
* If this is a shared layout animation and this node's shared projection is dirty then
|
||||
* we can't skip.
|
||||
*/
|
||||
if (isShared &&
|
||||
(this.isSharedProjectionDirty || this.isTransformDirty)) {
|
||||
canSkip = false;
|
||||
}
|
||||
/**
|
||||
* If we have resolved the target this frame we must recalculate the
|
||||
* projection to ensure it visually represents the internal calculations.
|
||||
*/
|
||||
if (this.resolvedRelativeTargetAt === frameData.timestamp) {
|
||||
canSkip = false;
|
||||
}
|
||||
if (canSkip)
|
||||
return;
|
||||
const { layout, layoutId } = this.options;
|
||||
/**
|
||||
* If this section of the tree isn't animating we can
|
||||
* delete our target sources for the following frame.
|
||||
*/
|
||||
this.isTreeAnimating = Boolean((this.parent && this.parent.isTreeAnimating) ||
|
||||
this.currentAnimation ||
|
||||
this.pendingAnimation);
|
||||
if (!this.isTreeAnimating) {
|
||||
this.targetDelta = this.relativeTarget = undefined;
|
||||
}
|
||||
if (!this.layout || !(layout || layoutId))
|
||||
return;
|
||||
/**
|
||||
* Reset the corrected box with the latest values from box, as we're then going
|
||||
* to perform mutative operations on it.
|
||||
*/
|
||||
copyBoxInto(this.layoutCorrected, this.layout.layoutBox);
|
||||
/**
|
||||
* Record previous tree scales before updating.
|
||||
*/
|
||||
const prevTreeScaleX = this.treeScale.x;
|
||||
const prevTreeScaleY = this.treeScale.y;
|
||||
/**
|
||||
* Apply all the parent deltas to this box to produce the corrected box. This
|
||||
* is the layout box, as it will appear on screen as a result of the transforms of its parents.
|
||||
*/
|
||||
applyTreeDeltas(this.layoutCorrected, this.treeScale, this.path, isShared);
|
||||
/**
|
||||
* If this layer needs to perform scale correction but doesn't have a target,
|
||||
* use the layout as the target.
|
||||
*/
|
||||
if (lead.layout &&
|
||||
!lead.target &&
|
||||
(this.treeScale.x !== 1 || this.treeScale.y !== 1)) {
|
||||
lead.target = lead.layout.layoutBox;
|
||||
lead.targetWithTransforms = createBox();
|
||||
}
|
||||
const { target } = lead;
|
||||
if (!target) {
|
||||
/**
|
||||
* If we don't have a target to project into, but we were previously
|
||||
* projecting, we want to remove the stored transform and schedule
|
||||
* a render to ensure the elements reflect the removed transform.
|
||||
*/
|
||||
if (this.prevProjectionDelta) {
|
||||
this.createProjectionDeltas();
|
||||
this.scheduleRender();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!this.projectionDelta || !this.prevProjectionDelta) {
|
||||
this.createProjectionDeltas();
|
||||
}
|
||||
else {
|
||||
copyAxisDeltaInto(this.prevProjectionDelta.x, this.projectionDelta.x);
|
||||
copyAxisDeltaInto(this.prevProjectionDelta.y, this.projectionDelta.y);
|
||||
}
|
||||
/**
|
||||
* Update the delta between the corrected box and the target box before user-set transforms were applied.
|
||||
* This will allow us to calculate the corrected borderRadius and boxShadow to compensate
|
||||
* for our layout reprojection, but still allow them to be scaled correctly by the user.
|
||||
* It might be that to simplify this we may want to accept that user-set scale is also corrected
|
||||
* and we wouldn't have to keep and calc both deltas, OR we could support a user setting
|
||||
* to allow people to choose whether these styles are corrected based on just the
|
||||
* layout reprojection or the final bounding box.
|
||||
*/
|
||||
calcBoxDelta(this.projectionDelta, this.layoutCorrected, target, this.latestValues);
|
||||
if (this.treeScale.x !== prevTreeScaleX ||
|
||||
this.treeScale.y !== prevTreeScaleY ||
|
||||
!axisDeltaEquals(this.projectionDelta.x, this.prevProjectionDelta.x) ||
|
||||
!axisDeltaEquals(this.projectionDelta.y, this.prevProjectionDelta.y)) {
|
||||
this.hasProjected = true;
|
||||
this.scheduleRender();
|
||||
this.notifyListeners("projectionUpdate", target);
|
||||
}
|
||||
/**
|
||||
* Increase debug counter for recalculated projections
|
||||
*/
|
||||
if (statsBuffer.value) {
|
||||
metrics.calculatedProjections++;
|
||||
}
|
||||
}
|
||||
hide() {
|
||||
this.isVisible = false;
|
||||
// TODO: Schedule render
|
||||
}
|
||||
show() {
|
||||
this.isVisible = true;
|
||||
// TODO: Schedule render
|
||||
}
|
||||
scheduleRender(notifyAll = true) {
|
||||
this.options.visualElement?.scheduleRender();
|
||||
if (notifyAll) {
|
||||
const stack = this.getStack();
|
||||
stack && stack.scheduleRender();
|
||||
}
|
||||
if (this.resumingFrom && !this.resumingFrom.instance) {
|
||||
this.resumingFrom = undefined;
|
||||
}
|
||||
}
|
||||
createProjectionDeltas() {
|
||||
this.prevProjectionDelta = createDelta();
|
||||
this.projectionDelta = createDelta();
|
||||
this.projectionDeltaWithTransform = createDelta();
|
||||
}
|
||||
setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false, pathFn) {
|
||||
const snapshot = this.snapshot;
|
||||
const snapshotLatestValues = snapshot ? snapshot.latestValues : {};
|
||||
const mixedValues = { ...this.latestValues };
|
||||
const targetDelta = createDelta();
|
||||
if (!this.relativeParent ||
|
||||
!this.relativeParent.options.layoutRoot) {
|
||||
this.relativeTarget = this.relativeTargetOrigin = undefined;
|
||||
}
|
||||
this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
|
||||
const relativeLayout = createBox();
|
||||
const snapshotSource = snapshot ? snapshot.source : undefined;
|
||||
const layoutSource = this.layout ? this.layout.source : undefined;
|
||||
const isSharedLayoutAnimation = snapshotSource !== layoutSource;
|
||||
const stack = this.getStack();
|
||||
const isOnlyMember = !stack || stack.members.length <= 1;
|
||||
const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
|
||||
!isOnlyMember &&
|
||||
this.options.crossfade === true &&
|
||||
!this.path.some(hasOpacityCrossfade));
|
||||
this.animationProgress = 0;
|
||||
let prevRelativeTarget;
|
||||
// The path decides whether the layout shift is worth curving
|
||||
// (distance floor) and resolves the interpolator from the delta.
|
||||
const interpolate = pathFn?.interpolateProjection(delta);
|
||||
this.mixTargetDelta = (latest) => {
|
||||
const progress = latest / 1000;
|
||||
const point = interpolate?.(progress);
|
||||
if (point) {
|
||||
targetDelta.x.translate = point.x;
|
||||
targetDelta.x.scale = mixNumber(delta.x.scale, 1, progress);
|
||||
targetDelta.x.origin = delta.x.origin;
|
||||
targetDelta.x.originPoint = delta.x.originPoint;
|
||||
targetDelta.y.translate = point.y;
|
||||
targetDelta.y.scale = mixNumber(delta.y.scale, 1, progress);
|
||||
targetDelta.y.origin = delta.y.origin;
|
||||
targetDelta.y.originPoint = delta.y.originPoint;
|
||||
}
|
||||
else {
|
||||
mixAxisDeltaLinear(targetDelta.x, delta.x, progress);
|
||||
mixAxisDeltaLinear(targetDelta.y, delta.y, progress);
|
||||
}
|
||||
this.setTargetDelta(targetDelta);
|
||||
if (this.relativeTarget &&
|
||||
this.relativeTargetOrigin &&
|
||||
this.layout &&
|
||||
this.relativeParent &&
|
||||
this.relativeParent.layout) {
|
||||
calcRelativePosition(relativeLayout, this.layout.layoutBox, this.relativeParent.layout.layoutBox, this.options.layoutAnchor || undefined);
|
||||
mixBox(this.relativeTarget, this.relativeTargetOrigin, relativeLayout, progress);
|
||||
/**
|
||||
* If this is an unchanged relative target we can consider the
|
||||
* projection not dirty.
|
||||
*/
|
||||
if (prevRelativeTarget &&
|
||||
boxEquals(this.relativeTarget, prevRelativeTarget)) {
|
||||
this.isProjectionDirty = false;
|
||||
}
|
||||
if (!prevRelativeTarget)
|
||||
prevRelativeTarget = createBox();
|
||||
copyBoxInto(prevRelativeTarget, this.relativeTarget);
|
||||
}
|
||||
if (isSharedLayoutAnimation) {
|
||||
this.animationValues = mixedValues;
|
||||
mixValues(mixedValues, snapshotLatestValues, this.latestValues, progress, shouldCrossfadeOpacity, isOnlyMember);
|
||||
}
|
||||
if (point && point.rotate !== undefined) {
|
||||
// Dedicated `pathRotation` channel, not `rotate`, so an
|
||||
// animating `rotate` is composed with, never clobbered.
|
||||
if (!this.animationValues)
|
||||
this.animationValues = mixedValues;
|
||||
this.animationValues.pathRotation = point.rotate;
|
||||
}
|
||||
this.root.scheduleUpdateProjection();
|
||||
this.scheduleRender();
|
||||
this.animationProgress = progress;
|
||||
};
|
||||
this.mixTargetDelta(this.options.layoutRoot ? 1000 : 0);
|
||||
}
|
||||
startAnimation(options) {
|
||||
this.notifyListeners("animationStart");
|
||||
this.currentAnimation?.stop();
|
||||
this.resumingFrom?.currentAnimation?.stop();
|
||||
if (this.pendingAnimation) {
|
||||
cancelFrame(this.pendingAnimation);
|
||||
this.pendingAnimation = undefined;
|
||||
}
|
||||
/**
|
||||
* Start the animation in the next frame to have a frame with progress 0,
|
||||
* where the target is the same as when the animation started, so we can
|
||||
* calculate the relative positions correctly for instant transitions.
|
||||
*/
|
||||
this.pendingAnimation = frame.update(() => {
|
||||
globalProjectionState.hasAnimatedSinceResize = true;
|
||||
this.motionValue || (this.motionValue = motionValue(0));
|
||||
this.motionValue.jump(0, false);
|
||||
this.currentAnimation = animateSingleValue(this.motionValue, [0, 1000], {
|
||||
...options,
|
||||
velocity: 0,
|
||||
isSync: true,
|
||||
onUpdate: (latest) => {
|
||||
this.mixTargetDelta(latest);
|
||||
options.onUpdate && options.onUpdate(latest);
|
||||
},
|
||||
onComplete: () => {
|
||||
options.onComplete && options.onComplete();
|
||||
this.completeAnimation();
|
||||
},
|
||||
});
|
||||
if (this.resumingFrom) {
|
||||
this.resumingFrom.currentAnimation = this.currentAnimation;
|
||||
}
|
||||
this.pendingAnimation = undefined;
|
||||
});
|
||||
}
|
||||
completeAnimation() {
|
||||
if (this.resumingFrom) {
|
||||
this.resumingFrom.currentAnimation = undefined;
|
||||
this.resumingFrom.preserveOpacity = undefined;
|
||||
}
|
||||
const stack = this.getStack();
|
||||
stack && stack.exitAnimationComplete();
|
||||
this.resumingFrom =
|
||||
this.currentAnimation =
|
||||
this.animationValues =
|
||||
undefined;
|
||||
this.notifyListeners("animationComplete");
|
||||
}
|
||||
finishAnimation() {
|
||||
if (this.currentAnimation) {
|
||||
this.mixTargetDelta && this.mixTargetDelta(animationTarget);
|
||||
this.currentAnimation.stop();
|
||||
}
|
||||
this.completeAnimation();
|
||||
}
|
||||
applyTransformsToTarget() {
|
||||
const lead = this.getLead();
|
||||
let { targetWithTransforms, target, layout, latestValues } = lead;
|
||||
if (!targetWithTransforms || !target || !layout)
|
||||
return;
|
||||
/**
|
||||
* If we're only animating position, and this element isn't the lead element,
|
||||
* then instead of projecting into the lead box we instead want to calculate
|
||||
* a new target that aligns the two boxes but maintains the layout shape.
|
||||
*/
|
||||
if (this !== lead &&
|
||||
this.layout &&
|
||||
layout &&
|
||||
shouldAnimatePositionOnly(this.options.animationType, this.layout.layoutBox, layout.layoutBox)) {
|
||||
target = this.target || createBox();
|
||||
const xLength = calcLength(this.layout.layoutBox.x);
|
||||
target.x.min = lead.target.x.min;
|
||||
target.x.max = target.x.min + xLength;
|
||||
const yLength = calcLength(this.layout.layoutBox.y);
|
||||
target.y.min = lead.target.y.min;
|
||||
target.y.max = target.y.min + yLength;
|
||||
}
|
||||
copyBoxInto(targetWithTransforms, target);
|
||||
/**
|
||||
* Apply the latest user-set transforms to the targetBox to produce the targetBoxFinal.
|
||||
* This is the final box that we will then project into by calculating a transform delta and
|
||||
* applying it to the corrected box.
|
||||
*/
|
||||
transformBox(targetWithTransforms, latestValues);
|
||||
/**
|
||||
* Update the delta between the corrected box and the final target box, after
|
||||
* user-set transforms are applied to it. This will be used by the renderer to
|
||||
* create a transform style that will reproject the element from its layout layout
|
||||
* into the desired bounding box.
|
||||
*/
|
||||
calcBoxDelta(this.projectionDeltaWithTransform, this.layoutCorrected, targetWithTransforms, latestValues);
|
||||
}
|
||||
registerSharedNode(layoutId, node) {
|
||||
if (!this.sharedNodes.has(layoutId)) {
|
||||
this.sharedNodes.set(layoutId, new NodeStack());
|
||||
}
|
||||
const stack = this.sharedNodes.get(layoutId);
|
||||
stack.add(node);
|
||||
const config = node.options.initialPromotionConfig;
|
||||
node.promote({
|
||||
transition: config ? config.transition : undefined,
|
||||
preserveFollowOpacity: config && config.shouldPreserveFollowOpacity
|
||||
? config.shouldPreserveFollowOpacity(node)
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
isLead() {
|
||||
const stack = this.getStack();
|
||||
return stack ? stack.lead === this : true;
|
||||
}
|
||||
getLead() {
|
||||
const { layoutId } = this.options;
|
||||
return layoutId ? this.getStack()?.lead || this : this;
|
||||
}
|
||||
getPrevLead() {
|
||||
const { layoutId } = this.options;
|
||||
return layoutId ? this.getStack()?.prevLead : undefined;
|
||||
}
|
||||
getStack() {
|
||||
const { layoutId } = this.options;
|
||||
if (layoutId)
|
||||
return this.root.sharedNodes.get(layoutId);
|
||||
}
|
||||
promote({ needsReset, transition, preserveFollowOpacity, } = {}) {
|
||||
const stack = this.getStack();
|
||||
if (stack)
|
||||
stack.promote(this, preserveFollowOpacity);
|
||||
if (needsReset) {
|
||||
this.projectionDelta = undefined;
|
||||
this.needsReset = true;
|
||||
}
|
||||
if (transition)
|
||||
this.setOptions({ transition });
|
||||
}
|
||||
relegate() {
|
||||
const stack = this.getStack();
|
||||
if (stack) {
|
||||
return stack.relegate(this);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
resetSkewAndRotation() {
|
||||
const { visualElement } = this.options;
|
||||
if (!visualElement)
|
||||
return;
|
||||
// If there's no detected skew or rotation values, we can early return without a forced render.
|
||||
let hasDistortingTransform = false;
|
||||
/**
|
||||
* An unrolled check for rotation values. Most elements don't have any rotation and
|
||||
* skipping the nested loop and new object creation is 50% faster.
|
||||
*/
|
||||
const { latestValues } = visualElement;
|
||||
if (latestValues.z ||
|
||||
latestValues.rotate ||
|
||||
latestValues.rotateX ||
|
||||
latestValues.rotateY ||
|
||||
latestValues.rotateZ ||
|
||||
latestValues.skewX ||
|
||||
latestValues.skewY) {
|
||||
hasDistortingTransform = true;
|
||||
}
|
||||
// If there's no distorting values, we don't need to do any more.
|
||||
if (!hasDistortingTransform)
|
||||
return;
|
||||
const resetValues = {};
|
||||
if (latestValues.z) {
|
||||
resetDistortingTransform("z", visualElement, resetValues, this.animationValues);
|
||||
}
|
||||
// Check the skew and rotate value of all axes and reset to 0
|
||||
for (let i = 0; i < transformAxes.length; i++) {
|
||||
resetDistortingTransform(`rotate${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
|
||||
resetDistortingTransform(`skew${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
|
||||
}
|
||||
// Force a render of this element to apply the transform with all skews and rotations
|
||||
// set to 0.
|
||||
visualElement.render();
|
||||
// Put back all the values we reset
|
||||
for (const key in resetValues) {
|
||||
visualElement.setStaticValue(key, resetValues[key]);
|
||||
if (this.animationValues) {
|
||||
this.animationValues[key] = resetValues[key];
|
||||
}
|
||||
}
|
||||
// Schedule a render for the next frame. This ensures we won't visually
|
||||
// see the element with the reset rotate value applied.
|
||||
visualElement.scheduleRender();
|
||||
}
|
||||
applyProjectionStyles(targetStyle, // CSSStyleDeclaration - doesn't allow numbers to be assigned to properties
|
||||
styleProp) {
|
||||
if (!this.instance || this.isSVG)
|
||||
return;
|
||||
if (!this.isVisible) {
|
||||
targetStyle.visibility = "hidden";
|
||||
return;
|
||||
}
|
||||
const transformTemplate = this.getTransformTemplate();
|
||||
if (this.needsReset) {
|
||||
this.needsReset = false;
|
||||
targetStyle.visibility = "";
|
||||
targetStyle.opacity = "";
|
||||
targetStyle.pointerEvents =
|
||||
resolveMotionValue(styleProp?.pointerEvents) || "";
|
||||
targetStyle.transform = transformTemplate
|
||||
? transformTemplate(this.latestValues, "")
|
||||
: "none";
|
||||
return;
|
||||
}
|
||||
const lead = this.getLead();
|
||||
if (!this.projectionDelta || !this.layout || !lead.target) {
|
||||
if (this.options.layoutId) {
|
||||
targetStyle.opacity =
|
||||
this.latestValues.opacity !== undefined
|
||||
? this.latestValues.opacity
|
||||
: 1;
|
||||
targetStyle.pointerEvents =
|
||||
resolveMotionValue(styleProp?.pointerEvents) || "";
|
||||
}
|
||||
if (this.hasProjected && !hasTransform(this.latestValues)) {
|
||||
targetStyle.transform = transformTemplate
|
||||
? transformTemplate({}, "")
|
||||
: "none";
|
||||
this.hasProjected = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
targetStyle.visibility = "";
|
||||
const valuesToRender = lead.animationValues || lead.latestValues;
|
||||
this.applyTransformsToTarget();
|
||||
let transform = buildProjectionTransform(this.projectionDeltaWithTransform, this.treeScale, valuesToRender);
|
||||
if (transformTemplate) {
|
||||
transform = transformTemplate(valuesToRender, transform);
|
||||
}
|
||||
targetStyle.transform = transform;
|
||||
const { x, y } = this.projectionDelta;
|
||||
targetStyle.transformOrigin = `${x.origin * 100}% ${y.origin * 100}% 0`;
|
||||
if (lead.animationValues) {
|
||||
/**
|
||||
* If the lead component is animating, assign this either the entering/leaving
|
||||
* opacity
|
||||
*/
|
||||
targetStyle.opacity =
|
||||
lead === this
|
||||
? valuesToRender.opacity ??
|
||||
this.latestValues.opacity ??
|
||||
1
|
||||
: this.preserveOpacity
|
||||
? this.latestValues.opacity
|
||||
: valuesToRender.opacityExit;
|
||||
}
|
||||
else {
|
||||
/**
|
||||
* Or we're not animating at all, set the lead component to its layout
|
||||
* opacity and other components to hidden.
|
||||
*/
|
||||
targetStyle.opacity =
|
||||
lead === this
|
||||
? valuesToRender.opacity !== undefined
|
||||
? valuesToRender.opacity
|
||||
: ""
|
||||
: valuesToRender.opacityExit !== undefined
|
||||
? valuesToRender.opacityExit
|
||||
: 0;
|
||||
}
|
||||
/**
|
||||
* Apply scale correction
|
||||
*/
|
||||
for (const key in scaleCorrectors) {
|
||||
if (valuesToRender[key] === undefined)
|
||||
continue;
|
||||
const { correct, applyTo, isCSSVariable } = scaleCorrectors[key];
|
||||
/**
|
||||
* Only apply scale correction to the value if we have an
|
||||
* active projection transform. Otherwise these values become
|
||||
* vulnerable to distortion if the element changes size without
|
||||
* a corresponding layout animation.
|
||||
*/
|
||||
const corrected = transform === "none"
|
||||
? valuesToRender[key]
|
||||
: correct(valuesToRender[key], lead);
|
||||
if (applyTo) {
|
||||
const num = applyTo.length;
|
||||
for (let i = 0; i < num; i++) {
|
||||
targetStyle[applyTo[i]] = corrected;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If this is a CSS variable, set it directly on the instance.
|
||||
// Replacing this function from creating styles to setting them
|
||||
// would be a good place to remove per frame object creation
|
||||
if (isCSSVariable) {
|
||||
this.options.visualElement.renderState.vars[key] = corrected;
|
||||
}
|
||||
else {
|
||||
targetStyle[key] = corrected;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Disable pointer events on follow components. This is to ensure
|
||||
* that if a follow component covers a lead component it doesn't block
|
||||
* pointer events on the lead.
|
||||
*/
|
||||
if (this.options.layoutId) {
|
||||
targetStyle.pointerEvents =
|
||||
lead === this
|
||||
? resolveMotionValue(styleProp?.pointerEvents) || ""
|
||||
: "none";
|
||||
}
|
||||
}
|
||||
clearSnapshot() {
|
||||
this.resumeFrom = this.snapshot = undefined;
|
||||
}
|
||||
// Only run on root
|
||||
resetTree() {
|
||||
this.root.nodes.forEach((node) => node.currentAnimation?.stop());
|
||||
this.root.nodes.forEach(clearMeasurements);
|
||||
this.root.sharedNodes.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
function updateLayout(node) {
|
||||
node.updateLayout();
|
||||
}
|
||||
function notifyLayoutUpdate(node) {
|
||||
const snapshot = node.resumeFrom?.snapshot || node.snapshot;
|
||||
if (node.isLead() &&
|
||||
node.layout &&
|
||||
snapshot &&
|
||||
node.hasListeners("didUpdate")) {
|
||||
const { layoutBox: layout, measuredBox: measuredLayout } = node.layout;
|
||||
const { animationType } = node.options;
|
||||
const isShared = snapshot.source !== node.layout.source;
|
||||
// TODO Maybe we want to also resize the layout snapshot so we don't trigger
|
||||
// animations for instance if layout="size" and an element has only changed position
|
||||
if (animationType === "size") {
|
||||
eachAxis((axis) => {
|
||||
const axisSnapshot = isShared
|
||||
? snapshot.measuredBox[axis]
|
||||
: snapshot.layoutBox[axis];
|
||||
const length = calcLength(axisSnapshot);
|
||||
axisSnapshot.min = layout[axis].min;
|
||||
axisSnapshot.max = axisSnapshot.min + length;
|
||||
});
|
||||
}
|
||||
else if (animationType === "x" || animationType === "y") {
|
||||
const snapAxis = animationType === "x" ? "y" : "x";
|
||||
copyAxisInto(isShared
|
||||
? snapshot.measuredBox[snapAxis]
|
||||
: snapshot.layoutBox[snapAxis], layout[snapAxis]);
|
||||
}
|
||||
else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
|
||||
eachAxis((axis) => {
|
||||
const axisSnapshot = isShared
|
||||
? snapshot.measuredBox[axis]
|
||||
: snapshot.layoutBox[axis];
|
||||
const length = calcLength(layout[axis]);
|
||||
axisSnapshot.max = axisSnapshot.min + length;
|
||||
/**
|
||||
* Ensure relative target gets resized and rerendererd
|
||||
*/
|
||||
if (node.relativeTarget && !node.currentAnimation) {
|
||||
node.isProjectionDirty = true;
|
||||
node.relativeTarget[axis].max =
|
||||
node.relativeTarget[axis].min + length;
|
||||
}
|
||||
});
|
||||
}
|
||||
const layoutDelta = createDelta();
|
||||
calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
|
||||
const visualDelta = createDelta();
|
||||
if (isShared) {
|
||||
calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
|
||||
}
|
||||
else {
|
||||
calcBoxDelta(visualDelta, layout, snapshot.layoutBox);
|
||||
}
|
||||
const hasLayoutChanged = !isDeltaZero(layoutDelta);
|
||||
let hasRelativeLayoutChanged = false;
|
||||
if (!node.resumeFrom) {
|
||||
const relativeParent = node.getClosestProjectingParent();
|
||||
/**
|
||||
* If the relativeParent is itself resuming from a different element then
|
||||
* the relative snapshot is not relavent
|
||||
*/
|
||||
if (relativeParent && !relativeParent.resumeFrom) {
|
||||
const { snapshot: parentSnapshot, layout: parentLayout } = relativeParent;
|
||||
if (parentSnapshot && parentLayout) {
|
||||
const anchor = node.options.layoutAnchor || undefined;
|
||||
const relativeSnapshot = createBox();
|
||||
calcRelativePosition(relativeSnapshot, snapshot.layoutBox, parentSnapshot.layoutBox, anchor);
|
||||
const relativeLayout = createBox();
|
||||
calcRelativePosition(relativeLayout, layout, parentLayout.layoutBox, anchor);
|
||||
if (!boxEqualsRounded(relativeSnapshot, relativeLayout)) {
|
||||
hasRelativeLayoutChanged = true;
|
||||
}
|
||||
if (relativeParent.options.layoutRoot) {
|
||||
node.relativeTarget = relativeLayout;
|
||||
node.relativeTargetOrigin = relativeSnapshot;
|
||||
node.relativeParent = relativeParent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node.notifyListeners("didUpdate", {
|
||||
layout,
|
||||
snapshot,
|
||||
delta: visualDelta,
|
||||
layoutDelta,
|
||||
hasLayoutChanged,
|
||||
hasRelativeLayoutChanged,
|
||||
});
|
||||
}
|
||||
else if (node.isLead()) {
|
||||
const { onExitComplete } = node.options;
|
||||
onExitComplete && onExitComplete();
|
||||
}
|
||||
/**
|
||||
* Clearing transition
|
||||
* TODO: Investigate why this transition is being passed in as {type: false } from Framer
|
||||
* and why we need it at all
|
||||
*/
|
||||
node.options.transition = undefined;
|
||||
}
|
||||
function propagateDirtyNodes(node) {
|
||||
/**
|
||||
* Increase debug counter for nodes encountered this frame
|
||||
*/
|
||||
if (statsBuffer.value) {
|
||||
metrics.nodes++;
|
||||
}
|
||||
if (!node.parent)
|
||||
return;
|
||||
/**
|
||||
* If this node isn't projecting, propagate isProjectionDirty. It will have
|
||||
* no performance impact but it will allow the next child that *is* projecting
|
||||
* but *isn't* dirty to just check its parent to see if *any* ancestor needs
|
||||
* correcting.
|
||||
*/
|
||||
if (!node.isProjecting()) {
|
||||
node.isProjectionDirty = node.parent.isProjectionDirty;
|
||||
}
|
||||
/**
|
||||
* Propagate isSharedProjectionDirty and isTransformDirty
|
||||
* throughout the whole tree. A future revision can take another look at
|
||||
* this but for safety we still recalcualte shared nodes.
|
||||
*/
|
||||
node.isSharedProjectionDirty || (node.isSharedProjectionDirty = Boolean(node.isProjectionDirty ||
|
||||
node.parent.isProjectionDirty ||
|
||||
node.parent.isSharedProjectionDirty));
|
||||
node.isTransformDirty || (node.isTransformDirty = node.parent.isTransformDirty);
|
||||
}
|
||||
function cleanDirtyNodes(node) {
|
||||
node.isProjectionDirty =
|
||||
node.isSharedProjectionDirty =
|
||||
node.isTransformDirty =
|
||||
false;
|
||||
}
|
||||
function clearSnapshot(node) {
|
||||
node.clearSnapshot();
|
||||
}
|
||||
function clearMeasurements(node) {
|
||||
node.clearMeasurements();
|
||||
}
|
||||
function forceLayoutMeasure(node) {
|
||||
node.isLayoutDirty = true;
|
||||
node.updateLayout();
|
||||
}
|
||||
function clearIsLayoutDirty(node) {
|
||||
node.isLayoutDirty = false;
|
||||
}
|
||||
/**
|
||||
* When a node is animation-blocked (e.g. during drag) and its component
|
||||
* didn't re-render (memoized), willUpdate() is never called so there's
|
||||
* no snapshot. Use the previous layout as a snapshot and mark dirty so
|
||||
* resetTransform/updateLayout/notifyLayoutUpdate process it normally.
|
||||
*/
|
||||
function ensureDraggedNodesSnapshotted(node) {
|
||||
if (node.isAnimationBlocked && node.layout && !node.isLayoutDirty) {
|
||||
node.snapshot = node.layout;
|
||||
node.isLayoutDirty = true;
|
||||
}
|
||||
}
|
||||
function resetTransformStyle(node) {
|
||||
const { visualElement } = node.options;
|
||||
if (visualElement && visualElement.getProps().onBeforeLayoutMeasure) {
|
||||
visualElement.notify("BeforeLayoutMeasure");
|
||||
}
|
||||
node.resetTransform();
|
||||
}
|
||||
function finishAnimation(node) {
|
||||
node.finishAnimation();
|
||||
node.targetDelta = node.relativeTarget = node.target = undefined;
|
||||
node.isProjectionDirty = true;
|
||||
}
|
||||
function resolveTargetDelta(node) {
|
||||
node.resolveTargetDelta();
|
||||
}
|
||||
function calcProjection(node) {
|
||||
node.calcProjection();
|
||||
}
|
||||
function resetSkewAndRotation(node) {
|
||||
node.resetSkewAndRotation();
|
||||
}
|
||||
function removeLeadSnapshots(stack) {
|
||||
stack.removeLeadSnapshot();
|
||||
}
|
||||
function mixAxisDeltaLinear(output, delta, p) {
|
||||
output.translate = mixNumber(delta.translate, 0, p);
|
||||
output.scale = mixNumber(delta.scale, 1, p);
|
||||
output.origin = delta.origin;
|
||||
output.originPoint = delta.originPoint;
|
||||
}
|
||||
function mixAxis(output, from, to, p) {
|
||||
output.min = mixNumber(from.min, to.min, p);
|
||||
output.max = mixNumber(from.max, to.max, p);
|
||||
}
|
||||
function mixBox(output, from, to, p) {
|
||||
mixAxis(output.x, from.x, to.x, p);
|
||||
mixAxis(output.y, from.y, to.y, p);
|
||||
}
|
||||
function hasOpacityCrossfade(node) {
|
||||
return (node.animationValues && node.animationValues.opacityExit !== undefined);
|
||||
}
|
||||
const defaultLayoutTransition = {
|
||||
duration: 0.45,
|
||||
ease: [0.4, 0, 0.1, 1],
|
||||
};
|
||||
const userAgentContains = (string) => typeof navigator !== "undefined" &&
|
||||
navigator.userAgent &&
|
||||
navigator.userAgent.toLowerCase().includes(string);
|
||||
/**
|
||||
* Measured bounding boxes must be rounded in Safari and
|
||||
* left untouched in Chrome, otherwise non-integer layouts within scaled-up elements
|
||||
* can appear to jump.
|
||||
*/
|
||||
const roundPoint = userAgentContains("applewebkit/") && !userAgentContains("chrome/")
|
||||
? Math.round
|
||||
: noop;
|
||||
function roundAxis(axis) {
|
||||
// Round to the nearest .5 pixels to support subpixel layouts
|
||||
axis.min = roundPoint(axis.min);
|
||||
axis.max = roundPoint(axis.max);
|
||||
}
|
||||
function roundBox(box) {
|
||||
roundAxis(box.x);
|
||||
roundAxis(box.y);
|
||||
}
|
||||
function shouldAnimatePositionOnly(animationType, snapshot, layout) {
|
||||
return (animationType === "position" ||
|
||||
(animationType === "preserve-aspect" &&
|
||||
!isNear(aspectRatio(snapshot), aspectRatio(layout), 0.2)));
|
||||
}
|
||||
function checkNodeWasScrollRoot(node) {
|
||||
return node !== node.root && node.scroll?.wasRoot;
|
||||
}
|
||||
|
||||
export { cleanDirtyNodes, createProjectionNode, mixAxis, mixBox, propagateDirtyNodes };
|
||||
//# sourceMappingURL=create-projection-node.mjs.map
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user