framer-motion 5.1.0 → 5.3.0

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.
@@ -135,6 +135,7 @@
135
135
  "whileTap",
136
136
  "whileFocus",
137
137
  "whileDrag",
138
+ "whileInView",
138
139
  ]),
139
140
  exit: createDefinition(["exit"]),
140
141
  drag: createDefinition(["drag", "dragControls"]),
@@ -147,6 +148,11 @@
147
148
  "onPanSessionStart",
148
149
  "onPanEnd",
149
150
  ]),
151
+ inView: createDefinition([
152
+ "whileInView",
153
+ "onViewportEnter",
154
+ "onViewportLeave",
155
+ ]),
150
156
  };
151
157
  function loadFeatures(features) {
152
158
  for (var key in features) {
@@ -929,6 +935,46 @@
929
935
  return functions ? functions.map(applyDefaultFilter).join(' ') : v;
930
936
  } });
931
937
 
938
+ function hueToRgb(p, q, t) {
939
+ if (t < 0)
940
+ t += 1;
941
+ if (t > 1)
942
+ t -= 1;
943
+ if (t < 1 / 6)
944
+ return p + (q - p) * 6 * t;
945
+ if (t < 1 / 2)
946
+ return q;
947
+ if (t < 2 / 3)
948
+ return p + (q - p) * (2 / 3 - t) * 6;
949
+ return p;
950
+ }
951
+ function hslaToRgba({ hue, saturation, lightness, alpha }) {
952
+ hue /= 360;
953
+ saturation /= 100;
954
+ lightness /= 100;
955
+ let red = 0;
956
+ let green = 0;
957
+ let blue = 0;
958
+ if (!saturation) {
959
+ red = green = blue = lightness;
960
+ }
961
+ else {
962
+ const q = lightness < 0.5
963
+ ? lightness * (1 + saturation)
964
+ : lightness + saturation - lightness * saturation;
965
+ const p = 2 * lightness - q;
966
+ red = hueToRgb(p, q, hue + 1 / 3);
967
+ green = hueToRgb(p, q, hue);
968
+ blue = hueToRgb(p, q, hue - 1 / 3);
969
+ }
970
+ return {
971
+ red: Math.round(red * 255),
972
+ green: Math.round(green * 255),
973
+ blue: Math.round(blue * 255),
974
+ alpha,
975
+ };
976
+ }
977
+
932
978
  const mixLinearColor = (from, to, v) => {
933
979
  const fromExpo = from * from;
934
980
  const toExpo = to * to;
@@ -938,24 +984,25 @@
938
984
  const getColorType = (v) => colorTypes.find((type) => type.test(v));
939
985
  const notAnimatable = (color) => `'${color}' is not an animatable color. Use the equivalent color code instead.`;
940
986
  const mixColor = (from, to) => {
941
- const fromColorType = getColorType(from);
942
- const toColorType = getColorType(to);
987
+ let fromColorType = getColorType(from);
988
+ let toColorType = getColorType(to);
943
989
  invariant(!!fromColorType, notAnimatable(from));
944
990
  invariant(!!toColorType, notAnimatable(to));
945
- invariant(fromColorType.transform === toColorType.transform, "Both colors must be hex/RGBA, OR both must be HSLA.");
946
- if (!fromColorType ||
947
- !toColorType ||
948
- fromColorType.transform !== toColorType.transform) {
949
- return (p) => `${p > 0 ? to : from}`;
950
- }
951
- const fromColor = fromColorType.parse(from);
952
- const toColor = toColorType.parse(to);
991
+ let fromColor = fromColorType.parse(from);
992
+ let toColor = toColorType.parse(to);
993
+ if (fromColorType === hsla) {
994
+ fromColor = hslaToRgba(fromColor);
995
+ fromColorType = rgba;
996
+ }
997
+ if (toColorType === hsla) {
998
+ toColor = hslaToRgba(toColor);
999
+ toColorType = rgba;
1000
+ }
953
1001
  const blended = Object.assign({}, fromColor);
954
- const mixFunc = fromColorType === hsla ? mix : mixLinearColor;
955
1002
  return (v) => {
956
1003
  for (const key in blended) {
957
1004
  if (key !== "alpha") {
958
- blended[key] = mixFunc(fromColor[key], toColor[key], v);
1005
+ blended[key] = mixLinearColor(fromColor[key], toColor[key], v);
959
1006
  }
960
1007
  }
961
1008
  blended.alpha = mix(fromColor.alpha, toColor.alpha, v);
@@ -3178,7 +3225,7 @@
3178
3225
  node.updateScroll();
3179
3226
  }
3180
3227
  var _d = this.options, layoutId = _d.layoutId, layout = _d.layout;
3181
- if (!layoutId && !layout)
3228
+ if (layoutId === undefined && !layout)
3182
3229
  return;
3183
3230
  var transformTemplate = (_c = this.options.visualElement) === null || _c === void 0 ? void 0 : _c.getProps().transformTemplate;
3184
3231
  this.prevTransformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
@@ -4558,6 +4605,10 @@
4558
4605
  "whileFocus",
4559
4606
  "whileTap",
4560
4607
  "whileHover",
4608
+ "whileInView",
4609
+ "onViewportEnter",
4610
+ "onViewportLeave",
4611
+ "viewport",
4561
4612
  "layoutScroll",
4562
4613
  ]);
