import { type RefObject, type ReactNode } from "react"; import type { BlockRenderContext } from "../types.js"; /** * Shared line-anchored annotation UI for the `annotated-code` and `diff` blocks. * * Both blocks render a numbered code surface plus a side "rail" of notes, where * each note targets a 1-based `lines` ref (`"3"` or `"3-5"`) and hovering a code * line ↔ its note cross-highlights. This module owns the pure pieces that were * identical between them so neither block forks the behavior: * * - `parseLineRange` — the forgiving 1-based `lines` range parser. * - `resolveAnnotations` / `buildLineMarkerMap` — turn a raw annotation list * into stable, marker-numbered, range-resolved records and a line→markers map. * - `rangeLabel` — the human "Line 8" / "Lines 3–6" label. * - `AnnotationGutterMarker` — the numbered amber pip placed on an annotated row * by both the diff grid and the annotated-code gutter. * - `AnnotationNoteRail` — the responsive list of note cards with two-way hover. * `showMarker` opts blocks into a leading numbered pip on each card so a note * can be matched to its `①`/`②` row marker. * * `AnnotatedCodeBlock` annotates a single code surface; `DiffBlock` annotates a * before/after grid (each annotation also carries a `side`). The shared types * here are intentionally minimal — callers pass their own `side` handling and * decide which rows a marker lands on; this module only owns the parsing, the * resolved-record shape, and the rendered marker + rail chrome. */ /** * Parse a 1-based `lines` ref (`"3"` or `"3-5"`) into an inclusive `[start,end]` * pair, clamped to `[1, lineCount]`. Returns `null` for malformed or fully * out-of-range refs so callers can ignore them gracefully. A reversed range * (`"5-3"`) is normalized; a partially out-of-range range is clamped. */ export declare function parseLineRange(ref: string, lineCount: number): { start: number; end: number; } | null; /** The minimal annotation shape the rail needs (a superset works too). */ export interface RailAnnotation { lines: string; label?: string; note: string; } export interface ResolvedAnnotation { /** Index in the original `annotations` array (stable hover key). */ index: number; /** 1-based marker number (authoring order). */ marker: number; annotation: A; range: { start: number; end: number; } | null; } /** * Resolve a raw annotation list into stable, marker-numbered records, parsing * each `lines` ref against `lineCount`. `lineCountFor` lets the diff block pick a * per-annotation line count (before-side vs after-side); annotated-code passes a * single constant. Markers are authoring-order, 1-based, and assigned to ALL * annotations (even unresolved ones) so numbering is stable regardless of which * refs happen to match. */ export declare function resolveAnnotations(annotations: A[] | undefined, lineCountFor: (annotation: A) => number): ResolvedAnnotation[]; /** Map a 1-based line number → the resolved annotations covering it. */ export declare function buildLineMarkerMap(resolved: ResolvedAnnotation[]): Map[]>; /** Human label for a resolved annotation's line span ("Line 8" / "Lines 3–6"). */ export declare function rangeLabel(item: ResolvedAnnotation): string; /** * The numbered amber pip rendered on an annotated code row's gutter. `active` * brightens it when its note (or a co-located row) is hovered. */ export declare function AnnotationGutterMarker({ marker, active, className, }: { marker: number; active: boolean; className?: string; }): import("react").JSX.Element; /** * One line-anchored note card: marker pip (when `showMarker`), the resolved line * span ("Line 8"), an optional label, and the markdown `note` (via * `ctx.renderMarkdown`). This is the single source of card markup, rendered both * by the visually-hidden a11y/test stack and by the on-hover portal popover. */ export declare function AnnotationCard({ item, ctx, active, showMarker, className, onMouseEnter, onMouseLeave, }: { item: ResolvedAnnotation; ctx: BlockRenderContext; active?: boolean; showMarker?: boolean; className?: string; onMouseEnter?: () => void; onMouseLeave?: () => void; }): import("react").JSX.Element; /** * Visually-hidden stack of every resolved note. It is NOT a visible column — it * sits in the flow but clipped to a 1px box (the `sr-only` pattern) so the note * text and the per-card marker pips stay in the accessibility tree and in the * DOM (assistive tech can reach them, and tests that read `textContent`/count * pips still see them) WITHOUT painting a persistent rail beside the code. The * visible card appears only on hover via {@link AnnotationHoverCard}. */ export declare function AnnotationHiddenStack({ items, ctx, showMarker, }: { items: ResolvedAnnotation[]; ctx: BlockRenderContext; showMarker?: boolean; }): import("react").JSX.Element | null; export declare function AnnotationInlineOverlayStack({ items, ctx, showMarker, containerRef, mode, side, preferredSide, }: { items: ResolvedAnnotation[]; ctx: BlockRenderContext; showMarker?: boolean; containerRef?: RefObject; mode?: "capture" | "margin"; side?: AnnotationMarginSide; preferredSide?: AnnotationSide; }): import("react").JSX.Element | null; /** The geometry the hover card anchors to (in viewport coordinates). */ export interface AnnotationAnchor { /** Right edge of the code block (where the card should start). */ codeRight: number; /** Left edge of the code block (for the below-fallback alignment). */ codeLeft: number; /** Vertical center of the hovered line. */ lineCenter: number; /** Bottom of the hovered line (for the below-fallback placement). */ lineBottom: number; } export type AnnotationSide = "left" | "right"; export type AnnotationMarginSide = AnnotationSide | "auto"; export declare function resolveAnnotationInlineOverlayPosition(anchor: { right: number; top: number; height: number; }, card: { width: number; height: number; }, viewport: { width: number; height: number; }): { top: number; right: number; }; export declare function resolveAnnotationCaptureOverlayPosition(anchor: { right: number; top: number; height: number; }, card: { width: number; height: number; }, viewport: { width: number; height: number; }, scroll?: { x: number; y: number; }): { top: number; left: number; }; export declare function resolveAnnotationMarginOverlayPosition(anchor: { left: number; right: number; top: number; height: number; }, card: { width: number; height: number; }, viewport: { width: number; height: number; }, options?: { side?: AnnotationMarginSide; preferredSide?: AnnotationSide; }): { top: number; left: number; visible: boolean; side: AnnotationSide; }; export declare function resolveAnnotationHoverCardPosition(anchor: AnnotationAnchor, card: { width: number; height: number; }, viewport: { width: number; height: number; }, options?: { preferredSide?: AnnotationSide; hoverFallbackSide?: AnnotationSide | "below"; allowOppositeSideFallback?: boolean; }): { top: number; left: number; }; export declare function useAnnotationMarginNotesAvailable({ containerRef, enabled, side, preferredSide, }: { containerRef: RefObject; enabled: boolean; side?: AnnotationMarginSide; preferredSide?: AnnotationSide; }): boolean; /** * The single on-hover note card, portaled to `document.body` and positioned * `fixed` so it escapes the code block's `overflow` and never reflows the code. * * Placement: by default it sits to the RIGHT of the code block's right edge, * vertically centered on the hovered line — so it never overlaps the code text. * If there isn't room to the right, it uses the LEFT of the code block when the * card can fit there without covering code. Only when neither side fits does it * overlap the code from the RIGHT edge with a small overhang, so the hover still * reads as an attached overlay instead of a left-aligned card. The card keeps * itself open while hovered (`onMouseEnter`/`onMouseLeave` forwarded) so it stays * readable; the caller adds the small hover-intent close delay. */ export declare function AnnotationHoverCard({ item, anchor, ctx, showMarker, preferredSide, hoverFallbackSide, onMouseEnter, onMouseLeave, onClose, onInteractOutside, }: { item: ResolvedAnnotation; anchor: AnnotationAnchor; ctx: BlockRenderContext; showMarker?: boolean; preferredSide?: AnnotationSide; hoverFallbackSide?: AnnotationSide | "below"; onMouseEnter?: () => void; onMouseLeave?: () => void; /** Called when the card should be dismissed (e.g. on scroll). */ onClose?: () => void; /** Called when focus or pointer intent moves to the surrounding UI. */ onInteractOutside?: () => void; }): import("react").ReactPortal | null; /** * Hover-intent + tap controller for the on-hover note card. Exposes * `activeIndex` + the captured `anchor`, plus `open`/`toggle`/`close`/ * `scheduleClose`/`cancelClose` handlers. * * - `open(index, anchor)` shows a card immediately (cancels any pending close). * - `toggle(index, anchor)` opens a card if it isn't already the active one, * or closes it if it is — used for click/tap on annotated rows so touch users * can access notes without hover. * - `close()` hides the card immediately (used on scroll to avoid stale cards). * - `scheduleClose()` hides after a short delay, so moving the pointer from the * code line across the gap into the card itself keeps it open. * - `cancelClose()` (call on card mouse-enter) keeps it open while reading. */ export declare function useAnnotationHover(delay?: number): { readonly activeIndex: number | null; readonly anchor: AnnotationAnchor | null; readonly open: (index: number, anchor: AnnotationAnchor) => void; readonly toggle: (index: number, anchor: AnnotationAnchor) => void; readonly close: () => void; readonly closeForScroll: () => void; readonly scheduleClose: () => void; readonly cancelClose: () => void; }; /** * Build an {@link AnnotationAnchor} from the code block element and the hovered * row element. The card anchors to the code block's RIGHT edge and the row's * vertical center, both in viewport coordinates (so a `fixed` portal lines up). */ export declare function anchorFromElements(codeEl: HTMLElement | null, rowEl: HTMLElement | null): AnnotationAnchor | null; /** * The responsive list of line-anchored note cards. Each card shows its marker * pip, the resolved line span ("Line 8"), an optional label, and the markdown * `note` (via `ctx.renderMarkdown`). Hovering a card sets the active index; * `activeIndex` driven from outside lets a hovered code row light its card and * vice-versa. Only annotations whose `range` resolved are listed. * * @deprecated Superseded by the on-hover {@link AnnotationHoverCard}; kept for * back-compat with any external importer. Both block read renderers now use the * hover popover anchored to the right of the code instead of a persistent rail. */ export declare function AnnotationNoteRail({ items, activeIndex, onActiveChange, ctx, className, showMarker, }: { items: ResolvedAnnotation[]; activeIndex: number | null; onActiveChange: (index: number | null) => void; ctx: BlockRenderContext; className?: string; /** Show a leading numbered pip on each card (diff block). */ showMarker?: boolean; }): import("react").JSX.Element; /** Whether a resolved list has at least one note worth rendering a rail for. */ export declare function hasRailAnnotations(items: ResolvedAnnotation[]): boolean; export type AnnotationRailChildren = ReactNode; //# sourceMappingURL=annotation-rail.d.ts.map