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.
@@ -11,6 +11,7 @@ var featureDefinitions = {
11
11
  "whileTap",
12
12
  "whileFocus",
13
13
  "whileDrag",
14
+ "whileInView",
14
15
  ]),
15
16
  exit: createDefinition(["exit"]),
16
17
  drag: createDefinition(["drag", "dragControls"]),
@@ -23,6 +24,11 @@ var featureDefinitions = {
23
24
  "onPanSessionStart",
24
25
  "onPanEnd",
25
26
  ]),
27
+ inView: createDefinition([
28
+ "whileInView",
29
+ "onViewportEnter",
30
+ "onViewportLeave",
31
+ ]),
26
32
  };
27
33
  function loadFeatures(features) {
28
34
  for (var key in features) {
@@ -1,9 +1,11 @@
1
1
  import { useFocusGesture } from '../../gestures/use-focus-gesture.mjs';
2
2
  import { useHoverGesture } from '../../gestures/use-hover-gesture.mjs';
3
3
  import { useTapGesture } from '../../gestures/use-tap-gesture.mjs';
4
+ import { useViewport } from './viewport/use-viewport.mjs';
4
5
  import { makeRenderlessComponent } from '../utils/make-renderless-component.mjs';
5
6
 
6
7
  var gestureAnimations = {
8
+ inView: makeRenderlessComponent(useViewport),
7
9
  tap: makeRenderlessComponent(useTapGesture),
8
10
  focus: makeRenderlessComponent(useFocusGesture),
9
11
  hover: makeRenderlessComponent(useHoverGesture),
@@ -0,0 +1,52 @@
1
+ import { __rest, __assign } from 'tslib';
2
+
3
+ /**
4
+ * Map an IntersectionHandler callback to an element. We only ever make one handler for one
5
+ * element, so even though these handlers might all be triggered by different
6
+ * observers, we can keep them in the same map.
7
+ */
8
+ var observerCallbacks = new WeakMap();
9
+ /**
10
+ * Multiple observers can be created for multiple element/document roots. Each with
11
+ * different settings. So here we store dictionaries of observers to each root,
12
+ * using serialised settings (threshold/margin) as lookup keys.
13
+ */
14
+ var observers = new WeakMap();
15
+ var fireObserverCallback = function (entry) {
16
+ var _a;
17
+ (_a = observerCallbacks.get(entry.target)) === null || _a === void 0 ? void 0 : _a(entry);
18
+ };
19
+ var fireAllObserverCallbacks = function (entries) {
20
+ entries.forEach(fireObserverCallback);
21
+ };
22
+ function initIntersectionObserver(_a) {
23
+ var root = _a.root, options = __rest(_a, ["root"]);
24
+ var lookupRoot = root || document;
25
+ /**
26
+ * If we don't have an observer lookup map for this root, create one.
27
+ */
28
+ if (!observers.has(lookupRoot)) {
29
+ observers.set(lookupRoot, {});
30
+ }
31
+ var rootObservers = observers.get(lookupRoot);
32
+ var key = JSON.stringify(options);
33
+ /**
34
+ * If we don't have an observer for this combination of root and settings,
35
+ * create one.
36
+ */
37
+ if (!rootObservers[key]) {
38
+ rootObservers[key] = new IntersectionObserver(fireAllObserverCallbacks, __assign({ root: root }, options));
39
+ }
40
+ return rootObservers[key];
41
+ }
42
+ function observeIntersection(element, options, callback) {
43
+ var rootInteresectionObserver = initIntersectionObserver(options);
44
+ observerCallbacks.set(element, callback);
45
+ rootInteresectionObserver.observe(element);
46
+ return function () {
47
+ observerCallbacks.delete(element);
48
+ rootInteresectionObserver.unobserve(element);
49
+ };
50
+ }
51
+
52
+ export { observeIntersection };
@@ -0,0 +1,97 @@
1
+ import { useRef, useEffect } from 'react';
2
+ import { AnimationType } from '../../../render/utils/types.mjs';
3
+ import { warnOnce } from '../../../utils/warn-once.mjs';
4
+ import { observeIntersection } from './observers.mjs';
5
+
6
+ function useViewport(_a) {
7
+ var visualElement = _a.visualElement, whileInView = _a.whileInView, onViewportEnter = _a.onViewportEnter, onViewportLeave = _a.onViewportLeave, _b = _a.viewport, viewport = _b === void 0 ? {} : _b;
8
+ var state = useRef({
9
+ hasEnteredView: false,
10
+ isInView: false,
11
+ });
12
+ var shouldObserve = Boolean(whileInView || onViewportEnter || onViewportLeave);
13
+ if (viewport.once && state.current.hasEnteredView)
14
+ shouldObserve = false;
15
+ var useObserver = typeof IntersectionObserver === "undefined"
16
+ ? useMissingIntersectionObserver
17
+ : useIntersectionObserver;
18
+ useObserver(shouldObserve, state.current, visualElement, viewport);
19
+ }
20
+ var thresholdNames = {
21
+ some: 0,
22
+ all: 1,
23
+ };
24
+ function useIntersectionObserver(shouldObserve, state, visualElement, _a) {
25
+ var root = _a.root, rootMargin = _a.margin, _b = _a.amount, amount = _b === void 0 ? "some" : _b, once = _a.once;
26
+ useEffect(function () {
27
+ if (!shouldObserve)
28
+ return;
29
+ var options = {
30
+ root: root === null || root === void 0 ? void 0 : root.current,
31
+ rootMargin: rootMargin,
32
+ threshold: typeof amount === "number" ? amount : thresholdNames[amount],
33
+ };
34
+ var intersectionCallback = function (entry) {
35
+ var _a;
36
+ var isIntersecting = entry.isIntersecting;
37
+ /**
38
+ * If there's been no change in the viewport state, early return.
39
+ */
40
+ if (state.isInView === isIntersecting)
41
+ return;
42
+ state.isInView = isIntersecting;
43
+ /**
44
+ * Handle hasEnteredView. If this is only meant to run once, and
45
+ * element isn't visible, early return. Otherwise set hasEnteredView to true.
46
+ */
47
+ if (once && !isIntersecting && state.hasEnteredView) {
48
+ return;
49
+ }
50
+ else if (isIntersecting) {
51
+ state.hasEnteredView = true;
52
+ }
53
+ (_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(AnimationType.InView, isIntersecting);
54
+ /**
55
+ * Use the latest committed props rather than the ones in scope
56
+ * when this observer is created
57
+ */
58
+ var props = visualElement.getProps();
59
+ var callback = isIntersecting
60
+ ? props.onViewportEnter
61
+ : props.onViewportLeave;
62
+ callback === null || callback === void 0 ? void 0 : callback();
63
+ };
64
+ return observeIntersection(visualElement.getInstance(), options, intersectionCallback);
65
+ }, [shouldObserve, root, rootMargin, amount]);
66
+ }
67
+ /**
68
+ * If IntersectionObserver is missing, we activate inView and fire onViewportEnter
69
+ * on mount. This way, the page will be in the state the author expects users
70
+ * to see it in for everyone.
71
+ */
72
+ function useMissingIntersectionObserver(shouldObserve, state, visualElement) {
73
+ useEffect(function () {
74
+ if (!shouldObserve)
75
+ return;
76
+ if (process.env.NODE_ENV !== "production") {
77
+ warnOnce(false, "IntersectionObserver not available on this device. whileInView animations will trigger on mount.");
78
+ }
79
+ /**
80
+ * Fire this in an rAF because, at this point, the animation state
81
+ * won't have flushed for the first time and there's certain logic in
82
+ * there that behaves differently on the initial animation.
83
+ *
84
+ * This hook should be quite rarely called so setting this in an rAF
85
+ * is preferred to changing the behaviour of the animation state.
86
+ */
87
+ requestAnimationFrame(function () {
88
+ var _a;
89
+ state.hasEnteredView = true;
90
+ var onViewportEnter = visualElement.getProps().onViewportEnter;
91
+ onViewportEnter === null || onViewportEnter === void 0 ? void 0 : onViewportEnter();
92
+ (_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(AnimationType.InView, true);
93
+ });
94
+ }, [shouldObserve]);
95
+ }
96
+
97
+ export { useViewport };
@@ -54,6 +54,10 @@ var validMotionProps = new Set([
54
54
  "whileFocus",
55
55
  "whileTap",
56
56
  "whileHover",
57
+ "whileInView",
58
+ "onViewportEnter",
59
+ "onViewportLeave",
60
+ "viewport",
57
61
  "layoutScroll",
58
62
  ]);
59
63
  /**
@@ -307,7 +307,7 @@ function createProjectionNode(_a) {
307
307
  node.updateScroll();
308
308
  }
309
309
  var _d = this.options, layoutId = _d.layoutId, layout = _d.layout;
310
- if (!layoutId && !layout)
310
+ if (layoutId === undefined && !layout)
311
311
  return;
312
312
  var transformTemplate = (_c = this.options.visualElement) === null || _c === void 0 ? void 0 : _c.getProps().transformTemplate;
313
313
  this.prevTransformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
@@ -115,11 +115,18 @@ var convertChangedValueTypes = function (target, visualElement, changedKeys) {
115
115
  var element = visualElement.getInstance();
116
116
  var elementComputedStyle = getComputedStyle(element);
117
117
  var display = elementComputedStyle.display;
118
+ var origin = {};
118
119
  // If the element is currently set to display: "none", make it visible before
119
120
  // measuring the target bounding box
120
121
  if (display === "none") {
121
122
  visualElement.setStaticValue("display", target.display || "block");
122
123
  }
124
+ /**
125
+ * Record origins before we render and update styles
126
+ */
127
+ changedKeys.forEach(function (key) {
128
+ origin[key] = positionalValues[key](originBbox, elementComputedStyle);
129
+ });
123
130
  // Apply the latest values (as set in checkAndConvertChangedValueTypes)
124
131
  visualElement.syncRender();
125
132
  var targetBbox = visualElement.measureViewportBox();
@@ -127,7 +134,7 @@ var convertChangedValueTypes = function (target, visualElement, changedKeys) {
127
134
  // Restore styles to their **calculated computed style**, not their actual
128
135
  // originally set style. This allows us to animate between equivalent pixel units.
129
136
  var value = visualElement.getValue(key);
130
- setAndResetVelocity(value, positionalValues[key](originBbox, elementComputedStyle));
137
+ setAndResetVelocity(value, origin[key]);
131
138
  target[key] = positionalValues[key](targetBbox, elementComputedStyle);
132
139
  });
133
140
  return target;
@@ -8,6 +8,7 @@ import { isVariantLabels, resolveVariant, isVariantLabel } from './variants.mjs'
8
8
 
9
9
  var variantPriorityOrder = [
10
10
  AnimationType.Animate,
11
+ AnimationType.InView,
11
12
  AnimationType.Focus,
12
13
  AnimationType.Hover,
13
14
  AnimationType.Tap,
@@ -324,6 +325,7 @@ function createState() {
324
325
  var _a;
325
326
  return _a = {},
326
327
  _a[AnimationType.Animate] = createTypeState(true),
328
+ _a[AnimationType.InView] = createTypeState(),
327
329
  _a[AnimationType.Hover] = createTypeState(),
328
330
  _a[AnimationType.Tap] = createTypeState(),
329
331
  _a[AnimationType.Drag] = createTypeState(),
@@ -5,6 +5,7 @@ var AnimationType;
5
5
  AnimationType["Tap"] = "whileTap";
6
6
  AnimationType["Drag"] = "whileDrag";
7
7
  AnimationType["Focus"] = "whileFocus";
8
+ AnimationType["InView"] = "whileInView";
8
9
  AnimationType["Exit"] = "exit";
9
10
  })(AnimationType || (AnimationType = {}));
10
11
 
@@ -0,0 +1,11 @@
1
+ var warned = new Set();
2
+ function warnOnce(condition, message, element) {
3
+ if (condition || warned.has(message))
4
+ return;
5
+ console.warn(message);
6
+ if (element)
7
+ console.warn(element);
8
+ warned.add(message);
9
+ }
10
+
11
+ export { warnOnce };
@@ -48,6 +48,7 @@ var featureDefinitions = {
48
48
  "whileTap",
49
49
  "whileFocus",
50
50
  "whileDrag",
51
+ "whileInView",
51
52
  ]),
52
53
  exit: createDefinition(["exit"]),
53
54
  drag: createDefinition(["drag", "dragControls"]),
@@ -60,6 +61,11 @@ var featureDefinitions = {
60
61
  "onPanSessionStart",
61
62
  "onPanEnd",
62
63
  ]),
64
+ inView: createDefinition([
65
+ "whileInView",
66
+ "onViewportEnter",
67
+ "onViewportLeave",
68
+ ]),
63
69
  };
64
70
  function loadFeatures(features) {
65
71
  for (var key in features) {
@@ -1988,7 +1994,7 @@ function createProjectionNode(_a) {
1988
1994
  node.updateScroll();
1989
1995
  }
1990
1996
  var _d = this.options, layoutId = _d.layoutId, layout = _d.layout;
1991
- if (!layoutId && !layout)
1997
+ if (layoutId === undefined && !layout)
1992
1998
  return;
1993
1999
  var transformTemplate = (_c = this.options.visualElement) === null || _c === void 0 ? void 0 : _c.getProps().transformTemplate;
1994
2000
  this.prevTransformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
@@ -3368,6 +3374,10 @@ var validMotionProps = new Set([
3368
3374
  "whileFocus",
3369
3375
  "whileTap",
3370
3376
  "whileHover",
3377
+ "whileInView",
3378
+ "onViewportEnter",
3379
+ "onViewportLeave",
3380
+ "viewport",
3371
3381
  "layoutScroll",
3372
3382
  ]);
3373
3383
  /**
@@ -3722,6 +3732,7 @@ var AnimationType;
3722
3732
  AnimationType["Tap"] = "whileTap";
3723
3733
  AnimationType["Drag"] = "whileDrag";
3724
3734
  AnimationType["Focus"] = "whileFocus";
3735
+ AnimationType["InView"] = "whileInView";
3725
3736
  AnimationType["Exit"] = "exit";
3726
3737
  })(AnimationType || (AnimationType = {}));
3727
3738
 
@@ -4031,12 +4042,163 @@ function useTapGesture(_a) {
4031
4042
  useUnmountEffect(removePointerEndListener);
4032
4043
  }
4033
4044
 
4045
+ var warned = new Set();
4046
+ function warnOnce(condition, message, element) {
4047
+ if (condition || warned.has(message))
4048
+ return;
4049
+ console.warn(message);
4050
+ if (element)
4051
+ console.warn(element);
4052
+ warned.add(message);
4053
+ }
4054
+
4055
+ /**
4056
+ * Map an IntersectionHandler callback to an element. We only ever make one handler for one
4057
+ * element, so even though these handlers might all be triggered by different
4058
+ * observers, we can keep them in the same map.
4059
+ */
4060
+ var observerCallbacks = new WeakMap();
4061
+ /**
4062
+ * Multiple observers can be created for multiple element/document roots. Each with
4063
+ * different settings. So here we store dictionaries of observers to each root,
4064
+ * using serialised settings (threshold/margin) as lookup keys.
4065
+ */
4066
+ var observers = new WeakMap();
4067
+ var fireObserverCallback = function (entry) {
4068
+ var _a;
4069
+ (_a = observerCallbacks.get(entry.target)) === null || _a === void 0 ? void 0 : _a(entry);
4070
+ };
4071
+ var fireAllObserverCallbacks = function (entries) {
4072
+ entries.forEach(fireObserverCallback);
4073
+ };
4074
+ function initIntersectionObserver(_a) {
4075
+ var root = _a.root, options = tslib.__rest(_a, ["root"]);
4076
+ var lookupRoot = root || document;
4077
+ /**
4078
+ * If we don't have an observer lookup map for this root, create one.
4079
+ */
4080
+ if (!observers.has(lookupRoot)) {
4081
+ observers.set(lookupRoot, {});
4082
+ }
4083
+ var rootObservers = observers.get(lookupRoot);
4084
+ var key = JSON.stringify(options);
4085
+ /**
4086
+ * If we don't have an observer for this combination of root and settings,
4087
+ * create one.
4088
+ */
4089
+ if (!rootObservers[key]) {
4090
+ rootObservers[key] = new IntersectionObserver(fireAllObserverCallbacks, tslib.__assign({ root: root }, options));
4091
+ }
4092
+ return rootObservers[key];
4093
+ }
4094
+ function observeIntersection(element, options, callback) {
4095
+ var rootInteresectionObserver = initIntersectionObserver(options);
4096
+ observerCallbacks.set(element, callback);
4097
+ rootInteresectionObserver.observe(element);
4098
+ return function () {
4099
+ observerCallbacks.delete(element);
4100
+ rootInteresectionObserver.unobserve(element);
4101
+ };
4102
+ }
4103
+
4104
+ function useViewport(_a) {
4105
+ var visualElement = _a.visualElement, whileInView = _a.whileInView, onViewportEnter = _a.onViewportEnter, onViewportLeave = _a.onViewportLeave, _b = _a.viewport, viewport = _b === void 0 ? {} : _b;
4106
+ var state = React.useRef({
4107
+ hasEnteredView: false,
4108
+ isInView: false,
4109
+ });
4110
+ var shouldObserve = Boolean(whileInView || onViewportEnter || onViewportLeave);
4111
+ if (viewport.once && state.current.hasEnteredView)
4112
+ shouldObserve = false;
4113
+ var useObserver = typeof IntersectionObserver === "undefined"
4114
+ ? useMissingIntersectionObserver
4115
+ : useIntersectionObserver;
4116
+ useObserver(shouldObserve, state.current, visualElement, viewport);
4117
+ }
4118
+ var thresholdNames = {
4119
+ some: 0,
4120
+ all: 1,
4121
+ };
4122
+ function useIntersectionObserver(shouldObserve, state, visualElement, _a) {
4123
+ var root = _a.root, rootMargin = _a.margin, _b = _a.amount, amount = _b === void 0 ? "some" : _b, once = _a.once;
4124
+ React.useEffect(function () {
4125
+ if (!shouldObserve)
4126
+ return;
4127
+ var options = {
4128
+ root: root === null || root === void 0 ? void 0 : root.current,
4129
+ rootMargin: rootMargin,
4130
+ threshold: typeof amount === "number" ? amount : thresholdNames[amount],
4131
+ };
4132
+ var intersectionCallback = function (entry) {
4133
+ var _a;
4134
+ var isIntersecting = entry.isIntersecting;
4135
+ /**
4136
+ * If there's been no change in the viewport state, early return.
4137
+ */
4138
+ if (state.isInView === isIntersecting)
4139
+ return;
4140
+ state.isInView = isIntersecting;
4141
+ /**
4142
+ * Handle hasEnteredView. If this is only meant to run once, and
4143
+ * element isn't visible, early return. Otherwise set hasEnteredView to true.
4144
+ */
4145
+ if (once && !isIntersecting && state.hasEnteredView) {
4146
+ return;
4147
+ }
4148
+ else if (isIntersecting) {
4149
+ state.hasEnteredView = true;
4150
+ }
4151
+ (_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(AnimationType.InView, isIntersecting);
4152
+ /**
4153
+ * Use the latest committed props rather than the ones in scope
4154
+ * when this observer is created
4155
+ */
4156
+ var props = visualElement.getProps();
4157
+ var callback = isIntersecting
4158
+ ? props.onViewportEnter
4159
+ : props.onViewportLeave;
4160
+ callback === null || callback === void 0 ? void 0 : callback();
4161
+ };
4162
+ return observeIntersection(visualElement.getInstance(), options, intersectionCallback);
4163
+ }, [shouldObserve, root, rootMargin, amount]);
4164
+ }
4165
+ /**
4166
+ * If IntersectionObserver is missing, we activate inView and fire onViewportEnter
4167
+ * on mount. This way, the page will be in the state the author expects users
4168
+ * to see it in for everyone.
4169
+ */
4170
+ function useMissingIntersectionObserver(shouldObserve, state, visualElement) {
4171
+ React.useEffect(function () {
4172
+ if (!shouldObserve)
4173
+ return;
4174
+ if (process.env.NODE_ENV !== "production") {
4175
+ warnOnce(false, "IntersectionObserver not available on this device. whileInView animations will trigger on mount.");
4176
+ }
4177
+ /**
4178
+ * Fire this in an rAF because, at this point, the animation state
4179
+ * won't have flushed for the first time and there's certain logic in
4180
+ * there that behaves differently on the initial animation.
4181
+ *
4182
+ * This hook should be quite rarely called so setting this in an rAF
4183
+ * is preferred to changing the behaviour of the animation state.
4184
+ */
4185
+ requestAnimationFrame(function () {
4186
+ var _a;
4187
+ state.hasEnteredView = true;
4188
+ var onViewportEnter = visualElement.getProps().onViewportEnter;
4189
+ onViewportEnter === null || onViewportEnter === void 0 ? void 0 : onViewportEnter();
4190
+ (_a = visualElement.animationState) === null || _a === void 0 ? void 0 : _a.setActive(AnimationType.InView, true);
4191
+ });
4192
+ }, [shouldObserve]);
4193
+ }
4194
+
4034
4195
  var makeRenderlessComponent = function (hook) { return function (props) {
4035
4196
  hook(props);
4036
4197
  return null;
4037
4198
  }; };
4038
4199
 
4039
4200
  var gestureAnimations = {
4201
+ inView: makeRenderlessComponent(useViewport),
4040
4202
  tap: makeRenderlessComponent(useTapGesture),
4041
4203
  focus: makeRenderlessComponent(useFocusGesture),
4042
4204
  hover: makeRenderlessComponent(useHoverGesture),
@@ -4407,6 +4569,7 @@ function shouldBlockAnimation(_a, key) {
4407
4569
 
4408
4570
  var variantPriorityOrder = [
4409
4571
  AnimationType.Animate,
4572
+ AnimationType.InView,
4410
4573
  AnimationType.Focus,
4411
4574
  AnimationType.Hover,
4412
4575
  AnimationType.Tap,
@@ -4723,6 +4886,7 @@ function createState() {
4723
4886
  var _a;
4724
4887
  return _a = {},
4725
4888
  _a[AnimationType.Animate] = createTypeState(true),
4889
+ _a[AnimationType.InView] = createTypeState(),
4726
4890
  _a[AnimationType.Hover] = createTypeState(),
4727
4891
  _a[AnimationType.Tap] = createTypeState(),
4728
4892
  _a[AnimationType.Drag] = createTypeState(),
@@ -6232,11 +6396,18 @@ var convertChangedValueTypes = function (target, visualElement, changedKeys) {
6232
6396
  var element = visualElement.getInstance();
6233
6397
  var elementComputedStyle = getComputedStyle(element);
6234
6398
  var display = elementComputedStyle.display;
6399
+ var origin = {};
6235
6400
  // If the element is currently set to display: "none", make it visible before
6236
6401
  // measuring the target bounding box
6237
6402
  if (display === "none") {
6238
6403
  visualElement.setStaticValue("display", target.display || "block");
6239
6404
  }
6405
+ /**
6406
+ * Record origins before we render and update styles
6407
+ */
6408
+ changedKeys.forEach(function (key) {
6409
+ origin[key] = positionalValues[key](originBbox, elementComputedStyle);
6410
+ });
6240
6411
  // Apply the latest values (as set in checkAndConvertChangedValueTypes)
6241
6412
  visualElement.syncRender();
6242
6413
  var targetBbox = visualElement.measureViewportBox();
@@ -6244,7 +6415,7 @@ var convertChangedValueTypes = function (target, visualElement, changedKeys) {
6244
6415
  // Restore styles to their **calculated computed style**, not their actual
6245
6416
  // originally set style. This allows us to animate between equivalent pixel units.
6246
6417
  var value = visualElement.getValue(key);
6247
- setAndResetVelocity(value, positionalValues[key](originBbox, elementComputedStyle));
6418
+ setAndResetVelocity(value, origin[key]);
6248
6419
  target[key] = positionalValues[key](targetBbox, elementComputedStyle);
6249
6420
  });
6250
6421
  return target;