4563
4614
  /**
@@ -4912,6 +4963,7 @@
4912
4963
  AnimationType["Tap"] = "whileTap";
4913
4964
  AnimationType["Drag"] = "whileDrag";
4914
4965
  AnimationType["Focus"] = "whileFocus";
4966
+ AnimationType["InView"] = "whileInView";
4915
4967
  AnimationType["Exit"] = "exit";
4916
4968
  })(AnimationType || (AnimationType = {}));
4917
4969
 
@@ -5221,12 +5273,163 @@
5221
5273
  useUnmountEffect(removePointerEndListener);
5222
5274
  }
5223
5275
 
5276
+ var warned = new Set();
5277
+ function warnOnce(condition, message, element) {
5278
+ if (condition || warned.has(message))
5279
+ return;
5280
+ console.warn(message);
5281
+ if (element)
5282
+ console.warn(element);
5283
+ warned.add(message);
5284
+ }
5285
+
5286
+ /**
5287
+ * Map an IntersectionHandler callback to an element. We only ever make one handler for one
5288
+ * element, so even though these handlers might all be triggered by different
5289
+ * observers, we can keep them in the same map.
5290
+ */
5291
+ var observerCallbacks = new WeakMap();
5292
+ /**
5293
+ * Multiple observers can be created for multiple element/document roots. Each with
5294
+ * different settings. So here we store dictionaries of observers to each root,
5295
+ * using serialised settings (threshold/margin) as lookup keys.
5296
+ */
5297
+ var observers = new WeakMap();
5298
+ var fireObserverCallback = function (entry) {
5299
+ var _a;
5300
+ (_a = observerCallbacks.get(entry.target)) === null || _a === void 0 ? void 0 : _a(entry);
5301
+ };
5302
+ var fireAllObserverCallbacks = function (entries) {
5303
+ entries.forEach(fireObserverCallback);
5304
+ };
5305
+ function initIntersectionObserver(_a) {
5306
+ var root = _a.root, options = __rest(_a, ["root"]);
5307
+ var lookupRoot = root || document;
5308
+ /**
5309
+ * If we don't have an observer lookup map for this root, create one.
5310
+ */
5311
+ if (!observers.has(lookupRoot)) {
5312
+ observers.set(lookupRoot, {});
5313
+ }
5314
+ var rootObservers = observers.get(lookupRoot);
5315
+ var key = JSON.stringify(options);
5316
+ /**
5317
+ * If we don't have an observer for this combination of root and settings,
5318
+ * create one.
5319
+ */
5320
+ if (!rootObservers[key]) {
5321
+ rootObservers[key] = new IntersectionObserver(fireAllObserverCallbacks, __assign({ root: root }, options));
5322
+ }
5323
+ return rootObservers[key];
5324
+ }
5325
+ function observeIntersection(element, options, callback) {
5326
+ var rootInteresectionObserver = initIntersectionObserver(options);
5327
+ observerCallbacks.set(element, callback);
5328
+ rootInteresectionObserver.observe(element);
5329
+ return function () {
5330
+ observerCallbacks.delete(element);
5331
+ rootInteresectionObserver.unobserve(element);
5332
+ };
5333
+ }
5334
+
5335
+ function useViewport(_a) {
5336
+ var visualElement = _a.visualElement, whileInView = _a.whileInView, onViewportEnter = _a.onViewportEnter, onViewportLeave = _a.onViewportLeave, _b = _a.viewport, viewport = _b === void 0 ? {} : _b;
5337
+ var state = React.useRef({
5338
+ hasEnteredView: false,
5339
+ isInView: false,
5340
+ });
5341
+ var shouldObserve = Boolean(whileInView || onViewportEnter || onViewportLeave);
5342
+ if (viewport.once && state.current.hasEnteredView)
5343
+ shouldObserve = false;
5344
+ var useObserver = typeof IntersectionObserver === "undefined"
5345
+ ? useMissingIntersectionObserver
5346
+ : useIntersectionObserver;
5347
+ useObserver(shouldObserve, state.current, visualElement, viewport);
5348
+ }
5349
+ var thresholdNames = {
5350
+ some: 0,
5351
+ all: 1,
5352
+ };
5353
+ function useIntersectionObserver(shouldObserve, state, visualElement, _a) {
5354
+ var root = _a.root, rootMargin = _a.margin, _b = _a.amount, amount = _b === void 0 ? "some" : _b, once = _a.once;
5355
+ React.useEffect(function () {
5356
+ if (!shouldObserve)
5357
+ return;
5358
+ var options = {
5359
+ root: root === null || root === void 0 ? void 0 : root.current,
5360
+ rootMargin: rootMargin,
5361
+ threshold: typeof amount === "number" ? amount : thresholdNames[amount],
5362
+ };
5363
+ var intersectionCallback = function (entry) {
5364
+ var _a;
5365
+ var isIntersecting = entry.isIntersecting;
5366
+ /**
5367
+ * If there's been no change in the viewport state, early return.
5368
+ */
5369
+ if (state.isInView === isIntersecting)
5370
+ return;
5371
+ state.isInView = isIntersecting;
5372
+ /**
5373
+ * Handle hasEnteredView. If this is only meant to run once, and
5374
+ * element isn't visible, early return. Otherwise set hasEnteredView to true.
5375
+ */
5376
+ if (once && !isIntersecting && state.hasEnteredView) {
5377
+ return;
5378
+ }
5379
+ else if (isIntersecting) {
5380
+ state.hasEnteredView = true;
5381
+ }
5382
+ (_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(AnimationType.InView, isIntersecting);
5383
+ /**
5384
+ * Use the latest committed props rather than the ones in scope
5385
+ * when this observer is created
5386
+ */
5387
+ var props = visualElement.getProps();
5388
+ var callback = isIntersecting
5389
+ ? props.onViewportEnter
5390
+ : props.onViewportLeave;
5391
+ callback === null || callback === void 0 ? void 0 : callback();
5392
+ };
5393
+ return observeIntersection(visualElement.getInstance(), options, intersectionCallback);
5394
+ }, [shouldObserve, root, rootMargin, amount]);
5395
+ }
5396
+ /**
5397
+ * If IntersectionObserver is missing, we activate inView and fire onViewportEnter
5398
+ * on mount. This way, the page will be in the state the author expects users
5399
+ * to see it in for everyone.
5400
+ */
5401
+ function useMissingIntersectionObserver(shouldObserve, state, visualElement) {
5402
+ React.useEffect(function () {
5403
+ if (!shouldObserve)
5404
+ return;
5405
+ {
5406
+ warnOnce(false, "IntersectionObserver not available on this device. whileInView animations will trigger on mount.");
5407
+ }
5408
+ /**
5409
+ * Fire this in an rAF because, at this point, the animation state
5410
+ * won't have flushed for the first time and there's certain logic in
5411
+ * there that behaves differently on the initial animation.
5412
+ *
5413
+ * This hook should be quite rarely called so setting this in an rAF
5414
+ * is preferred to changing the behaviour of the animation state.
5415
+ */
5416
+ requestAnimationFrame(function () {
5417
+ var _a;
5418
+ state.hasEnteredView = true;
5419
+ var onViewportEnter = visualElement.getProps().onViewportEnter;
5420
+ onViewportEnter === null || onViewportEnter === void 0 ? void 0 : onViewportEnter();
5421
+ (_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(AnimationType.InView, true);
5422
+ });
5423
+ }, [shouldObserve]);
5424
+ }
5425
+
5224
5426
  var makeRenderlessComponent = function (hook) { return function (props) {
5225
5427
  hook(props);
5226
5428
  return null;
5227
5429
  }; };
5228
5430
 
5229
5431
  var gestureAnimations = {
5432
+ inView: makeRenderlessComponent(useViewport),
5230
5433
  tap: makeRenderlessComponent(useTapGesture),
5231
5434
  focus: makeRenderlessComponent(useFocusGesture),
5232
5435
  hover: makeRenderlessComponent(useHoverGesture),
@@ -5597,6 +5800,7 @@
5597
5800
 
5598
5801
  var variantPriorityOrder = [
5599
5802
  AnimationType.Animate,
5803
+ AnimationType.InView,
5600
5804
  AnimationType.Focus,
5601
5805
  AnimationType.Hover,
5602
5806
  AnimationType.Tap,
@@ -5913,6 +6117,7 @@
5913
6117
  var _a;
5914
6118
  return _a = {},
5915
6119
  _a[AnimationType.Animate] = createTypeState(true),
6120
+ _a[AnimationType.InView] = createTypeState(),
5916
6121
  _a[AnimationType.Hover] = createTypeState(),
5917
6122
  _a[AnimationType.Tap] = createTypeState(),
5918
6123
  _a[AnimationType.Drag] = createTypeState(),
@@ -7422,11 +7627,18 @@
7422
7627
  var element = visualElement.getInstance();
7423
7628
  var elementComputedStyle = getComputedStyle(element);
7424
7629
  var display = elementComputedStyle.display;
7630
+ var origin = {};
7425
7631
  // If the element is currently set to display: "none", make it visible before
7426
7632
  // measuring the target bounding box
7427
7633
  if (display === "none") {
7428
7634
  visualElement.setStaticValue("display", target.display || "block");
7429
7635
  }
7636
+ /**
7637
+ * Record origins before we render and update styles
7638
+ */
7639
+ changedKeys.forEach(function (key) {
7640
+ origin[key] = positionalValues[key](originBbox, elementComputedStyle);
7641
+ });
7430
7642
  // Apply the latest values (as set in checkAndConvertChangedValueTypes)
7431
7643
  visualElement.syncRender();
7432
7644
  var targetBbox = visualElement.measureViewportBox();
@@ -7434,7 +7646,7 @@
7434
7646
  // Restore styles to their **calculated computed style**, not their actual
7435
7647
  // originally set style. This allows us to animate between equivalent pixel units.
7436
7648
  var value = visualElement.getValue(key);
7437
- setAndResetVelocity(value, positionalValues[key](originBbox, elementComputedStyle));
7649
+ setAndResetVelocity(value, origin[key]);
7438
7650
  target[key] = positionalValues[key](targetBbox, elementComputedStyle);
7439
7651
  });
7440
7652
  return target;