import type { LaneSource } from "./lane.js"; import { type LaneView } from "./view.js"; type Cat = string; export interface CatMeta { cat: Cat; label: string; color: string; } interface Ev { y: number; label: string; imp: number; cat: Cat; detail?: string; /** * influence scale: the largest visible time-span (world years) at which this * event still shows a label. Beyond it → a presence dot only; far beyond → * hidden. Lets minor events fade out sooner than major ones as you zoom out, * even in the same track. Omitted → derived from importance. */ influence?: number; /** * ± uncertainty in years. Many deep-time events are only known to a fuzzy * date — they render as a point when zoomed out and expand into a soft * uncertainty band once that range spans real pixels. Omitted → auto: a * small fraction of the age for prehistoric events, 0 (crisp) for dated ones. */ span?: number; /** * exact instant (Unix ms, UTC) — present ONLY when the event's source is a * real calendar date (dated()/ingested timestamps). Admits the event into * temporal folds; without it the event stays on the continuous axis only. */ tMs?: number; /** * source time precision, captured at ingest BEFORE parsing (Date.parse * normalizes absent fields and destroys it). calendar → the containing * cycle of that unit is the event's window; uncertainty → epistemic ± * bounds in years (deep time), which stays on the continuous fuzzy band * and never enters calendar folds. */ precision?: { kind: "calendar"; unit: "year" | "month" | "day" | "hour" | "minute"; } | { kind: "uncertainty"; beforeYears: number; afterYears: number; }; /** * conserved magnitude of the event (e.g. a commit's changed lines). Known * mass scales the glyph on a small discrete ladder and brightens it on the * track's heat ramp — the same vocabulary as the aggregated cells, so a * heat cell is literally the merged dot of its events. Absent = neutral * glyph and count-weight 1 in cells (never a fabricated average). */ mass?: number; /** * signed composition of `mass` (e.g. additions vs deletions). At readable * glyph sizes the dot discloses into a two-tone bar — gains above the * event's instant, losses below, proportional. Position carries the * meaning (up/down), so the conventional green/red is redundant, not * load-bearing (CVD-safe). Below the gate it stays the single mass dot. */ split?: { add: number; del: number; }; /** * per-event color override (hex). Lets several sources overlay in ONE * track — e.g. a calendar lane tinting events by their source calendar — * while the track keeps its own hue for aggregates. Glyphs, labels-dots * and search swatches use it; heat cells stay track-hued (they merge * events across colors). */ color?: string; /** * DURATION in ms (distinct from `span`, which is ±uncertainty): the event * truly occupies [tMs, tMs+durMs]. In the per-track fold it renders as * wrapped row fragments behind the glyph — a meeting becomes a block in * its day row, a multi-day event a text-selection ribbon — but only once * the block would be readable (≥ a few px), per the rg rule. */ durMs?: number; } export declare const massBucket: (mass: number | undefined) => number; /** cell weight: unknown (or invalid) mass counts exactly 1 (legacy count) */ export declare const massWeight: (mass: number | undefined) => number; interface Era { from: number; to: number; label: string; depth: number; color: string; } interface Cycle { label: string; period: number; color: string; from?: number; to?: number; } export interface SearchHit { label: string; detail?: string; cat: Cat; color: string; /** viewport-center world-y to focus */ center: number; /** world-span to fit (→ zoom = viewportHeight / scale) */ scale: number; /** folded mode: the hit's phase within its row (for the pulse highlight) */ phase?: number; } export type TimelineFold = "none" | "year" | "month" | "week" | "day" | "hour" | "decade" | "century" | "millennium"; export interface TimelineSource extends LaneSource { readonly categories: readonly CatMeta[]; isEnabled(cat: Cat): boolean; setEnabled(cat: Cat, on: boolean): void; /** substring search over event labels/details, ranked by importance */ find(query: string, limit?: number): SearchHit[]; /** how many events are loaded (statics + everything ingested so far) */ eventCount(): number; /** * the fold grid cell under a screen point (per-track fold mode): its * screen rect plus the time window it spans. Hosts use it for hover * highlights and slot-snapped creation. Null when the pointer is outside * a folded track band or no fold level is readable there. */ gridAt(sx: number, sy: number, view: LaneView): { x0: number; x1: number; y0: number; y1: number; t0: number; t1: number; /** continuous time at the pointer's x within its row (x-y plane inverse) */ t: number; /** the fold level the cell belongs to — pass to spanRects to freeze a gesture */ level: string; } | null; /** * screen rects of a time span in the current per-track fold: the wrapped * text-selection shape — partial start row, full middle rows, partial end * row. On the continuous axis (or fold unreadable) falls back to one * full-width band over the span's y range. `level` freezes the fold level * (a zoom mid-gesture must not re-pick it under the caller's feet). */ spanRects(t0: number, t1: number, view: LaneView, level?: string): Array<{ x0: number; x1: number; y0: number; y1: number; }>; /** called when lazily-fetched events arrive (host wires it to invalidate) */ setOnUpdate(fn: () => void): void; /** every English UI string (labels/details/headers/eras/cycles), for i18n */ strings(): string[]; /** install a label translator (en → localized); default identity */ setTranslate(fn: (s: string) => string): void; /** true when the log (symlog) time axis is active (vs linear) */ isLogAxis(): boolean; /** switch the time axis at runtime (host should re-fit afterwards) */ setLogAxis(on: boolean): void; /** current temporal fold ("none" = continuous axis) */ getFold(): TimelineFold; /** * switch the temporal fold at runtime. "year" folds dated modern events * into calendar-year rows (worldY = UTC year, x = phase within the year). * The host should re-frame afterwards — use tMsForWorld()/worldForTMs() * to keep the instant at the viewport center fixed across the switch. */ setFold(fold: TimelineFold, opts?: { animateFrom?: LaneView; }): void; /** the instant at a world-y under the CURRENT projection (null if none) */ tMsForWorld(worldY: number): number | null; /** folded mode: flash a transient ring at a search hit's (row, phase) */ setPulse(hit: SearchHit): void; /** the calendar-year row range fold-year would cover (dated events + now) */ foldRowRange(): { min: number; max: number; }; /** per-track folding: fold events inside each track whenever space allows */ isTrackFold(): boolean; setTrackFold(on: boolean): void; /** preference: OKLCH heat cells + presence wash in fold grids */ setHeatCells(on: boolean): void; /** preference: glide animation on fold-level changes */ setGlide(on: boolean): void; /** world-y of an instant under the CURRENT projection */ worldForTMs(tMs: number): number; /** the event under a screen point (for hover cards), or null */ eventAt(screenX: number, screenY: number, view: LaneView): { title: string; detail?: string; cat: string; } | null; } /** an event as timeline datasets author them (see Ev for field docs) */ export type TimelineEvent = Ev; export type TimelineEra = Era; export type TimelineCycle = Cycle; /** * A swappable dataset behind the timeline engine. The engine (tracks, symlog * axis, calendar folds, heat cells, declutter, search) is generic; what the * deep-time demo hard-codes — track list, static events, eras, cycles, axis * reach, lazy fetchers — arrives here instead. The `fetch` hook runs once per * draw; anything it ingests streams in like the deep-time feeds do. The lib * itself still does no I/O — datasets that fetch live in demo/host code. */ export interface TimelineDataset { title: string; tracks: CatMeta[]; statics: Ev[]; eras?: Era[]; cycles?: Cycle[]; /** oldest years-before-present the axis reaches (default: Big Bang) */ oldestYBP?: number; /** years after present the axis extends to (default 1e10) */ futureYears?: number; /** * initial-fit window bias, in years before present (top = older edge, * bot ≤ 0 reaches into the future). The full extent stays reachable by * scrolling — this only frames the opening view, e.g. a git dataset * opening on the recent weeks instead of its whole multi-year extent. */ fitYBP?: { top: number; bot: number; }; /** * max fold content measure in rem (default 45, the text-column rule). * Single-track full-page datasets (the calendar) pass Infinity so the * grid fills the viewport edge to edge. */ foldMaxContentRem?: number; /** per-frame lazy-loading hook (demo/host side — the engine never fetches) */ fetch?: (view: LaneView, api: TimelineFetchApi) => void; } /** helpers the engine lends a dataset's fetch hook */ export interface TimelineFetchApi { /** fetch a URL once per key (dedup + in-flight cap) and ingest the result; * `init` allows non-GET requests (e.g. a GraphQL POST) */ lazyFetch(key: string, url: string, mapFn: (data: unknown) => Ev[], init?: RequestInit): void; /** visible window in years-before-present (top = older edge) */ winYBP(view: LaneView): { top: number; bot: number; }; /** ISO timestamp of a years-before-present offset */ isoOf(yBP: number): string; /** years-before-present of a Unix-ms instant */ ybpOfMs(tMs: number): number; /** is this track currently toggled on? */ enabled(cat: string): boolean; /** push events straight in (for non-HTTP sources the host already has) */ ingest(evs: Ev[]): void; } export declare function createTimelineSource(opts?: { logAxis?: boolean; dataset?: TimelineDataset; }): TimelineSource; export {}; //# sourceMappingURL=timeline.d.ts.map