All files / src/motion/features/viewport observers.ts

21.74% Statements 5/23
0% Branches 0/10
0% Functions 0/5
21.74% Lines 5/23

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                      33x             33x         33x       33x                                                                   33x                              
type IntersectionHandler = (entry: IntersectionObserverEntry) => void
 
interface ElementIntersectionObservers {
    [key: string]: IntersectionObserver
}
 
/**
 * Map an IntersectionHandler callback to an element. We only ever make one handler for one
 * element, so even though these handlers might all be triggered by different
 * observers, we can keep them in the same map.
 */
const observerCallbacks = new WeakMap<Element, IntersectionHandler>()
 
/**
 * Multiple observers can be created for multiple element/document roots. Each with
 * different settings. So here we store dictionaries of observers to each root,
 * using serialised settings (threshold/margin) as lookup keys.
 */
const observers = new WeakMap<
    Element | Document,
    ElementIntersectionObservers
>()
 
const fireObserverCallback = (entry: IntersectionObserverEntry) => {
    observerCallbacks.get(entry.target)?.(entry)
}
 
const fireAllObserverCallbacks: IntersectionObserverCallback = (entries) => {
    entries.forEach(fireObserverCallback)
}
 
function initIntersectionObserver({
    root,
    ...options
}: IntersectionObserverInit): IntersectionObserver {
    const lookupRoot = root || document
 
    /**
     * If we don't have an observer lookup map for this root, create one.
     */
    if (!observers.has(lookupRoot)) {
        observers.set(lookupRoot, {})
    }
    const rootObservers = observers.get(lookupRoot)!
 
    const key = JSON.stringify(options)
 
    /**
     * If we don't have an observer for this combination of root and settings,
     * create one.
     */
    if (!rootObservers[key]) {
        rootObservers[key] = new IntersectionObserver(
            fireAllObserverCallbacks,
            { root, ...options }
        )
    }
 
    return rootObservers[key]
}
 
export function observeIntersection(
    element: Element,
    options: IntersectionObserverInit,
    callback: IntersectionHandler
) {
    const rootInteresectionObserver = initIntersectionObserver(options)
 
    observerCallbacks.set(element, callback)
    rootInteresectionObserver.observe(element)
 
    return () => {
        observerCallbacks.delete(element)
        rootInteresectionObserver.unobserve(element)
    }
}