@cleartrip/ct-design-intersection-observer 5.30.0 → 5.32.0-SNAPSHOT-native-main.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/dist/IntersectionObserver.native.d.ts.map +1 -1
- package/dist/type.d.ts +1 -0
- package/dist/type.d.ts.map +1 -1
- package/dist/useInViewType.d.ts +1 -0
- package/dist/useInViewType.d.ts.map +1 -1
- package/dist/useinView.native.d.ts +5 -1
- package/dist/useinView.native.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/IntersectionObserver.native.tsx +16 -4
- package/src/type.ts +5 -0
- package/src/useInViewType.ts +6 -0
- package/src/useinView.native.ts +183 -53
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IntersectionObserver.native.d.ts","sourceRoot":"","sources":["../packages/components/InterSectionObserver/src/IntersectionObserver.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,EAAE,yBAAyB,EAAE,MAAM,QAAQ,CAAC;AAEnD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,
|
|
1
|
+
{"version":3,"file":"IntersectionObserver.native.d.ts","sourceRoot":"","sources":["../packages/components/InterSectionObserver/src/IntersectionObserver.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,EAAE,yBAAyB,EAAE,MAAM,QAAQ,CAAC;AAEnD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAqD7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
package/dist/type.d.ts
CHANGED
package/dist/type.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../packages/components/InterSectionObserver/src/type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,MAAM,WAAW,yBAAyB;IAExC,QAAQ,EAAE,SAAS,CAAC;IAGpB,cAAc,EAAE,MAAM,IAAI,CAAC;IAG3B,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAGpC,0BAA0B,CAAC,EAAE,MAAM,IAAI,CAAC;IAGxC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAGrB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAGlB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAGvE,6BAA6B,CAAC,EAAE,OAAO,CAAC;IAGxC,WAAW,CAAC,EAAE,OAAO,CAAC;IAKtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAKvB,eAAe,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../packages/components/InterSectionObserver/src/type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,MAAM,WAAW,yBAAyB;IAExC,QAAQ,EAAE,SAAS,CAAC;IAGpB,cAAc,EAAE,MAAM,IAAI,CAAC;IAG3B,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAGpC,0BAA0B,CAAC,EAAE,MAAM,IAAI,CAAC;IAGxC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAGrB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAGlB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAGvE,6BAA6B,CAAC,EAAE,OAAO,CAAC;IAGxC,WAAW,CAAC,EAAE,OAAO,CAAC;IAKtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAKvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAK1B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAMlC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
package/dist/useInViewType.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface IntersectionOptions extends IntersectionObserverInit {
|
|
|
23
23
|
observeOnScroll?: boolean;
|
|
24
24
|
onUnmount?: () => void;
|
|
25
25
|
checkFullVisibility?: boolean;
|
|
26
|
+
observeWithScrollOffset?: boolean;
|
|
26
27
|
}
|
|
27
28
|
export declare enum SCROLLING_TYPE {
|
|
28
29
|
NONE = "none",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInViewType.d.ts","sourceRoot":"","sources":["../packages/components/InterSectionObserver/src/useInViewType.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;AAEjD,MAAM,WAAW,mBAAoB,SAAQ,wBAAwB;IAEnE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAEtB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB,WAAW,CAAC,EAAE,OAAO,CAAC;IAGtB,IAAI,CAAC,EAAE,OAAO,CAAC;IAGf,KAAK,CAAC,EAAE,MAAM,CAAC;IAKf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAGrC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAMpC,uBAAuB,CAAC,EAAE;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAKF,8BAA8B,CAAC,EAAE,OAAO,CAAC;IAKzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,0BAA0B,CAAC,EAAE,MAAM,IAAI,CAAC;IAGxC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAGhC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAEvE,6BAA6B,CAAC,EAAE,OAAO,CAAC;IAMxC,eAAe,CAAC,EAAE,OAAO,CAAC;IAK1B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAMvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useInViewType.d.ts","sourceRoot":"","sources":["../packages/components/InterSectionObserver/src/useInViewType.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC;AAEjD,MAAM,WAAW,mBAAoB,SAAQ,wBAAwB;IAEnE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAEtB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB,WAAW,CAAC,EAAE,OAAO,CAAC;IAGtB,IAAI,CAAC,EAAE,OAAO,CAAC;IAGf,KAAK,CAAC,EAAE,MAAM,CAAC;IAKf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAGrC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAMpC,uBAAuB,CAAC,EAAE;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAKF,8BAA8B,CAAC,EAAE,OAAO,CAAC;IAKzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,0BAA0B,CAAC,EAAE,MAAM,IAAI,CAAC;IAGxC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAGhC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAEvE,6BAA6B,CAAC,EAAE,OAAO,CAAC;IAMxC,eAAe,CAAC,EAAE,OAAO,CAAC;IAK1B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAMvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAM9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED,oBAAY,cAAc;IACxB,IAAI,SAAS;IACb,UAAU,eAAe;IACzB,QAAQ,aAAa;CACtB;AAED,UAAU,UAAU;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb"}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { IntersectionOptions, InViewHookResponse } from './useInViewType';
|
|
2
|
-
declare const
|
|
2
|
+
export declare const useInViewWithLayout: <T>({ threshold, timedThreshold, onChange, onIntersectionComplete, onTimedObservationComplete, minimumObservationTime, observerBox, gestureHorizontallyScrollable, triggerOnce, observeOnScroll, onUnmount, checkFullVisibility, observeWithScrollOffset, }: IntersectionOptions) => {
|
|
3
|
+
ref: InViewHookResponse<T>;
|
|
4
|
+
onLayout: () => void;
|
|
5
|
+
};
|
|
6
|
+
declare const useInView: <T>(options: IntersectionOptions) => InViewHookResponse<T>;
|
|
3
7
|
export default useInView;
|
|
4
8
|
//# sourceMappingURL=useinView.native.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useinView.native.d.ts","sourceRoot":"","sources":["../packages/components/InterSectionObserver/src/useinView.native.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useinView.native.d.ts","sourceRoot":"","sources":["../packages/components/InterSectionObserver/src/useinView.native.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AA0L1E,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,yPAcpC,mBAAmB,KAAG;IAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;CAsM1E,CAAC;AAEF,QAAA,MAAM,SAAS,GAAI,CAAC,EAAE,SAAS,mBAAmB,KAAG,kBAAkB,CAAC,CAAC,CAAwC,CAAC;AAElH,eAAe,SAAS,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleartrip/ct-design-intersection-observer",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.32.0-SNAPSHOT-native-main.1",
|
|
4
4
|
"description": "Intersection Observer Component",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"main": "./dist/ct-design-intersection-observer.cjs.js",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"src"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@cleartrip/ct-design-
|
|
28
|
-
"@cleartrip/ct-design-container": "5.
|
|
29
|
-
"@cleartrip/ct-design-style-manager": "5.
|
|
30
|
-
"@cleartrip/ct-design-theme": "5.
|
|
31
|
-
"@cleartrip/ct-design-list": "5.
|
|
32
|
-
"@cleartrip/ct-design-common-utils": "5.
|
|
27
|
+
"@cleartrip/ct-design-container": "5.32.0-SNAPSHOT-native-main.1",
|
|
28
|
+
"@cleartrip/ct-design-scroll-container": "5.32.0-SNAPSHOT-native-main.1",
|
|
29
|
+
"@cleartrip/ct-design-style-manager": "5.32.0-SNAPSHOT-native-main.1",
|
|
30
|
+
"@cleartrip/ct-design-theme": "5.32.0-SNAPSHOT-native-main.1",
|
|
31
|
+
"@cleartrip/ct-design-list": "5.32.0-SNAPSHOT-native-main.1",
|
|
32
|
+
"@cleartrip/ct-design-common-utils": "5.32.0-SNAPSHOT-native-main.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@cleartrip/ct-design-types": "5.
|
|
35
|
+
"@cleartrip/ct-design-types": "5.32.0-SNAPSHOT-native-main.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"react": ">=16.8.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import { View } from 'react-native';
|
|
4
|
-
import
|
|
4
|
+
import { useInViewWithLayout } from './useinView.native';
|
|
5
5
|
|
|
6
6
|
import { IntersectionObserverProps } from './type';
|
|
7
7
|
|
|
@@ -18,9 +18,10 @@ const InterSectionObserver: React.FC<IntersectionObserverProps> = ({
|
|
|
18
18
|
onIntersectionComplete,
|
|
19
19
|
onUnmount,
|
|
20
20
|
observeOnScroll,
|
|
21
|
+
observeWithScrollOffset,
|
|
21
22
|
testID,
|
|
22
23
|
}) => {
|
|
23
|
-
const ref =
|
|
24
|
+
const { ref, onLayout } = useInViewWithLayout<View>({
|
|
24
25
|
onChange: onIntersection,
|
|
25
26
|
onTimedObservationComplete,
|
|
26
27
|
onIntersectionComplete,
|
|
@@ -32,17 +33,28 @@ const InterSectionObserver: React.FC<IntersectionObserverProps> = ({
|
|
|
32
33
|
triggerOnce,
|
|
33
34
|
onUnmount,
|
|
34
35
|
observeOnScroll,
|
|
36
|
+
observeWithScrollOffset,
|
|
35
37
|
});
|
|
36
38
|
if (wrapped) {
|
|
37
39
|
return (
|
|
38
|
-
<View
|
|
40
|
+
<View
|
|
41
|
+
ref={ref}
|
|
42
|
+
testID={testID ? `${testID}-IntersectionObserver` : undefined}
|
|
43
|
+
collapsable={false}
|
|
44
|
+
onLayout={onLayout}
|
|
45
|
+
>
|
|
39
46
|
{children}
|
|
40
47
|
</View>
|
|
41
48
|
);
|
|
42
49
|
}
|
|
43
50
|
return (
|
|
44
51
|
<>
|
|
45
|
-
<View
|
|
52
|
+
<View
|
|
53
|
+
ref={ref}
|
|
54
|
+
testID={testID ? `${testID}-IntersectionObserver` : undefined}
|
|
55
|
+
collapsable={false}
|
|
56
|
+
onLayout={onLayout}
|
|
57
|
+
/>
|
|
46
58
|
{children}
|
|
47
59
|
</>
|
|
48
60
|
);
|
package/src/type.ts
CHANGED
|
@@ -41,6 +41,11 @@ export interface IntersectionObserverProps {
|
|
|
41
41
|
*/
|
|
42
42
|
observeOnScroll?: boolean;
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* compute visibility from the cached layout and parent scroll offset instead of measuring on every observe
|
|
46
|
+
*/
|
|
47
|
+
observeWithScrollOffset?: boolean;
|
|
48
|
+
|
|
44
49
|
/**
|
|
45
50
|
* Identifier used to locate this component in end-to-end tests.
|
|
46
51
|
* Forwarded to the observed React Native view.
|
package/src/useInViewType.ts
CHANGED
|
@@ -78,6 +78,12 @@ export interface IntersectionOptions extends IntersectionObserverInit {
|
|
|
78
78
|
* @default false
|
|
79
79
|
*/
|
|
80
80
|
checkFullVisibility?: boolean;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* compute visibility from the cached layout and parent scroll offset instead of measuring on every observe
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
observeWithScrollOffset?: boolean;
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
export enum SCROLLING_TYPE {
|
package/src/useinView.native.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef } from 'react';
|
|
1
|
+
import { RefObject, useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Dimensions, View } from 'react-native';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ObserveOptions,
|
|
6
|
+
useAddObservableChild,
|
|
7
|
+
useGetScrollOffset,
|
|
8
|
+
useGetScrolling,
|
|
9
|
+
} from '@cleartrip/ct-design-scroll-container';
|
|
5
10
|
import { useListItem } from '@cleartrip/ct-design-list';
|
|
6
11
|
import { promiseHandler } from '@cleartrip/ct-design-common-utils';
|
|
7
12
|
|
|
@@ -14,12 +19,27 @@ const { width, height } = Dimensions.get('window');
|
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
interface VisibilityState {
|
|
17
|
-
visibleRef:
|
|
18
|
-
timeoutVisibleRef:
|
|
19
|
-
timeoutVisibleStartRef:
|
|
20
|
-
triggerOnceValue:
|
|
22
|
+
visibleRef: RefObject<number>;
|
|
23
|
+
timeoutVisibleRef: RefObject<boolean>;
|
|
24
|
+
timeoutVisibleStartRef: RefObject<{ start: number; end: number }>;
|
|
25
|
+
triggerOnceValue: RefObject<boolean>;
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
/** window coordinates + size of the element, and the scroll offset at the time it was measured */
|
|
29
|
+
interface CachedLayout {
|
|
30
|
+
pageX: number;
|
|
31
|
+
pageY: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
scrollX: number;
|
|
35
|
+
scrollY: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** allowed difference (percentage points) between measure and offset based visibility */
|
|
39
|
+
const VISIBILITY_MISMATCH_TOLERANCE = 10;
|
|
40
|
+
/** px difference treated as equal while diagnosing a mismatch */
|
|
41
|
+
const PIXEL_TOLERANCE = 1;
|
|
42
|
+
|
|
23
43
|
const calculateVisibility = (
|
|
24
44
|
rectTop: number,
|
|
25
45
|
rectBottom: number,
|
|
@@ -66,6 +86,47 @@ const calculateVisibility = (
|
|
|
66
86
|
return { verticalVisibility: clamp(verticalVisibility), horizontalVisibility: clamp(horizontalVisibility) };
|
|
67
87
|
};
|
|
68
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Visibility percentage fed to handleVisibilityChange; 0 when not visible
|
|
91
|
+
*/
|
|
92
|
+
const getVisibility = (
|
|
93
|
+
pageX: number,
|
|
94
|
+
pageY: number,
|
|
95
|
+
rectWidth: number,
|
|
96
|
+
rectHeight: number,
|
|
97
|
+
observerBox: [number, number, number | undefined, number | undefined],
|
|
98
|
+
threshold: number[],
|
|
99
|
+
horizontal: boolean,
|
|
100
|
+
gestureHorizontallyScrollable: boolean,
|
|
101
|
+
checkFullVisibility: boolean,
|
|
102
|
+
): number => {
|
|
103
|
+
const { verticalVisibility, horizontalVisibility } = calculateVisibility(
|
|
104
|
+
pageY,
|
|
105
|
+
pageY + rectHeight,
|
|
106
|
+
rectHeight,
|
|
107
|
+
pageX,
|
|
108
|
+
pageX + rectWidth,
|
|
109
|
+
rectWidth,
|
|
110
|
+
observerBox,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const visiblePercentage = threshold[0] * 100;
|
|
114
|
+
const isVisible =
|
|
115
|
+
horizontal || gestureHorizontallyScrollable
|
|
116
|
+
? horizontalVisibility >= visiblePercentage
|
|
117
|
+
: verticalVisibility >= visiblePercentage;
|
|
118
|
+
const visibility = horizontal ? horizontalVisibility : verticalVisibility;
|
|
119
|
+
|
|
120
|
+
const totalVisibility = (horizontalVisibility * verticalVisibility) / 100;
|
|
121
|
+
if (totalVisibility >= visiblePercentage) {
|
|
122
|
+
return totalVisibility;
|
|
123
|
+
}
|
|
124
|
+
if (!checkFullVisibility && isVisible) {
|
|
125
|
+
return visibility;
|
|
126
|
+
}
|
|
127
|
+
return 0;
|
|
128
|
+
};
|
|
129
|
+
|
|
69
130
|
const handleVisibilityChange = (
|
|
70
131
|
visibility: number,
|
|
71
132
|
threshold: number[],
|
|
@@ -135,7 +196,7 @@ const handleVisibilityChange = (
|
|
|
135
196
|
}
|
|
136
197
|
};
|
|
137
198
|
|
|
138
|
-
const
|
|
199
|
+
export const useInViewWithLayout = <T>({
|
|
139
200
|
threshold = [0.01, 0.5],
|
|
140
201
|
timedThreshold = 1000,
|
|
141
202
|
onChange,
|
|
@@ -148,8 +209,13 @@ const useInView = <T>({
|
|
|
148
209
|
observeOnScroll = false,
|
|
149
210
|
onUnmount,
|
|
150
211
|
checkFullVisibility = false,
|
|
151
|
-
|
|
212
|
+
observeWithScrollOffset = false,
|
|
213
|
+
}: IntersectionOptions): { ref: InViewHookResponse<T>; onLayout: () => void } => {
|
|
152
214
|
const ref = useRef<View>(null);
|
|
215
|
+
const layoutRef = useRef<CachedLayout | null>(null);
|
|
216
|
+
// observe is registered once with the scroll container, so read the latest box from a ref
|
|
217
|
+
const observerBoxRef = useRef(observerBox);
|
|
218
|
+
observerBoxRef.current = observerBox;
|
|
153
219
|
const visibleRef = useRef(0);
|
|
154
220
|
const timeoutVisibleRef = useRef(false);
|
|
155
221
|
const timeoutVisibleStartRef = useRef({ start: 0, end: 0 });
|
|
@@ -157,9 +223,27 @@ const useInView = <T>({
|
|
|
157
223
|
|
|
158
224
|
const { horizontal } = useGetScrolling();
|
|
159
225
|
const { listId } = useListItem();
|
|
226
|
+
const getScrollOffset = useGetScrollOffset();
|
|
227
|
+
|
|
228
|
+
const cacheLayout = useCallback(
|
|
229
|
+
(pageX: number, pageY: number, layoutWidth: number, layoutHeight: number) => {
|
|
230
|
+
if (!getScrollOffset) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const { x, y } = getScrollOffset();
|
|
234
|
+
layoutRef.current = { pageX, pageY, width: layoutWidth, height: layoutHeight, scrollX: x, scrollY: y };
|
|
235
|
+
},
|
|
236
|
+
[getScrollOffset],
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const handleLayout = useCallback(() => {
|
|
240
|
+
ref.current?.measure((_x, _y, layoutWidth, layoutHeight, pageX, pageY) => {
|
|
241
|
+
cacheLayout(pageX, pageY, layoutWidth, layoutHeight);
|
|
242
|
+
});
|
|
243
|
+
}, [cacheLayout]);
|
|
160
244
|
|
|
161
245
|
const observerMethod = useCallback(
|
|
162
|
-
(visibleItems?: Set<number>, isScrolling?: boolean) => {
|
|
246
|
+
(visibleItems?: Set<number>, isScrolling?: boolean, options?: ObserveOptions) => {
|
|
163
247
|
if (!observeOnScroll && isScrolling) {
|
|
164
248
|
return;
|
|
165
249
|
}
|
|
@@ -182,6 +266,48 @@ const useInView = <T>({
|
|
|
182
266
|
return;
|
|
183
267
|
}
|
|
184
268
|
|
|
269
|
+
const visibilityOf = (x: number, y: number, w: number, h: number) =>
|
|
270
|
+
getVisibility(
|
|
271
|
+
x,
|
|
272
|
+
y,
|
|
273
|
+
w,
|
|
274
|
+
h,
|
|
275
|
+
observerBoxRef.current,
|
|
276
|
+
threshold,
|
|
277
|
+
!!horizontal,
|
|
278
|
+
gestureHorizontallyScrollable,
|
|
279
|
+
checkFullVisibility,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const state = {
|
|
283
|
+
visibleRef,
|
|
284
|
+
timeoutVisibleRef,
|
|
285
|
+
timeoutVisibleStartRef,
|
|
286
|
+
triggerOnceValue,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const callbacks = {
|
|
290
|
+
onChange,
|
|
291
|
+
onIntersectionComplete,
|
|
292
|
+
onTimedObservationComplete,
|
|
293
|
+
triggerOnce,
|
|
294
|
+
minimumObservationTime,
|
|
295
|
+
observerMethod,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const cachedLayout = layoutRef.current;
|
|
299
|
+
if (observeWithScrollOffset && cachedLayout && options?.scrollOffset && !options.refreshLayout) {
|
|
300
|
+
const { x, y } = options.scrollOffset;
|
|
301
|
+
const visibility = visibilityOf(
|
|
302
|
+
cachedLayout.pageX - (x - cachedLayout.scrollX),
|
|
303
|
+
cachedLayout.pageY - (y - cachedLayout.scrollY),
|
|
304
|
+
cachedLayout.width,
|
|
305
|
+
cachedLayout.height,
|
|
306
|
+
);
|
|
307
|
+
handleVisibilityChange(visibility, threshold, state, callbacks);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
185
311
|
promiseHandler.add(() => {
|
|
186
312
|
return new Promise<void>((resolve) => {
|
|
187
313
|
if (!ref.current) {
|
|
@@ -189,61 +315,63 @@ const useInView = <T>({
|
|
|
189
315
|
return;
|
|
190
316
|
}
|
|
191
317
|
ref.current?.measure((_x, _y, width, height, pageX, pageY) => {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
handleVisibilityChange(totalVisibility, threshold, state, callbacks);
|
|
229
|
-
} else if (!checkFullVisibility && isVisible) {
|
|
230
|
-
handleVisibilityChange(visibility, threshold, state, callbacks);
|
|
231
|
-
} else {
|
|
232
|
-
handleVisibilityChange(0, threshold, state, callbacks);
|
|
318
|
+
const visibility = visibilityOf(pageX, pageY, width, height);
|
|
319
|
+
|
|
320
|
+
// TODO: remove measure based logic once offset based logic is verified
|
|
321
|
+
const cached = layoutRef.current;
|
|
322
|
+
const scrollOffset = options?.scrollOffset;
|
|
323
|
+
if (cached && scrollOffset && !options?.refreshLayout) {
|
|
324
|
+
// measure resolves async, so compare against the offset at resolve time
|
|
325
|
+
const measuredAtOffset = getScrollOffset?.() ?? scrollOffset;
|
|
326
|
+
const offsetPageX = cached.pageX - (measuredAtOffset.x - cached.scrollX);
|
|
327
|
+
const offsetPageY = cached.pageY - (measuredAtOffset.y - cached.scrollY);
|
|
328
|
+
const clamp = (value: number) => Math.min(100, Math.max(0, value));
|
|
329
|
+
const measureVisibility = clamp(visibility);
|
|
330
|
+
const offsetVisibility = clamp(visibilityOf(offsetPageX, offsetPageY, cached.width, cached.height));
|
|
331
|
+
if (Math.abs(offsetVisibility - measureVisibility) > VISIBILITY_MISMATCH_TOLERANCE) {
|
|
332
|
+
const drift = { x: pageX - offsetPageX, y: pageY - offsetPageY };
|
|
333
|
+
const reasons: string[] = [];
|
|
334
|
+
if (
|
|
335
|
+
Math.abs(width - cached.width) > PIXEL_TOLERANCE ||
|
|
336
|
+
Math.abs(height - cached.height) > PIXEL_TOLERANCE
|
|
337
|
+
) {
|
|
338
|
+
reasons.push('size changed since cache: layout not refreshed');
|
|
339
|
+
}
|
|
340
|
+
if (Math.abs(drift.x) > PIXEL_TOLERANCE || Math.abs(drift.y) > PIXEL_TOLERANCE) {
|
|
341
|
+
reasons.push('position drift not explained by own scroll: parent moved / nested scroll / sticky');
|
|
342
|
+
}
|
|
343
|
+
console.error('[useInView] offset vs measure visibility mismatch', {
|
|
344
|
+
reasons,
|
|
345
|
+
diff: offsetVisibility - measureVisibility,
|
|
346
|
+
drift,
|
|
347
|
+
listId,
|
|
348
|
+
measure: { visibility: measureVisibility, pageX, pageY, width, height },
|
|
349
|
+
offset: { visibility: offsetVisibility, pageX: offsetPageX, pageY: offsetPageY, measuredAtOffset },
|
|
350
|
+
eventScrollOffset: scrollOffset,
|
|
351
|
+
cached,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
233
354
|
}
|
|
355
|
+
if (options?.refreshLayout) {
|
|
356
|
+
cacheLayout(pageX, pageY, width, height);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
handleVisibilityChange(visibility, threshold, state, callbacks);
|
|
234
360
|
resolve();
|
|
235
361
|
});
|
|
236
362
|
});
|
|
237
363
|
}, ref.current);
|
|
238
364
|
},
|
|
239
365
|
[
|
|
366
|
+
cacheLayout,
|
|
240
367
|
checkFullVisibility,
|
|
241
368
|
gestureHorizontallyScrollable,
|
|
369
|
+
getScrollOffset,
|
|
242
370
|
horizontal,
|
|
243
371
|
listId,
|
|
244
372
|
minimumObservationTime,
|
|
245
373
|
observeOnScroll,
|
|
246
|
-
|
|
374
|
+
observeWithScrollOffset,
|
|
247
375
|
onChange,
|
|
248
376
|
onIntersectionComplete,
|
|
249
377
|
onTimedObservationComplete,
|
|
@@ -271,7 +399,7 @@ const useInView = <T>({
|
|
|
271
399
|
|
|
272
400
|
useEffect(() => {
|
|
273
401
|
setTimeout(() => {
|
|
274
|
-
observerMethod();
|
|
402
|
+
observerMethod(undefined, false, { refreshLayout: true });
|
|
275
403
|
}, 400);
|
|
276
404
|
return () => {
|
|
277
405
|
handleUnmount();
|
|
@@ -279,7 +407,9 @@ const useInView = <T>({
|
|
|
279
407
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
280
408
|
}, [handleUnmount]);
|
|
281
409
|
|
|
282
|
-
return ref as InViewHookResponse<T
|
|
410
|
+
return { ref: ref as InViewHookResponse<T>, onLayout: handleLayout };
|
|
283
411
|
};
|
|
284
412
|
|
|
413
|
+
const useInView = <T>(options: IntersectionOptions): InViewHookResponse<T> => useInViewWithLayout<T>(options).ref;
|
|
414
|
+
|
|
285
415
|
export default useInView;
|