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