ar-timeline-view 0.0.0-stage → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ import type { Id, MovePatchData, MoveResultData, TimelineViewData } from "./types";
2
+ /** A non-2xx response. `body` is the parsed JSON body, if there was one. */
3
+ export declare class ApiError extends Error {
4
+ readonly status: number;
5
+ readonly body: unknown;
6
+ constructor(status: number, body: unknown);
7
+ }
8
+ /** Client for the REST API described in CLAUDE.md and docs/api/openapi.yaml. */
9
+ export declare class RestClient {
10
+ private readonly fetchImpl;
11
+ private readonly baseUrl;
12
+ constructor(baseUrl: string, fetchImpl?: typeof fetch);
13
+ getView(viewId: Id): Promise<TimelineViewData>;
14
+ moveEntry(viewId: Id, timelineId: Id, taskId: Id, patch: MovePatchData, version: number | undefined): Promise<MoveResultData>;
15
+ private request;
16
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Dates are handled in UTC throughout, so day and month arithmetic is not
3
+ * affected by daylight saving time in the viewer's zone.
4
+ */
5
+ export declare function parseDate(text: string): Date;
6
+ /** Formats as `YYYY-MM-DD`, adding `THH:mm` when the time is not midnight. */
7
+ export declare function formatDate(date: Date): string;
8
+ export declare function daysInMonth(year: number, month: number): number;
9
+ /** Adds calendar months, clamping the day to the end of the target month. */
10
+ export declare function addMonths(date: Date, months: number): Date;
11
+ export declare function addDays(date: Date, days: number): Date;
@@ -0,0 +1,47 @@
1
+ import type { Offset } from "./types";
2
+ export type DropTarget = {
3
+ timeline: string;
4
+ placement: "on";
5
+ line: number;
6
+ newLine: boolean;
7
+ } | {
8
+ timeline: string;
9
+ placement: "above" | "below";
10
+ };
11
+ /** A finished drag. IDs are the DOM's string form; the view maps them back. */
12
+ export interface DropEvent {
13
+ timeline: string;
14
+ task: string;
15
+ target: DropTarget;
16
+ /** Set when the target is a callout area. */
17
+ offset?: Offset;
18
+ }
19
+ /** A marker label dropped somewhere: its new `marker_offset`. The id is the DOM's string form. */
20
+ export interface MarkerDropEvent {
21
+ marker: string;
22
+ offset: Offset;
23
+ }
24
+ interface Rect {
25
+ left: number;
26
+ top: number;
27
+ right: number;
28
+ bottom: number;
29
+ width: number;
30
+ }
31
+ /**
32
+ * Callout offset for a label dropped at `box` in the callout area `area`.
33
+ * `at` is the attachment point as a fraction of the area's width. The
34
+ * attachment point is on the bar's edge: the area's bottom above the bar, its
35
+ * top below it. `y` is clamped so the label stays on its own side of the bar.
36
+ */
37
+ export declare function calloutOffset(box: Rect, area: Rect, at: number, placement: "above" | "below"): Offset;
38
+ /**
39
+ * Makes every `[data-entry]` inside `root` draggable. A press and release
40
+ * without moving past the drag threshold is a click instead, reported to
41
+ * `onClick` with the entry's element; a drag never reports a click.
42
+ * The same kind of click elsewhere in a timeline or on the view's heading
43
+ * (not on an entry) goes to `onBackgroundClick`, with the pressed element.
44
+ * Returns a function that detaches.
45
+ */
46
+ export declare function attachDrag(root: HTMLElement, onDrop: (event: DropEvent) => void, onClick?: (entry: HTMLElement, event: PointerEvent) => void, onBackgroundClick?: (target: HTMLElement, event: PointerEvent) => void, onMarkerDrop?: (event: MarkerDropEvent) => void): () => void;
47
+ export {};
@@ -0,0 +1,11 @@
1
+ export type DurationUnit = "h" | "d" | "w" | "m" | "y";
2
+ export interface Duration {
3
+ amount: number;
4
+ unit: DurationUnit;
5
+ }
6
+ /** Parses `"2d"`, `"3m"` and so on. `m` is months; there is no minutes unit. */
7
+ export declare function parseDuration(text: string): Duration;
8
+ /** Adds a duration. Months and years are calendar units, clamped to the month's end. */
9
+ export declare function addDuration(start: Date, { amount, unit }: Duration): Date;
10
+ /** A task is a milestone when its duration is missing or zero. */
11
+ export declare function isMilestoneDuration(text: string | undefined): boolean;
@@ -0,0 +1,28 @@
1
+ import "./styles.css";
2
+ import type { Id, MovePatchData, Offset, TimelineViewData } from "./types";
3
+ import { TimelineView, type TimelineEventName, type TimelineEvents, type TimelineHandlers, type TimelineOptions } from "./view";
4
+ export * from "./types";
5
+ export { ApiError, RestClient } from "./api";
6
+ export { MoveError, TimelineModel, firstLine, lineCount, patchFromData, trimLines, timelineToData } from "./model";
7
+ export { parseDuration, addDuration } from "./duration";
8
+ export { MoveCancelledError, TimelineView, type EntryMovedEvent, type MarkerMovedEvent, type MoveFailedEvent, type TimelineEvents, type TimelineEventName, type TimelineHandlers, type TimelineOptions, } from "./view";
9
+ export declare function init(container: string | HTMLElement, options?: TimelineOptions): TimelineView;
10
+ export declare const parse: (data: TimelineViewData) => void;
11
+ export declare const load: (viewId: Id) => Promise<void>;
12
+ export declare const serialize: () => TimelineViewData;
13
+ export declare const toSVG: () => string;
14
+ export declare const moveEntry: (timelineId: Id, taskId: Id, patch: MovePatchData) => Promise<void>;
15
+ export declare const attachEvent: <K extends TimelineEventName>(name: K, handler: TimelineHandlers[K]) => number;
16
+ export declare const detachEvent: (eventId: number) => boolean;
17
+ export declare const selectTask: (id: Id, add?: boolean) => boolean;
18
+ export declare const selectTimeline: (id: Id, add?: boolean) => boolean;
19
+ export declare const unselectTask: (id: Id) => boolean;
20
+ export declare const unselectTimeline: (id: Id) => boolean;
21
+ export declare const clearSelection: () => void;
22
+ export declare const getSelectedTasks: () => Id[];
23
+ export declare const getSelectedTimelines: () => Id[];
24
+ export declare const setMarkerOffset: (markerId: Id, offset: Offset) => void;
25
+ export declare const moveTimelineUp: (id: Id) => boolean;
26
+ export declare const moveTimelineDown: (id: Id) => boolean;
27
+ export declare const moveTimeline: (id: Id, delta: number) => boolean;
28
+ export declare const on: <K extends keyof TimelineEvents>(name: K, handler: (event: TimelineEvents[K]) => void) => () => void;
@@ -0,0 +1,127 @@
1
+ import type { Id, MilestoneSymbol, MovePatchData, NamePosition, Offset, Placement, Scale, TimelineData, TimelineViewData } from "./types";
2
+ export interface View {
3
+ id?: Id;
4
+ name: string;
5
+ absolute: boolean;
6
+ scale: Scale;
7
+ namePosition: NamePosition;
8
+ milestoneSymbol: MilestoneSymbol;
9
+ showDates: boolean;
10
+ /** Whether clicks select tasks and timelines. */
11
+ selection: boolean;
12
+ }
13
+ export interface Task {
14
+ id: Id;
15
+ text: string;
16
+ start: Date;
17
+ /** `start + duration`; equal to `start` for milestones. */
18
+ finish: Date;
19
+ duration?: string;
20
+ isMilestone: boolean;
21
+ symbol?: MilestoneSymbol;
22
+ /** CSS color overriding the theme's task or milestone color. */
23
+ color?: string;
24
+ }
25
+ export interface Marker {
26
+ id: Id;
27
+ text: string;
28
+ date: Date;
29
+ global: boolean;
30
+ color?: string;
31
+ /** Label position; `y ≤ 0` above the topmost bar, `y > 0` below the bottommost. */
32
+ offset: Offset;
33
+ }
34
+ export type Entry = {
35
+ taskId: Id;
36
+ placement: "on";
37
+ line: number;
38
+ } | {
39
+ taskId: Id;
40
+ placement: "above" | "below";
41
+ calloutOffset: Offset;
42
+ };
43
+ export interface Timeline {
44
+ id: Id;
45
+ name: string;
46
+ start: Date;
47
+ finish: Date;
48
+ entries: Entry[];
49
+ /** Ids of the (non-global) markers shown on this timeline. */
50
+ markers: Id[];
51
+ }
52
+ export interface MovePatch {
53
+ timelineId?: Id;
54
+ placement?: Placement;
55
+ line?: number;
56
+ newLine?: boolean;
57
+ calloutOffset?: Offset;
58
+ }
59
+ /** Mirrors the REST status codes, so a local move fails the same way a server move would. */
60
+ export type MoveErrorCode = "not_found" | "conflict" | "invalid";
61
+ export declare class MoveError extends Error {
62
+ readonly code: MoveErrorCode;
63
+ constructor(code: MoveErrorCode, message: string);
64
+ }
65
+ /** Default distance, in px, between the bar and a callout label without an offset. */
66
+ export declare const DEFAULT_CALLOUT_GAP = 16;
67
+ export declare const defaultOffset: (placement: "above" | "below") => Offset;
68
+ export declare class TimelineModel {
69
+ view: View;
70
+ tasks: Task[];
71
+ timelines: Timeline[];
72
+ markers: Marker[];
73
+ version?: number;
74
+ private constructor();
75
+ static fromData(data: TimelineViewData): TimelineModel;
76
+ toData(): TimelineViewData;
77
+ /** Converts and validates one timeline. */
78
+ timelineFromData(t: TimelineData): Timeline;
79
+ private entryFromData;
80
+ findTask(id: Id): Task | undefined;
81
+ /** Moves a marker's label. `y ≤ 0` puts it above the topmost bar, `y > 0` below the bottommost. */
82
+ setMarkerOffset(id: Id, offset: Offset): Marker;
83
+ findMarker(id: Id): Marker | undefined;
84
+ findTimeline(id: Id): Timeline | undefined;
85
+ findEntry(timelineId: Id, taskId: Id): Entry | undefined;
86
+ /**
87
+ * Moves an entry to another bar line, callout position, area or timeline.
88
+ * `newLine` inserts a new bar line at `line`, shifting the lines from there
89
+ * down; a `line` one past the last also opens one. Afterwards the affected
90
+ * timelines are trimmed (see `trimLines`): a line an entry leaves stays as
91
+ * an empty gap only between used lines.
92
+ *
93
+ * Returns the timelines the move changed (source first).
94
+ */
95
+ moveEntry(timelineId: Id, taskId: Id, patch: MovePatch): Timeline[];
96
+ /**
97
+ * Moves a timeline `delta` places in the stack: -1 is up, 1 is down.
98
+ * Returns false, changing nothing, if it would move past the top or bottom.
99
+ */
100
+ moveTimeline(id: Id, delta: number): boolean;
101
+ /** Replaces whole timelines, as returned by a successful REST move. */
102
+ replaceTimelines(data: TimelineData[]): void;
103
+ }
104
+ /**
105
+ * The time axis the view draws on: from the earliest timeline start to the
106
+ * latest finish. With `absolute`, every bar is placed on this axis.
107
+ */
108
+ export declare function viewRange(model: TimelineModel): {
109
+ start: Date;
110
+ finish: Date;
111
+ } | undefined;
112
+ /** The lowest `line` the bar entries use; 0 when there are none. */
113
+ export declare function firstLine(timeline: Timeline): number;
114
+ /**
115
+ * Removes blank bar lines above the first used one by renumbering the
116
+ * entries so the first used line is 0. Blank lines below the last used one
117
+ * need no trimming: `lineCount` doesn't count them. Gaps between used lines stay.
118
+ */
119
+ export declare function trimLines(timeline: Timeline): void;
120
+ /**
121
+ * Number of bar lines, counted from line 0: one more than the highest `line`
122
+ * the entries use, at least 1. It is never stored.
123
+ */
124
+ export declare function lineCount(timeline: Timeline): number;
125
+ export declare function timelineToData(t: Timeline): TimelineData;
126
+ /** Converts a public move body to the internal form. */
127
+ export declare function patchFromData(p: MovePatchData): MovePatch;
@@ -0,0 +1,25 @@
1
+ import { type TimelineModel } from "./model";
2
+ /** Bar line height in px. The drag code reads positions from the DOM, so only rendering uses this. */
3
+ export declare const BAR_LINE_HEIGHT = 28;
4
+ /**
5
+ * How far, in px, each callout's edge marker sticks out from the bar. The
6
+ * connector starts there: at the marker's outer edge, in its middle.
7
+ * Keep in step with .tlv-edge-bar and .tlv-edge-symbol in styles.css.
8
+ */
9
+ export declare const EDGE_BAR_HEIGHT = 6;
10
+ export declare const EDGE_SYMBOL_REACH = 8;
11
+ /** Replaces the contents of `root` with the rendered view. */
12
+ export declare function render(root: HTMLElement, model: TimelineModel): void;
13
+ /**
14
+ * Places each marker line: horizontally at its date on the first timeline's
15
+ * span, vertically from that timeline's bar top (or row top) to the last
16
+ * one's bar bottom (or row bottom). Being one element, it is drawn with
17
+ * the same width everywhere.
18
+ *
19
+ * Positions are measured in the viewport and converted using where a line
20
+ * placed at 0,0 actually lands, so borders and scrolling of the view (and
21
+ * how the browser treats them) don't matter.
22
+ */
23
+ export declare function layoutMarkerLines(root: HTMLElement): void;
24
+ /** Stops watching the view's layout (when the view is destroyed). */
25
+ export declare function unwatchLayout(root: HTMLElement): void;
@@ -0,0 +1,23 @@
1
+ import type { Scale } from "./types";
2
+ export interface Tick {
3
+ date: Date;
4
+ /** Position in the timeline's time span, 0 to 1. */
5
+ at: number;
6
+ label: string;
7
+ /**
8
+ * Running number of the period that starts at this tick, counted from a
9
+ * fixed date. Its parity shades periods alternately, the same way on every
10
+ * timeline whatever its start date.
11
+ */
12
+ serial: number;
13
+ }
14
+ /** Upper bound so a fine scale on a long span cannot create a huge DOM. */
15
+ export declare const MAX_TICKS = 1000;
16
+ /** Position of a date in the span, 0 at `start` and 1 at `finish`. Not clamped. */
17
+ export declare function fraction(date: Date, start: Date, finish: Date): number;
18
+ /** ISO 8601 week number. */
19
+ export declare function isoWeek(date: Date): number;
20
+ /** Running number of the scale period that holds `date`. */
21
+ export declare function periodSerial(date: Date, scale: Scale): number;
22
+ /** Tick marks for the time scale, from the first boundary at or after `start` up to `finish`. */
23
+ export declare function ticks(start: Date, finish: Date, scale: Scale): Tick[];
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SVG export. Rather than describing the layout a second time, this reads the
3
+ * rendered view: element boxes, measured text and computed colors. The result
4
+ * is plain SVG (rects, paths, lines, text) that matches what is on screen,
5
+ * including theming and dark mode. The view must be rendered and visible.
6
+ */
7
+ /** Builds an SVG document of the view rendered in `root`. */
8
+ export declare function toSVG(root: HTMLElement): string;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Public data format: what `parse()` accepts, what `serialize()` returns and
3
+ * what the REST API exchanges. Uses snake_case (see CLAUDE.md, Conventions).
4
+ */
5
+ export type Id = string | number;
6
+ export type Placement = "above" | "on" | "below";
7
+ export type Scale = "hour" | "day" | "week" | "month" | "year";
8
+ export type MilestoneSymbol = "diamond" | "circle" | "square" | "triangle" | "star";
9
+ export type NamePosition = "left" | "right";
10
+ /**
11
+ * Callout label position, in px, relative to the attachment point on the
12
+ * bar's edge: the middle of a task's span, or a milestone's date. `x` is to
13
+ * the label's left edge. `y` is to the label's edge nearest the bar, and
14
+ * also picks the side: `y > 0` is below the bar, `y ≤ 0` above it.
15
+ */
16
+ export interface Offset {
17
+ /** Px from the attachment point to the label's left edge. */
18
+ x: number;
19
+ y: number;
20
+ }
21
+ export interface ViewData {
22
+ /** Needed when a REST backend is configured. */
23
+ id?: Id;
24
+ /** Shown as a heading above the timelines. Empty for no heading. */
25
+ name: string;
26
+ /**
27
+ * `false` (default): each timeline's bar fills the full width, scaled to its
28
+ * own dates. `true`: all timelines share one time axis, from the earliest
29
+ * start to the latest finish, and each bar is drawn at its offset and width
30
+ * on it, so the same date lines up across timelines.
31
+ */
32
+ absolute?: boolean;
33
+ /** Unit of the time scale. Default: `day`. */
34
+ scale?: Scale;
35
+ /** Side of each timeline the name label is shown on. Default: `left`. */
36
+ name_position?: NamePosition;
37
+ /** Symbol used for milestones. Default: `diamond`. */
38
+ milestone_symbol?: MilestoneSymbol;
39
+ /** Show each timeline's start and finish dates, with "Start" and "Finish" captions, beside its bar. Default: `true`. */
40
+ show_dates?: boolean;
41
+ /**
42
+ * Clicking a task or timeline selects and highlights it, and fires
43
+ * `onTaskSelected` / `onTimelineSelected`. Default: `true`.
44
+ */
45
+ selection?: boolean;
46
+ }
47
+ /** A task's finish date is not stored: it is `start_date + duration`. */
48
+ export interface TaskData {
49
+ id: Id;
50
+ text: string;
51
+ start_date: string;
52
+ /** A whole number plus `h`, `d`, `w`, `m` or `y`. Missing or zero means milestone. */
53
+ duration?: string;
54
+ /** Overrides the view's `milestone_symbol` for this task. */
55
+ symbol?: MilestoneSymbol;
56
+ /**
57
+ * Any CSS color, such as `"#0a7f5a"` or `"rebeccapurple"`. Overrides the
58
+ * theme's `--tlv-task` (bars) or `--tlv-milestone` (symbols) for this task,
59
+ * on the bar and on its callout's edge marker.
60
+ */
61
+ color?: string;
62
+ }
63
+ /**
64
+ * Where a task appears on a timeline. The area follows from the fields:
65
+ * - no `callout_offset`: on the bar, on bar `line` (default 0);
66
+ * - `callout_offset` with `y > 0`: a callout below the bar;
67
+ * - `callout_offset` with `y ≤ 0`: a callout above the bar.
68
+ */
69
+ export interface TaskEntryData {
70
+ task: Id;
71
+ /** 0-based bar line, for an entry on the bar. Default: 0. Not allowed with `callout_offset`. */
72
+ line?: number;
73
+ /** Makes the entry a callout, and places its label (see `Offset`). */
74
+ callout_offset?: Offset;
75
+ }
76
+ /** Shows a (non-global) marker on this timeline. */
77
+ export interface MarkerEntryData {
78
+ marker: Id;
79
+ }
80
+ /** An entry is a task on the timeline, or a marker shown on it. */
81
+ export type EntryData = TaskEntryData | MarkerEntryData;
82
+ /**
83
+ * A date marker: a vertical line at `date` across the bars of the timelines
84
+ * that show it, with a label at its top or bottom end.
85
+ */
86
+ export interface MarkerData {
87
+ id: Id;
88
+ text: string;
89
+ /** ISO 8601: `YYYY-MM-DD` or `YYYY-MM-DDTHH:mm`. */
90
+ date: string;
91
+ /** `true`: shown on every timeline. `false` (default): only on timelines with a `{ marker: id }` entry. */
92
+ global?: boolean;
93
+ /** Any CSS color. Default: the theme's `--tlv-marker` (red). */
94
+ color?: string;
95
+ /**
96
+ * Places the label, like `callout_offset`: px from the line's end on the
97
+ * bar's edge to the label. `y ≤ 0`: above the topmost bar; `y > 0`: below
98
+ * the bottommost. Default: `{ x: 0, y: -16 }`.
99
+ */
100
+ marker_offset?: Offset;
101
+ }
102
+ export interface TimelineData {
103
+ id: Id;
104
+ name: string;
105
+ /** The timeline's time range. ISO 8601: `YYYY-MM-DD` or `YYYY-MM-DDTHH:mm`. */
106
+ start_date: string;
107
+ finish_date: string;
108
+ entries: EntryData[];
109
+ }
110
+ export interface TimelineViewData {
111
+ /** Server version, used for optimistic concurrency with the REST API. */
112
+ version?: number;
113
+ view: ViewData;
114
+ tasks: TaskData[];
115
+ timelines: TimelineData[];
116
+ /** Date markers. Optional. */
117
+ markers?: MarkerData[];
118
+ }
119
+ /** Body of `PATCH .../entries/{task_id}`, minus `version`. Send only what changes. */
120
+ export interface MovePatchData {
121
+ timeline_id?: Id;
122
+ placement?: Placement;
123
+ line?: number;
124
+ /** With placement `on`: insert a new bar line at `line` (0 to one past the last line); lines from there down shift by one. */
125
+ new_line?: boolean;
126
+ callout_offset?: Offset;
127
+ }
128
+ /** Response of a successful move. */
129
+ export interface MoveResultData {
130
+ version: number;
131
+ timelines: TimelineData[];
132
+ }
@@ -0,0 +1,173 @@
1
+ import type { Id, MovePatchData, Offset, TimelineViewData } from "./types";
2
+ export interface TimelineOptions {
3
+ /** Base URL of the REST backend. Without it, changes are only reported through events. */
4
+ api_url?: string;
5
+ /** Replacement for `fetch`, mainly for tests and mock backends. */
6
+ fetch?: typeof fetch;
7
+ }
8
+ export interface EntryMovedEvent {
9
+ timeline_id: Id;
10
+ task_id: Id;
11
+ patch: MovePatchData;
12
+ }
13
+ export interface MoveFailedEvent extends EntryMovedEvent {
14
+ error: unknown;
15
+ }
16
+ export interface MarkerMovedEvent {
17
+ marker_id: Id;
18
+ marker_offset: Offset;
19
+ }
20
+ export interface TimelineEvents {
21
+ /** A move was applied on screen. Fired before the REST request, if any, is sent. */
22
+ entry_moved: EntryMovedEvent;
23
+ /** A move was rejected, locally or by the server. The view shows the restored state. */
24
+ move_failed: MoveFailedEvent;
25
+ /**
26
+ * A marker's label was moved (dragged, or `setMarkerOffset`). Markers have
27
+ * no REST call yet, so saving the new `marker_offset` is up to the host.
28
+ */
29
+ marker_moved: MarkerMovedEvent;
30
+ }
31
+ type Handler<T> = (event: T) => void;
32
+ /**
33
+ * Handlers for `attachEvent`. Inside a handler written as a `function`, `this`
34
+ * is the TimelineView, with the same methods as the global `timeline`.
35
+ */
36
+ export interface TimelineHandlers {
37
+ /**
38
+ * A task bar, milestone or callout label was clicked (pressed and released
39
+ * without dragging). `id` is the task's id; `timelineId` the timeline it was
40
+ * clicked on, since a task can appear on several.
41
+ */
42
+ onTaskClick: (this: TimelineView, id: Id, e: MouseEvent, timelineId: Id) => void;
43
+ /**
44
+ * A timeline was clicked outside its tasks: its name, bar background, time
45
+ * scale, dates or callout areas.
46
+ */
47
+ onTimelineClick: (this: TimelineView, id: Id, e: MouseEvent) => void;
48
+ /** The selected tasks changed; `ids` are all selected tasks now, empty when none are. */
49
+ onTaskSelected: (this: TimelineView, ids: Id[]) => void;
50
+ /** The selected timelines changed; `ids` are all selected timelines now, empty when none are. */
51
+ onTimelineSelected: (this: TimelineView, ids: Id[]) => void;
52
+ }
53
+ export type TimelineEventName = keyof TimelineHandlers;
54
+ /** Thrown for moves queued behind one that failed; their changes were rolled back with it. */
55
+ export declare class MoveCancelledError extends Error {
56
+ constructor();
57
+ }
58
+ export declare class TimelineView {
59
+ readonly container: HTMLElement;
60
+ private model?;
61
+ private readonly api?;
62
+ private readonly listeners;
63
+ /** `attachEvent` handlers by event id. */
64
+ private readonly handlers;
65
+ private lastEventId;
66
+ /**
67
+ * Selection: tasks or timelines, never both at once. A selected task is
68
+ * highlighted wherever it appears.
69
+ */
70
+ private selected;
71
+ private readonly detachDrag;
72
+ /** REST requests run one at a time, in the order the moves were made. */
73
+ private queue;
74
+ private pending;
75
+ /** Bumped whenever local state is reset after a failure; queued requests from before are dropped. */
76
+ private generation;
77
+ constructor(container: string | HTMLElement, options?: TimelineOptions);
78
+ /** Loads data in the public format and renders it. */
79
+ parse(data: TimelineViewData): void;
80
+ /** Fetches a view from the REST backend and renders it. */
81
+ load(viewId: Id): Promise<void>;
82
+ /**
83
+ * The view as a standalone SVG document, as it looks on screen now. The
84
+ * view must be rendered in the page and visible, since the export reads
85
+ * its layout.
86
+ */
87
+ toSVG(): string;
88
+ /** The current data, in the same format `parse()` takes. */
89
+ serialize(): TimelineViewData;
90
+ on<K extends keyof TimelineEvents>(name: K, handler: Handler<TimelineEvents[K]>): () => void;
91
+ /**
92
+ * Subscribes to an event, such as `onTaskClick`. Returns an event id for
93
+ * `detachEvent`. Inside a `function` handler, `this` is this view.
94
+ */
95
+ attachEvent<K extends TimelineEventName>(name: K, handler: TimelineHandlers[K]): number;
96
+ /** Removes a handler added with `attachEvent`. Returns false if the id is unknown. */
97
+ detachEvent(eventId: number): boolean;
98
+ /** The selected tasks' ids, in the order they were selected; empty when timelines (or nothing) are selected. */
99
+ getSelectedTasks(): Id[];
100
+ /** The selected timelines' ids, in the order they were selected; empty when tasks (or nothing) are selected. */
101
+ getSelectedTimelines(): Id[];
102
+ /**
103
+ * Selects a task, highlighted wherever it appears. With `add`, it joins the
104
+ * tasks already selected (as Ctrl+Click does); otherwise it replaces the
105
+ * selection. Selecting a task always deselects timelines. Fires the selected
106
+ * events for what changed. Returns false when selection is turned off
107
+ * (`view.selection: false`).
108
+ */
109
+ selectTask(id: Id, add?: boolean): boolean;
110
+ /** Like `selectTask`, for a timeline. Selecting a timeline always deselects tasks. */
111
+ selectTimeline(id: Id, add?: boolean): boolean;
112
+ /** Deselects one task. Returns false if it wasn't selected. */
113
+ unselectTask(id: Id): boolean;
114
+ /** Deselects one timeline. Returns false if it wasn't selected. */
115
+ unselectTimeline(id: Id): boolean;
116
+ /** Deselects everything. */
117
+ clearSelection(): void;
118
+ /**
119
+ * Moves a marker's label, as dragging it does: `y ≤ 0` above the topmost
120
+ * bar showing the marker, `y > 0` below the bottommost. Fires `marker_moved`.
121
+ */
122
+ setMarkerOffset(markerId: Id, offset: Offset): void;
123
+ /** Moves a timeline one place up the stack. Returns false if it is already at the top. */
124
+ moveTimelineUp(id: Id): boolean;
125
+ /** Moves a timeline one place down the stack. Returns false if it is already at the bottom. */
126
+ moveTimelineDown(id: Id): boolean;
127
+ /**
128
+ * Moves a timeline `delta` places: negative is up, positive down. Returns
129
+ * false, changing nothing, past the top or bottom. The new order shows in
130
+ * `serialize()`; the REST API has no call for it yet.
131
+ */
132
+ moveTimeline(id: Id, delta: number): boolean;
133
+ /**
134
+ * Moves an entry: shows the move at once, fires `entry_moved` and, with a
135
+ * REST backend, sends the PATCH. Rolls back and fires `move_failed` if the
136
+ * move is invalid or the server rejects it.
137
+ */
138
+ moveEntry(timelineId: Id, taskId: Id, patch: MovePatchData): Promise<void>;
139
+ /** Removes the view's DOM and event listeners. */
140
+ destroy(): void;
141
+ private rollback;
142
+ private onDrop;
143
+ /** Calls every `attachEvent` handler for `name`, with this view as `this`. */
144
+ private callHandlers;
145
+ private onMarkerDrop;
146
+ private onEntryClick;
147
+ /** A click on a timeline outside its tasks, or on the view's heading. */
148
+ private onBackgroundClick;
149
+ /**
150
+ * Selection by clicking. Click: select only this item, or, if it is
151
+ * already selected, clear the selection. Ctrl+Click (Cmd+Click on a Mac):
152
+ * add or remove this item; on the other kind of item it starts a new
153
+ * selection with just this one.
154
+ */
155
+ private clickSelect;
156
+ private isSelected;
157
+ private select;
158
+ private unselect;
159
+ /**
160
+ * Sets the selection and updates the highlight. Fires `onTaskSelected`
161
+ * and/or `onTimelineSelected` for each kind whose selection changed (the
162
+ * kind being left reports an empty list).
163
+ */
164
+ private setSelection;
165
+ /** After new data: forgets selected tasks or timelines that are gone, or everything if selection is off. */
166
+ private dropStaleSelection;
167
+ /** Marks the selected tasks' elements, or the selected timelines, in the DOM. */
168
+ private applySelection;
169
+ private emit;
170
+ private requireModel;
171
+ private render;
172
+ }
173
+ export {};
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "ar-timeline-view",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.2",
4
+ "description": "A web-based timeline component for displaying and editing timelines.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "main": "./dist/timelineview.js",
11
+ "module": "./dist/timelineview.mjs",
12
+ "types": "./dist/types/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/types/index.d.ts",
16
+ "import": "./dist/timelineview.mjs",
17
+ "default": "./dist/timelineview.js"
18
+ },
19
+ "./timelineview.css": "./dist/timelineview.css"
20
+ },
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "vite build && tsc -p tsconfig.build.json && node scripts/export-examples.mjs",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "lint": "tsc --noEmit"
27
+ },
28
+ "devDependencies": {
29
+ "jsdom": "^31.0.0",
30
+ "puppeteer": "^25.16.0",
31
+ "typescript": "^7.0.2",
32
+ "vite": "^8.3.6",
33
+ "vitest": "^5.1.0"
34
+ }
35
+ }