import * as React from "react"; import type { AnchorProp } from "../../props/components/navigation.prop.js"; export type { AnchorContainerProp, AnchorDirectionProp, AnchorItemProp, AnchorProp, AnchorProp as AnchorProps, } from "../../props/components/navigation.prop.js"; /** * Anchor — Ant Design `Anchor` (6.6.5): the in-page section navigation, and — the part that makes * it a component rather than a composition — the thing that WORKS OUT which section is current. * * `NavList activeId` takes that answer as a prop. Nothing else in this library computes it. * * ## Why this resolves by measurement and not by `IntersectionObserver` * * The obvious build is an observer over every section with a thin `rootMargin` band, and it is * what Bootstrap's Scrollspy does (`rootMargin: "0px 0px -25%"`, `threshold: [0.1, 0.5, 1]`) and * what this repo's own `LegalDocumentShell` does (`"-10% 0px -75% 0px"`, threshold 0). It has two * failure modes that no choice of margin fixes, because they are properties of the question rather * than of the numbers: * * 1. **A section taller than the band reports nothing.** Scroll into the middle of a long * section and no element intersects, so the answer is empty and the implementation has to fall * back to "keep whatever was active" — which is a guess, and is wrong after any jump. * 2. **The answer depends on scroll DIRECTION.** With several short sections inside the band at * once, "first intersecting" and "last intersecting" disagree, and which is right depends on * which way the reader is moving. That is where scrollspy flicker comes from, and it is why * the good implementations end up bolting hysteresis onto the observer. * * Ant Design resolves it the other way and gets neither problem: on each scroll, take every * section whose block-start edge has crossed a single decision line, and pick the LAST one. It is * a pure function of scroll position, so it cannot oscillate at a fixed position, it needs no * hysteresis, and a section a mile tall is still the current one all the way down. That rule is * what is ported here, `bounds` and all. * * ## The three traps, and where each is handled * * **A click must not fight the resolver.** A click scrolls, the scroll fires the resolver, and the * resolver re-picks every section the page passes on the way — so the item you clicked lights up, * goes out, and comes back. `suppressedRef` holds the resolver off from the moment the * programmatic scroll starts until the container stops emitting `scroll` (see * `SCROLL_SETTLE_MS`), and a real `wheel`/`touchstart` releases it early. * * **The hash is state too.** Landing on `/pricing#enterprise` must select that entry, and must do * it without waiting for a scroll event that may never come — the browser's own hash jump does not * fire one when the section is already in view. It is therefore read in the state INITIALISER, so * the very first render is already correct, and the mount-time resolution is skipped when it * matched, rather than immediately overwriting it with a scroll position nobody has reached yet. * * **Reduced motion jumps.** `behavior: "smooth"` becomes `"auto"` under * `prefers-reduced-motion: reduce` (WCAG 2.3.3) — it still lands on the section, instantly. The * ink rail transitions on `--duration-fast` and snaps under the same query; the current item is * never conveyed by motion alone, it is conveyed by `aria-current`. * * ## Accessibility * * A named `