{"version":3,"file":"Anchor.mjs","names":["useControlledState"],"sources":["../../../src/base-ui/Anchor/Anchor.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport {\n  type CSSProperties,\n  memo,\n  type MouseEvent,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { useMergeRefs } from 'react-merge-refs';\nimport useControlledState from 'use-merge-value';\n\nimport { useEventCallback } from '@/hooks/useEventCallback';\n\nimport { styles } from './style';\nimport type { AnchorItem, AnchorProps } from './type';\n\ntype ScrollContainer = HTMLElement | Window;\n\nconst SCROLL_LOCK_FALLBACK_MS = 1000;\n\nconst flatten = (items: AnchorItem[]): AnchorItem[] =>\n  items.flatMap((item) => [item, ...flatten(item.children ?? [])]);\n\nconst getTarget = (href: string) => {\n  const hash = href.split('#')[1];\n  if (!hash) return null;\n  try {\n    return document.getElementById(decodeURIComponent(hash));\n  } catch {\n    return document.getElementById(hash);\n  }\n};\n\nconst isWindow = (container: ScrollContainer): container is Window => container === window;\n\nconst getRelativeTop = (element: HTMLElement, container: ScrollContainer) =>\n  element.getBoundingClientRect().top -\n  (isWindow(container) ? 0 : container.getBoundingClientRect().top);\n\nconst getScrollTop = (container: ScrollContainer) =>\n  isWindow(container) ? window.scrollY : container.scrollTop;\n\nconst getActiveKey = (\n  items: AnchorItem[],\n  container: ScrollContainer,\n  offset: number,\n): string | null => {\n  let activeKey: string | null = null;\n  let activeTop = -Infinity;\n  for (const item of flatten(items)) {\n    const target = getTarget(item.href);\n    if (!target) continue;\n    const top = getRelativeTop(target, container);\n    if (top <= offset + 1 && top >= activeTop) {\n      activeKey = item.key;\n      activeTop = top;\n    }\n  }\n  return activeKey;\n};\n\nconst Anchor = memo<AnchorProps>(\n  ({ activeKey, className, getContainer, items, offset = 0, onChange, onClick, ref, ...rest }) => {\n    const [mergedActiveKey, setMergedActiveKey] = useControlledState<string | null>(null, {\n      onChange,\n      value: activeKey,\n    });\n    const scrollLockRef = useRef(false);\n    const navRef = useRef<HTMLElement>(null);\n    const mergedRef = useMergeRefs([ref, navRef]);\n    const [marker, setMarker] = useState<CSSProperties>();\n    const lockTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n    const resolveContainer = useEventCallback((): ScrollContainer => getContainer?.() ?? window);\n    const setActive = useEventCallback(setMergedActiveKey);\n    const spy = useEventCallback(() => {\n      if (scrollLockRef.current) return;\n      setActive(getActiveKey(items, resolveContainer(), offset));\n    });\n\n    useEffect(() => {\n      const container = resolveContainer();\n      let frame = 0;\n      const handleScroll = () => {\n        cancelAnimationFrame(frame);\n        frame = requestAnimationFrame(spy);\n      };\n      spy();\n      container.addEventListener('scroll', handleScroll, { passive: true });\n      return () => {\n        cancelAnimationFrame(frame);\n        container.removeEventListener('scroll', handleScroll);\n      };\n    }, [items, offset, resolveContainer, spy]);\n\n    useEffect(() => () => clearTimeout(lockTimerRef.current), []);\n\n    useLayoutEffect(() => {\n      const nav = navRef.current;\n      if (!nav) return;\n      const place = () => {\n        const link = [...nav.querySelectorAll<HTMLElement>('a[data-key]')].find(\n          (element) => element.dataset.key === mergedActiveKey,\n        );\n        setMarker(\n          link ? { height: link.offsetHeight, insetBlockStart: link.offsetTop } : undefined,\n        );\n      };\n      place();\n      if (typeof ResizeObserver === 'undefined') return;\n      const observer = new ResizeObserver(place);\n      observer.observe(nav);\n      return () => observer.disconnect();\n    }, [items, mergedActiveKey]);\n\n    const handleClick = (event: MouseEvent<HTMLAnchorElement>, item: AnchorItem) => {\n      onClick?.(event, item);\n      if (event.defaultPrevented || event.button !== 0) return;\n      if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n\n      const target = getTarget(item.href);\n      if (!target) return;\n      event.preventDefault();\n\n      const container = resolveContainer();\n      const top = getRelativeTop(target, container) + getScrollTop(container) - offset;\n      const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n      // Smooth scrolling may never bring a heading near the page end up to the offset line, so the\n      // clicked item is pinned as active until the scroll settles.\n      const release = () => {\n        scrollLockRef.current = false;\n      };\n      scrollLockRef.current = true;\n      setActive(item.key);\n      clearTimeout(lockTimerRef.current);\n      lockTimerRef.current = setTimeout(release, SCROLL_LOCK_FALLBACK_MS);\n      container.addEventListener('scrollend', release, { once: true });\n      container.scrollTo({ behavior: reduceMotion ? 'auto' : 'smooth', top });\n    };\n\n    const renderList = (list: AnchorItem[]) => (\n      <ul className={styles.list}>\n        {list.map((item) => (\n          <li key={item.key}>\n            <a\n              aria-current={item.key === mergedActiveKey ? 'location' : undefined}\n              className={styles.link}\n              data-key={item.key}\n              href={item.href}\n              title={typeof item.title === 'string' ? item.title : undefined}\n              onClick={(event) => handleClick(event, item)}\n            >\n              {item.title}\n            </a>\n            {item.children && item.children.length > 0 && renderList(item.children)}\n          </li>\n        ))}\n      </ul>\n    );\n\n    return (\n      <nav className={cx(styles.root, className)} ref={mergedRef} {...rest}>\n        <span aria-hidden className={styles.marker} hidden={!marker} style={marker} />\n        {renderList(items)}\n      </nav>\n    );\n  },\n);\n\nAnchor.displayName = 'Anchor';\n\nexport default Anchor;\n"],"mappings":"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"}