Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | 254x 35x 10x 2x 8x 8x 10x 30x 5x 25x 11x 14x 13x 2x 11x 1x 10x 10x 10x 1x 9x 12x 2x 7x 7x 20x 20x | import type { IntersectionEntry } from "./types";
/**
* Check if IntersectionObserver API is supported in the current environment
* Returns false in SSR environments or browsers without support
*/
export function isIntersectionObserverSupported(): boolean {
return (
typeof window !== "undefined" &&
"IntersectionObserver" in window
);
}
/**
* Convert a native IntersectionObserverEntry to our IntersectionEntry type
* Provides a consistent interface with additional convenience properties
*
* @param nativeEntry - The native IntersectionObserverEntry from the browser
* @returns IntersectionEntry with all properties
*/
export function toIntersectionEntry(
nativeEntry: IntersectionObserverEntry
): IntersectionEntry {
return {
entry: nativeEntry,
isIntersecting: nativeEntry.isIntersecting,
intersectionRatio: nativeEntry.intersectionRatio,
target: nativeEntry.target,
boundingClientRect: nativeEntry.boundingClientRect,
intersectionRect: nativeEntry.intersectionRect,
rootBounds: nativeEntry.rootBounds,
time: nativeEntry.time,
};
}
/**
* Create an initial IntersectionEntry for SSR or before first observation
* Used when initialIsIntersecting is true
*
* @param isIntersecting - Whether to set initial state as intersecting
* @param target - Optional target element (null for SSR)
* @returns A mock IntersectionEntry
*/
export function createInitialEntry(
isIntersecting: boolean,
target: Element | null = null
): IntersectionEntry | null {
if (!isIntersecting) {
return null;
}
// Create a placeholder DOMRect for SSR
const emptyRect: DOMRectReadOnly = {
x: 0,
y: 0,
width: 0,
height: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
toJSON: () => ({}),
};
// Create a mock native entry
const mockNativeEntry = {
target: target as Element,
isIntersecting,
intersectionRatio: isIntersecting ? 1 : 0,
boundingClientRect: emptyRect,
intersectionRect: emptyRect,
rootBounds: null,
time: typeof performance !== "undefined" ? performance.now() : Date.now(),
} as IntersectionObserverEntry;
return {
entry: mockNativeEntry,
isIntersecting,
intersectionRatio: isIntersecting ? 1 : 0,
target: target as Element,
boundingClientRect: emptyRect,
intersectionRect: emptyRect,
rootBounds: null,
time: mockNativeEntry.time,
};
}
/**
* Normalize threshold to always be an array
* Handles both single number and array inputs
*
* @param threshold - Single threshold or array of thresholds
* @returns Array of threshold values
*/
export function normalizeThreshold(
threshold: number | number[] | undefined
): number[] {
if (threshold === undefined) {
return [0];
}
if (Array.isArray(threshold)) {
return threshold;
}
return [threshold];
}
/**
* Deep compare two IntersectionObserverInit options objects
* Used to determine if observer needs to be recreated
*
* @param a - First options object
* @param b - Second options object
* @returns true if options are equal
*/
export function areOptionsEqual(
a: IntersectionObserverInit,
b: IntersectionObserverInit
): boolean {
// Compare root
if (a.root !== b.root) {
return false;
}
// Compare rootMargin
if (a.rootMargin !== b.rootMargin) {
return false;
}
// Compare threshold (normalize to arrays for comparison)
const thresholdA = normalizeThreshold(a.threshold);
const thresholdB = normalizeThreshold(b.threshold);
if (thresholdA.length !== thresholdB.length) {
return false;
}
for (let i = 0; i < thresholdA.length; i++) {
if (thresholdA[i] !== thresholdB[i]) {
return false;
}
}
return true;
}
/**
* Create a no-op ref callback for SSR environments
* Returns a function that does nothing when called
*/
export function createNoopRef(): (node: Element | null) => void {
return () => {
// No-op for SSR
};
}
/**
* Validate rootMargin string format
* rootMargin follows CSS margin syntax: "10px", "10px 20px", "10px 20px 30px 40px"
*
* @param rootMargin - The rootMargin string to validate
* @returns true if valid format
*/
export function isValidRootMargin(rootMargin: string): boolean {
// Basic validation - rootMargin should contain px or %
// Browser will handle more detailed validation
const pattern = /^(-?\d+(\.\d+)?(px|%)?\s*){1,4}$/;
return pattern.test(rootMargin.trim());
}
|