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