@usefy/use-intersection-observer 0.0.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.
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Extended intersection entry with convenience properties
3
+ * Wraps the native IntersectionObserverEntry with additional computed values
4
+ */
5
+ interface IntersectionEntry {
6
+ /**
7
+ * The original native IntersectionObserverEntry
8
+ */
9
+ readonly entry: IntersectionObserverEntry;
10
+ /**
11
+ * Whether the target element is currently intersecting with the root
12
+ */
13
+ readonly isIntersecting: boolean;
14
+ /**
15
+ * The ratio of the target element that is visible (0.0 to 1.0)
16
+ */
17
+ readonly intersectionRatio: number;
18
+ /**
19
+ * The target element being observed
20
+ */
21
+ readonly target: Element;
22
+ /**
23
+ * The bounding rectangle of the target element
24
+ */
25
+ readonly boundingClientRect: DOMRectReadOnly;
26
+ /**
27
+ * The visible portion of the target element (intersection area)
28
+ */
29
+ readonly intersectionRect: DOMRectReadOnly;
30
+ /**
31
+ * The bounding rectangle of the root element (viewport if null)
32
+ */
33
+ readonly rootBounds: DOMRectReadOnly | null;
34
+ /**
35
+ * Timestamp when the intersection was recorded
36
+ */
37
+ readonly time: number;
38
+ }
39
+ /**
40
+ * Callback function called when intersection state changes
41
+ * @param entry - The intersection entry data
42
+ * @param inView - Whether the element is currently in view
43
+ */
44
+ type OnChangeCallback = (entry: IntersectionEntry, inView: boolean) => void;
45
+ /**
46
+ * Options for configuring the useIntersectionObserver hook
47
+ */
48
+ interface UseIntersectionObserverOptions {
49
+ /**
50
+ * Threshold(s) at which the callback is triggered
51
+ * A single number or array of numbers between 0.0 and 1.0
52
+ * - 0: Triggers as soon as any pixel is visible
53
+ * - 0.5: Triggers when 50% of the element is visible
54
+ * - 1.0: Triggers when the entire element is visible
55
+ * @default 0
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * // Single threshold
60
+ * useIntersectionObserver({ threshold: 0.5 })
61
+ *
62
+ * // Multiple thresholds for granular tracking
63
+ * useIntersectionObserver({ threshold: [0, 0.25, 0.5, 0.75, 1] })
64
+ * ```
65
+ */
66
+ threshold?: number | number[];
67
+ /**
68
+ * The root element used as the viewport for checking visibility
69
+ * If null (default), uses the browser viewport
70
+ * @default null
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * // Use a custom scrollable container
75
+ * const scrollContainerRef = useRef<HTMLDivElement>(null);
76
+ * useIntersectionObserver({ root: scrollContainerRef.current })
77
+ * ```
78
+ */
79
+ root?: Element | Document | null;
80
+ /**
81
+ * Margin around the root element (CSS margin syntax)
82
+ * Can be used to grow or shrink the root's bounding box before computing intersections
83
+ * Values can be pixels (px) or percentages (%)
84
+ * @default "0px"
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * // Trigger 100px before element enters viewport
89
+ * useIntersectionObserver({ rootMargin: "100px 0px" })
90
+ *
91
+ * // Trigger when element is 20% away from viewport
92
+ * useIntersectionObserver({ rootMargin: "20%" })
93
+ * ```
94
+ */
95
+ rootMargin?: string;
96
+ /**
97
+ * Stop observing after the element first becomes visible
98
+ * Useful for lazy loading images or one-time animations
99
+ * @default false
100
+ *
101
+ * @example
102
+ * ```tsx
103
+ * // Load image only once when it enters viewport
104
+ * const { ref, inView } = useIntersectionObserver({ triggerOnce: true });
105
+ * return <img ref={ref} src={inView ? imageSrc : placeholder} />;
106
+ * ```
107
+ */
108
+ triggerOnce?: boolean;
109
+ /**
110
+ * Enable or disable the observer dynamically
111
+ * When false, the observer is disconnected and no callbacks are fired
112
+ * @default true
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * // Disable observation while loading
117
+ * const { ref } = useIntersectionObserver({ enabled: !isLoading });
118
+ * ```
119
+ */
120
+ enabled?: boolean;
121
+ /**
122
+ * Initial intersection state before the first observation
123
+ * Useful for SSR/SSG scenarios where you want to assume a state
124
+ * @default false
125
+ *
126
+ * @example
127
+ * ```tsx
128
+ * // Assume above-the-fold content is visible initially
129
+ * useIntersectionObserver({ initialIsIntersecting: true })
130
+ * ```
131
+ */
132
+ initialIsIntersecting?: boolean;
133
+ /**
134
+ * Callback fired when intersection state changes
135
+ * Called with the intersection entry and a boolean indicating if in view
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * useIntersectionObserver({
140
+ * onChange: (entry, inView) => {
141
+ * if (inView) {
142
+ * analytics.track('element_viewed', { ratio: entry.intersectionRatio });
143
+ * }
144
+ * }
145
+ * })
146
+ * ```
147
+ */
148
+ onChange?: OnChangeCallback;
149
+ /**
150
+ * Delay in milliseconds before starting to observe
151
+ * Useful for preventing flash of content on fast scrolling
152
+ * @default 0
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * // Wait 100ms before starting observation
157
+ * useIntersectionObserver({ delay: 100 })
158
+ * ```
159
+ */
160
+ delay?: number;
161
+ }
162
+ /**
163
+ * Return type for the useIntersectionObserver hook
164
+ */
165
+ interface UseIntersectionObserverReturn {
166
+ /**
167
+ * The current intersection entry data
168
+ * null if element hasn't been observed yet or if SSR
169
+ */
170
+ entry: IntersectionEntry | null;
171
+ /**
172
+ * Boolean indicating whether the element is currently in view
173
+ * Shorthand for entry?.isIntersecting ?? initialIsIntersecting
174
+ */
175
+ inView: boolean;
176
+ /**
177
+ * Callback ref to attach to the target element
178
+ * Can be passed directly to the ref prop of any element
179
+ *
180
+ * @example
181
+ * ```tsx
182
+ * const { ref, inView } = useIntersectionObserver();
183
+ * return <div ref={ref}>{inView ? 'Visible!' : 'Not visible'}</div>;
184
+ * ```
185
+ */
186
+ ref: (node: Element | null) => void;
187
+ }
188
+
189
+ /**
190
+ * A React hook for observing element visibility using the Intersection Observer API.
191
+ *
192
+ * Features:
193
+ * - Efficient viewport/container visibility detection
194
+ * - Threshold-based intersection callbacks
195
+ * - TriggerOnce support for lazy loading patterns
196
+ * - Dynamic enable/disable support
197
+ * - SSR compatible with graceful degradation
198
+ * - TypeScript support with full type inference
199
+ *
200
+ * @param options - Configuration options for the observer
201
+ * @returns Object containing entry data, inView boolean, and ref callback
202
+ *
203
+ * @example
204
+ * ```tsx
205
+ * // Basic usage - check if element is visible
206
+ * function Component() {
207
+ * const { ref, inView } = useIntersectionObserver();
208
+ * return (
209
+ * <div ref={ref}>
210
+ * {inView ? 'Visible!' : 'Not visible'}
211
+ * </div>
212
+ * );
213
+ * }
214
+ * ```
215
+ *
216
+ * @example
217
+ * ```tsx
218
+ * // Lazy load image when it enters viewport
219
+ * function LazyImage({ src, alt }: { src: string; alt: string }) {
220
+ * const { ref, inView } = useIntersectionObserver({
221
+ * triggerOnce: true,
222
+ * threshold: 0.1,
223
+ * });
224
+ *
225
+ * return (
226
+ * <div ref={ref}>
227
+ * {inView ? (
228
+ * <img src={src} alt={alt} />
229
+ * ) : (
230
+ * <div className="placeholder" />
231
+ * )}
232
+ * </div>
233
+ * );
234
+ * }
235
+ * ```
236
+ *
237
+ * @example
238
+ * ```tsx
239
+ * // Infinite scroll with sentinel element
240
+ * function InfiniteList() {
241
+ * const { ref, inView } = useIntersectionObserver({
242
+ * threshold: 1.0,
243
+ * rootMargin: '100px',
244
+ * });
245
+ *
246
+ * useEffect(() => {
247
+ * if (inView) {
248
+ * loadMoreItems();
249
+ * }
250
+ * }, [inView]);
251
+ *
252
+ * return (
253
+ * <div>
254
+ * {items.map(item => <Item key={item.id} {...item} />)}
255
+ * <div ref={ref} />
256
+ * </div>
257
+ * );
258
+ * }
259
+ * ```
260
+ *
261
+ * @example
262
+ * ```tsx
263
+ * // Track scroll progress with multiple thresholds
264
+ * function ProgressTracker() {
265
+ * const { ref, entry } = useIntersectionObserver({
266
+ * threshold: [0, 0.25, 0.5, 0.75, 1.0],
267
+ * onChange: (entry, inView) => {
268
+ * console.log('Progress:', Math.round(entry.intersectionRatio * 100), '%');
269
+ * },
270
+ * });
271
+ *
272
+ * return <div ref={ref}>Long content...</div>;
273
+ * }
274
+ * ```
275
+ *
276
+ * @example
277
+ * ```tsx
278
+ * // Custom scroll container as root
279
+ * function ScrollContainer() {
280
+ * const containerRef = useRef<HTMLDivElement>(null);
281
+ * const { ref, inView } = useIntersectionObserver({
282
+ * root: containerRef.current,
283
+ * rootMargin: '0px',
284
+ * });
285
+ *
286
+ * return (
287
+ * <div ref={containerRef} style={{ overflow: 'auto', height: 400 }}>
288
+ * <div style={{ height: 1000 }}>
289
+ * <div ref={ref}>{inView ? 'In container view' : 'Outside'}</div>
290
+ * </div>
291
+ * </div>
292
+ * );
293
+ * }
294
+ * ```
295
+ */
296
+ declare function useIntersectionObserver(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn;
297
+
298
+ /**
299
+ * Check if IntersectionObserver API is supported in the current environment
300
+ * Returns false in SSR environments or browsers without support
301
+ */
302
+ declare function isIntersectionObserverSupported(): boolean;
303
+ /**
304
+ * Convert a native IntersectionObserverEntry to our IntersectionEntry type
305
+ * Provides a consistent interface with additional convenience properties
306
+ *
307
+ * @param nativeEntry - The native IntersectionObserverEntry from the browser
308
+ * @returns IntersectionEntry with all properties
309
+ */
310
+ declare function toIntersectionEntry(nativeEntry: IntersectionObserverEntry): IntersectionEntry;
311
+ /**
312
+ * Create an initial IntersectionEntry for SSR or before first observation
313
+ * Used when initialIsIntersecting is true
314
+ *
315
+ * @param isIntersecting - Whether to set initial state as intersecting
316
+ * @param target - Optional target element (null for SSR)
317
+ * @returns A mock IntersectionEntry
318
+ */
319
+ declare function createInitialEntry(isIntersecting: boolean, target?: Element | null): IntersectionEntry | null;
320
+
321
+ export { type IntersectionEntry, type OnChangeCallback, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, createInitialEntry, isIntersectionObserverSupported, toIntersectionEntry, useIntersectionObserver };
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Extended intersection entry with convenience properties
3
+ * Wraps the native IntersectionObserverEntry with additional computed values
4
+ */
5
+ interface IntersectionEntry {
6
+ /**
7
+ * The original native IntersectionObserverEntry
8
+ */
9
+ readonly entry: IntersectionObserverEntry;
10
+ /**
11
+ * Whether the target element is currently intersecting with the root
12
+ */
13
+ readonly isIntersecting: boolean;
14
+ /**
15
+ * The ratio of the target element that is visible (0.0 to 1.0)
16
+ */
17
+ readonly intersectionRatio: number;
18
+ /**
19
+ * The target element being observed
20
+ */
21
+ readonly target: Element;
22
+ /**
23
+ * The bounding rectangle of the target element
24
+ */
25
+ readonly boundingClientRect: DOMRectReadOnly;
26
+ /**
27
+ * The visible portion of the target element (intersection area)
28
+ */
29
+ readonly intersectionRect: DOMRectReadOnly;
30
+ /**
31
+ * The bounding rectangle of the root element (viewport if null)
32
+ */
33
+ readonly rootBounds: DOMRectReadOnly | null;
34
+ /**
35
+ * Timestamp when the intersection was recorded
36
+ */
37
+ readonly time: number;
38
+ }
39
+ /**
40
+ * Callback function called when intersection state changes
41
+ * @param entry - The intersection entry data
42
+ * @param inView - Whether the element is currently in view
43
+ */
44
+ type OnChangeCallback = (entry: IntersectionEntry, inView: boolean) => void;
45
+ /**
46
+ * Options for configuring the useIntersectionObserver hook
47
+ */
48
+ interface UseIntersectionObserverOptions {
49
+ /**
50
+ * Threshold(s) at which the callback is triggered
51
+ * A single number or array of numbers between 0.0 and 1.0
52
+ * - 0: Triggers as soon as any pixel is visible
53
+ * - 0.5: Triggers when 50% of the element is visible
54
+ * - 1.0: Triggers when the entire element is visible
55
+ * @default 0
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * // Single threshold
60
+ * useIntersectionObserver({ threshold: 0.5 })
61
+ *
62
+ * // Multiple thresholds for granular tracking
63
+ * useIntersectionObserver({ threshold: [0, 0.25, 0.5, 0.75, 1] })
64
+ * ```
65
+ */
66
+ threshold?: number | number[];
67
+ /**
68
+ * The root element used as the viewport for checking visibility
69
+ * If null (default), uses the browser viewport
70
+ * @default null
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * // Use a custom scrollable container
75
+ * const scrollContainerRef = useRef<HTMLDivElement>(null);
76
+ * useIntersectionObserver({ root: scrollContainerRef.current })
77
+ * ```
78
+ */
79
+ root?: Element | Document | null;
80
+ /**
81
+ * Margin around the root element (CSS margin syntax)
82
+ * Can be used to grow or shrink the root's bounding box before computing intersections
83
+ * Values can be pixels (px) or percentages (%)
84
+ * @default "0px"
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * // Trigger 100px before element enters viewport
89
+ * useIntersectionObserver({ rootMargin: "100px 0px" })
90
+ *
91
+ * // Trigger when element is 20% away from viewport
92
+ * useIntersectionObserver({ rootMargin: "20%" })
93
+ * ```
94
+ */
95
+ rootMargin?: string;
96
+ /**
97
+ * Stop observing after the element first becomes visible
98
+ * Useful for lazy loading images or one-time animations
99
+ * @default false
100
+ *
101
+ * @example
102
+ * ```tsx
103
+ * // Load image only once when it enters viewport
104
+ * const { ref, inView } = useIntersectionObserver({ triggerOnce: true });
105
+ * return <img ref={ref} src={inView ? imageSrc : placeholder} />;
106
+ * ```
107
+ */
108
+ triggerOnce?: boolean;
109
+ /**
110
+ * Enable or disable the observer dynamically
111
+ * When false, the observer is disconnected and no callbacks are fired
112
+ * @default true
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * // Disable observation while loading
117
+ * const { ref } = useIntersectionObserver({ enabled: !isLoading });
118
+ * ```
119
+ */
120
+ enabled?: boolean;
121
+ /**
122
+ * Initial intersection state before the first observation
123
+ * Useful for SSR/SSG scenarios where you want to assume a state
124
+ * @default false
125
+ *
126
+ * @example
127
+ * ```tsx
128
+ * // Assume above-the-fold content is visible initially
129
+ * useIntersectionObserver({ initialIsIntersecting: true })
130
+ * ```
131
+ */
132
+ initialIsIntersecting?: boolean;
133
+ /**
134
+ * Callback fired when intersection state changes
135
+ * Called with the intersection entry and a boolean indicating if in view
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * useIntersectionObserver({
140
+ * onChange: (entry, inView) => {
141
+ * if (inView) {
142
+ * analytics.track('element_viewed', { ratio: entry.intersectionRatio });
143
+ * }
144
+ * }
145
+ * })
146
+ * ```
147
+ */
148
+ onChange?: OnChangeCallback;
149
+ /**
150
+ * Delay in milliseconds before starting to observe
151
+ * Useful for preventing flash of content on fast scrolling
152
+ * @default 0
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * // Wait 100ms before starting observation
157
+ * useIntersectionObserver({ delay: 100 })
158
+ * ```
159
+ */
160
+ delay?: number;
161
+ }
162
+ /**
163
+ * Return type for the useIntersectionObserver hook
164
+ */
165
+ interface UseIntersectionObserverReturn {
166
+ /**
167
+ * The current intersection entry data
168
+ * null if element hasn't been observed yet or if SSR
169
+ */
170
+ entry: IntersectionEntry | null;
171
+ /**
172
+ * Boolean indicating whether the element is currently in view
173
+ * Shorthand for entry?.isIntersecting ?? initialIsIntersecting
174
+ */
175
+ inView: boolean;
176
+ /**
177
+ * Callback ref to attach to the target element
178
+ * Can be passed directly to the ref prop of any element
179
+ *
180
+ * @example
181
+ * ```tsx
182
+ * const { ref, inView } = useIntersectionObserver();
183
+ * return <div ref={ref}>{inView ? 'Visible!' : 'Not visible'}</div>;
184
+ * ```
185
+ */
186
+ ref: (node: Element | null) => void;
187
+ }
188
+
189
+ /**
190
+ * A React hook for observing element visibility using the Intersection Observer API.
191
+ *
192
+ * Features:
193
+ * - Efficient viewport/container visibility detection
194
+ * - Threshold-based intersection callbacks
195
+ * - TriggerOnce support for lazy loading patterns
196
+ * - Dynamic enable/disable support
197
+ * - SSR compatible with graceful degradation
198
+ * - TypeScript support with full type inference
199
+ *
200
+ * @param options - Configuration options for the observer
201
+ * @returns Object containing entry data, inView boolean, and ref callback
202
+ *
203
+ * @example
204
+ * ```tsx
205
+ * // Basic usage - check if element is visible
206
+ * function Component() {
207
+ * const { ref, inView } = useIntersectionObserver();
208
+ * return (
209
+ * <div ref={ref}>
210
+ * {inView ? 'Visible!' : 'Not visible'}
211
+ * </div>
212
+ * );
213
+ * }
214
+ * ```
215
+ *
216
+ * @example
217
+ * ```tsx
218
+ * // Lazy load image when it enters viewport
219
+ * function LazyImage({ src, alt }: { src: string; alt: string }) {
220
+ * const { ref, inView } = useIntersectionObserver({
221
+ * triggerOnce: true,
222
+ * threshold: 0.1,
223
+ * });
224
+ *
225
+ * return (
226
+ * <div ref={ref}>
227
+ * {inView ? (
228
+ * <img src={src} alt={alt} />
229
+ * ) : (
230
+ * <div className="placeholder" />
231
+ * )}
232
+ * </div>
233
+ * );
234
+ * }
235
+ * ```
236
+ *
237
+ * @example
238
+ * ```tsx
239
+ * // Infinite scroll with sentinel element
240
+ * function InfiniteList() {
241
+ * const { ref, inView } = useIntersectionObserver({
242
+ * threshold: 1.0,
243
+ * rootMargin: '100px',
244
+ * });
245
+ *
246
+ * useEffect(() => {
247
+ * if (inView) {
248
+ * loadMoreItems();
249
+ * }
250
+ * }, [inView]);
251
+ *
252
+ * return (
253
+ * <div>
254
+ * {items.map(item => <Item key={item.id} {...item} />)}
255
+ * <div ref={ref} />
256
+ * </div>
257
+ * );
258
+ * }
259
+ * ```
260
+ *
261
+ * @example
262
+ * ```tsx
263
+ * // Track scroll progress with multiple thresholds
264
+ * function ProgressTracker() {
265
+ * const { ref, entry } = useIntersectionObserver({
266
+ * threshold: [0, 0.25, 0.5, 0.75, 1.0],
267
+ * onChange: (entry, inView) => {
268
+ * console.log('Progress:', Math.round(entry.intersectionRatio * 100), '%');
269
+ * },
270
+ * });
271
+ *
272
+ * return <div ref={ref}>Long content...</div>;
273
+ * }
274
+ * ```
275
+ *
276
+ * @example
277
+ * ```tsx
278
+ * // Custom scroll container as root
279
+ * function ScrollContainer() {
280
+ * const containerRef = useRef<HTMLDivElement>(null);
281
+ * const { ref, inView } = useIntersectionObserver({
282
+ * root: containerRef.current,
283
+ * rootMargin: '0px',
284
+ * });
285
+ *
286
+ * return (
287
+ * <div ref={containerRef} style={{ overflow: 'auto', height: 400 }}>
288
+ * <div style={{ height: 1000 }}>
289
+ * <div ref={ref}>{inView ? 'In container view' : 'Outside'}</div>
290
+ * </div>
291
+ * </div>
292
+ * );
293
+ * }
294
+ * ```
295
+ */
296
+ declare function useIntersectionObserver(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn;
297
+
298
+ /**
299
+ * Check if IntersectionObserver API is supported in the current environment
300
+ * Returns false in SSR environments or browsers without support
301
+ */
302
+ declare function isIntersectionObserverSupported(): boolean;
303
+ /**
304
+ * Convert a native IntersectionObserverEntry to our IntersectionEntry type
305
+ * Provides a consistent interface with additional convenience properties
306
+ *
307
+ * @param nativeEntry - The native IntersectionObserverEntry from the browser
308
+ * @returns IntersectionEntry with all properties
309
+ */
310
+ declare function toIntersectionEntry(nativeEntry: IntersectionObserverEntry): IntersectionEntry;
311
+ /**
312
+ * Create an initial IntersectionEntry for SSR or before first observation
313
+ * Used when initialIsIntersecting is true
314
+ *
315
+ * @param isIntersecting - Whether to set initial state as intersecting
316
+ * @param target - Optional target element (null for SSR)
317
+ * @returns A mock IntersectionEntry
318
+ */
319
+ declare function createInitialEntry(isIntersecting: boolean, target?: Element | null): IntersectionEntry | null;
320
+
321
+ export { type IntersectionEntry, type OnChangeCallback, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, createInitialEntry, isIntersectionObserverSupported, toIntersectionEntry, useIntersectionObserver };