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