@tarsis/toolkit 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{gl-CjbUMD81.js → gl-CF6SrQxS.js} +1 -1
- package/dist/{gl-iTVe0k4w.cjs → gl-u7lLnVc7.cjs} +1 -1
- package/dist/hooks.cjs +1 -1
- package/dist/hooks.d.ts +17 -1
- package/dist/hooks.js +1 -1
- package/dist/{index-bCrWVOOV.js → index-DDE-qC4q.js} +9100 -7299
- package/dist/{index-CstBFEdC.cjs → index-DMypN5i0.cjs} +1 -1
- package/dist/{index-CW3ku3DU.js → index-DUlCyGFJ.js} +1 -1
- package/dist/{index-DwAKA1Mc.cjs → index-DrMeiPM1.cjs} +9112 -7300
- package/dist/index.cjs +14 -3
- package/dist/index.d.ts +277 -141
- package/dist/index.js +2 -2
- package/dist/styles.css +3057 -2824
- package/dist/{useWindowReady-Il0Ibn7I.cjs → useWindowReady-CIYJL39Z.cjs} +1440 -1419
- package/dist/{useWindowReady-DVV-s65K.js → useWindowReady-CYOtIur4.js} +1439 -1418
- package/package.json +13 -9
|
@@ -17,8 +17,6 @@ function useConstant(init) {
|
|
|
17
17
|
return ref.current;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const isBrowser = typeof window !== "undefined";
|
|
21
|
-
|
|
22
20
|
function addUniqueItem(arr, item) {
|
|
23
21
|
if (arr.indexOf(item) === -1)
|
|
24
22
|
arr.push(item);
|
|
@@ -2939,6 +2937,153 @@ function getValueTransition$1(transition, key) {
|
|
|
2939
2937
|
transition);
|
|
2940
2938
|
}
|
|
2941
2939
|
|
|
2940
|
+
const underDampedSpring = {
|
|
2941
|
+
type: "spring",
|
|
2942
|
+
stiffness: 500,
|
|
2943
|
+
damping: 25,
|
|
2944
|
+
restSpeed: 10,
|
|
2945
|
+
};
|
|
2946
|
+
const criticallyDampedSpring = (target) => ({
|
|
2947
|
+
type: "spring",
|
|
2948
|
+
stiffness: 550,
|
|
2949
|
+
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
2950
|
+
restSpeed: 10,
|
|
2951
|
+
});
|
|
2952
|
+
const keyframesTransition = {
|
|
2953
|
+
type: "keyframes",
|
|
2954
|
+
duration: 0.8,
|
|
2955
|
+
};
|
|
2956
|
+
/**
|
|
2957
|
+
* Default easing curve is a slightly shallower version of
|
|
2958
|
+
* the default browser easing curve.
|
|
2959
|
+
*/
|
|
2960
|
+
const ease = {
|
|
2961
|
+
type: "keyframes",
|
|
2962
|
+
ease: [0.25, 0.1, 0.35, 1],
|
|
2963
|
+
duration: 0.3,
|
|
2964
|
+
};
|
|
2965
|
+
const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
2966
|
+
if (keyframes.length > 2) {
|
|
2967
|
+
return keyframesTransition;
|
|
2968
|
+
}
|
|
2969
|
+
else if (transformProps.has(valueKey)) {
|
|
2970
|
+
return valueKey.startsWith("scale")
|
|
2971
|
+
? criticallyDampedSpring(keyframes[1])
|
|
2972
|
+
: underDampedSpring;
|
|
2973
|
+
}
|
|
2974
|
+
return ease;
|
|
2975
|
+
};
|
|
2976
|
+
|
|
2977
|
+
/**
|
|
2978
|
+
* Decide whether a transition is defined on a given Transition.
|
|
2979
|
+
* This filters out orchestration options and returns true
|
|
2980
|
+
* if any options are left.
|
|
2981
|
+
*/
|
|
2982
|
+
function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
|
|
2983
|
+
return !!Object.keys(transition).length;
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
const isNotNull = (value) => value !== null;
|
|
2987
|
+
function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
|
|
2988
|
+
const resolvedKeyframes = keyframes.filter(isNotNull);
|
|
2989
|
+
const index = repeat && repeatType !== "loop" && repeat % 2 === 1
|
|
2990
|
+
? 0
|
|
2991
|
+
: resolvedKeyframes.length - 1;
|
|
2992
|
+
return resolvedKeyframes[index]
|
|
2993
|
+
;
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
|
|
2997
|
+
const valueTransition = getValueTransition$1(transition, name) || {};
|
|
2998
|
+
/**
|
|
2999
|
+
* Most transition values are currently completely overwritten by value-specific
|
|
3000
|
+
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
3001
|
+
* delay actually does inherit from the root transition if not value-specific.
|
|
3002
|
+
*/
|
|
3003
|
+
const delay = valueTransition.delay || transition.delay || 0;
|
|
3004
|
+
/**
|
|
3005
|
+
* Elapsed isn't a public transition option but can be passed through from
|
|
3006
|
+
* optimized appear effects in milliseconds.
|
|
3007
|
+
*/
|
|
3008
|
+
let { elapsed = 0 } = transition;
|
|
3009
|
+
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
3010
|
+
const options = {
|
|
3011
|
+
keyframes: Array.isArray(target) ? target : [null, target],
|
|
3012
|
+
ease: "easeOut",
|
|
3013
|
+
velocity: value.getVelocity(),
|
|
3014
|
+
...valueTransition,
|
|
3015
|
+
delay: -elapsed,
|
|
3016
|
+
onUpdate: (v) => {
|
|
3017
|
+
value.set(v);
|
|
3018
|
+
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
3019
|
+
},
|
|
3020
|
+
onComplete: () => {
|
|
3021
|
+
onComplete();
|
|
3022
|
+
valueTransition.onComplete && valueTransition.onComplete();
|
|
3023
|
+
},
|
|
3024
|
+
name,
|
|
3025
|
+
motionValue: value,
|
|
3026
|
+
element: isHandoff ? undefined : element,
|
|
3027
|
+
};
|
|
3028
|
+
/**
|
|
3029
|
+
* If there's no transition defined for this value, we can generate
|
|
3030
|
+
* unique transition settings for this value.
|
|
3031
|
+
*/
|
|
3032
|
+
if (!isTransitionDefined(valueTransition)) {
|
|
3033
|
+
Object.assign(options, getDefaultTransition(name, options));
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Both WAAPI and our internal animation functions use durations
|
|
3037
|
+
* as defined by milliseconds, while our external API defines them
|
|
3038
|
+
* as seconds.
|
|
3039
|
+
*/
|
|
3040
|
+
options.duration && (options.duration = secondsToMilliseconds(options.duration));
|
|
3041
|
+
options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay));
|
|
3042
|
+
/**
|
|
3043
|
+
* Support deprecated way to set initial value. Prefer keyframe syntax.
|
|
3044
|
+
*/
|
|
3045
|
+
if (options.from !== undefined) {
|
|
3046
|
+
options.keyframes[0] = options.from;
|
|
3047
|
+
}
|
|
3048
|
+
let shouldSkip = false;
|
|
3049
|
+
if (options.type === false ||
|
|
3050
|
+
(options.duration === 0 && !options.repeatDelay)) {
|
|
3051
|
+
makeAnimationInstant(options);
|
|
3052
|
+
if (options.delay === 0) {
|
|
3053
|
+
shouldSkip = true;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
if (MotionGlobalConfig.instantAnimations ||
|
|
3057
|
+
MotionGlobalConfig.skipAnimations) {
|
|
3058
|
+
shouldSkip = true;
|
|
3059
|
+
makeAnimationInstant(options);
|
|
3060
|
+
options.delay = 0;
|
|
3061
|
+
}
|
|
3062
|
+
/**
|
|
3063
|
+
* If the transition type or easing has been explicitly set by the user
|
|
3064
|
+
* then we don't want to allow flattening the animation.
|
|
3065
|
+
*/
|
|
3066
|
+
options.allowFlatten = !valueTransition.type && !valueTransition.ease;
|
|
3067
|
+
/**
|
|
3068
|
+
* If we can or must skip creating the animation, and apply only
|
|
3069
|
+
* the final keyframe, do so. We also check once keyframes are resolved but
|
|
3070
|
+
* this early check prevents the need to create an animation at all.
|
|
3071
|
+
*/
|
|
3072
|
+
if (shouldSkip && !isHandoff && value.get() !== undefined) {
|
|
3073
|
+
const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
|
|
3074
|
+
if (finalKeyframe !== undefined) {
|
|
3075
|
+
frame.update(() => {
|
|
3076
|
+
options.onUpdate(finalKeyframe);
|
|
3077
|
+
options.onComplete();
|
|
3078
|
+
});
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
return valueTransition.isSync
|
|
3083
|
+
? new JSAnimation(options)
|
|
3084
|
+
: new AsyncMotionValueAnimation(options);
|
|
3085
|
+
};
|
|
3086
|
+
|
|
2942
3087
|
const positionalKeys = new Set([
|
|
2943
3088
|
"width",
|
|
2944
3089
|
"height",
|
|
@@ -2950,449 +3095,77 @@ const positionalKeys = new Set([
|
|
|
2950
3095
|
]);
|
|
2951
3096
|
|
|
2952
3097
|
/**
|
|
2953
|
-
*
|
|
3098
|
+
* Maximum time between the value of two frames, beyond which we
|
|
3099
|
+
* assume the velocity has since been 0.
|
|
2954
3100
|
*/
|
|
2955
|
-
const
|
|
2956
|
-
|
|
2957
|
-
|
|
3101
|
+
const MAX_VELOCITY_DELTA = 30;
|
|
3102
|
+
const isFloat = (value) => {
|
|
3103
|
+
return !isNaN(parseFloat(value));
|
|
3104
|
+
};
|
|
3105
|
+
const collectMotionValues = {
|
|
3106
|
+
current: undefined,
|
|
2958
3107
|
};
|
|
2959
|
-
|
|
2960
|
-
/**
|
|
2961
|
-
* Tests a provided value against a ValueType
|
|
2962
|
-
*/
|
|
2963
|
-
const testValueType = (v) => (type) => type.test(v);
|
|
2964
|
-
|
|
2965
|
-
/**
|
|
2966
|
-
* A list of value types commonly used for dimensions
|
|
2967
|
-
*/
|
|
2968
|
-
const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
|
|
2969
3108
|
/**
|
|
2970
|
-
*
|
|
3109
|
+
* `MotionValue` is used to track the state and velocity of motion values.
|
|
3110
|
+
*
|
|
3111
|
+
* @public
|
|
2971
3112
|
*/
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3113
|
+
class MotionValue {
|
|
3114
|
+
/**
|
|
3115
|
+
* @param init - The initiating value
|
|
3116
|
+
* @param config - Optional configuration options
|
|
3117
|
+
*
|
|
3118
|
+
* - `transformer`: A function to transform incoming values with.
|
|
3119
|
+
*/
|
|
3120
|
+
constructor(init, options = {}) {
|
|
3121
|
+
/**
|
|
3122
|
+
* Tracks whether this value can output a velocity. Currently this is only true
|
|
3123
|
+
* if the value is numerical, but we might be able to widen the scope here and support
|
|
3124
|
+
* other value types.
|
|
3125
|
+
*
|
|
3126
|
+
* @internal
|
|
3127
|
+
*/
|
|
3128
|
+
this.canTrackVelocity = null;
|
|
3129
|
+
/**
|
|
3130
|
+
* An object containing a SubscriptionManager for each active event.
|
|
3131
|
+
*/
|
|
3132
|
+
this.events = {};
|
|
3133
|
+
this.updateAndNotify = (v) => {
|
|
3134
|
+
const currentTime = time.now();
|
|
3135
|
+
/**
|
|
3136
|
+
* If we're updating the value during another frame or eventloop
|
|
3137
|
+
* than the previous frame, then the we set the previous frame value
|
|
3138
|
+
* to current.
|
|
3139
|
+
*/
|
|
3140
|
+
if (this.updatedAt !== currentTime) {
|
|
3141
|
+
this.setPrevFrameValue();
|
|
3142
|
+
}
|
|
3143
|
+
this.prev = this.current;
|
|
3144
|
+
this.setCurrent(v);
|
|
3145
|
+
// Update update subscribers
|
|
3146
|
+
if (this.current !== this.prev) {
|
|
3147
|
+
this.events.change?.notify(this.current);
|
|
3148
|
+
if (this.dependents) {
|
|
3149
|
+
for (const dependent of this.dependents) {
|
|
3150
|
+
dependent.dirty();
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
this.hasAnimated = false;
|
|
3156
|
+
this.setCurrent(init);
|
|
3157
|
+
this.owner = options.owner;
|
|
2977
3158
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
3159
|
+
setCurrent(current) {
|
|
3160
|
+
this.current = current;
|
|
3161
|
+
this.updatedAt = time.now();
|
|
3162
|
+
if (this.canTrackVelocity === null && current !== undefined) {
|
|
3163
|
+
this.canTrackVelocity = isFloat(this.current);
|
|
3164
|
+
}
|
|
2980
3165
|
}
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
/**
|
|
2987
|
-
* Properties that should default to 1 or 100%
|
|
2988
|
-
*/
|
|
2989
|
-
const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
|
|
2990
|
-
function applyDefaultFilter(v) {
|
|
2991
|
-
const [name, value] = v.slice(0, -1).split("(");
|
|
2992
|
-
if (name === "drop-shadow")
|
|
2993
|
-
return v;
|
|
2994
|
-
const [number] = value.match(floatRegex) || [];
|
|
2995
|
-
if (!number)
|
|
2996
|
-
return v;
|
|
2997
|
-
const unit = value.replace(number, "");
|
|
2998
|
-
let defaultValue = maxDefaults.has(name) ? 1 : 0;
|
|
2999
|
-
if (number !== value)
|
|
3000
|
-
defaultValue *= 100;
|
|
3001
|
-
return name + "(" + defaultValue + unit + ")";
|
|
3002
|
-
}
|
|
3003
|
-
const functionRegex = /\b([a-z-]*)\(.*?\)/gu;
|
|
3004
|
-
const filter = {
|
|
3005
|
-
...complex,
|
|
3006
|
-
getAnimatableNone: (v) => {
|
|
3007
|
-
const functions = v.match(functionRegex);
|
|
3008
|
-
return functions ? functions.map(applyDefaultFilter).join(" ") : v;
|
|
3009
|
-
},
|
|
3010
|
-
};
|
|
3011
|
-
|
|
3012
|
-
const int = {
|
|
3013
|
-
...number,
|
|
3014
|
-
transform: Math.round,
|
|
3015
|
-
};
|
|
3016
|
-
|
|
3017
|
-
const transformValueTypes = {
|
|
3018
|
-
rotate: degrees,
|
|
3019
|
-
rotateX: degrees,
|
|
3020
|
-
rotateY: degrees,
|
|
3021
|
-
rotateZ: degrees,
|
|
3022
|
-
scale,
|
|
3023
|
-
scaleX: scale,
|
|
3024
|
-
scaleY: scale,
|
|
3025
|
-
scaleZ: scale,
|
|
3026
|
-
skew: degrees,
|
|
3027
|
-
skewX: degrees,
|
|
3028
|
-
skewY: degrees,
|
|
3029
|
-
distance: px,
|
|
3030
|
-
translateX: px,
|
|
3031
|
-
translateY: px,
|
|
3032
|
-
translateZ: px,
|
|
3033
|
-
x: px,
|
|
3034
|
-
y: px,
|
|
3035
|
-
z: px,
|
|
3036
|
-
perspective: px,
|
|
3037
|
-
transformPerspective: px,
|
|
3038
|
-
opacity: alpha,
|
|
3039
|
-
originX: progressPercentage,
|
|
3040
|
-
originY: progressPercentage,
|
|
3041
|
-
originZ: px,
|
|
3042
|
-
};
|
|
3043
|
-
|
|
3044
|
-
const numberValueTypes = {
|
|
3045
|
-
// Border props
|
|
3046
|
-
borderWidth: px,
|
|
3047
|
-
borderTopWidth: px,
|
|
3048
|
-
borderRightWidth: px,
|
|
3049
|
-
borderBottomWidth: px,
|
|
3050
|
-
borderLeftWidth: px,
|
|
3051
|
-
borderRadius: px,
|
|
3052
|
-
radius: px,
|
|
3053
|
-
borderTopLeftRadius: px,
|
|
3054
|
-
borderTopRightRadius: px,
|
|
3055
|
-
borderBottomRightRadius: px,
|
|
3056
|
-
borderBottomLeftRadius: px,
|
|
3057
|
-
// Positioning props
|
|
3058
|
-
width: px,
|
|
3059
|
-
maxWidth: px,
|
|
3060
|
-
height: px,
|
|
3061
|
-
maxHeight: px,
|
|
3062
|
-
top: px,
|
|
3063
|
-
right: px,
|
|
3064
|
-
bottom: px,
|
|
3065
|
-
left: px,
|
|
3066
|
-
inset: px,
|
|
3067
|
-
insetBlock: px,
|
|
3068
|
-
insetBlockStart: px,
|
|
3069
|
-
insetBlockEnd: px,
|
|
3070
|
-
insetInline: px,
|
|
3071
|
-
insetInlineStart: px,
|
|
3072
|
-
insetInlineEnd: px,
|
|
3073
|
-
// Spacing props
|
|
3074
|
-
padding: px,
|
|
3075
|
-
paddingTop: px,
|
|
3076
|
-
paddingRight: px,
|
|
3077
|
-
paddingBottom: px,
|
|
3078
|
-
paddingLeft: px,
|
|
3079
|
-
paddingBlock: px,
|
|
3080
|
-
paddingBlockStart: px,
|
|
3081
|
-
paddingBlockEnd: px,
|
|
3082
|
-
paddingInline: px,
|
|
3083
|
-
paddingInlineStart: px,
|
|
3084
|
-
paddingInlineEnd: px,
|
|
3085
|
-
margin: px,
|
|
3086
|
-
marginTop: px,
|
|
3087
|
-
marginRight: px,
|
|
3088
|
-
marginBottom: px,
|
|
3089
|
-
marginLeft: px,
|
|
3090
|
-
marginBlock: px,
|
|
3091
|
-
marginBlockStart: px,
|
|
3092
|
-
marginBlockEnd: px,
|
|
3093
|
-
marginInline: px,
|
|
3094
|
-
marginInlineStart: px,
|
|
3095
|
-
marginInlineEnd: px,
|
|
3096
|
-
// Misc
|
|
3097
|
-
backgroundPositionX: px,
|
|
3098
|
-
backgroundPositionY: px,
|
|
3099
|
-
...transformValueTypes,
|
|
3100
|
-
zIndex: int,
|
|
3101
|
-
// SVG
|
|
3102
|
-
fillOpacity: alpha,
|
|
3103
|
-
strokeOpacity: alpha,
|
|
3104
|
-
numOctaves: int,
|
|
3105
|
-
};
|
|
3106
|
-
|
|
3107
|
-
/**
|
|
3108
|
-
* A map of default value types for common values
|
|
3109
|
-
*/
|
|
3110
|
-
const defaultValueTypes = {
|
|
3111
|
-
...numberValueTypes,
|
|
3112
|
-
// Color props
|
|
3113
|
-
color,
|
|
3114
|
-
backgroundColor: color,
|
|
3115
|
-
outlineColor: color,
|
|
3116
|
-
fill: color,
|
|
3117
|
-
stroke: color,
|
|
3118
|
-
// Border props
|
|
3119
|
-
borderColor: color,
|
|
3120
|
-
borderTopColor: color,
|
|
3121
|
-
borderRightColor: color,
|
|
3122
|
-
borderBottomColor: color,
|
|
3123
|
-
borderLeftColor: color,
|
|
3124
|
-
filter,
|
|
3125
|
-
WebkitFilter: filter,
|
|
3126
|
-
};
|
|
3127
|
-
/**
|
|
3128
|
-
* Gets the default ValueType for the provided value key
|
|
3129
|
-
*/
|
|
3130
|
-
const getDefaultValueType = (key) => defaultValueTypes[key];
|
|
3131
|
-
|
|
3132
|
-
function getAnimatableNone(key, value) {
|
|
3133
|
-
let defaultValueType = getDefaultValueType(key);
|
|
3134
|
-
if (defaultValueType !== filter)
|
|
3135
|
-
defaultValueType = complex;
|
|
3136
|
-
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
|
|
3137
|
-
return defaultValueType.getAnimatableNone
|
|
3138
|
-
? defaultValueType.getAnimatableNone(value)
|
|
3139
|
-
: undefined;
|
|
3140
|
-
}
|
|
3141
|
-
|
|
3142
|
-
/**
|
|
3143
|
-
* If we encounter keyframes like "none" or "0" and we also have keyframes like
|
|
3144
|
-
* "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
|
|
3145
|
-
* the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
|
|
3146
|
-
* zero equivalents, i.e. "#fff0" or "0px 0px".
|
|
3147
|
-
*/
|
|
3148
|
-
const invalidTemplates = new Set(["auto", "none", "0"]);
|
|
3149
|
-
function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
|
|
3150
|
-
let i = 0;
|
|
3151
|
-
let animatableTemplate = undefined;
|
|
3152
|
-
while (i < unresolvedKeyframes.length && !animatableTemplate) {
|
|
3153
|
-
const keyframe = unresolvedKeyframes[i];
|
|
3154
|
-
if (typeof keyframe === "string" &&
|
|
3155
|
-
!invalidTemplates.has(keyframe) &&
|
|
3156
|
-
analyseComplexValue(keyframe).values.length) {
|
|
3157
|
-
animatableTemplate = unresolvedKeyframes[i];
|
|
3158
|
-
}
|
|
3159
|
-
i++;
|
|
3160
|
-
}
|
|
3161
|
-
if (animatableTemplate && name) {
|
|
3162
|
-
for (const noneIndex of noneKeyframeIndexes) {
|
|
3163
|
-
unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
|
|
3168
|
-
class DOMKeyframesResolver extends KeyframeResolver {
|
|
3169
|
-
constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
|
|
3170
|
-
super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
|
|
3171
|
-
}
|
|
3172
|
-
readKeyframes() {
|
|
3173
|
-
const { unresolvedKeyframes, element, name } = this;
|
|
3174
|
-
if (!element || !element.current)
|
|
3175
|
-
return;
|
|
3176
|
-
super.readKeyframes();
|
|
3177
|
-
/**
|
|
3178
|
-
* If any keyframe is a CSS variable, we need to find its value by sampling the element
|
|
3179
|
-
*/
|
|
3180
|
-
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
3181
|
-
let keyframe = unresolvedKeyframes[i];
|
|
3182
|
-
if (typeof keyframe === "string") {
|
|
3183
|
-
keyframe = keyframe.trim();
|
|
3184
|
-
if (isCSSVariableToken(keyframe)) {
|
|
3185
|
-
const resolved = getVariableValue(keyframe, element.current);
|
|
3186
|
-
if (resolved !== undefined) {
|
|
3187
|
-
unresolvedKeyframes[i] = resolved;
|
|
3188
|
-
}
|
|
3189
|
-
if (i === unresolvedKeyframes.length - 1) {
|
|
3190
|
-
this.finalKeyframe = keyframe;
|
|
3191
|
-
}
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
}
|
|
3195
|
-
/**
|
|
3196
|
-
* Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
|
|
3197
|
-
* This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
|
|
3198
|
-
* have a far bigger performance impact.
|
|
3199
|
-
*/
|
|
3200
|
-
this.resolveNoneKeyframes();
|
|
3201
|
-
/**
|
|
3202
|
-
* Check to see if unit type has changed. If so schedule jobs that will
|
|
3203
|
-
* temporarily set styles to the destination keyframes.
|
|
3204
|
-
* Skip if we have more than two keyframes or this isn't a positional value.
|
|
3205
|
-
* TODO: We can throw if there are multiple keyframes and the value type changes.
|
|
3206
|
-
*/
|
|
3207
|
-
if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
|
|
3208
|
-
return;
|
|
3209
|
-
}
|
|
3210
|
-
const [origin, target] = unresolvedKeyframes;
|
|
3211
|
-
const originType = findDimensionValueType(origin);
|
|
3212
|
-
const targetType = findDimensionValueType(target);
|
|
3213
|
-
/**
|
|
3214
|
-
* If one keyframe contains embedded CSS variables (e.g. in calc()) and the other
|
|
3215
|
-
* doesn't, we need to measure to convert to pixels. This handles GitHub issue #3410.
|
|
3216
|
-
*/
|
|
3217
|
-
const originHasVar = containsCSSVariable(origin);
|
|
3218
|
-
const targetHasVar = containsCSSVariable(target);
|
|
3219
|
-
if (originHasVar !== targetHasVar && positionalValues[name]) {
|
|
3220
|
-
this.needsMeasurement = true;
|
|
3221
|
-
return;
|
|
3222
|
-
}
|
|
3223
|
-
/**
|
|
3224
|
-
* Either we don't recognise these value types or we can animate between them.
|
|
3225
|
-
*/
|
|
3226
|
-
if (originType === targetType)
|
|
3227
|
-
return;
|
|
3228
|
-
/**
|
|
3229
|
-
* If both values are numbers or pixels, we can animate between them by
|
|
3230
|
-
* converting them to numbers.
|
|
3231
|
-
*/
|
|
3232
|
-
if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
|
|
3233
|
-
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
3234
|
-
const value = unresolvedKeyframes[i];
|
|
3235
|
-
if (typeof value === "string") {
|
|
3236
|
-
unresolvedKeyframes[i] = parseFloat(value);
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
}
|
|
3240
|
-
else if (positionalValues[name]) {
|
|
3241
|
-
/**
|
|
3242
|
-
* Else, the only way to resolve this is by measuring the element.
|
|
3243
|
-
*/
|
|
3244
|
-
this.needsMeasurement = true;
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
resolveNoneKeyframes() {
|
|
3248
|
-
const { unresolvedKeyframes, name } = this;
|
|
3249
|
-
const noneKeyframeIndexes = [];
|
|
3250
|
-
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
3251
|
-
if (unresolvedKeyframes[i] === null ||
|
|
3252
|
-
isNone(unresolvedKeyframes[i])) {
|
|
3253
|
-
noneKeyframeIndexes.push(i);
|
|
3254
|
-
}
|
|
3255
|
-
}
|
|
3256
|
-
if (noneKeyframeIndexes.length) {
|
|
3257
|
-
makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
measureInitialState() {
|
|
3261
|
-
const { element, unresolvedKeyframes, name } = this;
|
|
3262
|
-
if (!element || !element.current)
|
|
3263
|
-
return;
|
|
3264
|
-
if (name === "height") {
|
|
3265
|
-
this.suspendedScrollY = window.pageYOffset;
|
|
3266
|
-
}
|
|
3267
|
-
this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
|
|
3268
|
-
unresolvedKeyframes[0] = this.measuredOrigin;
|
|
3269
|
-
// Set final key frame to measure after next render
|
|
3270
|
-
const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
|
|
3271
|
-
if (measureKeyframe !== undefined) {
|
|
3272
|
-
element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
measureEndState() {
|
|
3276
|
-
const { element, name, unresolvedKeyframes } = this;
|
|
3277
|
-
if (!element || !element.current)
|
|
3278
|
-
return;
|
|
3279
|
-
const value = element.getValue(name);
|
|
3280
|
-
value && value.jump(this.measuredOrigin, false);
|
|
3281
|
-
const finalKeyframeIndex = unresolvedKeyframes.length - 1;
|
|
3282
|
-
const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
|
|
3283
|
-
unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
|
|
3284
|
-
if (finalKeyframe !== null && this.finalKeyframe === undefined) {
|
|
3285
|
-
this.finalKeyframe = finalKeyframe;
|
|
3286
|
-
}
|
|
3287
|
-
// If we removed transform values, reapply them before the next render
|
|
3288
|
-
if (this.removedTransforms?.length) {
|
|
3289
|
-
this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
|
|
3290
|
-
element
|
|
3291
|
-
.getValue(unsetTransformName)
|
|
3292
|
-
.set(unsetTransformValue);
|
|
3293
|
-
});
|
|
3294
|
-
}
|
|
3295
|
-
this.resolveNoneKeyframes();
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
|
|
3299
|
-
function resolveElements(elementOrSelector, scope, selectorCache) {
|
|
3300
|
-
if (elementOrSelector instanceof EventTarget) {
|
|
3301
|
-
return [elementOrSelector];
|
|
3302
|
-
}
|
|
3303
|
-
else if (typeof elementOrSelector === "string") {
|
|
3304
|
-
let root = document;
|
|
3305
|
-
if (scope) {
|
|
3306
|
-
root = scope.current;
|
|
3307
|
-
}
|
|
3308
|
-
const elements = selectorCache?.[elementOrSelector] ??
|
|
3309
|
-
root.querySelectorAll(elementOrSelector);
|
|
3310
|
-
return elements ? Array.from(elements) : [];
|
|
3311
|
-
}
|
|
3312
|
-
return Array.from(elementOrSelector);
|
|
3313
|
-
}
|
|
3314
|
-
|
|
3315
|
-
/**
|
|
3316
|
-
* Provided a value and a ValueType, returns the value as that value type.
|
|
3317
|
-
*/
|
|
3318
|
-
const getValueAsType = (value, type) => {
|
|
3319
|
-
return type && typeof value === "number"
|
|
3320
|
-
? type.transform(value)
|
|
3321
|
-
: value;
|
|
3322
|
-
};
|
|
3323
|
-
|
|
3324
|
-
/**
|
|
3325
|
-
* Maximum time between the value of two frames, beyond which we
|
|
3326
|
-
* assume the velocity has since been 0.
|
|
3327
|
-
*/
|
|
3328
|
-
const MAX_VELOCITY_DELTA = 30;
|
|
3329
|
-
const isFloat = (value) => {
|
|
3330
|
-
return !isNaN(parseFloat(value));
|
|
3331
|
-
};
|
|
3332
|
-
const collectMotionValues = {
|
|
3333
|
-
current: undefined,
|
|
3334
|
-
};
|
|
3335
|
-
/**
|
|
3336
|
-
* `MotionValue` is used to track the state and velocity of motion values.
|
|
3337
|
-
*
|
|
3338
|
-
* @public
|
|
3339
|
-
*/
|
|
3340
|
-
class MotionValue {
|
|
3341
|
-
/**
|
|
3342
|
-
* @param init - The initiating value
|
|
3343
|
-
* @param config - Optional configuration options
|
|
3344
|
-
*
|
|
3345
|
-
* - `transformer`: A function to transform incoming values with.
|
|
3346
|
-
*/
|
|
3347
|
-
constructor(init, options = {}) {
|
|
3348
|
-
/**
|
|
3349
|
-
* Tracks whether this value can output a velocity. Currently this is only true
|
|
3350
|
-
* if the value is numerical, but we might be able to widen the scope here and support
|
|
3351
|
-
* other value types.
|
|
3352
|
-
*
|
|
3353
|
-
* @internal
|
|
3354
|
-
*/
|
|
3355
|
-
this.canTrackVelocity = null;
|
|
3356
|
-
/**
|
|
3357
|
-
* An object containing a SubscriptionManager for each active event.
|
|
3358
|
-
*/
|
|
3359
|
-
this.events = {};
|
|
3360
|
-
this.updateAndNotify = (v) => {
|
|
3361
|
-
const currentTime = time.now();
|
|
3362
|
-
/**
|
|
3363
|
-
* If we're updating the value during another frame or eventloop
|
|
3364
|
-
* than the previous frame, then the we set the previous frame value
|
|
3365
|
-
* to current.
|
|
3366
|
-
*/
|
|
3367
|
-
if (this.updatedAt !== currentTime) {
|
|
3368
|
-
this.setPrevFrameValue();
|
|
3369
|
-
}
|
|
3370
|
-
this.prev = this.current;
|
|
3371
|
-
this.setCurrent(v);
|
|
3372
|
-
// Update update subscribers
|
|
3373
|
-
if (this.current !== this.prev) {
|
|
3374
|
-
this.events.change?.notify(this.current);
|
|
3375
|
-
if (this.dependents) {
|
|
3376
|
-
for (const dependent of this.dependents) {
|
|
3377
|
-
dependent.dirty();
|
|
3378
|
-
}
|
|
3379
|
-
}
|
|
3380
|
-
}
|
|
3381
|
-
};
|
|
3382
|
-
this.hasAnimated = false;
|
|
3383
|
-
this.setCurrent(init);
|
|
3384
|
-
this.owner = options.owner;
|
|
3385
|
-
}
|
|
3386
|
-
setCurrent(current) {
|
|
3387
|
-
this.current = current;
|
|
3388
|
-
this.updatedAt = time.now();
|
|
3389
|
-
if (this.canTrackVelocity === null && current !== undefined) {
|
|
3390
|
-
this.canTrackVelocity = isFloat(this.current);
|
|
3391
|
-
}
|
|
3392
|
-
}
|
|
3393
|
-
setPrevFrameValue(prevFrameValue = this.current) {
|
|
3394
|
-
this.prevFrameValue = prevFrameValue;
|
|
3395
|
-
this.prevUpdatedAt = this.updatedAt;
|
|
3166
|
+
setPrevFrameValue(prevFrameValue = this.current) {
|
|
3167
|
+
this.prevFrameValue = prevFrameValue;
|
|
3168
|
+
this.prevUpdatedAt = this.updatedAt;
|
|
3396
3169
|
}
|
|
3397
3170
|
/**
|
|
3398
3171
|
* Adds a function that will be notified when the `MotionValue` is updated.
|
|
@@ -3639,667 +3412,584 @@ function motionValue(init, options) {
|
|
|
3639
3412
|
return new MotionValue(init, options);
|
|
3640
3413
|
}
|
|
3641
3414
|
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
function isSVGElement(element) {
|
|
3650
|
-
return isObject(element) && "ownerSVGElement" in element;
|
|
3415
|
+
function getValueState(visualElement) {
|
|
3416
|
+
const state = [{}, {}];
|
|
3417
|
+
visualElement?.values.forEach((value, key) => {
|
|
3418
|
+
state[0][key] = value.get();
|
|
3419
|
+
state[1][key] = value.getVelocity();
|
|
3420
|
+
});
|
|
3421
|
+
return state;
|
|
3651
3422
|
}
|
|
3652
|
-
|
|
3653
|
-
/**
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3423
|
+
function resolveVariantFromProps(props, definition, custom, visualElement) {
|
|
3424
|
+
/**
|
|
3425
|
+
* If the variant definition is a function, resolve.
|
|
3426
|
+
*/
|
|
3427
|
+
if (typeof definition === "function") {
|
|
3428
|
+
const [current, velocity] = getValueState(visualElement);
|
|
3429
|
+
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
3430
|
+
}
|
|
3431
|
+
/**
|
|
3432
|
+
* If the variant definition is a variant label, or
|
|
3433
|
+
* the function returned a variant label, resolve.
|
|
3434
|
+
*/
|
|
3435
|
+
if (typeof definition === "string") {
|
|
3436
|
+
definition = props.variants && props.variants[definition];
|
|
3437
|
+
}
|
|
3438
|
+
/**
|
|
3439
|
+
* At this point we've resolved both functions and variant labels,
|
|
3440
|
+
* but the resolved variant label might itself have been a function.
|
|
3441
|
+
* If so, resolve. This can only have returned a valid target object.
|
|
3442
|
+
*/
|
|
3443
|
+
if (typeof definition === "function") {
|
|
3444
|
+
const [current, velocity] = getValueState(visualElement);
|
|
3445
|
+
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
3446
|
+
}
|
|
3447
|
+
return definition;
|
|
3659
3448
|
}
|
|
3660
3449
|
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
* A list of all ValueTypes
|
|
3665
|
-
*/
|
|
3666
|
-
const valueTypes = [...dimensionValueTypes, color, complex];
|
|
3667
|
-
/**
|
|
3668
|
-
* Tests a value against the list of ValueTypes
|
|
3669
|
-
*/
|
|
3670
|
-
const findValueType = (v) => valueTypes.find(testValueType(v));
|
|
3671
|
-
|
|
3672
|
-
/**
|
|
3673
|
-
* @public
|
|
3674
|
-
*/
|
|
3675
|
-
const MotionConfigContext = React.createContext({
|
|
3676
|
-
transformPagePoint: (p) => p,
|
|
3677
|
-
isStatic: false,
|
|
3678
|
-
reducedMotion: "never",
|
|
3679
|
-
});
|
|
3680
|
-
|
|
3681
|
-
const featureProps = {
|
|
3682
|
-
animation: [
|
|
3683
|
-
"animate",
|
|
3684
|
-
"variants",
|
|
3685
|
-
"whileHover",
|
|
3686
|
-
"whileTap",
|
|
3687
|
-
"exit",
|
|
3688
|
-
"whileInView",
|
|
3689
|
-
"whileFocus",
|
|
3690
|
-
"whileDrag",
|
|
3691
|
-
],
|
|
3692
|
-
exit: ["exit"],
|
|
3693
|
-
drag: ["drag", "dragControls"],
|
|
3694
|
-
focus: ["whileFocus"],
|
|
3695
|
-
hover: ["whileHover", "onHoverStart", "onHoverEnd"],
|
|
3696
|
-
tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
|
|
3697
|
-
pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
|
|
3698
|
-
inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
|
|
3699
|
-
layout: ["layout", "layoutId"],
|
|
3700
|
-
};
|
|
3701
|
-
const featureDefinitions = {};
|
|
3702
|
-
for (const key in featureProps) {
|
|
3703
|
-
featureDefinitions[key] = {
|
|
3704
|
-
isEnabled: (props) => featureProps[key].some((name) => !!props[name]),
|
|
3705
|
-
};
|
|
3450
|
+
function resolveVariant(visualElement, definition, custom) {
|
|
3451
|
+
const props = visualElement.getProps();
|
|
3452
|
+
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
|
|
3706
3453
|
}
|
|
3707
3454
|
|
|
3708
|
-
|
|
3709
|
-
return (v
|
|
3710
|
-
|
|
3711
|
-
typeof v.start === "function");
|
|
3712
|
-
}
|
|
3455
|
+
const isKeyframesTarget = (v) => {
|
|
3456
|
+
return Array.isArray(v);
|
|
3457
|
+
};
|
|
3713
3458
|
|
|
3714
3459
|
/**
|
|
3715
|
-
*
|
|
3460
|
+
* Set VisualElement's MotionValue, creating a new MotionValue for it if
|
|
3461
|
+
* it doesn't exist.
|
|
3716
3462
|
*/
|
|
3717
|
-
function
|
|
3718
|
-
|
|
3463
|
+
function setMotionValue(visualElement, key, value) {
|
|
3464
|
+
if (visualElement.hasValue(key)) {
|
|
3465
|
+
visualElement.getValue(key).set(value);
|
|
3466
|
+
}
|
|
3467
|
+
else {
|
|
3468
|
+
visualElement.addValue(key, motionValue(value));
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
function resolveFinalValueInKeyframes(v) {
|
|
3472
|
+
// TODO maybe throw if v.length - 1 is placeholder token?
|
|
3473
|
+
return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
|
|
3474
|
+
}
|
|
3475
|
+
function setTarget(visualElement, definition) {
|
|
3476
|
+
const resolved = resolveVariant(visualElement, definition);
|
|
3477
|
+
let { transitionEnd = {}, transition = {}, ...target } = resolved || {};
|
|
3478
|
+
target = { ...target, ...transitionEnd };
|
|
3479
|
+
for (const key in target) {
|
|
3480
|
+
const value = resolveFinalValueInKeyframes(target[key]);
|
|
3481
|
+
setMotionValue(visualElement, key, value);
|
|
3482
|
+
}
|
|
3719
3483
|
}
|
|
3720
3484
|
|
|
3721
|
-
const
|
|
3722
|
-
"animate",
|
|
3723
|
-
"whileInView",
|
|
3724
|
-
"whileFocus",
|
|
3725
|
-
"whileHover",
|
|
3726
|
-
"whileTap",
|
|
3727
|
-
"whileDrag",
|
|
3728
|
-
"exit",
|
|
3729
|
-
];
|
|
3730
|
-
const variantProps = ["initial", ...variantPriorityOrder];
|
|
3485
|
+
const isMotionValue = (value) => Boolean(value && value.getVelocity);
|
|
3731
3486
|
|
|
3732
|
-
function
|
|
3733
|
-
return (
|
|
3734
|
-
variantProps.some((name) => isVariantLabel(props[name])));
|
|
3735
|
-
}
|
|
3736
|
-
function isVariantNode(props) {
|
|
3737
|
-
return Boolean(isControllingVariants(props) || props.variants);
|
|
3487
|
+
function isWillChangeMotionValue(value) {
|
|
3488
|
+
return Boolean(isMotionValue(value) && value.add);
|
|
3738
3489
|
}
|
|
3739
3490
|
|
|
3740
|
-
function
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3491
|
+
function addValueToWillChange(visualElement, key) {
|
|
3492
|
+
const willChange = visualElement.getValue("willChange");
|
|
3493
|
+
/**
|
|
3494
|
+
* It could be that a user has set willChange to a regular MotionValue,
|
|
3495
|
+
* in which case we can't add the value to it.
|
|
3496
|
+
*/
|
|
3497
|
+
if (isWillChangeMotionValue(willChange)) {
|
|
3498
|
+
return willChange.add(key);
|
|
3499
|
+
}
|
|
3500
|
+
else if (!willChange && MotionGlobalConfig.WillChange) {
|
|
3501
|
+
const newWillChange = new MotionGlobalConfig.WillChange("auto");
|
|
3502
|
+
visualElement.addValue("willChange", newWillChange);
|
|
3503
|
+
newWillChange.add(key);
|
|
3504
|
+
}
|
|
3744
3505
|
}
|
|
3745
|
-
/**
|
|
3746
|
-
* We always correct borderRadius as a percentage rather than pixels to reduce paints.
|
|
3747
|
-
* For example, if you are projecting a box that is 100px wide with a 10px borderRadius
|
|
3748
|
-
* into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
|
|
3749
|
-
* borderRadius in both states. If we animate between the two in pixels that will trigger
|
|
3750
|
-
* a paint each time. If we animate between the two in percentage we'll avoid a paint.
|
|
3751
|
-
*/
|
|
3752
|
-
const correctBorderRadius = {
|
|
3753
|
-
correct: (latest, node) => {
|
|
3754
|
-
if (!node.target)
|
|
3755
|
-
return latest;
|
|
3756
|
-
/**
|
|
3757
|
-
* If latest is a string, if it's a percentage we can return immediately as it's
|
|
3758
|
-
* going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
|
|
3759
|
-
*/
|
|
3760
|
-
if (typeof latest === "string") {
|
|
3761
|
-
if (px.test(latest)) {
|
|
3762
|
-
latest = parseFloat(latest);
|
|
3763
|
-
}
|
|
3764
|
-
else {
|
|
3765
|
-
return latest;
|
|
3766
|
-
}
|
|
3767
|
-
}
|
|
3768
|
-
/**
|
|
3769
|
-
* If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
|
|
3770
|
-
* pixel value as a percentage of each axis
|
|
3771
|
-
*/
|
|
3772
|
-
const x = pixelsToPercent(latest, node.target.x);
|
|
3773
|
-
const y = pixelsToPercent(latest, node.target.y);
|
|
3774
|
-
return `${x}% ${y}%`;
|
|
3775
|
-
},
|
|
3776
|
-
};
|
|
3777
3506
|
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
const shadow = complex.parse(latest);
|
|
3782
|
-
// TODO: Doesn't support multiple shadows
|
|
3783
|
-
if (shadow.length > 5)
|
|
3784
|
-
return original;
|
|
3785
|
-
const template = complex.createTransformer(latest);
|
|
3786
|
-
const offset = typeof shadow[0] !== "number" ? 1 : 0;
|
|
3787
|
-
// Calculate the overall context scale
|
|
3788
|
-
const xScale = projectionDelta.x.scale * treeScale.x;
|
|
3789
|
-
const yScale = projectionDelta.y.scale * treeScale.y;
|
|
3790
|
-
shadow[0 + offset] /= xScale;
|
|
3791
|
-
shadow[1 + offset] /= yScale;
|
|
3792
|
-
/**
|
|
3793
|
-
* Ideally we'd correct x and y scales individually, but because blur and
|
|
3794
|
-
* spread apply to both we have to take a scale average and apply that instead.
|
|
3795
|
-
* We could potentially improve the outcome of this by incorporating the ratio between
|
|
3796
|
-
* the two scales.
|
|
3797
|
-
*/
|
|
3798
|
-
const averageScale = mixNumber$1(xScale, yScale, 0.5);
|
|
3799
|
-
// Blur
|
|
3800
|
-
if (typeof shadow[2 + offset] === "number")
|
|
3801
|
-
shadow[2 + offset] /= averageScale;
|
|
3802
|
-
// Spread
|
|
3803
|
-
if (typeof shadow[3 + offset] === "number")
|
|
3804
|
-
shadow[3 + offset] /= averageScale;
|
|
3805
|
-
return template(shadow);
|
|
3806
|
-
},
|
|
3807
|
-
};
|
|
3507
|
+
function camelToDash(str) {
|
|
3508
|
+
return str.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
|
|
3509
|
+
}
|
|
3808
3510
|
|
|
3809
|
-
const
|
|
3810
|
-
|
|
3811
|
-
...correctBorderRadius,
|
|
3812
|
-
applyTo: [
|
|
3813
|
-
"borderTopLeftRadius",
|
|
3814
|
-
"borderTopRightRadius",
|
|
3815
|
-
"borderBottomLeftRadius",
|
|
3816
|
-
"borderBottomRightRadius",
|
|
3817
|
-
],
|
|
3818
|
-
},
|
|
3819
|
-
borderTopLeftRadius: correctBorderRadius,
|
|
3820
|
-
borderTopRightRadius: correctBorderRadius,
|
|
3821
|
-
borderBottomLeftRadius: correctBorderRadius,
|
|
3822
|
-
borderBottomRightRadius: correctBorderRadius,
|
|
3823
|
-
boxShadow: correctBoxShadow,
|
|
3824
|
-
};
|
|
3511
|
+
const optimizedAppearDataId = "framerAppearId";
|
|
3512
|
+
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
3825
3513
|
|
|
3826
|
-
function
|
|
3827
|
-
return
|
|
3828
|
-
key.startsWith("origin") ||
|
|
3829
|
-
((layout || layoutId !== undefined) &&
|
|
3830
|
-
(!!scaleCorrectors[key] || key === "opacity")));
|
|
3514
|
+
function getOptimisedAppearId(visualElement) {
|
|
3515
|
+
return visualElement.props[optimizedAppearDataAttribute];
|
|
3831
3516
|
}
|
|
3832
3517
|
|
|
3833
|
-
const translateAlias = {
|
|
3834
|
-
x: "translateX",
|
|
3835
|
-
y: "translateY",
|
|
3836
|
-
z: "translateZ",
|
|
3837
|
-
transformPerspective: "perspective",
|
|
3838
|
-
};
|
|
3839
|
-
const numTransforms = transformPropOrder.length;
|
|
3840
3518
|
/**
|
|
3841
|
-
*
|
|
3842
|
-
*
|
|
3843
|
-
*
|
|
3844
|
-
*
|
|
3519
|
+
* Decide whether we should block this animation. Previously, we achieved this
|
|
3520
|
+
* just by checking whether the key was listed in protectedKeys, but this
|
|
3521
|
+
* posed problems if an animation was triggered by afterChildren and protectedKeys
|
|
3522
|
+
* had been set to true in the meantime.
|
|
3845
3523
|
*/
|
|
3846
|
-
function
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
/**
|
|
3851
|
-
* Loop over all possible transforms in order, adding the ones that
|
|
3852
|
-
* are present to the transform string.
|
|
3853
|
-
*/
|
|
3854
|
-
for (let i = 0; i < numTransforms; i++) {
|
|
3855
|
-
const key = transformPropOrder[i];
|
|
3856
|
-
const value = latestValues[key];
|
|
3857
|
-
if (value === undefined)
|
|
3858
|
-
continue;
|
|
3859
|
-
let valueIsDefault = true;
|
|
3860
|
-
if (typeof value === "number") {
|
|
3861
|
-
valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
|
|
3862
|
-
}
|
|
3863
|
-
else {
|
|
3864
|
-
valueIsDefault = parseFloat(value) === 0;
|
|
3865
|
-
}
|
|
3866
|
-
if (!valueIsDefault || transformTemplate) {
|
|
3867
|
-
const valueAsType = getValueAsType(value, numberValueTypes[key]);
|
|
3868
|
-
if (!valueIsDefault) {
|
|
3869
|
-
transformIsDefault = false;
|
|
3870
|
-
const transformName = translateAlias[key] || key;
|
|
3871
|
-
transformString += `${transformName}(${valueAsType}) `;
|
|
3872
|
-
}
|
|
3873
|
-
if (transformTemplate) {
|
|
3874
|
-
transform[key] = valueAsType;
|
|
3875
|
-
}
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
transformString = transformString.trim();
|
|
3879
|
-
// If we have a custom `transform` template, pass our transform values and
|
|
3880
|
-
// generated transformString to that before returning
|
|
3881
|
-
if (transformTemplate) {
|
|
3882
|
-
transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
|
|
3883
|
-
}
|
|
3884
|
-
else if (transformIsDefault) {
|
|
3885
|
-
transformString = "none";
|
|
3886
|
-
}
|
|
3887
|
-
return transformString;
|
|
3524
|
+
function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
|
|
3525
|
+
const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
|
|
3526
|
+
needsAnimating[key] = false;
|
|
3527
|
+
return shouldBlock;
|
|
3888
3528
|
}
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
if (transformProps.has(key)) {
|
|
3904
|
-
// If this is a transform, flag to enable further transform processing
|
|
3905
|
-
hasTransform = true;
|
|
3529
|
+
function animateTarget(visualElement, targetAndTransition, { delay = 0, transitionOverride, type } = {}) {
|
|
3530
|
+
let { transition = visualElement.getDefaultTransition(), transitionEnd, ...target } = targetAndTransition;
|
|
3531
|
+
if (transitionOverride)
|
|
3532
|
+
transition = transitionOverride;
|
|
3533
|
+
const animations = [];
|
|
3534
|
+
const animationTypeState = type &&
|
|
3535
|
+
visualElement.animationState &&
|
|
3536
|
+
visualElement.animationState.getState()[type];
|
|
3537
|
+
for (const key in target) {
|
|
3538
|
+
const value = visualElement.getValue(key, visualElement.latestValues[key] ?? null);
|
|
3539
|
+
const valueTarget = target[key];
|
|
3540
|
+
if (valueTarget === undefined ||
|
|
3541
|
+
(animationTypeState &&
|
|
3542
|
+
shouldBlockAnimation(animationTypeState, key))) {
|
|
3906
3543
|
continue;
|
|
3907
3544
|
}
|
|
3908
|
-
|
|
3909
|
-
|
|
3545
|
+
const valueTransition = {
|
|
3546
|
+
delay,
|
|
3547
|
+
...getValueTransition$1(transition || {}, key),
|
|
3548
|
+
};
|
|
3549
|
+
/**
|
|
3550
|
+
* If the value is already at the defined target, skip the animation.
|
|
3551
|
+
*/
|
|
3552
|
+
const currentValue = value.get();
|
|
3553
|
+
if (currentValue !== undefined &&
|
|
3554
|
+
!value.isAnimating &&
|
|
3555
|
+
!Array.isArray(valueTarget) &&
|
|
3556
|
+
valueTarget === currentValue &&
|
|
3557
|
+
!valueTransition.velocity) {
|
|
3910
3558
|
continue;
|
|
3911
3559
|
}
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3560
|
+
/**
|
|
3561
|
+
* If this is the first time a value is being animated, check
|
|
3562
|
+
* to see if we're handling off from an existing animation.
|
|
3563
|
+
*/
|
|
3564
|
+
let isHandoff = false;
|
|
3565
|
+
if (window.MotionHandoffAnimation) {
|
|
3566
|
+
const appearId = getOptimisedAppearId(visualElement);
|
|
3567
|
+
if (appearId) {
|
|
3568
|
+
const startTime = window.MotionHandoffAnimation(appearId, key, frame);
|
|
3569
|
+
if (startTime !== null) {
|
|
3570
|
+
valueTransition.startTime = startTime;
|
|
3571
|
+
isHandoff = true;
|
|
3572
|
+
}
|
|
3923
3573
|
}
|
|
3924
3574
|
}
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
* If we have previously created a transform but currently don't have any,
|
|
3933
|
-
* reset transform style to none.
|
|
3934
|
-
*/
|
|
3935
|
-
style.transform = "none";
|
|
3575
|
+
addValueToWillChange(visualElement, key);
|
|
3576
|
+
value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && positionalKeys.has(key)
|
|
3577
|
+
? { type: false }
|
|
3578
|
+
: valueTransition, visualElement, isHandoff));
|
|
3579
|
+
const animation = value.animation;
|
|
3580
|
+
if (animation) {
|
|
3581
|
+
animations.push(animation);
|
|
3936
3582
|
}
|
|
3937
3583
|
}
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
style.transformOrigin = `${originX} ${originY} ${originZ}`;
|
|
3584
|
+
if (transitionEnd) {
|
|
3585
|
+
Promise.all(animations).then(() => {
|
|
3586
|
+
frame.update(() => {
|
|
3587
|
+
transitionEnd && setTarget(visualElement, transitionEnd);
|
|
3588
|
+
});
|
|
3589
|
+
});
|
|
3945
3590
|
}
|
|
3591
|
+
return animations;
|
|
3946
3592
|
}
|
|
3947
3593
|
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
array: "strokeDasharray",
|
|
3594
|
+
/**
|
|
3595
|
+
* ValueType for "auto"
|
|
3596
|
+
*/
|
|
3597
|
+
const auto = {
|
|
3598
|
+
test: (v) => v === "auto",
|
|
3599
|
+
parse: (v) => v,
|
|
3955
3600
|
};
|
|
3601
|
+
|
|
3956
3602
|
/**
|
|
3957
|
-
*
|
|
3958
|
-
* our custom pathLength, pathSpacing and pathOffset into stroke-dashoffset
|
|
3959
|
-
* and stroke-dasharray attributes.
|
|
3960
|
-
*
|
|
3961
|
-
* This function is mutative to reduce per-frame GC.
|
|
3603
|
+
* Tests a provided value against a ValueType
|
|
3962
3604
|
*/
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3605
|
+
const testValueType = (v) => (type) => type.test(v);
|
|
3606
|
+
|
|
3607
|
+
/**
|
|
3608
|
+
* A list of value types commonly used for dimensions
|
|
3609
|
+
*/
|
|
3610
|
+
const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
|
|
3611
|
+
/**
|
|
3612
|
+
* Tests a dimensional value against the list of dimension ValueTypes
|
|
3613
|
+
*/
|
|
3614
|
+
const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
|
|
3615
|
+
|
|
3616
|
+
function isNone(value) {
|
|
3617
|
+
if (typeof value === "number") {
|
|
3618
|
+
return value === 0;
|
|
3619
|
+
}
|
|
3620
|
+
else if (value !== null) {
|
|
3621
|
+
return value === "none" || value === "0" || isZeroValueString(value);
|
|
3622
|
+
}
|
|
3623
|
+
else {
|
|
3624
|
+
return true;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
/**
|
|
3629
|
+
* Properties that should default to 1 or 100%
|
|
3630
|
+
*/
|
|
3631
|
+
const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
|
|
3632
|
+
function applyDefaultFilter(v) {
|
|
3633
|
+
const [name, value] = v.slice(0, -1).split("(");
|
|
3634
|
+
if (name === "drop-shadow")
|
|
3635
|
+
return v;
|
|
3636
|
+
const [number] = value.match(floatRegex) || [];
|
|
3637
|
+
if (!number)
|
|
3638
|
+
return v;
|
|
3639
|
+
const unit = value.replace(number, "");
|
|
3640
|
+
let defaultValue = maxDefaults.has(name) ? 1 : 0;
|
|
3641
|
+
if (number !== value)
|
|
3642
|
+
defaultValue *= 100;
|
|
3643
|
+
return name + "(" + defaultValue + unit + ")";
|
|
3975
3644
|
}
|
|
3645
|
+
const functionRegex = /\b([a-z-]*)\(.*?\)/gu;
|
|
3646
|
+
const filter = {
|
|
3647
|
+
...complex,
|
|
3648
|
+
getAnimatableNone: (v) => {
|
|
3649
|
+
const functions = v.match(functionRegex);
|
|
3650
|
+
return functions ? functions.map(applyDefaultFilter).join(" ") : v;
|
|
3651
|
+
},
|
|
3652
|
+
};
|
|
3653
|
+
|
|
3654
|
+
const int = {
|
|
3655
|
+
...number,
|
|
3656
|
+
transform: Math.round,
|
|
3657
|
+
};
|
|
3658
|
+
|
|
3659
|
+
const transformValueTypes = {
|
|
3660
|
+
rotate: degrees,
|
|
3661
|
+
rotateX: degrees,
|
|
3662
|
+
rotateY: degrees,
|
|
3663
|
+
rotateZ: degrees,
|
|
3664
|
+
scale,
|
|
3665
|
+
scaleX: scale,
|
|
3666
|
+
scaleY: scale,
|
|
3667
|
+
scaleZ: scale,
|
|
3668
|
+
skew: degrees,
|
|
3669
|
+
skewX: degrees,
|
|
3670
|
+
skewY: degrees,
|
|
3671
|
+
distance: px,
|
|
3672
|
+
translateX: px,
|
|
3673
|
+
translateY: px,
|
|
3674
|
+
translateZ: px,
|
|
3675
|
+
x: px,
|
|
3676
|
+
y: px,
|
|
3677
|
+
z: px,
|
|
3678
|
+
perspective: px,
|
|
3679
|
+
transformPerspective: px,
|
|
3680
|
+
opacity: alpha,
|
|
3681
|
+
originX: progressPercentage,
|
|
3682
|
+
originY: progressPercentage,
|
|
3683
|
+
originZ: px,
|
|
3684
|
+
};
|
|
3685
|
+
|
|
3686
|
+
const numberValueTypes = {
|
|
3687
|
+
// Border props
|
|
3688
|
+
borderWidth: px,
|
|
3689
|
+
borderTopWidth: px,
|
|
3690
|
+
borderRightWidth: px,
|
|
3691
|
+
borderBottomWidth: px,
|
|
3692
|
+
borderLeftWidth: px,
|
|
3693
|
+
borderRadius: px,
|
|
3694
|
+
radius: px,
|
|
3695
|
+
borderTopLeftRadius: px,
|
|
3696
|
+
borderTopRightRadius: px,
|
|
3697
|
+
borderBottomRightRadius: px,
|
|
3698
|
+
borderBottomLeftRadius: px,
|
|
3699
|
+
// Positioning props
|
|
3700
|
+
width: px,
|
|
3701
|
+
maxWidth: px,
|
|
3702
|
+
height: px,
|
|
3703
|
+
maxHeight: px,
|
|
3704
|
+
top: px,
|
|
3705
|
+
right: px,
|
|
3706
|
+
bottom: px,
|
|
3707
|
+
left: px,
|
|
3708
|
+
inset: px,
|
|
3709
|
+
insetBlock: px,
|
|
3710
|
+
insetBlockStart: px,
|
|
3711
|
+
insetBlockEnd: px,
|
|
3712
|
+
insetInline: px,
|
|
3713
|
+
insetInlineStart: px,
|
|
3714
|
+
insetInlineEnd: px,
|
|
3715
|
+
// Spacing props
|
|
3716
|
+
padding: px,
|
|
3717
|
+
paddingTop: px,
|
|
3718
|
+
paddingRight: px,
|
|
3719
|
+
paddingBottom: px,
|
|
3720
|
+
paddingLeft: px,
|
|
3721
|
+
paddingBlock: px,
|
|
3722
|
+
paddingBlockStart: px,
|
|
3723
|
+
paddingBlockEnd: px,
|
|
3724
|
+
paddingInline: px,
|
|
3725
|
+
paddingInlineStart: px,
|
|
3726
|
+
paddingInlineEnd: px,
|
|
3727
|
+
margin: px,
|
|
3728
|
+
marginTop: px,
|
|
3729
|
+
marginRight: px,
|
|
3730
|
+
marginBottom: px,
|
|
3731
|
+
marginLeft: px,
|
|
3732
|
+
marginBlock: px,
|
|
3733
|
+
marginBlockStart: px,
|
|
3734
|
+
marginBlockEnd: px,
|
|
3735
|
+
marginInline: px,
|
|
3736
|
+
marginInlineStart: px,
|
|
3737
|
+
marginInlineEnd: px,
|
|
3738
|
+
// Misc
|
|
3739
|
+
backgroundPositionX: px,
|
|
3740
|
+
backgroundPositionY: px,
|
|
3741
|
+
...transformValueTypes,
|
|
3742
|
+
zIndex: int,
|
|
3743
|
+
// SVG
|
|
3744
|
+
fillOpacity: alpha,
|
|
3745
|
+
strokeOpacity: alpha,
|
|
3746
|
+
numOctaves: int,
|
|
3747
|
+
};
|
|
3976
3748
|
|
|
3977
3749
|
/**
|
|
3978
|
-
*
|
|
3750
|
+
* A map of default value types for common values
|
|
3751
|
+
*/
|
|
3752
|
+
const defaultValueTypes = {
|
|
3753
|
+
...numberValueTypes,
|
|
3754
|
+
// Color props
|
|
3755
|
+
color,
|
|
3756
|
+
backgroundColor: color,
|
|
3757
|
+
outlineColor: color,
|
|
3758
|
+
fill: color,
|
|
3759
|
+
stroke: color,
|
|
3760
|
+
// Border props
|
|
3761
|
+
borderColor: color,
|
|
3762
|
+
borderTopColor: color,
|
|
3763
|
+
borderRightColor: color,
|
|
3764
|
+
borderBottomColor: color,
|
|
3765
|
+
borderLeftColor: color,
|
|
3766
|
+
filter,
|
|
3767
|
+
WebkitFilter: filter,
|
|
3768
|
+
};
|
|
3769
|
+
/**
|
|
3770
|
+
* Gets the default ValueType for the provided value key
|
|
3979
3771
|
*/
|
|
3980
|
-
const
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3772
|
+
const getDefaultValueType = (key) => defaultValueTypes[key];
|
|
3773
|
+
|
|
3774
|
+
function getAnimatableNone(key, value) {
|
|
3775
|
+
let defaultValueType = getDefaultValueType(key);
|
|
3776
|
+
if (defaultValueType !== filter)
|
|
3777
|
+
defaultValueType = complex;
|
|
3778
|
+
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
|
|
3779
|
+
return defaultValueType.getAnimatableNone
|
|
3780
|
+
? defaultValueType.getAnimatableNone(value)
|
|
3781
|
+
: undefined;
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3986
3784
|
/**
|
|
3987
|
-
*
|
|
3785
|
+
* If we encounter keyframes like "none" or "0" and we also have keyframes like
|
|
3786
|
+
* "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
|
|
3787
|
+
* the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
|
|
3788
|
+
* zero equivalents, i.e. "#fff0" or "0px 0px".
|
|
3988
3789
|
*/
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
state.attrs.viewBox = state.style.viewBox;
|
|
3790
|
+
const invalidTemplates = new Set(["auto", "none", "0"]);
|
|
3791
|
+
function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
|
|
3792
|
+
let i = 0;
|
|
3793
|
+
let animatableTemplate = undefined;
|
|
3794
|
+
while (i < unresolvedKeyframes.length && !animatableTemplate) {
|
|
3795
|
+
const keyframe = unresolvedKeyframes[i];
|
|
3796
|
+
if (typeof keyframe === "string" &&
|
|
3797
|
+
!invalidTemplates.has(keyframe) &&
|
|
3798
|
+
analyseComplexValue(keyframe).values.length) {
|
|
3799
|
+
animatableTemplate = unresolvedKeyframes[i];
|
|
4000
3800
|
}
|
|
4001
|
-
|
|
3801
|
+
i++;
|
|
4002
3802
|
}
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
* However, we apply transforms as CSS transforms.
|
|
4008
|
-
* So if we detect a transform, transformOrigin we take it from attrs and copy it into style.
|
|
4009
|
-
*/
|
|
4010
|
-
if (attrs.transform) {
|
|
4011
|
-
style.transform = attrs.transform;
|
|
4012
|
-
delete attrs.transform;
|
|
3803
|
+
if (animatableTemplate && name) {
|
|
3804
|
+
for (const noneIndex of noneKeyframeIndexes) {
|
|
3805
|
+
unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
|
|
3806
|
+
}
|
|
4013
3807
|
}
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
class DOMKeyframesResolver extends KeyframeResolver {
|
|
3811
|
+
constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
|
|
3812
|
+
super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
|
|
4017
3813
|
}
|
|
4018
|
-
|
|
3814
|
+
readKeyframes() {
|
|
3815
|
+
const { unresolvedKeyframes, element, name } = this;
|
|
3816
|
+
if (!element || !element.current)
|
|
3817
|
+
return;
|
|
3818
|
+
super.readKeyframes();
|
|
4019
3819
|
/**
|
|
4020
|
-
*
|
|
4021
|
-
* Therefore, transformBox becomes a fill-box
|
|
3820
|
+
* If any keyframe is a CSS variable, we need to find its value by sampling the element
|
|
4022
3821
|
*/
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
3822
|
+
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
3823
|
+
let keyframe = unresolvedKeyframes[i];
|
|
3824
|
+
if (typeof keyframe === "string") {
|
|
3825
|
+
keyframe = keyframe.trim();
|
|
3826
|
+
if (isCSSVariableToken(keyframe)) {
|
|
3827
|
+
const resolved = getVariableValue(keyframe, element.current);
|
|
3828
|
+
if (resolved !== undefined) {
|
|
3829
|
+
unresolvedKeyframes[i] = resolved;
|
|
3830
|
+
}
|
|
3831
|
+
if (i === unresolvedKeyframes.length - 1) {
|
|
3832
|
+
this.finalKeyframe = keyframe;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
/**
|
|
3838
|
+
* Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
|
|
3839
|
+
* This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
|
|
3840
|
+
* have a far bigger performance impact.
|
|
3841
|
+
*/
|
|
3842
|
+
this.resolveNoneKeyframes();
|
|
3843
|
+
/**
|
|
3844
|
+
* Check to see if unit type has changed. If so schedule jobs that will
|
|
3845
|
+
* temporarily set styles to the destination keyframes.
|
|
3846
|
+
* Skip if we have more than two keyframes or this isn't a positional value.
|
|
3847
|
+
* TODO: We can throw if there are multiple keyframes and the value type changes.
|
|
3848
|
+
*/
|
|
3849
|
+
if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
|
|
3850
|
+
return;
|
|
3851
|
+
}
|
|
3852
|
+
const [origin, target] = unresolvedKeyframes;
|
|
3853
|
+
const originType = findDimensionValueType(origin);
|
|
3854
|
+
const targetType = findDimensionValueType(target);
|
|
3855
|
+
/**
|
|
3856
|
+
* If one keyframe contains embedded CSS variables (e.g. in calc()) and the other
|
|
3857
|
+
* doesn't, we need to measure to convert to pixels. This handles GitHub issue #3410.
|
|
3858
|
+
*/
|
|
3859
|
+
const originHasVar = containsCSSVariable(origin);
|
|
3860
|
+
const targetHasVar = containsCSSVariable(target);
|
|
3861
|
+
if (originHasVar !== targetHasVar && positionalValues[name]) {
|
|
3862
|
+
this.needsMeasurement = true;
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
/**
|
|
3866
|
+
* Either we don't recognise these value types or we can animate between them.
|
|
3867
|
+
*/
|
|
3868
|
+
if (originType === targetType)
|
|
3869
|
+
return;
|
|
3870
|
+
/**
|
|
3871
|
+
* If both values are numbers or pixels, we can animate between them by
|
|
3872
|
+
* converting them to numbers.
|
|
3873
|
+
*/
|
|
3874
|
+
if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
|
|
3875
|
+
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
3876
|
+
const value = unresolvedKeyframes[i];
|
|
3877
|
+
if (typeof value === "string") {
|
|
3878
|
+
unresolvedKeyframes[i] = parseFloat(value);
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
else if (positionalValues[name]) {
|
|
3883
|
+
/**
|
|
3884
|
+
* Else, the only way to resolve this is by measuring the element.
|
|
3885
|
+
*/
|
|
3886
|
+
this.needsMeasurement = true;
|
|
4030
3887
|
}
|
|
4031
3888
|
}
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
}
|
|
4044
|
-
|
|
4045
|
-
const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg";
|
|
4046
|
-
|
|
4047
|
-
function getValueState(visualElement) {
|
|
4048
|
-
const state = [{}, {}];
|
|
4049
|
-
visualElement?.values.forEach((value, key) => {
|
|
4050
|
-
state[0][key] = value.get();
|
|
4051
|
-
state[1][key] = value.getVelocity();
|
|
4052
|
-
});
|
|
4053
|
-
return state;
|
|
4054
|
-
}
|
|
4055
|
-
function resolveVariantFromProps(props, definition, custom, visualElement) {
|
|
4056
|
-
/**
|
|
4057
|
-
* If the variant definition is a function, resolve.
|
|
4058
|
-
*/
|
|
4059
|
-
if (typeof definition === "function") {
|
|
4060
|
-
const [current, velocity] = getValueState(visualElement);
|
|
4061
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
4062
|
-
}
|
|
4063
|
-
/**
|
|
4064
|
-
* If the variant definition is a variant label, or
|
|
4065
|
-
* the function returned a variant label, resolve.
|
|
4066
|
-
*/
|
|
4067
|
-
if (typeof definition === "string") {
|
|
4068
|
-
definition = props.variants && props.variants[definition];
|
|
3889
|
+
resolveNoneKeyframes() {
|
|
3890
|
+
const { unresolvedKeyframes, name } = this;
|
|
3891
|
+
const noneKeyframeIndexes = [];
|
|
3892
|
+
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
3893
|
+
if (unresolvedKeyframes[i] === null ||
|
|
3894
|
+
isNone(unresolvedKeyframes[i])) {
|
|
3895
|
+
noneKeyframeIndexes.push(i);
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
if (noneKeyframeIndexes.length) {
|
|
3899
|
+
makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
|
|
3900
|
+
}
|
|
4069
3901
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
3902
|
+
measureInitialState() {
|
|
3903
|
+
const { element, unresolvedKeyframes, name } = this;
|
|
3904
|
+
if (!element || !element.current)
|
|
3905
|
+
return;
|
|
3906
|
+
if (name === "height") {
|
|
3907
|
+
this.suspendedScrollY = window.pageYOffset;
|
|
3908
|
+
}
|
|
3909
|
+
this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
|
|
3910
|
+
unresolvedKeyframes[0] = this.measuredOrigin;
|
|
3911
|
+
// Set final key frame to measure after next render
|
|
3912
|
+
const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
|
|
3913
|
+
if (measureKeyframe !== undefined) {
|
|
3914
|
+
element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
|
|
3915
|
+
}
|
|
4078
3916
|
}
|
|
4079
|
-
|
|
4080
|
-
}
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
3917
|
+
measureEndState() {
|
|
3918
|
+
const { element, name, unresolvedKeyframes } = this;
|
|
3919
|
+
if (!element || !element.current)
|
|
3920
|
+
return;
|
|
3921
|
+
const value = element.getValue(name);
|
|
3922
|
+
value && value.jump(this.measuredOrigin, false);
|
|
3923
|
+
const finalKeyframeIndex = unresolvedKeyframes.length - 1;
|
|
3924
|
+
const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
|
|
3925
|
+
unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
|
|
3926
|
+
if (finalKeyframe !== null && this.finalKeyframe === undefined) {
|
|
3927
|
+
this.finalKeyframe = finalKeyframe;
|
|
3928
|
+
}
|
|
3929
|
+
// If we removed transform values, reapply them before the next render
|
|
3930
|
+
if (this.removedTransforms?.length) {
|
|
3931
|
+
this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
|
|
3932
|
+
element
|
|
3933
|
+
.getValue(unsetTransformName)
|
|
3934
|
+
.set(unsetTransformValue);
|
|
3935
|
+
});
|
|
4092
3936
|
}
|
|
3937
|
+
this.resolveNoneKeyframes();
|
|
4093
3938
|
}
|
|
4094
|
-
return newValues;
|
|
4095
3939
|
}
|
|
4096
3940
|
|
|
4097
|
-
function
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
newValues[targetKey] = props[key];
|
|
3941
|
+
function resolveElements(elementOrSelector, scope, selectorCache) {
|
|
3942
|
+
if (elementOrSelector instanceof EventTarget) {
|
|
3943
|
+
return [elementOrSelector];
|
|
3944
|
+
}
|
|
3945
|
+
else if (typeof elementOrSelector === "string") {
|
|
3946
|
+
let root = document;
|
|
3947
|
+
if (scope) {
|
|
3948
|
+
root = scope.current;
|
|
4106
3949
|
}
|
|
3950
|
+
const elements = selectorCache?.[elementOrSelector] ??
|
|
3951
|
+
root.querySelectorAll(elementOrSelector);
|
|
3952
|
+
return elements ? Array.from(elements) : [];
|
|
4107
3953
|
}
|
|
4108
|
-
return
|
|
3954
|
+
return Array.from(elementOrSelector);
|
|
4109
3955
|
}
|
|
4110
3956
|
|
|
4111
3957
|
/**
|
|
4112
|
-
*
|
|
3958
|
+
* Provided a value and a ValueType, returns the value as that value type.
|
|
4113
3959
|
*/
|
|
4114
|
-
const
|
|
3960
|
+
const getValueAsType = (value, type) => {
|
|
3961
|
+
return type && typeof value === "number"
|
|
3962
|
+
? type.transform(value)
|
|
3963
|
+
: value;
|
|
3964
|
+
};
|
|
4115
3965
|
|
|
4116
|
-
const
|
|
4117
|
-
|
|
3966
|
+
const { schedule: microtask} =
|
|
3967
|
+
/* @__PURE__ */ createRenderBatcher(queueMicrotask, false);
|
|
4118
3968
|
|
|
4119
3969
|
/**
|
|
4120
|
-
*
|
|
4121
|
-
*
|
|
4122
|
-
* as a map of single-axis min/max values.
|
|
4123
|
-
*/
|
|
4124
|
-
function convertBoundingBoxToBox({ top, left, right, bottom, }) {
|
|
4125
|
-
return {
|
|
4126
|
-
x: { min: left, max: right },
|
|
4127
|
-
y: { min: top, max: bottom },
|
|
4128
|
-
};
|
|
4129
|
-
}
|
|
4130
|
-
function convertBoxToBoundingBox({ x, y }) {
|
|
4131
|
-
return { top: y.min, right: x.max, bottom: y.max, left: x.min };
|
|
4132
|
-
}
|
|
4133
|
-
/**
|
|
4134
|
-
* Applies a TransformPoint function to a bounding box. TransformPoint is usually a function
|
|
4135
|
-
* provided by Framer to allow measured points to be corrected for device scaling. This is used
|
|
4136
|
-
* when measuring DOM elements and DOM event points.
|
|
3970
|
+
* Checks if an element is an SVG element in a way
|
|
3971
|
+
* that works across iframes
|
|
4137
3972
|
*/
|
|
4138
|
-
function
|
|
4139
|
-
|
|
4140
|
-
return point;
|
|
4141
|
-
const topLeft = transformPoint({ x: point.left, y: point.top });
|
|
4142
|
-
const bottomRight = transformPoint({ x: point.right, y: point.bottom });
|
|
4143
|
-
return {
|
|
4144
|
-
top: topLeft.y,
|
|
4145
|
-
left: topLeft.x,
|
|
4146
|
-
bottom: bottomRight.y,
|
|
4147
|
-
right: bottomRight.x,
|
|
4148
|
-
};
|
|
4149
|
-
}
|
|
4150
|
-
|
|
4151
|
-
function isIdentityScale(scale) {
|
|
4152
|
-
return scale === undefined || scale === 1;
|
|
4153
|
-
}
|
|
4154
|
-
function hasScale({ scale, scaleX, scaleY }) {
|
|
4155
|
-
return (!isIdentityScale(scale) ||
|
|
4156
|
-
!isIdentityScale(scaleX) ||
|
|
4157
|
-
!isIdentityScale(scaleY));
|
|
4158
|
-
}
|
|
4159
|
-
function hasTransform(values) {
|
|
4160
|
-
return (hasScale(values) ||
|
|
4161
|
-
has2DTranslate(values) ||
|
|
4162
|
-
values.z ||
|
|
4163
|
-
values.rotate ||
|
|
4164
|
-
values.rotateX ||
|
|
4165
|
-
values.rotateY ||
|
|
4166
|
-
values.skewX ||
|
|
4167
|
-
values.skewY);
|
|
4168
|
-
}
|
|
4169
|
-
function has2DTranslate(values) {
|
|
4170
|
-
return is2DTranslate(values.x) || is2DTranslate(values.y);
|
|
4171
|
-
}
|
|
4172
|
-
function is2DTranslate(value) {
|
|
4173
|
-
return value && value !== "0%";
|
|
3973
|
+
function isSVGElement(element) {
|
|
3974
|
+
return isObject(element) && "ownerSVGElement" in element;
|
|
4174
3975
|
}
|
|
4175
3976
|
|
|
4176
3977
|
/**
|
|
4177
|
-
*
|
|
4178
|
-
|
|
4179
|
-
function scalePoint(point, scale, originPoint) {
|
|
4180
|
-
const distanceFromOrigin = point - originPoint;
|
|
4181
|
-
const scaled = scale * distanceFromOrigin;
|
|
4182
|
-
return originPoint + scaled;
|
|
4183
|
-
}
|
|
4184
|
-
/**
|
|
4185
|
-
* Applies a translate/scale delta to a point
|
|
4186
|
-
*/
|
|
4187
|
-
function applyPointDelta(point, translate, scale, originPoint, boxScale) {
|
|
4188
|
-
if (boxScale !== undefined) {
|
|
4189
|
-
point = scalePoint(point, boxScale, originPoint);
|
|
4190
|
-
}
|
|
4191
|
-
return scalePoint(point, scale, originPoint) + translate;
|
|
4192
|
-
}
|
|
4193
|
-
/**
|
|
4194
|
-
* Applies a translate/scale delta to an axis
|
|
4195
|
-
*/
|
|
4196
|
-
function applyAxisDelta(axis, translate = 0, scale = 1, originPoint, boxScale) {
|
|
4197
|
-
axis.min = applyPointDelta(axis.min, translate, scale, originPoint, boxScale);
|
|
4198
|
-
axis.max = applyPointDelta(axis.max, translate, scale, originPoint, boxScale);
|
|
4199
|
-
}
|
|
4200
|
-
/**
|
|
4201
|
-
* Applies a translate/scale delta to a box
|
|
4202
|
-
*/
|
|
4203
|
-
function applyBoxDelta(box, { x, y }) {
|
|
4204
|
-
applyAxisDelta(box.x, x.translate, x.scale, x.originPoint);
|
|
4205
|
-
applyAxisDelta(box.y, y.translate, y.scale, y.originPoint);
|
|
4206
|
-
}
|
|
4207
|
-
const TREE_SCALE_SNAP_MIN = 0.999999999999;
|
|
4208
|
-
const TREE_SCALE_SNAP_MAX = 1.0000000000001;
|
|
4209
|
-
/**
|
|
4210
|
-
* Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms
|
|
4211
|
-
* in a tree upon our box before then calculating how to project it into our desired viewport-relative box
|
|
4212
|
-
*
|
|
4213
|
-
* This is the final nested loop within updateLayoutDelta for future refactoring
|
|
3978
|
+
* Checks if an element is specifically an SVGSVGElement (the root SVG element)
|
|
3979
|
+
* in a way that works across iframes
|
|
4214
3980
|
*/
|
|
4215
|
-
function
|
|
4216
|
-
|
|
4217
|
-
if (!treeLength)
|
|
4218
|
-
return;
|
|
4219
|
-
// Reset the treeScale
|
|
4220
|
-
treeScale.x = treeScale.y = 1;
|
|
4221
|
-
let node;
|
|
4222
|
-
let delta;
|
|
4223
|
-
for (let i = 0; i < treeLength; i++) {
|
|
4224
|
-
node = treePath[i];
|
|
4225
|
-
delta = node.projectionDelta;
|
|
4226
|
-
/**
|
|
4227
|
-
* TODO: Prefer to remove this, but currently we have motion components with
|
|
4228
|
-
* display: contents in Framer.
|
|
4229
|
-
*/
|
|
4230
|
-
const { visualElement } = node.options;
|
|
4231
|
-
if (visualElement &&
|
|
4232
|
-
visualElement.props.style &&
|
|
4233
|
-
visualElement.props.style.display === "contents") {
|
|
4234
|
-
continue;
|
|
4235
|
-
}
|
|
4236
|
-
if (isSharedTransition &&
|
|
4237
|
-
node.options.layoutScroll &&
|
|
4238
|
-
node.scroll &&
|
|
4239
|
-
node !== node.root) {
|
|
4240
|
-
transformBox(box, {
|
|
4241
|
-
x: -node.scroll.offset.x,
|
|
4242
|
-
y: -node.scroll.offset.y,
|
|
4243
|
-
});
|
|
4244
|
-
}
|
|
4245
|
-
if (delta) {
|
|
4246
|
-
// Incoporate each ancestor's scale into a cumulative treeScale for this component
|
|
4247
|
-
treeScale.x *= delta.x.scale;
|
|
4248
|
-
treeScale.y *= delta.y.scale;
|
|
4249
|
-
// Apply each ancestor's calculated delta into this component's recorded layout box
|
|
4250
|
-
applyBoxDelta(box, delta);
|
|
4251
|
-
}
|
|
4252
|
-
if (isSharedTransition && hasTransform(node.latestValues)) {
|
|
4253
|
-
transformBox(box, node.latestValues);
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
|
-
/**
|
|
4257
|
-
* Snap tree scale back to 1 if it's within a non-perceivable threshold.
|
|
4258
|
-
* This will help reduce useless scales getting rendered.
|
|
4259
|
-
*/
|
|
4260
|
-
if (treeScale.x < TREE_SCALE_SNAP_MAX &&
|
|
4261
|
-
treeScale.x > TREE_SCALE_SNAP_MIN) {
|
|
4262
|
-
treeScale.x = 1.0;
|
|
4263
|
-
}
|
|
4264
|
-
if (treeScale.y < TREE_SCALE_SNAP_MAX &&
|
|
4265
|
-
treeScale.y > TREE_SCALE_SNAP_MIN) {
|
|
4266
|
-
treeScale.y = 1.0;
|
|
4267
|
-
}
|
|
4268
|
-
}
|
|
4269
|
-
function translateAxis(axis, distance) {
|
|
4270
|
-
axis.min = axis.min + distance;
|
|
4271
|
-
axis.max = axis.max + distance;
|
|
3981
|
+
function isSVGSVGElement(element) {
|
|
3982
|
+
return isSVGElement(element) && element.tagName === "svg";
|
|
4272
3983
|
}
|
|
3984
|
+
|
|
4273
3985
|
/**
|
|
4274
|
-
*
|
|
4275
|
-
* This function basically acts as a bridge between a flat motion value map
|
|
4276
|
-
* and applyAxisDelta
|
|
3986
|
+
* A list of all ValueTypes
|
|
4277
3987
|
*/
|
|
4278
|
-
|
|
4279
|
-
const originPoint = mixNumber$1(axis.min, axis.max, axisOrigin);
|
|
4280
|
-
// Apply the axis delta to the final axis
|
|
4281
|
-
applyAxisDelta(axis, axisTranslate, axisScale, originPoint, boxScale);
|
|
4282
|
-
}
|
|
3988
|
+
const valueTypes = [...dimensionValueTypes, color, complex];
|
|
4283
3989
|
/**
|
|
4284
|
-
*
|
|
3990
|
+
* Tests a value against the list of ValueTypes
|
|
4285
3991
|
*/
|
|
4286
|
-
|
|
4287
|
-
transformAxis(box.x, transform.x, transform.scaleX, transform.scale, transform.originX);
|
|
4288
|
-
transformAxis(box.y, transform.y, transform.scaleY, transform.scale, transform.originY);
|
|
4289
|
-
}
|
|
4290
|
-
|
|
4291
|
-
function measureViewportBox(instance, transformPoint) {
|
|
4292
|
-
return convertBoundingBoxToBox(transformBoxPoints(instance.getBoundingClientRect(), transformPoint));
|
|
4293
|
-
}
|
|
4294
|
-
function measurePageBox(element, rootProjectionNode, transformPagePoint) {
|
|
4295
|
-
const viewportBox = measureViewportBox(element, transformPagePoint);
|
|
4296
|
-
const { scroll } = rootProjectionNode;
|
|
4297
|
-
if (scroll) {
|
|
4298
|
-
translateAxis(viewportBox.x, scroll.offset.x);
|
|
4299
|
-
translateAxis(viewportBox.y, scroll.offset.y);
|
|
4300
|
-
}
|
|
4301
|
-
return viewportBox;
|
|
4302
|
-
}
|
|
3992
|
+
const findValueType = (v) => valueTypes.find(testValueType(v));
|
|
4303
3993
|
|
|
4304
3994
|
const createAxisDelta = () => ({
|
|
4305
3995
|
translate: 0,
|
|
@@ -4321,6 +4011,7 @@ const createBox = () => ({
|
|
|
4321
4011
|
const prefersReducedMotion = { current: null };
|
|
4322
4012
|
const hasReducedMotionListener = { current: false };
|
|
4323
4013
|
|
|
4014
|
+
const isBrowser = typeof window !== "undefined";
|
|
4324
4015
|
function initPrefersReducedMotion() {
|
|
4325
4016
|
hasReducedMotionListener.current = true;
|
|
4326
4017
|
if (!isBrowser)
|
|
@@ -4338,6 +4029,42 @@ function initPrefersReducedMotion() {
|
|
|
4338
4029
|
|
|
4339
4030
|
const visualElementStore = new WeakMap();
|
|
4340
4031
|
|
|
4032
|
+
function isAnimationControls(v) {
|
|
4033
|
+
return (v !== null &&
|
|
4034
|
+
typeof v === "object" &&
|
|
4035
|
+
typeof v.start === "function");
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
/**
|
|
4039
|
+
* Decides if the supplied variable is variant label
|
|
4040
|
+
*/
|
|
4041
|
+
function isVariantLabel(v) {
|
|
4042
|
+
return typeof v === "string" || Array.isArray(v);
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
const variantPriorityOrder = [
|
|
4046
|
+
"animate",
|
|
4047
|
+
"whileInView",
|
|
4048
|
+
"whileFocus",
|
|
4049
|
+
"whileHover",
|
|
4050
|
+
"whileTap",
|
|
4051
|
+
"whileDrag",
|
|
4052
|
+
"exit",
|
|
4053
|
+
];
|
|
4054
|
+
const variantProps = ["initial", ...variantPriorityOrder];
|
|
4055
|
+
|
|
4056
|
+
function isControllingVariants(props) {
|
|
4057
|
+
return (isAnimationControls(props.animate) ||
|
|
4058
|
+
variantProps.some((name) => isVariantLabel(props[name])));
|
|
4059
|
+
}
|
|
4060
|
+
function isVariantNode(props) {
|
|
4061
|
+
return Boolean(isControllingVariants(props) || props.variants);
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
/**
|
|
4065
|
+
* Updates motion values from props changes.
|
|
4066
|
+
* Uses `any` type for element to avoid circular dependencies with VisualElement.
|
|
4067
|
+
*/
|
|
4341
4068
|
function updateMotionValuesFromProps(element, next, prev) {
|
|
4342
4069
|
for (const key in next) {
|
|
4343
4070
|
const nextValue = next[key];
|
|
@@ -4394,6 +4121,23 @@ const propEventHandlers = [
|
|
|
4394
4121
|
"LayoutAnimationStart",
|
|
4395
4122
|
"LayoutAnimationComplete",
|
|
4396
4123
|
];
|
|
4124
|
+
/**
|
|
4125
|
+
* Static feature definitions - can be injected by framework layer
|
|
4126
|
+
*/
|
|
4127
|
+
let featureDefinitions = {};
|
|
4128
|
+
/**
|
|
4129
|
+
* Set feature definitions for all VisualElements.
|
|
4130
|
+
* This should be called by the framework layer (e.g., framer-motion) during initialization.
|
|
4131
|
+
*/
|
|
4132
|
+
function setFeatureDefinitions(definitions) {
|
|
4133
|
+
featureDefinitions = definitions;
|
|
4134
|
+
}
|
|
4135
|
+
/**
|
|
4136
|
+
* Get the current feature definitions
|
|
4137
|
+
*/
|
|
4138
|
+
function getFeatureDefinitions() {
|
|
4139
|
+
return featureDefinitions;
|
|
4140
|
+
}
|
|
4397
4141
|
/**
|
|
4398
4142
|
* A VisualElement is an imperative abstraction around UI elements such as
|
|
4399
4143
|
* HTMLElement, SVGElement, Three.Object3D etc.
|
|
@@ -4594,7 +4338,7 @@ class VisualElement {
|
|
|
4594
4338
|
this.scheduleRender();
|
|
4595
4339
|
});
|
|
4596
4340
|
let removeSyncCheck;
|
|
4597
|
-
if (window.MotionCheckAppearSync) {
|
|
4341
|
+
if (typeof window !== "undefined" && window.MotionCheckAppearSync) {
|
|
4598
4342
|
removeSyncCheck = window.MotionCheckAppearSync(this, key, value);
|
|
4599
4343
|
}
|
|
4600
4344
|
this.valueSubscriptions.set(key, () => {
|
|
@@ -4692,7 +4436,7 @@ class VisualElement {
|
|
|
4692
4436
|
this.propEventSubscriptions[key] = this.on(key, listener);
|
|
4693
4437
|
}
|
|
4694
4438
|
}
|
|
4695
|
-
this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps, this), this.prevMotionValues);
|
|
4439
|
+
this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps || {}, this), this.prevMotionValues);
|
|
4696
4440
|
if (this.handleChildMotionValue) {
|
|
4697
4441
|
this.handleChildMotionValue();
|
|
4698
4442
|
}
|
|
@@ -4871,29 +4615,328 @@ class DOMVisualElement extends VisualElement {
|
|
|
4871
4615
|
*/
|
|
4872
4616
|
return a.compareDocumentPosition(b) & 2 ? 1 : -1;
|
|
4873
4617
|
}
|
|
4874
|
-
getBaseTargetFromProps(props, key) {
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4618
|
+
getBaseTargetFromProps(props, key) {
|
|
4619
|
+
const style = props.style;
|
|
4620
|
+
return style ? style[key] : undefined;
|
|
4621
|
+
}
|
|
4622
|
+
removeValueFromRenderState(key, { vars, style }) {
|
|
4623
|
+
delete vars[key];
|
|
4624
|
+
delete style[key];
|
|
4625
|
+
}
|
|
4626
|
+
handleChildMotionValue() {
|
|
4627
|
+
if (this.childSubscription) {
|
|
4628
|
+
this.childSubscription();
|
|
4629
|
+
delete this.childSubscription;
|
|
4630
|
+
}
|
|
4631
|
+
const { children } = this.props;
|
|
4632
|
+
if (isMotionValue(children)) {
|
|
4633
|
+
this.childSubscription = children.on("change", (latest) => {
|
|
4634
|
+
if (this.current) {
|
|
4635
|
+
this.current.textContent = `${latest}`;
|
|
4636
|
+
}
|
|
4637
|
+
});
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
/**
|
|
4643
|
+
* Bounding boxes tend to be defined as top, left, right, bottom. For various operations
|
|
4644
|
+
* it's easier to consider each axis individually. This function returns a bounding box
|
|
4645
|
+
* as a map of single-axis min/max values.
|
|
4646
|
+
*/
|
|
4647
|
+
function convertBoundingBoxToBox({ top, left, right, bottom, }) {
|
|
4648
|
+
return {
|
|
4649
|
+
x: { min: left, max: right },
|
|
4650
|
+
y: { min: top, max: bottom },
|
|
4651
|
+
};
|
|
4652
|
+
}
|
|
4653
|
+
function convertBoxToBoundingBox({ x, y }) {
|
|
4654
|
+
return { top: y.min, right: x.max, bottom: y.max, left: x.min };
|
|
4655
|
+
}
|
|
4656
|
+
/**
|
|
4657
|
+
* Applies a TransformPoint function to a bounding box. TransformPoint is usually a function
|
|
4658
|
+
* provided by Framer to allow measured points to be corrected for device scaling. This is used
|
|
4659
|
+
* when measuring DOM elements and DOM event points.
|
|
4660
|
+
*/
|
|
4661
|
+
function transformBoxPoints(point, transformPoint) {
|
|
4662
|
+
if (!transformPoint)
|
|
4663
|
+
return point;
|
|
4664
|
+
const topLeft = transformPoint({ x: point.left, y: point.top });
|
|
4665
|
+
const bottomRight = transformPoint({ x: point.right, y: point.bottom });
|
|
4666
|
+
return {
|
|
4667
|
+
top: topLeft.y,
|
|
4668
|
+
left: topLeft.x,
|
|
4669
|
+
bottom: bottomRight.y,
|
|
4670
|
+
right: bottomRight.x,
|
|
4671
|
+
};
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
function isIdentityScale(scale) {
|
|
4675
|
+
return scale === undefined || scale === 1;
|
|
4676
|
+
}
|
|
4677
|
+
function hasScale({ scale, scaleX, scaleY }) {
|
|
4678
|
+
return (!isIdentityScale(scale) ||
|
|
4679
|
+
!isIdentityScale(scaleX) ||
|
|
4680
|
+
!isIdentityScale(scaleY));
|
|
4681
|
+
}
|
|
4682
|
+
function hasTransform(values) {
|
|
4683
|
+
return (hasScale(values) ||
|
|
4684
|
+
has2DTranslate(values) ||
|
|
4685
|
+
values.z ||
|
|
4686
|
+
values.rotate ||
|
|
4687
|
+
values.rotateX ||
|
|
4688
|
+
values.rotateY ||
|
|
4689
|
+
values.skewX ||
|
|
4690
|
+
values.skewY);
|
|
4691
|
+
}
|
|
4692
|
+
function has2DTranslate(values) {
|
|
4693
|
+
return is2DTranslate(values.x) || is2DTranslate(values.y);
|
|
4694
|
+
}
|
|
4695
|
+
function is2DTranslate(value) {
|
|
4696
|
+
return value && value !== "0%";
|
|
4697
|
+
}
|
|
4698
|
+
|
|
4699
|
+
/**
|
|
4700
|
+
* Scales a point based on a factor and an originPoint
|
|
4701
|
+
*/
|
|
4702
|
+
function scalePoint(point, scale, originPoint) {
|
|
4703
|
+
const distanceFromOrigin = point - originPoint;
|
|
4704
|
+
const scaled = scale * distanceFromOrigin;
|
|
4705
|
+
return originPoint + scaled;
|
|
4706
|
+
}
|
|
4707
|
+
/**
|
|
4708
|
+
* Applies a translate/scale delta to a point
|
|
4709
|
+
*/
|
|
4710
|
+
function applyPointDelta(point, translate, scale, originPoint, boxScale) {
|
|
4711
|
+
if (boxScale !== undefined) {
|
|
4712
|
+
point = scalePoint(point, boxScale, originPoint);
|
|
4713
|
+
}
|
|
4714
|
+
return scalePoint(point, scale, originPoint) + translate;
|
|
4715
|
+
}
|
|
4716
|
+
/**
|
|
4717
|
+
* Applies a translate/scale delta to an axis
|
|
4718
|
+
*/
|
|
4719
|
+
function applyAxisDelta(axis, translate = 0, scale = 1, originPoint, boxScale) {
|
|
4720
|
+
axis.min = applyPointDelta(axis.min, translate, scale, originPoint, boxScale);
|
|
4721
|
+
axis.max = applyPointDelta(axis.max, translate, scale, originPoint, boxScale);
|
|
4722
|
+
}
|
|
4723
|
+
/**
|
|
4724
|
+
* Applies a translate/scale delta to a box
|
|
4725
|
+
*/
|
|
4726
|
+
function applyBoxDelta(box, { x, y }) {
|
|
4727
|
+
applyAxisDelta(box.x, x.translate, x.scale, x.originPoint);
|
|
4728
|
+
applyAxisDelta(box.y, y.translate, y.scale, y.originPoint);
|
|
4729
|
+
}
|
|
4730
|
+
const TREE_SCALE_SNAP_MIN = 0.999999999999;
|
|
4731
|
+
const TREE_SCALE_SNAP_MAX = 1.0000000000001;
|
|
4732
|
+
/**
|
|
4733
|
+
* Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms
|
|
4734
|
+
* in a tree upon our box before then calculating how to project it into our desired viewport-relative box
|
|
4735
|
+
*
|
|
4736
|
+
* This is the final nested loop within updateLayoutDelta for future refactoring
|
|
4737
|
+
*/
|
|
4738
|
+
function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) {
|
|
4739
|
+
const treeLength = treePath.length;
|
|
4740
|
+
if (!treeLength)
|
|
4741
|
+
return;
|
|
4742
|
+
// Reset the treeScale
|
|
4743
|
+
treeScale.x = treeScale.y = 1;
|
|
4744
|
+
let node;
|
|
4745
|
+
let delta;
|
|
4746
|
+
for (let i = 0; i < treeLength; i++) {
|
|
4747
|
+
node = treePath[i];
|
|
4748
|
+
delta = node.projectionDelta;
|
|
4749
|
+
/**
|
|
4750
|
+
* TODO: Prefer to remove this, but currently we have motion components with
|
|
4751
|
+
* display: contents in Framer.
|
|
4752
|
+
*/
|
|
4753
|
+
const { visualElement } = node.options;
|
|
4754
|
+
if (visualElement &&
|
|
4755
|
+
visualElement.props.style &&
|
|
4756
|
+
visualElement.props.style.display === "contents") {
|
|
4757
|
+
continue;
|
|
4758
|
+
}
|
|
4759
|
+
if (isSharedTransition &&
|
|
4760
|
+
node.options.layoutScroll &&
|
|
4761
|
+
node.scroll &&
|
|
4762
|
+
node !== node.root) {
|
|
4763
|
+
transformBox(box, {
|
|
4764
|
+
x: -node.scroll.offset.x,
|
|
4765
|
+
y: -node.scroll.offset.y,
|
|
4766
|
+
});
|
|
4767
|
+
}
|
|
4768
|
+
if (delta) {
|
|
4769
|
+
// Incoporate each ancestor's scale into a cumulative treeScale for this component
|
|
4770
|
+
treeScale.x *= delta.x.scale;
|
|
4771
|
+
treeScale.y *= delta.y.scale;
|
|
4772
|
+
// Apply each ancestor's calculated delta into this component's recorded layout box
|
|
4773
|
+
applyBoxDelta(box, delta);
|
|
4774
|
+
}
|
|
4775
|
+
if (isSharedTransition && hasTransform(node.latestValues)) {
|
|
4776
|
+
transformBox(box, node.latestValues);
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
/**
|
|
4780
|
+
* Snap tree scale back to 1 if it's within a non-perceivable threshold.
|
|
4781
|
+
* This will help reduce useless scales getting rendered.
|
|
4782
|
+
*/
|
|
4783
|
+
if (treeScale.x < TREE_SCALE_SNAP_MAX &&
|
|
4784
|
+
treeScale.x > TREE_SCALE_SNAP_MIN) {
|
|
4785
|
+
treeScale.x = 1.0;
|
|
4786
|
+
}
|
|
4787
|
+
if (treeScale.y < TREE_SCALE_SNAP_MAX &&
|
|
4788
|
+
treeScale.y > TREE_SCALE_SNAP_MIN) {
|
|
4789
|
+
treeScale.y = 1.0;
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
function translateAxis(axis, distance) {
|
|
4793
|
+
axis.min = axis.min + distance;
|
|
4794
|
+
axis.max = axis.max + distance;
|
|
4795
|
+
}
|
|
4796
|
+
/**
|
|
4797
|
+
* Apply a transform to an axis from the latest resolved motion values.
|
|
4798
|
+
* This function basically acts as a bridge between a flat motion value map
|
|
4799
|
+
* and applyAxisDelta
|
|
4800
|
+
*/
|
|
4801
|
+
function transformAxis(axis, axisTranslate, axisScale, boxScale, axisOrigin = 0.5) {
|
|
4802
|
+
const originPoint = mixNumber$1(axis.min, axis.max, axisOrigin);
|
|
4803
|
+
// Apply the axis delta to the final axis
|
|
4804
|
+
applyAxisDelta(axis, axisTranslate, axisScale, originPoint, boxScale);
|
|
4805
|
+
}
|
|
4806
|
+
/**
|
|
4807
|
+
* Apply a transform to a box from the latest resolved motion values.
|
|
4808
|
+
*/
|
|
4809
|
+
function transformBox(box, transform) {
|
|
4810
|
+
transformAxis(box.x, transform.x, transform.scaleX, transform.scale, transform.originX);
|
|
4811
|
+
transformAxis(box.y, transform.y, transform.scaleY, transform.scale, transform.originY);
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4814
|
+
function measureViewportBox(instance, transformPoint) {
|
|
4815
|
+
return convertBoundingBoxToBox(transformBoxPoints(instance.getBoundingClientRect(), transformPoint));
|
|
4816
|
+
}
|
|
4817
|
+
function measurePageBox(element, rootProjectionNode, transformPagePoint) {
|
|
4818
|
+
const viewportBox = measureViewportBox(element, transformPagePoint);
|
|
4819
|
+
const { scroll } = rootProjectionNode;
|
|
4820
|
+
if (scroll) {
|
|
4821
|
+
translateAxis(viewportBox.x, scroll.offset.x);
|
|
4822
|
+
translateAxis(viewportBox.y, scroll.offset.y);
|
|
4823
|
+
}
|
|
4824
|
+
return viewportBox;
|
|
4825
|
+
}
|
|
4826
|
+
|
|
4827
|
+
const translateAlias = {
|
|
4828
|
+
x: "translateX",
|
|
4829
|
+
y: "translateY",
|
|
4830
|
+
z: "translateZ",
|
|
4831
|
+
transformPerspective: "perspective",
|
|
4832
|
+
};
|
|
4833
|
+
const numTransforms = transformPropOrder.length;
|
|
4834
|
+
/**
|
|
4835
|
+
* Build a CSS transform style from individual x/y/scale etc properties.
|
|
4836
|
+
*
|
|
4837
|
+
* This outputs with a default order of transforms/scales/rotations, this can be customised by
|
|
4838
|
+
* providing a transformTemplate function.
|
|
4839
|
+
*/
|
|
4840
|
+
function buildTransform(latestValues, transform, transformTemplate) {
|
|
4841
|
+
// The transform string we're going to build into.
|
|
4842
|
+
let transformString = "";
|
|
4843
|
+
let transformIsDefault = true;
|
|
4844
|
+
/**
|
|
4845
|
+
* Loop over all possible transforms in order, adding the ones that
|
|
4846
|
+
* are present to the transform string.
|
|
4847
|
+
*/
|
|
4848
|
+
for (let i = 0; i < numTransforms; i++) {
|
|
4849
|
+
const key = transformPropOrder[i];
|
|
4850
|
+
const value = latestValues[key];
|
|
4851
|
+
if (value === undefined)
|
|
4852
|
+
continue;
|
|
4853
|
+
let valueIsDefault = true;
|
|
4854
|
+
if (typeof value === "number") {
|
|
4855
|
+
valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
|
|
4856
|
+
}
|
|
4857
|
+
else {
|
|
4858
|
+
valueIsDefault = parseFloat(value) === 0;
|
|
4859
|
+
}
|
|
4860
|
+
if (!valueIsDefault || transformTemplate) {
|
|
4861
|
+
const valueAsType = getValueAsType(value, numberValueTypes[key]);
|
|
4862
|
+
if (!valueIsDefault) {
|
|
4863
|
+
transformIsDefault = false;
|
|
4864
|
+
const transformName = translateAlias[key] || key;
|
|
4865
|
+
transformString += `${transformName}(${valueAsType}) `;
|
|
4866
|
+
}
|
|
4867
|
+
if (transformTemplate) {
|
|
4868
|
+
transform[key] = valueAsType;
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
transformString = transformString.trim();
|
|
4873
|
+
// If we have a custom `transform` template, pass our transform values and
|
|
4874
|
+
// generated transformString to that before returning
|
|
4875
|
+
if (transformTemplate) {
|
|
4876
|
+
transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
|
|
4878
4877
|
}
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
delete style[key];
|
|
4878
|
+
else if (transformIsDefault) {
|
|
4879
|
+
transformString = "none";
|
|
4882
4880
|
}
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4881
|
+
return transformString;
|
|
4882
|
+
}
|
|
4883
|
+
|
|
4884
|
+
function buildHTMLStyles(state, latestValues, transformTemplate) {
|
|
4885
|
+
const { style, vars, transformOrigin } = state;
|
|
4886
|
+
// Track whether we encounter any transform or transformOrigin values.
|
|
4887
|
+
let hasTransform = false;
|
|
4888
|
+
let hasTransformOrigin = false;
|
|
4889
|
+
/**
|
|
4890
|
+
* Loop over all our latest animated values and decide whether to handle them
|
|
4891
|
+
* as a style or CSS variable.
|
|
4892
|
+
*
|
|
4893
|
+
* Transforms and transform origins are kept separately for further processing.
|
|
4894
|
+
*/
|
|
4895
|
+
for (const key in latestValues) {
|
|
4896
|
+
const value = latestValues[key];
|
|
4897
|
+
if (transformProps.has(key)) {
|
|
4898
|
+
// If this is a transform, flag to enable further transform processing
|
|
4899
|
+
hasTransform = true;
|
|
4900
|
+
continue;
|
|
4887
4901
|
}
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4902
|
+
else if (isCSSVariableName(key)) {
|
|
4903
|
+
vars[key] = value;
|
|
4904
|
+
continue;
|
|
4905
|
+
}
|
|
4906
|
+
else {
|
|
4907
|
+
// Convert the value to its default value type, ie 0 -> "0px"
|
|
4908
|
+
const valueAsType = getValueAsType(value, numberValueTypes[key]);
|
|
4909
|
+
if (key.startsWith("origin")) {
|
|
4910
|
+
// If this is a transform origin, flag and enable further transform-origin processing
|
|
4911
|
+
hasTransformOrigin = true;
|
|
4912
|
+
transformOrigin[key] =
|
|
4913
|
+
valueAsType;
|
|
4914
|
+
}
|
|
4915
|
+
else {
|
|
4916
|
+
style[key] = valueAsType;
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
if (!latestValues.transform) {
|
|
4921
|
+
if (hasTransform || transformTemplate) {
|
|
4922
|
+
style.transform = buildTransform(latestValues, state.transform, transformTemplate);
|
|
4923
|
+
}
|
|
4924
|
+
else if (style.transform) {
|
|
4925
|
+
/**
|
|
4926
|
+
* If we have previously created a transform but currently don't have any,
|
|
4927
|
+
* reset transform style to none.
|
|
4928
|
+
*/
|
|
4929
|
+
style.transform = "none";
|
|
4895
4930
|
}
|
|
4896
4931
|
}
|
|
4932
|
+
/**
|
|
4933
|
+
* Build a transformOrigin style. Uses the same defaults as the browser for
|
|
4934
|
+
* undefined origins.
|
|
4935
|
+
*/
|
|
4936
|
+
if (hasTransformOrigin) {
|
|
4937
|
+
const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin;
|
|
4938
|
+
style.transformOrigin = `${originX} ${originY} ${originZ}`;
|
|
4939
|
+
}
|
|
4897
4940
|
}
|
|
4898
4941
|
|
|
4899
4942
|
function renderHTML(element, { style, vars }, styleProp, projection) {
|
|
@@ -4912,6 +4955,116 @@ function renderHTML(element, { style, vars }, styleProp, projection) {
|
|
|
4912
4955
|
}
|
|
4913
4956
|
}
|
|
4914
4957
|
|
|
4958
|
+
function pixelsToPercent(pixels, axis) {
|
|
4959
|
+
if (axis.max === axis.min)
|
|
4960
|
+
return 0;
|
|
4961
|
+
return (pixels / (axis.max - axis.min)) * 100;
|
|
4962
|
+
}
|
|
4963
|
+
/**
|
|
4964
|
+
* We always correct borderRadius as a percentage rather than pixels to reduce paints.
|
|
4965
|
+
* For example, if you are projecting a box that is 100px wide with a 10px borderRadius
|
|
4966
|
+
* into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
|
|
4967
|
+
* borderRadius in both states. If we animate between the two in pixels that will trigger
|
|
4968
|
+
* a paint each time. If we animate between the two in percentage we'll avoid a paint.
|
|
4969
|
+
*/
|
|
4970
|
+
const correctBorderRadius = {
|
|
4971
|
+
correct: (latest, node) => {
|
|
4972
|
+
if (!node.target)
|
|
4973
|
+
return latest;
|
|
4974
|
+
/**
|
|
4975
|
+
* If latest is a string, if it's a percentage we can return immediately as it's
|
|
4976
|
+
* going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
|
|
4977
|
+
*/
|
|
4978
|
+
if (typeof latest === "string") {
|
|
4979
|
+
if (px.test(latest)) {
|
|
4980
|
+
latest = parseFloat(latest);
|
|
4981
|
+
}
|
|
4982
|
+
else {
|
|
4983
|
+
return latest;
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
/**
|
|
4987
|
+
* If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
|
|
4988
|
+
* pixel value as a percentage of each axis
|
|
4989
|
+
*/
|
|
4990
|
+
const x = pixelsToPercent(latest, node.target.x);
|
|
4991
|
+
const y = pixelsToPercent(latest, node.target.y);
|
|
4992
|
+
return `${x}% ${y}%`;
|
|
4993
|
+
},
|
|
4994
|
+
};
|
|
4995
|
+
|
|
4996
|
+
const correctBoxShadow = {
|
|
4997
|
+
correct: (latest, { treeScale, projectionDelta }) => {
|
|
4998
|
+
const original = latest;
|
|
4999
|
+
const shadow = complex.parse(latest);
|
|
5000
|
+
// TODO: Doesn't support multiple shadows
|
|
5001
|
+
if (shadow.length > 5)
|
|
5002
|
+
return original;
|
|
5003
|
+
const template = complex.createTransformer(latest);
|
|
5004
|
+
const offset = typeof shadow[0] !== "number" ? 1 : 0;
|
|
5005
|
+
// Calculate the overall context scale
|
|
5006
|
+
const xScale = projectionDelta.x.scale * treeScale.x;
|
|
5007
|
+
const yScale = projectionDelta.y.scale * treeScale.y;
|
|
5008
|
+
shadow[0 + offset] /= xScale;
|
|
5009
|
+
shadow[1 + offset] /= yScale;
|
|
5010
|
+
/**
|
|
5011
|
+
* Ideally we'd correct x and y scales individually, but because blur and
|
|
5012
|
+
* spread apply to both we have to take a scale average and apply that instead.
|
|
5013
|
+
* We could potentially improve the outcome of this by incorporating the ratio between
|
|
5014
|
+
* the two scales.
|
|
5015
|
+
*/
|
|
5016
|
+
const averageScale = mixNumber$1(xScale, yScale, 0.5);
|
|
5017
|
+
// Blur
|
|
5018
|
+
if (typeof shadow[2 + offset] === "number")
|
|
5019
|
+
shadow[2 + offset] /= averageScale;
|
|
5020
|
+
// Spread
|
|
5021
|
+
if (typeof shadow[3 + offset] === "number")
|
|
5022
|
+
shadow[3 + offset] /= averageScale;
|
|
5023
|
+
return template(shadow);
|
|
5024
|
+
},
|
|
5025
|
+
};
|
|
5026
|
+
|
|
5027
|
+
const scaleCorrectors = {
|
|
5028
|
+
borderRadius: {
|
|
5029
|
+
...correctBorderRadius,
|
|
5030
|
+
applyTo: [
|
|
5031
|
+
"borderTopLeftRadius",
|
|
5032
|
+
"borderTopRightRadius",
|
|
5033
|
+
"borderBottomLeftRadius",
|
|
5034
|
+
"borderBottomRightRadius",
|
|
5035
|
+
],
|
|
5036
|
+
},
|
|
5037
|
+
borderTopLeftRadius: correctBorderRadius,
|
|
5038
|
+
borderTopRightRadius: correctBorderRadius,
|
|
5039
|
+
borderBottomLeftRadius: correctBorderRadius,
|
|
5040
|
+
borderBottomRightRadius: correctBorderRadius,
|
|
5041
|
+
boxShadow: correctBoxShadow,
|
|
5042
|
+
};
|
|
5043
|
+
|
|
5044
|
+
function isForcedMotionValue(key, { layout, layoutId }) {
|
|
5045
|
+
return (transformProps.has(key) ||
|
|
5046
|
+
key.startsWith("origin") ||
|
|
5047
|
+
((layout || layoutId !== undefined) &&
|
|
5048
|
+
(!!scaleCorrectors[key] || key === "opacity")));
|
|
5049
|
+
}
|
|
5050
|
+
|
|
5051
|
+
function scrapeMotionValuesFromProps$1(props, prevProps, visualElement) {
|
|
5052
|
+
const style = props.style;
|
|
5053
|
+
const prevStyle = prevProps?.style;
|
|
5054
|
+
const newValues = {};
|
|
5055
|
+
if (!style)
|
|
5056
|
+
return newValues;
|
|
5057
|
+
for (const key in style) {
|
|
5058
|
+
if (isMotionValue(style[key]) ||
|
|
5059
|
+
(prevStyle && isMotionValue(prevStyle[key])) ||
|
|
5060
|
+
isForcedMotionValue(key, props) ||
|
|
5061
|
+
visualElement?.getValue(key)?.liveStyle !== undefined) {
|
|
5062
|
+
newValues[key] = style[key];
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
return newValues;
|
|
5066
|
+
}
|
|
5067
|
+
|
|
4915
5068
|
function getComputedStyle$1(element) {
|
|
4916
5069
|
return window.getComputedStyle(element);
|
|
4917
5070
|
}
|
|
@@ -4935,14 +5088,111 @@ class HTMLVisualElement extends DOMVisualElement {
|
|
|
4935
5088
|
return typeof value === "string" ? value.trim() : value;
|
|
4936
5089
|
}
|
|
4937
5090
|
}
|
|
4938
|
-
measureInstanceViewportBox(instance, { transformPagePoint }) {
|
|
4939
|
-
return measureViewportBox(instance, transformPagePoint);
|
|
5091
|
+
measureInstanceViewportBox(instance, { transformPagePoint }) {
|
|
5092
|
+
return measureViewportBox(instance, transformPagePoint);
|
|
5093
|
+
}
|
|
5094
|
+
build(renderState, latestValues, props) {
|
|
5095
|
+
buildHTMLStyles(renderState, latestValues, props.transformTemplate);
|
|
5096
|
+
}
|
|
5097
|
+
scrapeMotionValuesFromProps(props, prevProps, visualElement) {
|
|
5098
|
+
return scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
|
|
5102
|
+
const dashKeys = {
|
|
5103
|
+
offset: "stroke-dashoffset",
|
|
5104
|
+
array: "stroke-dasharray",
|
|
5105
|
+
};
|
|
5106
|
+
const camelKeys = {
|
|
5107
|
+
offset: "strokeDashoffset",
|
|
5108
|
+
array: "strokeDasharray",
|
|
5109
|
+
};
|
|
5110
|
+
/**
|
|
5111
|
+
* Build SVG path properties. Uses the path's measured length to convert
|
|
5112
|
+
* our custom pathLength, pathSpacing and pathOffset into stroke-dashoffset
|
|
5113
|
+
* and stroke-dasharray attributes.
|
|
5114
|
+
*
|
|
5115
|
+
* This function is mutative to reduce per-frame GC.
|
|
5116
|
+
*/
|
|
5117
|
+
function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true) {
|
|
5118
|
+
// Normalise path length by setting SVG attribute pathLength to 1
|
|
5119
|
+
attrs.pathLength = 1;
|
|
5120
|
+
// We use dash case when setting attributes directly to the DOM node and camel case
|
|
5121
|
+
// when defining props on a React component.
|
|
5122
|
+
const keys = useDashCase ? dashKeys : camelKeys;
|
|
5123
|
+
// Build the dash offset
|
|
5124
|
+
attrs[keys.offset] = px.transform(-offset);
|
|
5125
|
+
// Build the dash array
|
|
5126
|
+
const pathLength = px.transform(length);
|
|
5127
|
+
const pathSpacing = px.transform(spacing);
|
|
5128
|
+
attrs[keys.array] = `${pathLength} ${pathSpacing}`;
|
|
5129
|
+
}
|
|
5130
|
+
|
|
5131
|
+
/**
|
|
5132
|
+
* CSS Motion Path properties that should remain as CSS styles on SVG elements.
|
|
5133
|
+
*/
|
|
5134
|
+
const cssMotionPathProperties = [
|
|
5135
|
+
"offsetDistance",
|
|
5136
|
+
"offsetPath",
|
|
5137
|
+
"offsetRotate",
|
|
5138
|
+
"offsetAnchor",
|
|
5139
|
+
];
|
|
5140
|
+
/**
|
|
5141
|
+
* Build SVG visual attributes, like cx and style.transform
|
|
5142
|
+
*/
|
|
5143
|
+
function buildSVGAttrs(state, { attrX, attrY, attrScale, pathLength, pathSpacing = 1, pathOffset = 0,
|
|
5144
|
+
// This is object creation, which we try to avoid per-frame.
|
|
5145
|
+
...latest }, isSVGTag, transformTemplate, styleProp) {
|
|
5146
|
+
buildHTMLStyles(state, latest, transformTemplate);
|
|
5147
|
+
/**
|
|
5148
|
+
* For svg tags we just want to make sure viewBox is animatable and treat all the styles
|
|
5149
|
+
* as normal HTML tags.
|
|
5150
|
+
*/
|
|
5151
|
+
if (isSVGTag) {
|
|
5152
|
+
if (state.style.viewBox) {
|
|
5153
|
+
state.attrs.viewBox = state.style.viewBox;
|
|
5154
|
+
}
|
|
5155
|
+
return;
|
|
5156
|
+
}
|
|
5157
|
+
state.attrs = state.style;
|
|
5158
|
+
state.style = {};
|
|
5159
|
+
const { attrs, style } = state;
|
|
5160
|
+
/**
|
|
5161
|
+
* However, we apply transforms as CSS transforms.
|
|
5162
|
+
* So if we detect a transform, transformOrigin we take it from attrs and copy it into style.
|
|
5163
|
+
*/
|
|
5164
|
+
if (attrs.transform) {
|
|
5165
|
+
style.transform = attrs.transform;
|
|
5166
|
+
delete attrs.transform;
|
|
5167
|
+
}
|
|
5168
|
+
if (style.transform || attrs.transformOrigin) {
|
|
5169
|
+
style.transformOrigin = attrs.transformOrigin ?? "50% 50%";
|
|
5170
|
+
delete attrs.transformOrigin;
|
|
4940
5171
|
}
|
|
4941
|
-
|
|
4942
|
-
|
|
5172
|
+
if (style.transform) {
|
|
5173
|
+
/**
|
|
5174
|
+
* SVG's element transform-origin uses its own median as a reference.
|
|
5175
|
+
* Therefore, transformBox becomes a fill-box
|
|
5176
|
+
*/
|
|
5177
|
+
style.transformBox = styleProp?.transformBox ?? "fill-box";
|
|
5178
|
+
delete attrs.transformBox;
|
|
4943
5179
|
}
|
|
4944
|
-
|
|
4945
|
-
|
|
5180
|
+
for (const key of cssMotionPathProperties) {
|
|
5181
|
+
if (attrs[key] !== undefined) {
|
|
5182
|
+
style[key] = attrs[key];
|
|
5183
|
+
delete attrs[key];
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
// Render attrX/attrY/attrScale as attributes
|
|
5187
|
+
if (attrX !== undefined)
|
|
5188
|
+
attrs.x = attrX;
|
|
5189
|
+
if (attrY !== undefined)
|
|
5190
|
+
attrs.y = attrY;
|
|
5191
|
+
if (attrScale !== undefined)
|
|
5192
|
+
attrs.scale = attrScale;
|
|
5193
|
+
// Build SVG path if one has been defined
|
|
5194
|
+
if (pathLength !== undefined) {
|
|
5195
|
+
buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false);
|
|
4946
5196
|
}
|
|
4947
5197
|
}
|
|
4948
5198
|
|
|
@@ -4965,336 +5215,107 @@ const camelCaseAttributes = new Set([
|
|
|
4965
5215
|
"surfaceScale",
|
|
4966
5216
|
"specularConstant",
|
|
4967
5217
|
"specularExponent",
|
|
4968
|
-
"stdDeviation",
|
|
4969
|
-
"tableValues",
|
|
4970
|
-
"viewBox",
|
|
4971
|
-
"gradientTransform",
|
|
4972
|
-
"pathLength",
|
|
4973
|
-
"startOffset",
|
|
4974
|
-
"textLength",
|
|
4975
|
-
"lengthAdjust",
|
|
4976
|
-
]);
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
class SVGVisualElement extends DOMVisualElement {
|
|
4986
|
-
constructor() {
|
|
4987
|
-
super(...arguments);
|
|
4988
|
-
this.type = "svg";
|
|
4989
|
-
this.isSVGTag = false;
|
|
4990
|
-
this.measureInstanceViewportBox = createBox;
|
|
4991
|
-
}
|
|
4992
|
-
getBaseTargetFromProps(props, key) {
|
|
4993
|
-
return props[key];
|
|
4994
|
-
}
|
|
4995
|
-
readValueFromInstance(instance, key) {
|
|
4996
|
-
if (transformProps.has(key)) {
|
|
4997
|
-
const defaultType = getDefaultValueType(key);
|
|
4998
|
-
return defaultType ? defaultType.default || 0 : 0;
|
|
4999
|
-
}
|
|
5000
|
-
key = !camelCaseAttributes.has(key) ? camelToDash(key) : key;
|
|
5001
|
-
return instance.getAttribute(key);
|
|
5002
|
-
}
|
|
5003
|
-
scrapeMotionValuesFromProps(props, prevProps, visualElement) {
|
|
5004
|
-
return scrapeMotionValuesFromProps(props, prevProps, visualElement);
|
|
5005
|
-
}
|
|
5006
|
-
build(renderState, latestValues, props) {
|
|
5007
|
-
buildSVGAttrs(renderState, latestValues, this.isSVGTag, props.transformTemplate, props.style);
|
|
5008
|
-
}
|
|
5009
|
-
renderInstance(instance, renderState, styleProp, projection) {
|
|
5010
|
-
renderSVG(instance, renderState, styleProp, projection);
|
|
5011
|
-
}
|
|
5012
|
-
mount(instance) {
|
|
5013
|
-
this.isSVGTag = isSVGTag(instance.tagName);
|
|
5014
|
-
super.mount(instance);
|
|
5015
|
-
}
|
|
5016
|
-
}
|
|
5017
|
-
|
|
5018
|
-
function resolveVariant(visualElement, definition, custom) {
|
|
5019
|
-
const props = visualElement.getProps();
|
|
5020
|
-
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
|
|
5021
|
-
}
|
|
5022
|
-
|
|
5023
|
-
const isKeyframesTarget = (v) => {
|
|
5024
|
-
return Array.isArray(v);
|
|
5025
|
-
};
|
|
5026
|
-
|
|
5027
|
-
/**
|
|
5028
|
-
* Set VisualElement's MotionValue, creating a new MotionValue for it if
|
|
5029
|
-
* it doesn't exist.
|
|
5030
|
-
*/
|
|
5031
|
-
function setMotionValue(visualElement, key, value) {
|
|
5032
|
-
if (visualElement.hasValue(key)) {
|
|
5033
|
-
visualElement.getValue(key).set(value);
|
|
5034
|
-
}
|
|
5035
|
-
else {
|
|
5036
|
-
visualElement.addValue(key, motionValue(value));
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
5039
|
-
function resolveFinalValueInKeyframes(v) {
|
|
5040
|
-
// TODO maybe throw if v.length - 1 is placeholder token?
|
|
5041
|
-
return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
|
|
5042
|
-
}
|
|
5043
|
-
function setTarget(visualElement, definition) {
|
|
5044
|
-
const resolved = resolveVariant(visualElement, definition);
|
|
5045
|
-
let { transitionEnd = {}, transition = {}, ...target } = resolved || {};
|
|
5046
|
-
target = { ...target, ...transitionEnd };
|
|
5047
|
-
for (const key in target) {
|
|
5048
|
-
const value = resolveFinalValueInKeyframes(target[key]);
|
|
5049
|
-
setMotionValue(visualElement, key, value);
|
|
5050
|
-
}
|
|
5051
|
-
}
|
|
5052
|
-
|
|
5053
|
-
function isWillChangeMotionValue(value) {
|
|
5054
|
-
return Boolean(isMotionValue(value) && value.add);
|
|
5055
|
-
}
|
|
5056
|
-
|
|
5057
|
-
function addValueToWillChange(visualElement, key) {
|
|
5058
|
-
const willChange = visualElement.getValue("willChange");
|
|
5059
|
-
/**
|
|
5060
|
-
* It could be that a user has set willChange to a regular MotionValue,
|
|
5061
|
-
* in which case we can't add the value to it.
|
|
5062
|
-
*/
|
|
5063
|
-
if (isWillChangeMotionValue(willChange)) {
|
|
5064
|
-
return willChange.add(key);
|
|
5065
|
-
}
|
|
5066
|
-
else if (!willChange && MotionGlobalConfig.WillChange) {
|
|
5067
|
-
const newWillChange = new MotionGlobalConfig.WillChange("auto");
|
|
5068
|
-
visualElement.addValue("willChange", newWillChange);
|
|
5069
|
-
newWillChange.add(key);
|
|
5070
|
-
}
|
|
5071
|
-
}
|
|
5072
|
-
|
|
5073
|
-
function getOptimisedAppearId(visualElement) {
|
|
5074
|
-
return visualElement.props[optimizedAppearDataAttribute];
|
|
5075
|
-
}
|
|
5076
|
-
|
|
5077
|
-
const isNotNull = (value) => value !== null;
|
|
5078
|
-
function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
|
|
5079
|
-
const resolvedKeyframes = keyframes.filter(isNotNull);
|
|
5080
|
-
const index = repeat && repeatType !== "loop" && repeat % 2 === 1
|
|
5081
|
-
? 0
|
|
5082
|
-
: resolvedKeyframes.length - 1;
|
|
5083
|
-
return resolvedKeyframes[index]
|
|
5084
|
-
;
|
|
5085
|
-
}
|
|
5086
|
-
|
|
5087
|
-
const underDampedSpring = {
|
|
5088
|
-
type: "spring",
|
|
5089
|
-
stiffness: 500,
|
|
5090
|
-
damping: 25,
|
|
5091
|
-
restSpeed: 10,
|
|
5092
|
-
};
|
|
5093
|
-
const criticallyDampedSpring = (target) => ({
|
|
5094
|
-
type: "spring",
|
|
5095
|
-
stiffness: 550,
|
|
5096
|
-
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
5097
|
-
restSpeed: 10,
|
|
5098
|
-
});
|
|
5099
|
-
const keyframesTransition = {
|
|
5100
|
-
type: "keyframes",
|
|
5101
|
-
duration: 0.8,
|
|
5102
|
-
};
|
|
5103
|
-
/**
|
|
5104
|
-
* Default easing curve is a slightly shallower version of
|
|
5105
|
-
* the default browser easing curve.
|
|
5106
|
-
*/
|
|
5107
|
-
const ease = {
|
|
5108
|
-
type: "keyframes",
|
|
5109
|
-
ease: [0.25, 0.1, 0.35, 1],
|
|
5110
|
-
duration: 0.3,
|
|
5111
|
-
};
|
|
5112
|
-
const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
5113
|
-
if (keyframes.length > 2) {
|
|
5114
|
-
return keyframesTransition;
|
|
5115
|
-
}
|
|
5116
|
-
else if (transformProps.has(valueKey)) {
|
|
5117
|
-
return valueKey.startsWith("scale")
|
|
5118
|
-
? criticallyDampedSpring(keyframes[1])
|
|
5119
|
-
: underDampedSpring;
|
|
5120
|
-
}
|
|
5121
|
-
return ease;
|
|
5122
|
-
};
|
|
5123
|
-
|
|
5124
|
-
/**
|
|
5125
|
-
* Decide whether a transition is defined on a given Transition.
|
|
5126
|
-
* This filters out orchestration options and returns true
|
|
5127
|
-
* if any options are left.
|
|
5128
|
-
*/
|
|
5129
|
-
function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
|
|
5130
|
-
return !!Object.keys(transition).length;
|
|
5131
|
-
}
|
|
5132
|
-
|
|
5133
|
-
const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
|
|
5134
|
-
const valueTransition = getValueTransition$1(transition, name) || {};
|
|
5135
|
-
/**
|
|
5136
|
-
* Most transition values are currently completely overwritten by value-specific
|
|
5137
|
-
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
5138
|
-
* delay actually does inherit from the root transition if not value-specific.
|
|
5139
|
-
*/
|
|
5140
|
-
const delay = valueTransition.delay || transition.delay || 0;
|
|
5141
|
-
/**
|
|
5142
|
-
* Elapsed isn't a public transition option but can be passed through from
|
|
5143
|
-
* optimized appear effects in milliseconds.
|
|
5144
|
-
*/
|
|
5145
|
-
let { elapsed = 0 } = transition;
|
|
5146
|
-
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
5147
|
-
const options = {
|
|
5148
|
-
keyframes: Array.isArray(target) ? target : [null, target],
|
|
5149
|
-
ease: "easeOut",
|
|
5150
|
-
velocity: value.getVelocity(),
|
|
5151
|
-
...valueTransition,
|
|
5152
|
-
delay: -elapsed,
|
|
5153
|
-
onUpdate: (v) => {
|
|
5154
|
-
value.set(v);
|
|
5155
|
-
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
5156
|
-
},
|
|
5157
|
-
onComplete: () => {
|
|
5158
|
-
onComplete();
|
|
5159
|
-
valueTransition.onComplete && valueTransition.onComplete();
|
|
5160
|
-
},
|
|
5161
|
-
name,
|
|
5162
|
-
motionValue: value,
|
|
5163
|
-
element: isHandoff ? undefined : element,
|
|
5164
|
-
};
|
|
5165
|
-
/**
|
|
5166
|
-
* If there's no transition defined for this value, we can generate
|
|
5167
|
-
* unique transition settings for this value.
|
|
5168
|
-
*/
|
|
5169
|
-
if (!isTransitionDefined(valueTransition)) {
|
|
5170
|
-
Object.assign(options, getDefaultTransition(name, options));
|
|
5171
|
-
}
|
|
5172
|
-
/**
|
|
5173
|
-
* Both WAAPI and our internal animation functions use durations
|
|
5174
|
-
* as defined by milliseconds, while our external API defines them
|
|
5175
|
-
* as seconds.
|
|
5176
|
-
*/
|
|
5177
|
-
options.duration && (options.duration = secondsToMilliseconds(options.duration));
|
|
5178
|
-
options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay));
|
|
5179
|
-
/**
|
|
5180
|
-
* Support deprecated way to set initial value. Prefer keyframe syntax.
|
|
5181
|
-
*/
|
|
5182
|
-
if (options.from !== undefined) {
|
|
5183
|
-
options.keyframes[0] = options.from;
|
|
5218
|
+
"stdDeviation",
|
|
5219
|
+
"tableValues",
|
|
5220
|
+
"viewBox",
|
|
5221
|
+
"gradientTransform",
|
|
5222
|
+
"pathLength",
|
|
5223
|
+
"startOffset",
|
|
5224
|
+
"textLength",
|
|
5225
|
+
"lengthAdjust",
|
|
5226
|
+
]);
|
|
5227
|
+
|
|
5228
|
+
const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg";
|
|
5229
|
+
|
|
5230
|
+
function renderSVG(element, renderState, _styleProp, projection) {
|
|
5231
|
+
renderHTML(element, renderState, undefined, projection);
|
|
5232
|
+
for (const key in renderState.attrs) {
|
|
5233
|
+
element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]);
|
|
5184
5234
|
}
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
|
|
5238
|
+
const newValues = scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
|
|
5239
|
+
for (const key in props) {
|
|
5240
|
+
if (isMotionValue(props[key]) ||
|
|
5241
|
+
isMotionValue(prevProps[key])) {
|
|
5242
|
+
const targetKey = transformPropOrder.indexOf(key) !== -1
|
|
5243
|
+
? "attr" + key.charAt(0).toUpperCase() + key.substring(1)
|
|
5244
|
+
: key;
|
|
5245
|
+
newValues[targetKey] = props[key];
|
|
5191
5246
|
}
|
|
5192
5247
|
}
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5248
|
+
return newValues;
|
|
5249
|
+
}
|
|
5250
|
+
|
|
5251
|
+
class SVGVisualElement extends DOMVisualElement {
|
|
5252
|
+
constructor() {
|
|
5253
|
+
super(...arguments);
|
|
5254
|
+
this.type = "svg";
|
|
5255
|
+
this.isSVGTag = false;
|
|
5256
|
+
this.measureInstanceViewportBox = createBox;
|
|
5198
5257
|
}
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
* the final keyframe, do so. We also check once keyframes are resolved but
|
|
5207
|
-
* this early check prevents the need to create an animation at all.
|
|
5208
|
-
*/
|
|
5209
|
-
if (shouldSkip && !isHandoff && value.get() !== undefined) {
|
|
5210
|
-
const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
|
|
5211
|
-
if (finalKeyframe !== undefined) {
|
|
5212
|
-
frame.update(() => {
|
|
5213
|
-
options.onUpdate(finalKeyframe);
|
|
5214
|
-
options.onComplete();
|
|
5215
|
-
});
|
|
5216
|
-
return;
|
|
5258
|
+
getBaseTargetFromProps(props, key) {
|
|
5259
|
+
return props[key];
|
|
5260
|
+
}
|
|
5261
|
+
readValueFromInstance(instance, key) {
|
|
5262
|
+
if (transformProps.has(key)) {
|
|
5263
|
+
const defaultType = getDefaultValueType(key);
|
|
5264
|
+
return defaultType ? defaultType.default || 0 : 0;
|
|
5217
5265
|
}
|
|
5266
|
+
key = !camelCaseAttributes.has(key) ? camelToDash(key) : key;
|
|
5267
|
+
return instance.getAttribute(key);
|
|
5218
5268
|
}
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5269
|
+
scrapeMotionValuesFromProps(props, prevProps, visualElement) {
|
|
5270
|
+
return scrapeMotionValuesFromProps(props, prevProps, visualElement);
|
|
5271
|
+
}
|
|
5272
|
+
build(renderState, latestValues, props) {
|
|
5273
|
+
buildSVGAttrs(renderState, latestValues, this.isSVGTag, props.transformTemplate, props.style);
|
|
5274
|
+
}
|
|
5275
|
+
renderInstance(instance, renderState, styleProp, projection) {
|
|
5276
|
+
renderSVG(instance, renderState, styleProp, projection);
|
|
5277
|
+
}
|
|
5278
|
+
mount(instance) {
|
|
5279
|
+
this.isSVGTag = isSVGTag(instance.tagName);
|
|
5280
|
+
super.mount(instance);
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5223
5283
|
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
* just by checking whether the key was listed in protectedKeys, but this
|
|
5227
|
-
* posed problems if an animation was triggered by afterChildren and protectedKeys
|
|
5228
|
-
* had been set to true in the meantime.
|
|
5229
|
-
*/
|
|
5230
|
-
function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
|
|
5231
|
-
const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
|
|
5232
|
-
needsAnimating[key] = false;
|
|
5233
|
-
return shouldBlock;
|
|
5284
|
+
function isObjectKey(key, object) {
|
|
5285
|
+
return key in object;
|
|
5234
5286
|
}
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
const valueTarget = target[key];
|
|
5246
|
-
if (valueTarget === undefined ||
|
|
5247
|
-
(animationTypeState &&
|
|
5248
|
-
shouldBlockAnimation(animationTypeState, key))) {
|
|
5249
|
-
continue;
|
|
5250
|
-
}
|
|
5251
|
-
const valueTransition = {
|
|
5252
|
-
delay,
|
|
5253
|
-
...getValueTransition$1(transition || {}, key),
|
|
5254
|
-
};
|
|
5255
|
-
/**
|
|
5256
|
-
* If the value is already at the defined target, skip the animation.
|
|
5257
|
-
*/
|
|
5258
|
-
const currentValue = value.get();
|
|
5259
|
-
if (currentValue !== undefined &&
|
|
5260
|
-
!value.isAnimating &&
|
|
5261
|
-
!Array.isArray(valueTarget) &&
|
|
5262
|
-
valueTarget === currentValue &&
|
|
5263
|
-
!valueTransition.velocity) {
|
|
5264
|
-
continue;
|
|
5265
|
-
}
|
|
5266
|
-
/**
|
|
5267
|
-
* If this is the first time a value is being animated, check
|
|
5268
|
-
* to see if we're handling off from an existing animation.
|
|
5269
|
-
*/
|
|
5270
|
-
let isHandoff = false;
|
|
5271
|
-
if (window.MotionHandoffAnimation) {
|
|
5272
|
-
const appearId = getOptimisedAppearId(visualElement);
|
|
5273
|
-
if (appearId) {
|
|
5274
|
-
const startTime = window.MotionHandoffAnimation(appearId, key, frame);
|
|
5275
|
-
if (startTime !== null) {
|
|
5276
|
-
valueTransition.startTime = startTime;
|
|
5277
|
-
isHandoff = true;
|
|
5278
|
-
}
|
|
5287
|
+
class ObjectVisualElement extends VisualElement {
|
|
5288
|
+
constructor() {
|
|
5289
|
+
super(...arguments);
|
|
5290
|
+
this.type = "object";
|
|
5291
|
+
}
|
|
5292
|
+
readValueFromInstance(instance, key) {
|
|
5293
|
+
if (isObjectKey(key, instance)) {
|
|
5294
|
+
const value = instance[key];
|
|
5295
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
5296
|
+
return value;
|
|
5279
5297
|
}
|
|
5280
5298
|
}
|
|
5281
|
-
|
|
5282
|
-
value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && positionalKeys.has(key)
|
|
5283
|
-
? { type: false }
|
|
5284
|
-
: valueTransition, visualElement, isHandoff));
|
|
5285
|
-
const animation = value.animation;
|
|
5286
|
-
if (animation) {
|
|
5287
|
-
animations.push(animation);
|
|
5288
|
-
}
|
|
5299
|
+
return undefined;
|
|
5289
5300
|
}
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5301
|
+
getBaseTargetFromProps() {
|
|
5302
|
+
return undefined;
|
|
5303
|
+
}
|
|
5304
|
+
removeValueFromRenderState(key, renderState) {
|
|
5305
|
+
delete renderState.output[key];
|
|
5306
|
+
}
|
|
5307
|
+
measureInstanceViewportBox() {
|
|
5308
|
+
return createBox();
|
|
5309
|
+
}
|
|
5310
|
+
build(renderState, latestValues) {
|
|
5311
|
+
Object.assign(renderState.output, latestValues);
|
|
5312
|
+
}
|
|
5313
|
+
renderInstance(instance, { output }) {
|
|
5314
|
+
Object.assign(instance, output);
|
|
5315
|
+
}
|
|
5316
|
+
sortInstanceNodePosition() {
|
|
5317
|
+
return 0;
|
|
5296
5318
|
}
|
|
5297
|
-
return animations;
|
|
5298
5319
|
}
|
|
5299
5320
|
|
|
5300
5321
|
function animateSingleValue(value, keyframes, options) {
|
|
@@ -5303,6 +5324,15 @@ function animateSingleValue(value, keyframes, options) {
|
|
|
5303
5324
|
return motionValue$1.animation;
|
|
5304
5325
|
}
|
|
5305
5326
|
|
|
5327
|
+
/**
|
|
5328
|
+
* @public
|
|
5329
|
+
*/
|
|
5330
|
+
const MotionConfigContext = React.createContext({
|
|
5331
|
+
transformPagePoint: (p) => p,
|
|
5332
|
+
isStatic: false,
|
|
5333
|
+
reducedMotion: "never",
|
|
5334
|
+
});
|
|
5335
|
+
|
|
5306
5336
|
/**
|
|
5307
5337
|
* Creates a `MotionValue` to track the state and velocity of a value.
|
|
5308
5338
|
*
|
|
@@ -5670,43 +5700,6 @@ function getValueTransition(transition, key) {
|
|
|
5670
5700
|
const isNumber = (keyframe) => typeof keyframe === "number";
|
|
5671
5701
|
const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
|
|
5672
5702
|
|
|
5673
|
-
function isObjectKey(key, object) {
|
|
5674
|
-
return key in object;
|
|
5675
|
-
}
|
|
5676
|
-
class ObjectVisualElement extends VisualElement {
|
|
5677
|
-
constructor() {
|
|
5678
|
-
super(...arguments);
|
|
5679
|
-
this.type = "object";
|
|
5680
|
-
}
|
|
5681
|
-
readValueFromInstance(instance, key) {
|
|
5682
|
-
if (isObjectKey(key, instance)) {
|
|
5683
|
-
const value = instance[key];
|
|
5684
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
5685
|
-
return value;
|
|
5686
|
-
}
|
|
5687
|
-
}
|
|
5688
|
-
return undefined;
|
|
5689
|
-
}
|
|
5690
|
-
getBaseTargetFromProps() {
|
|
5691
|
-
return undefined;
|
|
5692
|
-
}
|
|
5693
|
-
removeValueFromRenderState(key, renderState) {
|
|
5694
|
-
delete renderState.output[key];
|
|
5695
|
-
}
|
|
5696
|
-
measureInstanceViewportBox() {
|
|
5697
|
-
return createBox();
|
|
5698
|
-
}
|
|
5699
|
-
build(renderState, latestValues) {
|
|
5700
|
-
Object.assign(renderState.output, latestValues);
|
|
5701
|
-
}
|
|
5702
|
-
renderInstance(instance, { output }) {
|
|
5703
|
-
Object.assign(instance, output);
|
|
5704
|
-
}
|
|
5705
|
-
sortInstanceNodePosition() {
|
|
5706
|
-
return 0;
|
|
5707
|
-
}
|
|
5708
|
-
}
|
|
5709
|
-
|
|
5710
5703
|
function createDOMVisualElement(element) {
|
|
5711
5704
|
const options = {
|
|
5712
5705
|
presenceContext: null,
|
|
@@ -8950,28 +8943,56 @@ const useLiveRef = (value) => {
|
|
|
8950
8943
|
return ref;
|
|
8951
8944
|
};
|
|
8952
8945
|
|
|
8953
|
-
const useDebounce = (callback, delay) => {
|
|
8946
|
+
const useDebounce = (callback, delay, { maxWait, leading = false } = {}) => {
|
|
8954
8947
|
const timeoutRef = React.useRef(null);
|
|
8948
|
+
const maxTimeoutMetadataRef = React.useRef(null);
|
|
8955
8949
|
const callbackRef = useLiveRef(callback);
|
|
8956
|
-
React.
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8950
|
+
const clearTimers = React.useCallback(() => {
|
|
8951
|
+
if (timeoutRef.current) {
|
|
8952
|
+
clearTimeout(timeoutRef.current);
|
|
8953
|
+
timeoutRef.current = null;
|
|
8954
|
+
}
|
|
8955
|
+
if (maxTimeoutMetadataRef.current) {
|
|
8956
|
+
clearTimeout(maxTimeoutMetadataRef.current.timeout);
|
|
8957
|
+
maxTimeoutMetadataRef.current = null;
|
|
8958
|
+
}
|
|
8963
8959
|
}, []);
|
|
8960
|
+
const invokeCallback = React.useCallback(
|
|
8961
|
+
(args) => {
|
|
8962
|
+
clearTimers();
|
|
8963
|
+
callbackRef.current(...args);
|
|
8964
|
+
},
|
|
8965
|
+
[clearTimers, callbackRef]
|
|
8966
|
+
);
|
|
8967
|
+
React.useEffect(() => {
|
|
8968
|
+
return clearTimers;
|
|
8969
|
+
}, [clearTimers]);
|
|
8964
8970
|
return React.useCallback(
|
|
8965
8971
|
(...args) => {
|
|
8972
|
+
const isNewCycle = timeoutRef.current === null;
|
|
8973
|
+
if (leading && isNewCycle) {
|
|
8974
|
+
callbackRef.current(...args);
|
|
8975
|
+
}
|
|
8966
8976
|
if (timeoutRef.current) {
|
|
8967
8977
|
clearTimeout(timeoutRef.current);
|
|
8968
8978
|
}
|
|
8979
|
+
if (maxWait && !maxTimeoutMetadataRef.current) {
|
|
8980
|
+
maxTimeoutMetadataRef.current = {
|
|
8981
|
+
timeout: setTimeout(() => {
|
|
8982
|
+
if (maxTimeoutMetadataRef.current) {
|
|
8983
|
+
invokeCallback(maxTimeoutMetadataRef.current.args);
|
|
8984
|
+
}
|
|
8985
|
+
}, maxWait),
|
|
8986
|
+
args
|
|
8987
|
+
};
|
|
8988
|
+
} else if (maxTimeoutMetadataRef.current) {
|
|
8989
|
+
maxTimeoutMetadataRef.current.args = args;
|
|
8990
|
+
}
|
|
8969
8991
|
timeoutRef.current = setTimeout(() => {
|
|
8970
|
-
|
|
8971
|
-
timeoutRef.current = null;
|
|
8992
|
+
invokeCallback(args);
|
|
8972
8993
|
}, delay);
|
|
8973
8994
|
},
|
|
8974
|
-
[
|
|
8995
|
+
[delay, maxWait, leading, invokeCallback, callbackRef]
|
|
8975
8996
|
);
|
|
8976
8997
|
};
|
|
8977
8998
|
|
|
@@ -9221,10 +9242,10 @@ exports.convertBoxToBoundingBox = convertBoxToBoundingBox;
|
|
|
9221
9242
|
exports.createBox = createBox;
|
|
9222
9243
|
exports.createDelta = createDelta;
|
|
9223
9244
|
exports.createScopedAnimate = createScopedAnimate;
|
|
9224
|
-
exports.featureDefinitions = featureDefinitions;
|
|
9225
9245
|
exports.frame = frame;
|
|
9226
9246
|
exports.frameData = frameData;
|
|
9227
9247
|
exports.frameSteps = frameSteps;
|
|
9248
|
+
exports.getFeatureDefinitions = getFeatureDefinitions;
|
|
9228
9249
|
exports.getOptimisedAppearId = getOptimisedAppearId;
|
|
9229
9250
|
exports.getValueTransition = getValueTransition$1;
|
|
9230
9251
|
exports.has2DTranslate = has2DTranslate;
|
|
@@ -9234,7 +9255,6 @@ exports.hasTransform = hasTransform;
|
|
|
9234
9255
|
exports.initPrefersReducedMotion = initPrefersReducedMotion;
|
|
9235
9256
|
exports.interpolate = interpolate;
|
|
9236
9257
|
exports.isAnimationControls = isAnimationControls;
|
|
9237
|
-
exports.isBrowser = isBrowser;
|
|
9238
9258
|
exports.isControllingVariants = isControllingVariants;
|
|
9239
9259
|
exports.isForcedMotionValue = isForcedMotionValue;
|
|
9240
9260
|
exports.isKeyframesTarget = isKeyframesTarget;
|
|
@@ -9266,6 +9286,7 @@ exports.scalePoint = scalePoint;
|
|
|
9266
9286
|
exports.scrapeMotionValuesFromProps = scrapeMotionValuesFromProps$1;
|
|
9267
9287
|
exports.scrapeMotionValuesFromProps$1 = scrapeMotionValuesFromProps;
|
|
9268
9288
|
exports.secondsToMilliseconds = secondsToMilliseconds;
|
|
9289
|
+
exports.setFeatureDefinitions = setFeatureDefinitions;
|
|
9269
9290
|
exports.time = time;
|
|
9270
9291
|
exports.transformBox = transformBox;
|
|
9271
9292
|
exports.translateAxis = translateAxis;
|