{"version":3,"file":"sticky.mjs","names":[],"sources":["../../../../package/common/sticky/sticky.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport cx from 'classnames';\nimport type { StickyProps } from './sticky.types';\nimport { stickyStack } from './stickyStack';\nimport type { StickyStackResult } from './stickyStack';\nimport { getScrollParent } from '../utils/scrollAncestors';\n\nimport styles from './sticky.module.pcss';\n\ninterface Metrics {\n  width: number;\n  left: number;\n  height: number;\n}\n\nexport default function Sticky({\n  children,\n  offsetTop = 0,\n  offsetElement,\n  stickToScrollParent = false,\n  boundary,\n  boundaryInset = 0,\n  zIndex = 20,\n  disabled = false,\n  onStuckChange,\n  className,\n  style,\n  classNames,\n  styles: slotStyles,\n  ...rest\n}: StickyProps) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const contentRef = useRef<HTMLDivElement>(null);\n  const stackIdRef = useRef<symbol | null>(null);\n  const scrollParentRef = useRef<HTMLElement | null>(null);\n  const [metrics, setMetrics] = useState<Metrics>({ width: 0, left: 0, height: 0 });\n  const [measuredOffset, setMeasuredOffset] = useState<number | null>(null);\n  const [scrollParentTop, setScrollParentTop] = useState(0);\n  const effectiveOffsetTop = offsetElement?.current\n    ? (measuredOffset ?? offsetElement.current.offsetHeight)\n    : offsetTop;\n  const [stackResult, setStackResult] = useState<StickyStackResult>({ isStuck: false, top: effectiveOffsetTop });\n\n  const onStuckChangeRef = useRef(onStuckChange);\n  useEffect(() => {\n    onStuckChangeRef.current = onStuckChange;\n  }, [onStuckChange]);\n\n  // The stack entry is registered once, so the boundary reaches it through a ref\n  // rather than the closure: the one handed in may still be empty on first render.\n  const boundaryRef = useRef(boundary);\n  const boundaryInsetRef = useRef(boundaryInset);\n  useLayoutEffect(() => {\n    boundaryRef.current = boundary;\n    boundaryInsetRef.current = boundaryInset;\n  });\n\n  // Local geometry (width/left/height) doesn't depend on other Stickies — only the\n  // effective `top` and `isStuck` (from the stack coordinator) do.\n  const measure = useCallback(() => {\n    const root = rootRef.current;\n    const content = contentRef.current;\n    if (!root || !content) return;\n    const rootRect = root.getBoundingClientRect();\n    setMetrics({ width: root.clientWidth, left: rootRect.left, height: content.offsetHeight });\n    stickyStack.schedule();\n  }, []);\n\n  useEffect(() => {\n    const el = offsetElement?.current;\n    if (!el) return;\n    const updateFromElement = () => setMeasuredOffset(el.offsetHeight);\n    updateFromElement();\n    const ro = new ResizeObserver(updateFromElement);\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [offsetElement]);\n\n  useLayoutEffect(() => {\n    scrollParentRef.current = stickToScrollParent ? getScrollParent(rootRef.current) : null;\n  }, [stickToScrollParent]);\n\n  useLayoutEffect(() => {\n    const id = stickyStack.register(\n      {\n        node: rootRef.current as HTMLElement,\n        offsetTop: effectiveOffsetTop,\n        disabled,\n        getMetrics: () => {\n          const root = rootRef.current;\n          const content = contentRef.current;\n          if (!root || !content) return null;\n          const rect = root.getBoundingClientRect();\n          const parentRect = scrollParentRef.current?.getBoundingClientRect();\n          const newParentTop = parentRect?.top ?? 0;\n          setScrollParentTop((prev) => (prev === newParentTop ? prev : newParentTop));\n          // Report top/left relative to the scroll parent's box so the stack's stuck/stacking\n          // math stays in the same coordinate space regardless of what it's pinned relative to.\n          const top = parentRect ? rect.top - parentRect.top : rect.top;\n          const left = parentRect ? rect.left - parentRect.left : rect.left;\n          const boundaryEl = boundaryRef.current?.current ?? null;\n          const boundaryRect = boundaryEl?.getBoundingClientRect();\n          return {\n            top,\n            left,\n            width: root.clientWidth,\n            contentHeight: content.offsetHeight,\n            container: scrollParentRef.current,\n            boundary: boundaryEl,\n            boundaryBottom: boundaryRect ? boundaryRect.bottom - newParentTop - boundaryInsetRef.current : null,\n          };\n        },\n      },\n      setStackResult,\n    );\n    stackIdRef.current = id;\n    return () => {\n      stickyStack.unregister(id);\n      stackIdRef.current = null;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    if (stackIdRef.current) {\n      stickyStack.updateEntry(stackIdRef.current, { offsetTop: effectiveOffsetTop, disabled });\n    }\n    stickyStack.schedule();\n  }, [effectiveOffsetTop, disabled]);\n\n  useLayoutEffect(() => {\n    measure();\n  }, [measure, children]);\n\n  useEffect(() => {\n    const ro = new ResizeObserver(measure);\n    if (rootRef.current) ro.observe(rootRef.current);\n    if (contentRef.current) ro.observe(contentRef.current);\n    if (scrollParentRef.current) ro.observe(scrollParentRef.current);\n    return () => ro.disconnect();\n  }, [measure, stickToScrollParent]);\n\n  const firstRun = useRef(true);\n  useEffect(() => {\n    if (firstRun.current) {\n      firstRun.current = false;\n      return;\n    }\n    onStuckChangeRef.current?.(stackResult.isStuck);\n  }, [stackResult.isStuck]);\n\n  // stackResult.top is relative to the scroll parent's box (or the viewport if there isn't\n  // one) — convert back to a viewport-absolute value for the fixed `top`.\n  const contentStyle: React.CSSProperties | undefined = stackResult.isStuck\n    ? {\n        position: 'fixed',\n        top: scrollParentTop + stackResult.top,\n        left: metrics.left,\n        width: metrics.width,\n        zIndex,\n        ...slotStyles?.content,\n      }\n    : slotStyles?.content;\n\n  return (\n    <div\n      ref={rootRef}\n      className={cx(styles.sticky, className, classNames?.root)}\n      style={{ ...slotStyles?.root, ...style }}\n      {...rest}\n    >\n      {stackResult.isStuck && (\n        <div\n          aria-hidden\n          className={cx(styles.stickyPlaceholder, classNames?.placeholder)}\n          style={{ height: metrics.height, ...slotStyles?.placeholder }}\n        />\n      )}\n      <div\n        ref={contentRef}\n        className={cx(styles.stickyContent, stackResult.isStuck && styles.stickyContentStuck, classNames?.content)}\n        style={contentStyle}\n      >\n        {children}\n      </div>\n    </div>\n  );\n}\n"],"mappings":"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"}