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