@futurejj/react-native-visibility-sensor 1.3.21 → 1.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,32 +26,28 @@ npm install @futurejj/react-native-visibility-sensor
26
26
 
27
27
  ## Usage
28
28
 
29
- ```typescript
29
+ ```tsx
30
30
  import React from 'react';
31
31
  import { ScrollView, Text } from 'react-native';
32
32
  import { VisibilitySensor } from '@futurejj/react-native-visibility-sensor';
33
33
 
34
34
  export default function VisibilitySensorExample() {
35
- const [isInView, setIsInView] = React.useState(false);
36
-
37
- function checkVisible(isVisible: boolean) {
38
- if (isVisible) {
39
- setIsInView(isVisible);
40
- } else {
41
- setIsInView(isVisible);
42
- }
43
- }
35
+ const [isVisible, setIsVisible] = React.useState(false);
36
+ const [percentVisible, setPercentVisible] = useState<number>(0);
44
37
 
45
38
  return (
46
39
  <ScrollView>
47
40
  <VisibilitySensor
48
- onChange={(isVisible) => checkVisible(isVisible)}
49
- threshold={{
50
- left: 100,
51
- right: 100,
52
- }}
53
- style={[styles.item, { backgroundColor: isInView ? 'green' : 'red' }]}>
54
- <Text>This View is currently visible? {isInView ? 'yes' : 'no'}</Text>
41
+ onChange={setIsVisible}
42
+ onPercentChange={setPercentVisible} // optional callback for % change
43
+ threshold={{ top: 100, bottom: 100 }}
44
+ style={[styles.item, { backgroundColor: isVisible ? 'green' : 'red' }]}>
45
+
46
+ {/* Visibility state */}
47
+ <Text>This View is currently visible? {isVisible ? 'yes' : 'no'}</Text>
48
+
49
+ {/* Percent visibility state */}
50
+ <Text>{`Percent visible: ${percentVisible}%`}</Text>
55
51
  </VisibilitySensor>
56
52
  </ScrollView>
57
53
  );
@@ -61,13 +57,14 @@ export default function VisibilitySensorExample() {
61
57
 
62
58
  `VisibilitySensorProps` extends `ViewProps` from React Native, which includes common properties for all views, such as `style`, `onLayout`, etc.
63
59
 
64
- | Property | Type | Required | Description |
65
- | ----------- | ------------------------------------------------------- | -------- | ------------------------------------------------------------ |
66
- | onChange | (visible: boolean) => void | Yes | Callback function that fires when visibility changes. |
67
- | disabled | boolean | No | If `true`, disables the sensor. |
68
- | triggerOnce | boolean | No | If `true`, the sensor will only trigger once. |
69
- | delay | number \| undefined | No | The delay in milliseconds before the sensor triggers. |
70
- | threshold | [VisibilitySensorThreshold](#visibilitysensorthreshold) | No | Defines the part of the view that must be visible for the sensor to trigger. |
60
+ | Property | Type | Required | Description |
61
+ | --------------- | ------------------------------------------------------- | -------- | ------------------------------------------------------------ |
62
+ | onChange | (visible: boolean) => void | Yes | Callback function that fires when visibility changes. |
63
+ | onPercentChange | (percentVisible: number) => void | No | Callback function that fires when visibility % changes. |
64
+ | disabled | boolean | No | If `true`, disables the sensor. |
65
+ | triggerOnce | boolean | No | If `true`, the sensor will only trigger once. |
66
+ | delay | number \| undefined | No | The delay in milliseconds before the sensor triggers. |
67
+ | threshold | [VisibilitySensorThreshold](#visibilitysensorthreshold) | No | Defines the part of the view that must be visible for the sensor to trigger. |
71
68
 
72
69
  Additionally, all properties from `ViewProps` are also applicable.
73
70
 
@@ -8,6 +8,14 @@ var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _jsxRuntime = require("react/jsx-runtime");
10
10
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ var MeasurementState = /*#__PURE__*/function (MeasurementState) {
12
+ MeasurementState["IDLE"] = "IDLE";
13
+ // Not yet measured
14
+ MeasurementState["MEASURING"] = "MEASURING";
15
+ // Measurement in progress
16
+ MeasurementState["MEASURED"] = "MEASURED"; // Has valid measurements
17
+ return MeasurementState;
18
+ }(MeasurementState || {});
11
19
  function useInterval(callback, delay) {
12
20
  const savedCallback = (0, _react.useRef)(callback);
13
21
  (0, _react.useEffect)(() => {
@@ -24,6 +32,7 @@ function useInterval(callback, delay) {
24
32
  const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
25
33
  const {
26
34
  onChange,
35
+ onPercentChange,
27
36
  disabled = false,
28
37
  triggerOnce = false,
29
38
  delay,
@@ -31,10 +40,14 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
31
40
  children,
32
41
  ...rest
33
42
  } = props;
34
- const localRef = (0, _react.useRef)(null);
35
43
  (0, _react.useImperativeHandle)(ref, () => ({
36
44
  getInnerRef: () => localRef.current
37
45
  }));
46
+ const window = (0, _reactNative.useWindowDimensions)();
47
+ const localRef = (0, _react.useRef)(null);
48
+ const isMountedRef = (0, _react.useRef)(true);
49
+ const measurementStateRef = (0, _react.useRef)(MeasurementState.IDLE);
50
+ const lastPercentRef = (0, _react.useRef)(undefined);
38
51
  const [rectDimensions, setRectDimensions] = (0, _react.useState)({
39
52
  rectTop: 0,
40
53
  rectBottom: 0,
@@ -45,12 +58,18 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
45
58
  });
46
59
  const [lastValue, setLastValue] = (0, _react.useState)(undefined);
47
60
  const [active, setActive] = (0, _react.useState)(false);
48
- const hasMeasuredRef = (0, _react.useRef)(false);
49
- const measureInnerView = () => {
61
+ const measureInnerView = (0, _react.useCallback)(() => {
50
62
  /* Check if the sensor is active to prevent unnecessary measurements
51
63
  This avoids running measurements when the sensor is disabled or stopped */
52
- if (!active) return;
64
+ if (!active || !isMountedRef.current || measurementStateRef.current === MeasurementState.MEASURING) {
65
+ return;
66
+ }
67
+ measurementStateRef.current = MeasurementState.MEASURING;
53
68
  localRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
69
+ // Check if component is still mounted before setting state because measurement can be asynchronous
70
+ if (!isMountedRef.current) {
71
+ return;
72
+ }
54
73
  const dimensions = {
55
74
  rectTop: pageY,
56
75
  rectBottom: pageY + height,
@@ -61,12 +80,13 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
61
80
  };
62
81
  if (rectDimensions.rectTop !== dimensions.rectTop || rectDimensions.rectBottom !== dimensions.rectBottom || rectDimensions.rectLeft !== dimensions.rectLeft || rectDimensions.rectRight !== dimensions.rectRight || rectDimensions.rectWidth !== dimensions.rectWidth || rectDimensions.rectHeight !== dimensions.rectHeight) {
63
82
  setRectDimensions(dimensions);
64
- /* Set hasMeasuredRef to true to indicate that a valid measurement has been taken
65
- This ensures visibility checks only proceed after initial measurement */
66
- hasMeasuredRef.current = true;
67
83
  }
84
+
85
+ /* Set measurementStateRef to MEASURED to indicate that a valid measurement has
86
+ been taken. This ensures visibility checks only proceed after initial measurement */
87
+ measurementStateRef.current = MeasurementState.MEASURED;
68
88
  });
69
- };
89
+ }, [active, rectDimensions]);
70
90
  useInterval(measureInnerView, delay || 100);
71
91
  const startWatching = (0, _react.useCallback)(() => {
72
92
  if (!active) setActive(true);
@@ -76,9 +96,37 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
76
96
  setActive(false);
77
97
  /* Reset measurement state when stopping to ensure fresh measurements
78
98
  when the sensor is reactivated */
79
- hasMeasuredRef.current = false;
99
+ measurementStateRef.current = MeasurementState.IDLE; // Reset state
80
100
  }
81
101
  }, [active]);
102
+
103
+ // Effect to trigger initial measurement when component becomes active:
104
+ (0, _react.useEffect)(() => {
105
+ let timer;
106
+ if (active && measurementStateRef.current === MeasurementState.IDLE) {
107
+ // Use setTimeout with 0 delay to ensure layout is complete
108
+ timer = setTimeout(() => {
109
+ measureInnerView();
110
+ }, 0);
111
+ }
112
+ return () => {
113
+ if (timer) clearTimeout(timer);
114
+ };
115
+ }, [active, measureInnerView]);
116
+
117
+ // Reset measurement state when dimensions change:
118
+ (0, _react.useEffect)(() => {
119
+ if (isMountedRef.current && measurementStateRef.current === MeasurementState.MEASURED) {
120
+ // Reset measurement state to force remeasurement with new dimensions
121
+ measurementStateRef.current = MeasurementState.IDLE;
122
+ }
123
+ }, [window]);
124
+ (0, _react.useEffect)(() => {
125
+ isMountedRef.current = true;
126
+ return () => {
127
+ isMountedRef.current = false;
128
+ };
129
+ }, []);
82
130
  (0, _react.useEffect)(() => {
83
131
  if (!disabled) {
84
132
  startWatching();
@@ -91,8 +139,9 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
91
139
  /* Ensure visibility checks only run when the sensor is active and
92
140
  at least one measurement has been completed. This prevents
93
141
  premature visibility calculations with invalid or stale dimensions */
94
- if (!active || !hasMeasuredRef.current) return;
95
- const window = _reactNative.Dimensions.get('window');
142
+ if (!active || measurementStateRef.current !== MeasurementState.MEASURED || !isMountedRef.current) {
143
+ return;
144
+ }
96
145
  const isVisible = rectDimensions.rectTop + (threshold.top || 0) <= window.height &&
97
146
  // Top edge is within the bottom of the window
98
147
  rectDimensions.rectBottom - (threshold.bottom || 0) >= 0 &&
@@ -101,6 +150,39 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
101
150
  // Left edge is within the right of the window
102
151
  rectDimensions.rectRight - (threshold.right || 0) >= 0; // Right edge is within the left of the window
103
152
 
153
+ // Calculate percent visible if callback is requested / provided
154
+ if (onPercentChange && rectDimensions.rectWidth > 0 && rectDimensions.rectHeight > 0 && isVisible // Don't perform % calculation if not visible for efficiency
155
+ ) {
156
+ // Thresholds reduce the effective viewport
157
+ const viewportTop = 0 + (threshold.top || 0);
158
+ const viewportBottom = window.height - (threshold.bottom || 0);
159
+ const viewportLeft = 0 + (threshold.left || 0);
160
+ const viewportRight = window.width - (threshold.right || 0);
161
+
162
+ // Calculate the visible portion of the element within the reduced viewport
163
+ const visibleTop = Math.max(viewportTop, rectDimensions.rectTop);
164
+ const visibleBottom = Math.min(viewportBottom, rectDimensions.rectBottom);
165
+ const visibleLeft = Math.max(viewportLeft, rectDimensions.rectLeft);
166
+ const visibleRight = Math.min(viewportRight, rectDimensions.rectRight);
167
+
168
+ // Calculate visible dimensions
169
+ const visibleHeight = Math.max(0, visibleBottom - visibleTop);
170
+ const visibleWidth = Math.max(0, visibleRight - visibleLeft);
171
+
172
+ // Calculate percent visible based on actual element area
173
+ const visibleArea = visibleHeight * visibleWidth;
174
+ const totalArea = rectDimensions.rectHeight * rectDimensions.rectWidth;
175
+ const percentVisible = totalArea > 0 ? Math.round(visibleArea / totalArea * 100) : 0;
176
+
177
+ // Only fire callback if percent has changed
178
+ if (lastPercentRef.current !== percentVisible) {
179
+ lastPercentRef.current = percentVisible;
180
+ onPercentChange(percentVisible);
181
+ }
182
+ } else if (onPercentChange && rectDimensions.rectWidth > 0 && rectDimensions.rectHeight > 0 && !isVisible // If not visible, always report 0%
183
+ ) {
184
+ onPercentChange(0);
185
+ }
104
186
  if (lastValue !== isVisible) {
105
187
  setLastValue(isVisible);
106
188
  onChange(isVisible);
@@ -109,7 +191,7 @@ const VisibilitySensor = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => {
109
191
  }
110
192
  }
111
193
  // eslint-disable-next-line react-hooks/exhaustive-deps
112
- }, [rectDimensions, lastValue, active]);
194
+ }, [rectDimensions, window, lastValue, active, onPercentChange]);
113
195
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
114
196
  ref: localRef,
115
197
  ...rest,
@@ -1 +1 @@
1
- {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","useInterval","callback","delay","savedCallback","useRef","useEffect","current","undefined","id","setInterval","clearInterval","VisibilitySensor","forwardRef","props","ref","onChange","disabled","triggerOnce","threshold","children","rest","localRef","useImperativeHandle","getInnerRef","rectDimensions","setRectDimensions","useState","rectTop","rectBottom","rectLeft","rectRight","rectWidth","rectHeight","lastValue","setLastValue","active","setActive","hasMeasuredRef","measureInnerView","measure","_x","_y","width","height","pageX","pageY","dimensions","startWatching","useCallback","stopWatching","window","Dimensions","isVisible","top","bottom","left","right","jsx","View","_default","exports"],"sourceRoot":"../../src","sources":["VisibilitySensor.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","MeasurementState","useInterval","callback","delay","savedCallback","useRef","useEffect","current","undefined","id","setInterval","clearInterval","VisibilitySensor","forwardRef","props","ref","onChange","onPercentChange","disabled","triggerOnce","threshold","children","rest","useImperativeHandle","getInnerRef","localRef","window","useWindowDimensions","isMountedRef","measurementStateRef","IDLE","lastPercentRef","rectDimensions","setRectDimensions","useState","rectTop","rectBottom","rectLeft","rectRight","rectWidth","rectHeight","lastValue","setLastValue","active","setActive","measureInnerView","useCallback","MEASURING","measure","_x","_y","width","height","pageX","pageY","dimensions","MEASURED","startWatching","stopWatching","timer","setTimeout","clearTimeout","isVisible","top","bottom","left","right","viewportTop","viewportBottom","viewportLeft","viewportRight","visibleTop","Math","max","visibleBottom","min","visibleLeft","visibleRight","visibleHeight","visibleWidth","visibleArea","totalArea","percentVisible","round","jsx","View","_default","exports"],"sourceRoot":"../../src","sources":["VisibilitySensor.tsx"],"mappings":"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","ignoreList":[]}
@@ -1,8 +1,16 @@
1
1
  "use strict";
2
2
 
3
3
  import React, { useCallback, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
4
- import { Dimensions, View } from 'react-native';
4
+ import { useWindowDimensions, View } from 'react-native';
5
5
  import { jsx as _jsx } from "react/jsx-runtime";
6
+ var MeasurementState = /*#__PURE__*/function (MeasurementState) {
7
+ MeasurementState["IDLE"] = "IDLE";
8
+ // Not yet measured
9
+ MeasurementState["MEASURING"] = "MEASURING";
10
+ // Measurement in progress
11
+ MeasurementState["MEASURED"] = "MEASURED"; // Has valid measurements
12
+ return MeasurementState;
13
+ }(MeasurementState || {});
6
14
  function useInterval(callback, delay) {
7
15
  const savedCallback = useRef(callback);
8
16
  useEffect(() => {
@@ -19,6 +27,7 @@ function useInterval(callback, delay) {
19
27
  const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
20
28
  const {
21
29
  onChange,
30
+ onPercentChange,
22
31
  disabled = false,
23
32
  triggerOnce = false,
24
33
  delay,
@@ -26,10 +35,14 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
26
35
  children,
27
36
  ...rest
28
37
  } = props;
29
- const localRef = useRef(null);
30
38
  useImperativeHandle(ref, () => ({
31
39
  getInnerRef: () => localRef.current
32
40
  }));
41
+ const window = useWindowDimensions();
42
+ const localRef = useRef(null);
43
+ const isMountedRef = useRef(true);
44
+ const measurementStateRef = useRef(MeasurementState.IDLE);
45
+ const lastPercentRef = useRef(undefined);
33
46
  const [rectDimensions, setRectDimensions] = useState({
34
47
  rectTop: 0,
35
48
  rectBottom: 0,
@@ -40,12 +53,18 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
40
53
  });
41
54
  const [lastValue, setLastValue] = useState(undefined);
42
55
  const [active, setActive] = useState(false);
43
- const hasMeasuredRef = useRef(false);
44
- const measureInnerView = () => {
56
+ const measureInnerView = useCallback(() => {
45
57
  /* Check if the sensor is active to prevent unnecessary measurements
46
58
  This avoids running measurements when the sensor is disabled or stopped */
47
- if (!active) return;
59
+ if (!active || !isMountedRef.current || measurementStateRef.current === MeasurementState.MEASURING) {
60
+ return;
61
+ }
62
+ measurementStateRef.current = MeasurementState.MEASURING;
48
63
  localRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
64
+ // Check if component is still mounted before setting state because measurement can be asynchronous
65
+ if (!isMountedRef.current) {
66
+ return;
67
+ }
49
68
  const dimensions = {
50
69
  rectTop: pageY,
51
70
  rectBottom: pageY + height,
@@ -56,12 +75,13 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
56
75
  };
57
76
  if (rectDimensions.rectTop !== dimensions.rectTop || rectDimensions.rectBottom !== dimensions.rectBottom || rectDimensions.rectLeft !== dimensions.rectLeft || rectDimensions.rectRight !== dimensions.rectRight || rectDimensions.rectWidth !== dimensions.rectWidth || rectDimensions.rectHeight !== dimensions.rectHeight) {
58
77
  setRectDimensions(dimensions);
59
- /* Set hasMeasuredRef to true to indicate that a valid measurement has been taken
60
- This ensures visibility checks only proceed after initial measurement */
61
- hasMeasuredRef.current = true;
62
78
  }
79
+
80
+ /* Set measurementStateRef to MEASURED to indicate that a valid measurement has
81
+ been taken. This ensures visibility checks only proceed after initial measurement */
82
+ measurementStateRef.current = MeasurementState.MEASURED;
63
83
  });
64
- };
84
+ }, [active, rectDimensions]);
65
85
  useInterval(measureInnerView, delay || 100);
66
86
  const startWatching = useCallback(() => {
67
87
  if (!active) setActive(true);
@@ -71,9 +91,37 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
71
91
  setActive(false);
72
92
  /* Reset measurement state when stopping to ensure fresh measurements
73
93
  when the sensor is reactivated */
74
- hasMeasuredRef.current = false;
94
+ measurementStateRef.current = MeasurementState.IDLE; // Reset state
75
95
  }
76
96
  }, [active]);
97
+
98
+ // Effect to trigger initial measurement when component becomes active:
99
+ useEffect(() => {
100
+ let timer;
101
+ if (active && measurementStateRef.current === MeasurementState.IDLE) {
102
+ // Use setTimeout with 0 delay to ensure layout is complete
103
+ timer = setTimeout(() => {
104
+ measureInnerView();
105
+ }, 0);
106
+ }
107
+ return () => {
108
+ if (timer) clearTimeout(timer);
109
+ };
110
+ }, [active, measureInnerView]);
111
+
112
+ // Reset measurement state when dimensions change:
113
+ useEffect(() => {
114
+ if (isMountedRef.current && measurementStateRef.current === MeasurementState.MEASURED) {
115
+ // Reset measurement state to force remeasurement with new dimensions
116
+ measurementStateRef.current = MeasurementState.IDLE;
117
+ }
118
+ }, [window]);
119
+ useEffect(() => {
120
+ isMountedRef.current = true;
121
+ return () => {
122
+ isMountedRef.current = false;
123
+ };
124
+ }, []);
77
125
  useEffect(() => {
78
126
  if (!disabled) {
79
127
  startWatching();
@@ -86,8 +134,9 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
86
134
  /* Ensure visibility checks only run when the sensor is active and
87
135
  at least one measurement has been completed. This prevents
88
136
  premature visibility calculations with invalid or stale dimensions */
89
- if (!active || !hasMeasuredRef.current) return;
90
- const window = Dimensions.get('window');
137
+ if (!active || measurementStateRef.current !== MeasurementState.MEASURED || !isMountedRef.current) {
138
+ return;
139
+ }
91
140
  const isVisible = rectDimensions.rectTop + (threshold.top || 0) <= window.height &&
92
141
  // Top edge is within the bottom of the window
93
142
  rectDimensions.rectBottom - (threshold.bottom || 0) >= 0 &&
@@ -96,6 +145,39 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
96
145
  // Left edge is within the right of the window
97
146
  rectDimensions.rectRight - (threshold.right || 0) >= 0; // Right edge is within the left of the window
98
147
 
148
+ // Calculate percent visible if callback is requested / provided
149
+ if (onPercentChange && rectDimensions.rectWidth > 0 && rectDimensions.rectHeight > 0 && isVisible // Don't perform % calculation if not visible for efficiency
150
+ ) {
151
+ // Thresholds reduce the effective viewport
152
+ const viewportTop = 0 + (threshold.top || 0);
153
+ const viewportBottom = window.height - (threshold.bottom || 0);
154
+ const viewportLeft = 0 + (threshold.left || 0);
155
+ const viewportRight = window.width - (threshold.right || 0);
156
+
157
+ // Calculate the visible portion of the element within the reduced viewport
158
+ const visibleTop = Math.max(viewportTop, rectDimensions.rectTop);
159
+ const visibleBottom = Math.min(viewportBottom, rectDimensions.rectBottom);
160
+ const visibleLeft = Math.max(viewportLeft, rectDimensions.rectLeft);
161
+ const visibleRight = Math.min(viewportRight, rectDimensions.rectRight);
162
+
163
+ // Calculate visible dimensions
164
+ const visibleHeight = Math.max(0, visibleBottom - visibleTop);
165
+ const visibleWidth = Math.max(0, visibleRight - visibleLeft);
166
+
167
+ // Calculate percent visible based on actual element area
168
+ const visibleArea = visibleHeight * visibleWidth;
169
+ const totalArea = rectDimensions.rectHeight * rectDimensions.rectWidth;
170
+ const percentVisible = totalArea > 0 ? Math.round(visibleArea / totalArea * 100) : 0;
171
+
172
+ // Only fire callback if percent has changed
173
+ if (lastPercentRef.current !== percentVisible) {
174
+ lastPercentRef.current = percentVisible;
175
+ onPercentChange(percentVisible);
176
+ }
177
+ } else if (onPercentChange && rectDimensions.rectWidth > 0 && rectDimensions.rectHeight > 0 && !isVisible // If not visible, always report 0%
178
+ ) {
179
+ onPercentChange(0);
180
+ }
99
181
  if (lastValue !== isVisible) {
100
182
  setLastValue(isVisible);
101
183
  onChange(isVisible);
@@ -104,7 +186,7 @@ const VisibilitySensor = /*#__PURE__*/forwardRef((props, ref) => {
104
186
  }
105
187
  }
106
188
  // eslint-disable-next-line react-hooks/exhaustive-deps
107
- }, [rectDimensions, lastValue, active]);
189
+ }, [rectDimensions, window, lastValue, active, onPercentChange]);
108
190
  return /*#__PURE__*/_jsx(View, {
109
191
  ref: localRef,
110
192
  ...rest,
@@ -1 +1 @@
1
- {"version":3,"names":["React","useCallback","useEffect","useRef","useState","forwardRef","useImperativeHandle","Dimensions","View","jsx","_jsx","useInterval","callback","delay","savedCallback","current","undefined","id","setInterval","clearInterval","VisibilitySensor","props","ref","onChange","disabled","triggerOnce","threshold","children","rest","localRef","getInnerRef","rectDimensions","setRectDimensions","rectTop","rectBottom","rectLeft","rectRight","rectWidth","rectHeight","lastValue","setLastValue","active","setActive","hasMeasuredRef","measureInnerView","measure","_x","_y","width","height","pageX","pageY","dimensions","startWatching","stopWatching","window","get","isVisible","top","bottom","left","right"],"sourceRoot":"../../src","sources":["VisibilitySensor.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["React","useCallback","useEffect","useRef","useState","forwardRef","useImperativeHandle","useWindowDimensions","View","jsx","_jsx","MeasurementState","useInterval","callback","delay","savedCallback","current","undefined","id","setInterval","clearInterval","VisibilitySensor","props","ref","onChange","onPercentChange","disabled","triggerOnce","threshold","children","rest","getInnerRef","localRef","window","isMountedRef","measurementStateRef","IDLE","lastPercentRef","rectDimensions","setRectDimensions","rectTop","rectBottom","rectLeft","rectRight","rectWidth","rectHeight","lastValue","setLastValue","active","setActive","measureInnerView","MEASURING","measure","_x","_y","width","height","pageX","pageY","dimensions","MEASURED","startWatching","stopWatching","timer","setTimeout","clearTimeout","isVisible","top","bottom","left","right","viewportTop","viewportBottom","viewportLeft","viewportRight","visibleTop","Math","max","visibleBottom","min","visibleLeft","visibleRight","visibleHeight","visibleWidth","visibleArea","totalArea","percentVisible","round"],"sourceRoot":"../../src","sources":["VisibilitySensor.tsx"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"VisibilitySensor.d.ts","sourceRoot":"","sources":["../../src/VisibilitySensor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EAEtB,MAAM,0BAA0B,CAAC;AAmBlC,QAAA,MAAM,gBAAgB,mGA2HrB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"VisibilitySensor.d.ts","sourceRoot":"","sources":["../../src/VisibilitySensor.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EAEtB,MAAM,0BAA0B,CAAC;AAyBlC,QAAA,MAAM,gBAAgB,mGAmOrB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import type { View, ViewProps } from 'react-native';
2
2
  export interface VisibilitySensorProps extends ViewProps {
3
3
  onChange: (visible: boolean) => void;
4
+ onPercentChange?: (percentVisible: number) => void;
4
5
  disabled?: boolean;
5
6
  triggerOnce?: boolean;
6
7
  delay?: number | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"visibilitySensor.types.d.ts","sourceRoot":"","sources":["../../src/visibilitySensor.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,qBAAsB,SAAQ,SAAS;IACtD,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,yBAAyB,CAAC;CACvC;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"visibilitySensor.types.d.ts","sourceRoot":"","sources":["../../src/visibilitySensor.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,qBAAsB,SAAQ,SAAS;IACtD,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,eAAe,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,yBAAyB,CAAC;CACvC;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futurejj/react-native-visibility-sensor",
3
- "version": "1.3.21",
3
+ "version": "1.4.0-beta.1",
4
4
  "description": "A React Native wrapper to check whether a component is in the view port to track impressions and clicks",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -6,13 +6,19 @@ import React, {
6
6
  forwardRef,
7
7
  useImperativeHandle,
8
8
  } from 'react';
9
- import { Dimensions, type ScaledSize, View } from 'react-native';
9
+ import { useWindowDimensions, View } from 'react-native';
10
10
  import type {
11
11
  VisibilitySensorRef,
12
12
  VisibilitySensorProps,
13
13
  RectDimensionsState,
14
14
  } from './visibilitySensor.types';
15
15
 
16
+ enum MeasurementState {
17
+ IDLE = 'IDLE', // Not yet measured
18
+ MEASURING = 'MEASURING', // Measurement in progress
19
+ MEASURED = 'MEASURED', // Has valid measurements
20
+ }
21
+
16
22
  function useInterval(callback: () => void, delay: number | null) {
17
23
  const savedCallback = useRef(callback);
18
24
 
@@ -34,6 +40,7 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
34
40
  (props, ref) => {
35
41
  const {
36
42
  onChange,
43
+ onPercentChange,
37
44
  disabled = false,
38
45
  triggerOnce = false,
39
46
  delay,
@@ -42,12 +49,17 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
42
49
  ...rest
43
50
  } = props;
44
51
 
45
- const localRef = useRef<View>(null);
46
-
47
52
  useImperativeHandle(ref, () => ({
48
53
  getInnerRef: () => localRef.current,
49
54
  }));
50
55
 
56
+ const window = useWindowDimensions();
57
+
58
+ const localRef = useRef<View>(null);
59
+ const isMountedRef = useRef(true);
60
+ const measurementStateRef = useRef<MeasurementState>(MeasurementState.IDLE);
61
+ const lastPercentRef = useRef<number | undefined>(undefined);
62
+
51
63
  const [rectDimensions, setRectDimensions] = useState<RectDimensionsState>({
52
64
  rectTop: 0,
53
65
  rectBottom: 0,
@@ -58,12 +70,19 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
58
70
  });
59
71
  const [lastValue, setLastValue] = useState<boolean | undefined>(undefined);
60
72
  const [active, setActive] = useState<boolean>(false);
61
- const hasMeasuredRef = useRef(false);
62
73
 
63
- const measureInnerView = () => {
74
+ const measureInnerView = useCallback(() => {
64
75
  /* Check if the sensor is active to prevent unnecessary measurements
65
76
  This avoids running measurements when the sensor is disabled or stopped */
66
- if (!active) return;
77
+ if (
78
+ !active ||
79
+ !isMountedRef.current ||
80
+ measurementStateRef.current === MeasurementState.MEASURING
81
+ ) {
82
+ return;
83
+ }
84
+
85
+ measurementStateRef.current = MeasurementState.MEASURING;
67
86
 
68
87
  localRef.current?.measure(
69
88
  (
@@ -74,6 +93,11 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
74
93
  pageX: number,
75
94
  pageY: number
76
95
  ) => {
96
+ // Check if component is still mounted before setting state because measurement can be asynchronous
97
+ if (!isMountedRef.current) {
98
+ return;
99
+ }
100
+
77
101
  const dimensions = {
78
102
  rectTop: pageY,
79
103
  rectBottom: pageY + height,
@@ -91,13 +115,14 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
91
115
  rectDimensions.rectHeight !== dimensions.rectHeight
92
116
  ) {
93
117
  setRectDimensions(dimensions);
94
- /* Set hasMeasuredRef to true to indicate that a valid measurement has been taken
95
- This ensures visibility checks only proceed after initial measurement */
96
- hasMeasuredRef.current = true;
97
118
  }
119
+
120
+ /* Set measurementStateRef to MEASURED to indicate that a valid measurement has
121
+ been taken. This ensures visibility checks only proceed after initial measurement */
122
+ measurementStateRef.current = MeasurementState.MEASURED;
98
123
  }
99
124
  );
100
- };
125
+ }, [active, rectDimensions]);
101
126
 
102
127
  useInterval(measureInnerView, delay || 100);
103
128
 
@@ -110,10 +135,44 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
110
135
  setActive(false);
111
136
  /* Reset measurement state when stopping to ensure fresh measurements
112
137
  when the sensor is reactivated */
113
- hasMeasuredRef.current = false;
138
+ measurementStateRef.current = MeasurementState.IDLE; // Reset state
114
139
  }
115
140
  }, [active]);
116
141
 
142
+ // Effect to trigger initial measurement when component becomes active:
143
+ useEffect(() => {
144
+ let timer: ReturnType<typeof setTimeout>;
145
+
146
+ if (active && measurementStateRef.current === MeasurementState.IDLE) {
147
+ // Use setTimeout with 0 delay to ensure layout is complete
148
+ timer = setTimeout(() => {
149
+ measureInnerView();
150
+ }, 0);
151
+ }
152
+
153
+ return () => {
154
+ if (timer) clearTimeout(timer);
155
+ };
156
+ }, [active, measureInnerView]);
157
+
158
+ // Reset measurement state when dimensions change:
159
+ useEffect(() => {
160
+ if (
161
+ isMountedRef.current &&
162
+ measurementStateRef.current === MeasurementState.MEASURED
163
+ ) {
164
+ // Reset measurement state to force remeasurement with new dimensions
165
+ measurementStateRef.current = MeasurementState.IDLE;
166
+ }
167
+ }, [window]);
168
+
169
+ useEffect(() => {
170
+ isMountedRef.current = true;
171
+ return () => {
172
+ isMountedRef.current = false;
173
+ };
174
+ }, []);
175
+
117
176
  useEffect(() => {
118
177
  if (!disabled) {
119
178
  startWatching();
@@ -128,15 +187,66 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
128
187
  /* Ensure visibility checks only run when the sensor is active and
129
188
  at least one measurement has been completed. This prevents
130
189
  premature visibility calculations with invalid or stale dimensions */
131
- if (!active || !hasMeasuredRef.current) return;
190
+ if (
191
+ !active ||
192
+ measurementStateRef.current !== MeasurementState.MEASURED ||
193
+ !isMountedRef.current
194
+ ) {
195
+ return;
196
+ }
132
197
 
133
- const window: ScaledSize = Dimensions.get('window');
134
198
  const isVisible: boolean =
135
199
  rectDimensions.rectTop + (threshold.top || 0) <= window.height && // Top edge is within the bottom of the window
136
200
  rectDimensions.rectBottom - (threshold.bottom || 0) >= 0 && // Bottom edge is within the top of the window
137
201
  rectDimensions.rectLeft + (threshold.left || 0) <= window.width && // Left edge is within the right of the window
138
202
  rectDimensions.rectRight - (threshold.right || 0) >= 0; // Right edge is within the left of the window
139
203
 
204
+ // Calculate percent visible if callback is requested / provided
205
+ if (
206
+ onPercentChange &&
207
+ rectDimensions.rectWidth > 0 &&
208
+ rectDimensions.rectHeight > 0 &&
209
+ isVisible // Don't perform % calculation if not visible for efficiency
210
+ ) {
211
+ // Thresholds reduce the effective viewport
212
+ const viewportTop = 0 + (threshold.top || 0);
213
+ const viewportBottom = window.height - (threshold.bottom || 0);
214
+ const viewportLeft = 0 + (threshold.left || 0);
215
+ const viewportRight = window.width - (threshold.right || 0);
216
+
217
+ // Calculate the visible portion of the element within the reduced viewport
218
+ const visibleTop = Math.max(viewportTop, rectDimensions.rectTop);
219
+ const visibleBottom = Math.min(
220
+ viewportBottom,
221
+ rectDimensions.rectBottom
222
+ );
223
+ const visibleLeft = Math.max(viewportLeft, rectDimensions.rectLeft);
224
+ const visibleRight = Math.min(viewportRight, rectDimensions.rectRight);
225
+
226
+ // Calculate visible dimensions
227
+ const visibleHeight = Math.max(0, visibleBottom - visibleTop);
228
+ const visibleWidth = Math.max(0, visibleRight - visibleLeft);
229
+
230
+ // Calculate percent visible based on actual element area
231
+ const visibleArea = visibleHeight * visibleWidth;
232
+ const totalArea = rectDimensions.rectHeight * rectDimensions.rectWidth;
233
+ const percentVisible =
234
+ totalArea > 0 ? Math.round((visibleArea / totalArea) * 100) : 0;
235
+
236
+ // Only fire callback if percent has changed
237
+ if (lastPercentRef.current !== percentVisible) {
238
+ lastPercentRef.current = percentVisible;
239
+ onPercentChange(percentVisible);
240
+ }
241
+ } else if (
242
+ onPercentChange &&
243
+ rectDimensions.rectWidth > 0 &&
244
+ rectDimensions.rectHeight > 0 &&
245
+ !isVisible // If not visible, always report 0%
246
+ ) {
247
+ onPercentChange(0);
248
+ }
249
+
140
250
  if (lastValue !== isVisible) {
141
251
  setLastValue(isVisible);
142
252
  onChange(isVisible);
@@ -145,7 +255,7 @@ const VisibilitySensor = forwardRef<VisibilitySensorRef, VisibilitySensorProps>(
145
255
  }
146
256
  }
147
257
  // eslint-disable-next-line react-hooks/exhaustive-deps
148
- }, [rectDimensions, lastValue, active]);
258
+ }, [rectDimensions, window, lastValue, active, onPercentChange]);
149
259
 
150
260
  return (
151
261
  <View ref={localRef} {...rest}>
@@ -2,6 +2,7 @@ import type { View, ViewProps } from 'react-native';
2
2
 
3
3
  export interface VisibilitySensorProps extends ViewProps {
4
4
  onChange: (visible: boolean) => void;
5
+ onPercentChange?: (percentVisible: number) => void;
5
6
  disabled?: boolean;
6
7
  triggerOnce?: boolean;
7
8
  delay?: number | undefined;