All files / hooks/use-scroll-position/src utils.ts

100% Statements 17/17
100% Branches 17/17
100% Functions 6/6
100% Lines 13/13

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                1x             63x                   9x                 37x                               24x 22x 9x 5x                         35x 34x 25x   9x                 35x    
import type { RefObject } from "react";
import type { ScrollPosition, ScrollPositionTarget } from "./types";
 
/**
 * The inert, frozen zero scroll offset used as the initial value and the
 * SSR / server-render fallback. Frozen so it can be shared as a stable
 * reference without risk of accidental mutation.
 */
export const ZERO_SCROLL_POSITION: ScrollPosition = Object.freeze({ x: 0, y: 0 });
 
/**
 * Whether the code is running in a browser environment (i.e. `window` exists).
 * Returns `false` during SSR.
 */
export function isBrowser(): boolean {
  return typeof window !== "undefined";
}
 
/**
 * Narrow an arbitrary value to a React `RefObject` (an object exposing a
 * `current` property).
 */
function isRefObject(
  value: HTMLElement | RefObject<HTMLElement | null>
): value is RefObject<HTMLElement | null> {
  return typeof value === "object" && value !== null && "current" in value;
}
 
/**
 * Whether the given value is the global `window` object.
 */
export function isWindow(
  value: Window | HTMLElement | null
): value is Window {
  return isBrowser() && value === window;
}
 
/**
 * Resolve the user-facing `element` option into the actual scroll target to
 * attach a listener to and read offsets from.
 *
 * - `null` / `undefined` → the global `window`.
 * - A `RefObject` → its `current` element (may be `null` if not attached yet).
 * - A raw `HTMLElement` → itself.
 *
 * Returns `null` on the server (no `window`), so callers can no-op safely.
 */
export function resolveScrollTarget(
  element: ScrollPositionTarget
): Window | HTMLElement | null {
  if (!isBrowser()) return null;
  if (element == null) return window;
  if (isRefObject(element)) return element.current;
  return element;
}
 
/**
 * Read the current {@link ScrollPosition} from a resolved scroll target.
 *
 * For the window it reads `scrollX`/`scrollY`; for an element it reads
 * `scrollLeft`/`scrollTop`. Returns {@link ZERO_SCROLL_POSITION} when the target
 * is `null` (unattached ref / SSR).
 */
export function getScrollPosition(
  target: Window | HTMLElement | null
): ScrollPosition {
  if (target === null) return ZERO_SCROLL_POSITION;
  if (isWindow(target)) {
    return { x: target.scrollX, y: target.scrollY };
  }
  return { x: target.scrollLeft, y: target.scrollTop };
}
 
/**
 * Structural equality check for two {@link ScrollPosition} values. Used to bail
 * out of a state update when a scroll event produced an identical offset, so
 * React can skip the re-render.
 */
export function arePositionsEqual(a: ScrollPosition, b: ScrollPosition): boolean {
  return a.x === b.x && a.y === b.y;
}