{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table-of-contents",
  "title": "Table of Contents",
  "description": "Lists the sections of a page, from data or read from the headings, follows the one being read and jumps to any of them with motion that respects reduced motion.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json",
    "https://miseui.junsu4621.workers.dev/r/popover.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/table-of-contents.tsx",
      "content": "import type * as React from \"react\"\r\nimport { useMemo, useRef, useState } from \"react\"\r\nimport { List } from \"lucide-react\"\r\nimport { useReducedMotion } from \"motion/react\"\r\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/registry/miseui/ui/popover\"\r\nimport { TocList, type TableOfContentsVariant } from \"@/registry/miseui/ui/table-of-contents-list\"\r\nimport type { TocItem } from \"@/registry/miseui/ui/table-of-contents-math\"\r\nimport { useActiveHeading, useHeadings } from \"@/registry/miseui/ui/table-of-contents-state\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\ntype TableOfContentsEffect = \"spring\"\r\n\r\nconst ROOT_CLASS: Record<TableOfContentsVariant, string> = {\r\n  line: \"min-w-0\",\r\n  plain: \"min-w-0\",\r\n  dots: \"min-w-0\",\r\n  floating: \"z-40 min-w-0 max-sm:fixed max-sm:right-4 max-sm:bottom-[calc(1rem+env(safe-area-inset-bottom))] max-sm:max-w-[calc(100%-2rem)] sm:sticky sm:top-4\",\r\n}\r\n\r\ninterface CollapsedProps {\r\n  label: string\r\n  items: readonly TocItem[]\r\n  active: string | null\r\n  spring: boolean\r\n  onSelect: (id: string) => void\r\n}\r\n\r\n/** On a phone the list would cover the article, so it folds into a button that still says where you are. */\r\nfunction CollapsedToc({ label, items, active, spring, onSelect }: CollapsedProps) {\r\n  const [open, setOpen] = useState(false)\r\n  const jumpedRef = useRef(false)\r\n  const current = items.find((item) => item.id === active)?.title\r\n  return (\r\n    <Popover open={open} onOpenChange={setOpen}>\r\n      <PopoverTrigger\r\n        data-slot=\"toc-trigger\"\r\n        aria-label={current ? `${label}: ${current}` : label}\r\n        className=\"flex h-10 max-w-full items-center gap-2 rounded-full border bg-popover px-4 text-sm font-medium text-popover-foreground shadow-lg outline-none transition-[scale] duration-(--duration-fast) ease-(--ease-mise) focus-visible:ring-[3px] focus-visible:ring-ring/50 active:scale-[0.97] active:duration-0 motion-reduce:active:scale-100 sm:hidden\"\r\n      >\r\n        <List aria-hidden=\"true\" className=\"size-4 shrink-0\" />\r\n        <span className=\"min-w-0 truncate\">{current ?? label}</span>\r\n      </PopoverTrigger>\r\n      <PopoverContent\r\n        side=\"top\"\r\n        align=\"end\"\r\n        className=\"max-h-[60dvh] w-64 max-w-(--radix-popover-content-available-width) overflow-y-auto p-2\"\r\n        // The jump already moved focus to the heading; handing it back to this button would undo that.\r\n        onCloseAutoFocus={(event) => {\r\n          if (jumpedRef.current) event.preventDefault()\r\n          jumpedRef.current = false\r\n        }}\r\n      >\r\n        <TocList\r\n          items={items}\r\n          variant=\"floating\"\r\n          active={active}\r\n          spring={spring}\r\n          onSelect={(id) => {\r\n            jumpedRef.current = true\r\n            onSelect(id)\r\n            setOpen(false)\r\n          }}\r\n        />\r\n      </PopoverContent>\r\n    </Popover>\r\n  )\r\n}\r\n\r\ntype TableOfContentsProps = Omit<React.ComponentProps<\"nav\">, \"title\"> & {\r\n  /** The sections, if you already have them. */\r\n  items?: readonly TocItem[]\r\n  /** Or read the headings from this element, and follow it as its content changes. Missing ids are added. */\r\n  containerRef?: React.RefObject<HTMLElement | null>\r\n  /** Which headings to read from the container. */\r\n  selector?: string\r\n  /** The element that scrolls, when it is not the page. */\r\n  scrollRef?: React.RefObject<HTMLElement | null>\r\n  /** Space kept above a heading after a jump, such as a sticky header's height, in px. */\r\n  scrollOffset?: number\r\n  /** `line` a rail with a marker, `plain` text only, `dots` small outline numbers, `floating` a card that folds into a button on phones. */\r\n  variant?: TableOfContentsVariant\r\n  /** `spring` slides one marker between the rows. */\r\n  effect?: TableOfContentsEffect\r\n  /** Name of the landmark, and the button text on phones when no section is current. */\r\n  label?: string\r\n  /** A visible heading above the list. */\r\n  title?: React.ReactNode\r\n  /** Called after a jump, for example to write the hash into the address. */\r\n  onNavigate?: (id: string) => void\r\n}\r\n\r\nfunction TableOfContents({\r\n  items,\r\n  containerRef,\r\n  selector = \"h2, h3\",\r\n  scrollRef,\r\n  scrollOffset = 16,\r\n  variant = \"line\",\r\n  effect,\r\n  label = \"On this page\",\r\n  title,\r\n  onNavigate,\r\n  className,\r\n  ...props\r\n}: TableOfContentsProps) {\r\n  const reduceMotion = useReducedMotion() ?? false\r\n  const headings = useHeadings(items, containerRef, selector)\r\n  const ids = useMemo(() => headings.map((heading) => heading.id), [headings])\r\n  const { active, goTo } = useActiveHeading(ids, { scrollRef, offset: scrollOffset, reduceMotion })\r\n  const spring = effect === \"spring\" && !reduceMotion\r\n  if (headings.length === 0) return null\r\n  const onSelect = (id: string) => {\r\n    goTo(id)\r\n    onNavigate?.(id)\r\n  }\r\n  const floating = variant === \"floating\"\r\n  const heading = title ? <p data-slot=\"toc-title\" className=\"mb-2 text-[13px] leading-5 font-medium text-foreground\">{title}</p> : null\r\n  const list = <TocList items={headings} variant={variant} active={active} spring={spring} onSelect={onSelect} />\r\n  return (\r\n    <nav aria-label={label} data-slot=\"table-of-contents\" data-variant={variant} data-effect={effect} className={cn(ROOT_CLASS[variant], className)} {...props}>\r\n      {floating ? (\r\n        <>\r\n          <div data-slot=\"toc-card\" className=\"rounded-lg border bg-popover p-2 text-popover-foreground shadow-sm max-sm:hidden\">\r\n            {heading}\r\n            {list}\r\n          </div>\r\n          <CollapsedToc label={label} items={headings} active={active} spring={spring} onSelect={onSelect} />\r\n        </>\r\n      ) : (\r\n        <>\r\n          {heading}\r\n          {list}\r\n        </>\r\n      )}\r\n    </nav>\r\n  )\r\n}\r\n\r\nexport { TableOfContents, type TableOfContentsProps, type TableOfContentsVariant, type TableOfContentsEffect, type TocItem }\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/table-of-contents-list.tsx",
      "content": "import { useEffect, useRef, useState } from \"react\"\r\nimport { motion } from \"motion/react\"\r\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\r\nimport { depths, outlineNumbers, type TocItem } from \"@/registry/miseui/ui/table-of-contents-math\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\ntype TableOfContentsVariant = \"line\" | \"plain\" | \"dots\" | \"floating\"\r\n\r\nconst ROW_CLASS: Record<TableOfContentsVariant, string> = {\r\n  line: \"py-1 pr-2\",\r\n  plain: \"rounded-md py-1 pr-2\",\r\n  dots: \"flex items-start gap-2 py-1 pr-2\",\r\n  floating: \"rounded-md py-1 pr-2\",\r\n}\r\n\r\nconst DEPTH_CLASS: Record<TableOfContentsVariant, readonly string[]> = {\r\n  line: [\"pl-3\", \"pl-6\", \"pl-9\"],\r\n  plain: [\"pl-2\", \"pl-6\", \"pl-10\"],\r\n  dots: [\"pl-0\", \"pl-4\", \"pl-8\"],\r\n  floating: [\"pl-2\", \"pl-6\", \"pl-10\"],\r\n}\r\n\r\nconst MARKER_CLASS: Record<TableOfContentsVariant, string> = {\r\n  line: \"-left-px w-0.5 bg-mise\",\r\n  plain: \"-z-10 rounded-md bg-accent\",\r\n  dots: \"-z-10 rounded-sm bg-mise\",\r\n  floating: \"-z-10 rounded-md bg-accent\",\r\n}\r\n\r\nconst STATIC_MARKER_FIT: Record<TableOfContentsVariant, string> = {\r\n  line: \"inset-y-0\",\r\n  plain: \"inset-0\",\r\n  dots: \"inset-0\",\r\n  floating: \"inset-0\",\r\n}\r\n\r\ninterface MarkerBox {\r\n  x: number\r\n  y: number\r\n  width: number\r\n  height: number\r\n}\r\n\r\n/**\r\n * Offsets, not rects: a rect taken while a popover zooms in comes out scaled, and the marker would stay wrong until\r\n * the next change. A layoutId is no use either, as the list usually sits in a sticky column and it would chase the page.\r\n */\r\nfunction measureMarker(list: HTMLElement): MarkerBox | null {\r\n  const row = list.querySelector<HTMLElement>(\"[aria-current]\")\r\n  if (!row) return null\r\n  const mark = row.querySelector<HTMLElement>(\"[data-slot='toc-mark']\")\r\n  const target = mark ?? row\r\n  return {\r\n    x: row.offsetLeft + (mark?.offsetLeft ?? 0),\r\n    y: row.offsetTop + (mark?.offsetTop ?? 0),\r\n    width: target.offsetWidth,\r\n    height: target.offsetHeight,\r\n  }\r\n}\r\n\r\nfunction useMarkerBox(listRef: React.RefObject<HTMLUListElement | null>, active: string | null, enabled: boolean) {\r\n  const [box, setBox] = useState<MarkerBox | null>(null)\r\n  useEffect(() => {\r\n    const list = listRef.current\r\n    if (!enabled || !list || !active) return\r\n    // Fires once on observe, so a change of the active row and a resize take the same path.\r\n    const observer = new ResizeObserver(() => setBox(measureMarker(list)))\r\n    observer.observe(list)\r\n    return () => observer.disconnect()\r\n  }, [listRef, active, enabled])\r\n  return box\r\n}\r\n\r\nfunction SpringMarker({ variant, box }: { variant: TableOfContentsVariant; box: MarkerBox }) {\r\n  // `line` keeps its rail position and width from the class and only travels along it.\r\n  const line = variant === \"line\"\r\n  return (\r\n    <motion.span\r\n      aria-hidden=\"true\"\r\n      data-slot=\"toc-marker\"\r\n      className={cn(\"pointer-events-none absolute top-0\", !line && \"left-0\", MARKER_CLASS[variant])}\r\n      initial={false}\r\n      animate={line ? { y: box.y, height: box.height } : { x: box.x, y: box.y, width: box.width, height: box.height }}\r\n      transition={SPRING_DEFAULT}\r\n    />\r\n  )\r\n}\r\n\r\nfunction StaticMarker({ variant }: { variant: TableOfContentsVariant }) {\r\n  return <span aria-hidden=\"true\" data-slot=\"toc-marker\" className={cn(\"pointer-events-none absolute\", MARKER_CLASS[variant], STATIC_MARKER_FIT[variant])} />\r\n}\r\n\r\ninterface TocRowProps {\r\n  item: TocItem\r\n  variant: TableOfContentsVariant\r\n  number: string\r\n  depth: number\r\n  current: boolean\r\n  /** A marker of its own in the row; with the spring one marker travels at list level. */\r\n  showMarker: boolean\r\n  onSelect: (id: string) => void\r\n}\r\n\r\nfunction TocRow({ item, variant, number, depth, current, showMarker, onSelect }: TocRowProps) {\r\n  const dots = variant === \"dots\"\r\n  return (\r\n    <li className=\"min-w-0\">\r\n      <a\r\n        href={`#${item.id}`}\r\n        aria-current={current ? \"location\" : undefined}\r\n        onClick={(event) => {\r\n          // A modified click means a new tab or window, which the browser should handle.\r\n          if (event.metaKey || event.ctrlKey || event.shiftKey) return\r\n          event.preventDefault()\r\n          onSelect(item.id)\r\n        }}\r\n        className={cn(\r\n          \"relative block rounded-sm text-[13px] leading-5 wrap-break-word outline-none\",\r\n          \"transition-colors duration-(--duration-fast) ease-(--ease-mise) hover:text-foreground\",\r\n          \"focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-inset pointer-coarse:py-2.5\",\r\n          current ? \"font-medium text-foreground\" : \"text-muted-foreground\",\r\n          ROW_CLASS[variant],\r\n          DEPTH_CLASS[variant][depth],\r\n        )}\r\n      >\r\n        {showMarker && !dots ? <StaticMarker variant={variant} /> : null}\r\n        {dots ? (\r\n          <span aria-hidden=\"true\" data-slot=\"toc-mark\" className=\"relative inline-flex h-5 min-w-6 shrink-0 items-center justify-center px-1 text-xs tabular-nums\">\r\n            {showMarker ? <StaticMarker variant={variant} /> : null}\r\n            <span className={current ? \"text-mise-foreground\" : undefined}>{number}</span>\r\n          </span>\r\n        ) : null}\r\n        <span className=\"min-w-0\">{item.title}</span>\r\n      </a>\r\n    </li>\r\n  )\r\n}\r\n\r\n/** `plain` has no marker at rest: the text getting darker is its whole signal. Only the spring gives it a travelling fill. */\r\nfunction restsWithMarker(variant: TableOfContentsVariant) {\r\n  return variant !== \"plain\"\r\n}\r\n\r\ninterface TocListProps {\r\n  items: readonly TocItem[]\r\n  variant: TableOfContentsVariant\r\n  active: string | null\r\n  spring: boolean\r\n  onSelect: (id: string) => void\r\n}\r\n\r\nfunction TocList({ items, variant, active, spring, onSelect }: TocListProps) {\r\n  const listRef = useRef<HTMLUListElement>(null)\r\n  const box = useMarkerBox(listRef, active, spring)\r\n  const numbers = outlineNumbers(items)\r\n  const indents = depths(items)\r\n  return (\r\n    <ul ref={listRef} data-slot=\"toc-list\" className={cn(\"relative isolate grid\", variant === \"line\" && \"border-l\")}>\r\n      {spring && box && active ? <SpringMarker variant={variant} box={box} /> : null}\r\n      {items.map((item, index) => {\r\n        const current = item.id === active\r\n        return (\r\n          <TocRow\r\n            key={item.id}\r\n            item={item}\r\n            variant={variant}\r\n            number={numbers[index]}\r\n            depth={indents[index]}\r\n            current={current}\r\n            showMarker={current && !spring && restsWithMarker(variant)}\r\n            onSelect={onSelect}\r\n          />\r\n        )\r\n      })}\r\n    </ul>\r\n  )\r\n}\r\n\r\nexport { TocList, measureMarker, type TableOfContentsVariant }\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/table-of-contents-math.ts",
      "content": "interface TocItem {\n  id: string\n  title: string\n  /** Heading level, such as 2 for an h2. Only the differences matter; the shallowest becomes the top. */\n  level?: number\n}\n\ntype HeadingStatus = \"above\" | \"inside\" | \"below\"\n\n/** Keeps letters of every alphabet, so a Korean or Japanese heading gets a real address. */\nfunction slugify(text: string) {\n  return text\n    .trim()\n    .toLowerCase()\n    .replace(/[^\\p{L}\\p{N}]+/gu, \"-\")\n    .replace(/^-+|-+$/g, \"\")\n}\n\nfunction freeId(base: string, used: ReadonlySet<string>, attempt = 1): string {\n  const candidate = attempt === 1 ? base : `${base}-${attempt}`\n  return used.has(candidate) ? freeId(base, used, attempt + 1) : candidate\n}\n\n/** Numbers follow the outline: 1, 1.1, 1.2, 2. A jump from h2 to h4 counts as one step deeper. */\nfunction outlineNumbers(items: readonly TocItem[]) {\n  const levels = items.map((item) => item.level ?? 0)\n  return levels.reduce<{ numbers: readonly string[]; stack: readonly { level: number; count: number }[] }>(\n    (state, level) => {\n      const kept = state.stack.filter((entry) => entry.level < level)\n      const same = state.stack.find((entry) => entry.level === level)\n      const stack = [...kept, { level, count: (same?.count ?? 0) + 1 }]\n      return { numbers: [...state.numbers, stack.map((entry) => entry.count).join(\".\")], stack }\n    },\n    { numbers: [], stack: [] },\n  ).numbers\n}\n\n/** Indent steps per item, capped at two so a deep outline cannot eat a narrow column. */\nfunction depths(items: readonly TocItem[]) {\n  const levels = items.map((item) => item.level ?? 0)\n  const top = Math.min(...levels)\n  return levels.map((level) => Math.min(2, level - top))\n}\n\nfunction headingStatus(isIntersecting: boolean, top: number, bandTop: number): HeadingStatus {\n  if (isIntersecting) return \"inside\"\n  return top < bandTop ? \"above\" : \"below\"\n}\n\n/**\n * The first heading in the band. With none in it the reader is inside a long section, which belongs to the last\n * heading already passed. That also holds when scrolling back up, which keeping the previous value would get wrong.\n */\nfunction nextActive(ids: readonly string[], statuses: ReadonlyMap<string, HeadingStatus>) {\n  const inside = ids.find((id) => statuses.get(id) === \"inside\")\n  if (inside) return inside\n  return ids.filter((id) => statuses.get(id) === \"above\").at(-1) ?? null\n}\n\n/** A short last section can never reach the band, so the very end of the scroll has to select it. */\nfunction atScrollEnd(scrollTop: number, clientHeight: number, scrollHeight: number) {\n  return scrollHeight > clientHeight && scrollTop + clientHeight >= scrollHeight - 2\n}\n\nfunction scrollTopFor(targetTop: number, rootTop: number, currentScroll: number, offset: number) {\n  return currentScroll + targetTop - rootTop - offset\n}\n\nfunction readHeadings(container: HTMLElement, selector: string): readonly TocItem[] {\n  const elements = Array.from(container.querySelectorAll<HTMLElement>(selector))\n  const taken = new Set(elements.map((element) => element.id).filter(Boolean))\n  return elements.map((element) => {\n    const title = element.textContent?.trim() ?? \"\"\n    if (!element.id) {\n      // An anchor needs an id, and headings written by hand or from Markdown often have none.\n      element.id = freeId(slugify(title) || \"section\", taken)\n      taken.add(element.id)\n    }\n    return { id: element.id, title, level: Number(element.tagName.slice(1)) || 0 }\n  })\n}\n\nfunction sameItems(a: readonly TocItem[], b: readonly TocItem[]) {\n  return a.length === b.length && a.every((item, index) => item.id === b[index].id && item.title === b[index].title && item.level === b[index].level)\n}\n\nexport { atScrollEnd, depths, headingStatus, nextActive, outlineNumbers, readHeadings, sameItems, scrollTopFor, slugify, type HeadingStatus, type TocItem }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/table-of-contents-state.ts",
      "content": "import type * as React from \"react\"\nimport { useCallback, useEffect, useRef, useState } from \"react\"\nimport {\n  atScrollEnd,\n  headingStatus,\n  nextActive,\n  readHeadings,\n  sameItems,\n  scrollTopFor,\n  type HeadingStatus,\n  type TocItem,\n} from \"@/registry/miseui/ui/table-of-contents-math\"\n\n/** How long a clicked heading holds the marker if the browser never reports the end of the scroll. */\nconst CLICK_LOCK_MS = 1200\n/** Below this share of the viewport a heading is not yet \"the section being read\". */\nconst READING_BAND_BOTTOM = \"-60%\"\n\nconst NO_ITEMS: readonly TocItem[] = []\n\n/** Items win. Otherwise the headings are read from the container and followed as its content changes. */\nfunction useHeadings(items: readonly TocItem[] | undefined, containerRef: React.RefObject<HTMLElement | null> | undefined, selector: string) {\n  const [read, setRead] = useState(NO_ITEMS)\n  useEffect(() => {\n    const container = containerRef?.current\n    if (items || !container) return\n    let alive = true\n    const refresh = () => {\n      if (!alive) return\n      const next = readHeadings(container, selector)\n      setRead((previous) => (sameItems(previous, next) ? previous : next))\n    }\n    // Attributes are left out of the watch list: reading the headings writes ids, which would wake it forever.\n    const observer = new MutationObserver(refresh)\n    observer.observe(container, { childList: true, subtree: true, characterData: true })\n    queueMicrotask(refresh)\n    return () => {\n      alive = false\n      observer.disconnect()\n    }\n  }, [items, containerRef, selector])\n  return items ?? read\n}\n\ninterface ActiveOptions {\n  scrollRef?: React.RefObject<HTMLElement | null>\n  offset: number\n  reduceMotion: boolean\n}\n\n/** Follows the headings while nothing holds the marker in place. */\nfunction useReadingPosition(\n  idKey: string,\n  scrollRef: React.RefObject<HTMLElement | null> | undefined,\n  offset: number,\n  lockedRef: React.RefObject<boolean>,\n  setActive: (id: string | null) => void,\n) {\n  useEffect(() => {\n    const ids = idKey ? idKey.split(\" \") : []\n    if (ids.length === 0) return\n    const root = scrollRef?.current ?? null\n    const metrics = root ?? document.documentElement\n    let statuses: ReadonlyMap<string, HeadingStatus> = new Map()\n    const publish = () => {\n      if (lockedRef.current) return\n      const end = atScrollEnd(metrics.scrollTop, metrics.clientHeight, metrics.scrollHeight)\n      setActive(end ? (ids.at(-1) ?? null) : nextActive(ids, statuses))\n    }\n    const observer = new IntersectionObserver(\n      (entries) => {\n        const updates = entries.map(\n          (entry) => [entry.target.id, headingStatus(entry.isIntersecting, entry.boundingClientRect.top, entry.rootBounds?.top ?? 0)] as const,\n        )\n        statuses = new Map([...statuses, ...updates])\n        publish()\n      },\n      { root, rootMargin: `-${offset}px 0px ${READING_BAND_BOTTOM} 0px` },\n    )\n    ids.flatMap((id) => document.getElementById(id) ?? []).forEach((element) => observer.observe(element))\n    const scroller: EventTarget = root ?? window\n    const release = () => {\n      lockedRef.current = false\n    }\n    scroller.addEventListener(\"scroll\", publish, { passive: true })\n    scroller.addEventListener(\"scrollend\", release)\n    return () => {\n      observer.disconnect()\n      scroller.removeEventListener(\"scroll\", publish)\n      scroller.removeEventListener(\"scrollend\", release)\n    }\n  }, [idKey, scrollRef, offset, lockedRef, setActive])\n}\n\nfunction useActiveHeading(ids: readonly string[], { scrollRef, offset, reduceMotion }: ActiveOptions) {\n  const [active, setActive] = useState<string | null>(null)\n  const lockedRef = useRef(false)\n  const timerRef = useRef<number | undefined>(undefined)\n  useReadingPosition(ids.join(\" \"), scrollRef, offset, lockedRef, setActive)\n  useEffect(() => () => window.clearTimeout(timerRef.current), [])\n\n  const goTo = useCallback(\n    (id: string) => {\n      const target = document.getElementById(id)\n      if (!target) return\n      // While a smooth scroll passes other headings the marker would run through them, so it holds on the destination.\n      lockedRef.current = true\n      window.clearTimeout(timerRef.current)\n      timerRef.current = window.setTimeout(() => {\n        lockedRef.current = false\n      }, CLICK_LOCK_MS)\n      setActive(id)\n      const root = scrollRef?.current\n      const behavior: ScrollBehavior = reduceMotion ? \"auto\" : \"smooth\"\n      const top = target.getBoundingClientRect().top\n      if (root) root.scrollTo({ top: scrollTopFor(top, root.getBoundingClientRect().top, root.scrollTop, offset), behavior })\n      else window.scrollTo({ top: scrollTopFor(top, 0, window.scrollY, offset), behavior })\n      // Without this, Tab continues from the link instead of from the section someone just jumped to.\n      if (!target.hasAttribute(\"tabindex\")) target.setAttribute(\"tabindex\", \"-1\")\n      target.focus({ preventScroll: true })\n    },\n    [scrollRef, offset, reduceMotion],\n  )\n\n  return { active, goTo }\n}\n\nexport { useActiveHeading, useHeadings }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}