@jaeungkim/gantt-chart 0.3.0 → 0.4.0

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -101
  3. package/dist/components/GanttBar.d.ts +23 -0
  4. package/dist/components/GanttChartHeader.d.ts +17 -0
  5. package/dist/components/GanttDependencyArrows.d.ts +15 -0
  6. package/dist/components/GanttDragGuides.d.ts +9 -0
  7. package/dist/components/GanttGridSplitter.d.ts +14 -0
  8. package/dist/components/GanttMarkers.d.ts +18 -0
  9. package/dist/components/GanttTaskGrid.d.ts +42 -0
  10. package/dist/components/ScaleSelector.d.ts +11 -0
  11. package/dist/constants/gantt.d.ts +47 -0
  12. package/dist/core/calendar.d.ts +45 -0
  13. package/dist/core/criticalPath.d.ts +67 -0
  14. package/dist/core/dates.d.ts +32 -0
  15. package/dist/core/index.d.ts +16 -0
  16. package/dist/core/scheduling.d.ts +104 -0
  17. package/dist/core/tree.d.ts +42 -0
  18. package/dist/core/types.d.ts +76 -0
  19. package/dist/gantt-chart.css +1 -1
  20. package/dist/hooks/useGanttBarDrag.d.ts +35 -0
  21. package/dist/hooks/useGanttDrawCreate.d.ts +31 -0
  22. package/dist/hooks/useGanttExportApi.d.ts +30 -0
  23. package/dist/hooks/useGanttHistoryApi.d.ts +28 -0
  24. package/dist/hooks/useGanttLinkDrag.d.ts +31 -0
  25. package/dist/hooks/useGanttProgressDrag.d.ts +16 -0
  26. package/dist/hooks/useGanttRowDrag.d.ts +33 -0
  27. package/dist/hooks/useGanttScrollApi.d.ts +63 -0
  28. package/dist/hooks/useGanttSelectors.d.ts +23 -0
  29. package/dist/hooks/useGanttVirtualization.d.ts +32 -0
  30. package/dist/hooks/useResolvedTheme.d.ts +15 -0
  31. package/dist/index.cjs +4 -0
  32. package/dist/index.d.cts +13 -0
  33. package/dist/index.d.ts +13 -51
  34. package/dist/index.js +5819 -0
  35. package/dist/pages/Gantt.d.ts +306 -0
  36. package/dist/stores/context.d.ts +10 -0
  37. package/dist/stores/store.d.ts +118 -0
  38. package/dist/types/gantt.d.ts +283 -0
  39. package/dist/types/task.d.ts +111 -0
  40. package/dist/utils/a11y.d.ts +141 -0
  41. package/dist/utils/arrowPath.d.ts +66 -0
  42. package/dist/utils/dependency.d.ts +42 -0
  43. package/dist/utils/grouping.d.ts +81 -0
  44. package/dist/utils/headerUtils.d.ts +6 -0
  45. package/dist/utils/history.d.ts +56 -0
  46. package/dist/utils/i18n.d.ts +16 -0
  47. package/dist/utils/mutation.d.ts +47 -0
  48. package/dist/utils/pngExport.d.ts +67 -0
  49. package/dist/utils/pointerGesture.d.ts +31 -0
  50. package/dist/utils/rowDrag.d.ts +69 -0
  51. package/dist/utils/timeline.d.ts +161 -0
  52. package/dist/utils/transformData.d.ts +15 -0
  53. package/dist/utils/viewport.d.ts +95 -0
  54. package/package.json +47 -32
  55. package/dist/index.cjs.js +0 -4
  56. package/dist/index.es.js +0 -2357
  57. package/dist/readmeImg.png +0 -0
@@ -0,0 +1,283 @@
1
+ import { Dayjs } from 'dayjs';
2
+ import { CSSProperties, MouseEvent as ReactMouseEvent, MouseEventHandler, PointerEventHandler, ReactNode } from 'react';
3
+ import { SchedulingPolicy, WorkingCalendar } from 'core';
4
+ import { Task, TaskTransformed } from './task';
5
+ /**
6
+ * What the drag handlers need to reschedule successors.
7
+ * Assembled once in Gantt and handed down, so the engine's configuration reaches the
8
+ * drag hook without every component in between knowing about it.
9
+ */
10
+ export interface GanttScheduling {
11
+ policy: SchedulingPolicy;
12
+ calendar: WorkingCalendar;
13
+ hierarchy: boolean;
14
+ onCycle?: (taskIds: string[]) => void;
15
+ }
16
+ /** Theme type - 'light', 'dark', or 'system' (follows the OS setting) */
17
+ export type GanttTheme = 'light' | 'dark' | 'system';
18
+ export type GanttScaleKey = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
19
+ /** Unit the top header row groups by ('quarter' has no dayjs equivalent - see core/dates) */
20
+ export type GanttLabelUnit = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
21
+ /** What a bar drag is doing - moving the whole bar, or resizing one edge */
22
+ export type GanttDragMode = 'bar' | 'left' | 'right';
23
+ /** Window a bar may be dragged within - either end may be left open */
24
+ export interface GanttDragBounds {
25
+ min?: Dayjs;
26
+ max?: Dayjs;
27
+ }
28
+ /** Fixed timeline window, replacing the auto-fit to the task dates */
29
+ export interface GanttVisibleRange {
30
+ start?: Dayjs;
31
+ end?: Dayjs;
32
+ }
33
+ export interface GanttScaleConfig {
34
+ labelUnit: GanttLabelUnit;
35
+ tickUnit: 'minute' | 'hour' | 'day' | 'week' | 'month';
36
+ unitPerTick: number;
37
+ dragStepUnit: 'minute' | 'hour' | 'day' | 'week';
38
+ dragStepAmount: number;
39
+ basePxPerDragStep: number;
40
+ formatTickLabel?: (date: Dayjs) => string;
41
+ formatHeaderLabel?: (date: Dayjs) => string;
42
+ }
43
+ /**
44
+ * Replaces the generated labels of one scale
45
+ * Every entry is optional - whatever is left out keeps the built-in (or locale) label
46
+ */
47
+ export interface GanttScaleFormat {
48
+ /** Bottom header row - one label per tick */
49
+ tick?: (date: Dayjs) => string;
50
+ /** Top header row - one label per group */
51
+ header?: (date: Dayjs) => string;
52
+ /** Drag tooltip and drag guide label */
53
+ tooltip?: (date: Dayjs) => string;
54
+ }
55
+ /** Per-scale label overrides, e.g. `{ quarter: { header: (d) => ... } }` */
56
+ export type GanttFormatOverrides = Partial<Record<GanttScaleKey, GanttScaleFormat>>;
57
+ /** Everything that decides how a date turns into a label */
58
+ export interface GanttLocaleOptions {
59
+ /** BCP 47 tag handed to `Intl.DateTimeFormat`, e.g. `'ko-KR'` (default: the built-in English labels) */
60
+ locale?: string;
61
+ /** Per-scale label overrides - win over both the locale and the built-in labels */
62
+ formats?: GanttFormatOverrides;
63
+ /** First day of the week, 0 = Sunday .. 6 = Saturday (only affects week grouping) */
64
+ firstDayOfWeek?: number;
65
+ }
66
+ /** The three label formatters resolved for one scale */
67
+ export interface GanttFormatters {
68
+ tick: (date: Dayjs) => string;
69
+ header: (date: Dayjs) => string;
70
+ tooltip: (date: Dayjs) => string;
71
+ }
72
+ export interface GanttBottomRowCell {
73
+ startDate: Dayjs;
74
+ widthPx: number;
75
+ }
76
+ export interface GanttTopHeaderGroup {
77
+ startDate: Dayjs;
78
+ widthPx: number;
79
+ label: string;
80
+ }
81
+ /**
82
+ * A column of the task grid on the left
83
+ *
84
+ * Every header label and cell body comes from here - the library hardcodes no strings.
85
+ * The first column is the tree column, so indentation and the expander attach to it.
86
+ */
87
+ export interface GanttColumn {
88
+ /** React key, and the task field read when there is no render */
89
+ key: string;
90
+ /** What to draw in the header (a string or an element) */
91
+ header: ReactNode;
92
+ /** Column width in px (default 120) */
93
+ width?: number;
94
+ /** Cell renderer - without it, task[key] is shown as a string */
95
+ render?: (task: TaskTransformed) => ReactNode;
96
+ }
97
+ /**
98
+ * How rows are grouped into swimlanes
99
+ *
100
+ * A string reads that field off the task; a function returns the group value
101
+ * itself, which doubles as the header label. Anything empty, null or undefined
102
+ * lands in the "Ungrouped" bucket.
103
+ */
104
+ export type GanttGroupBy = string | ((task: TaskTransformed) => string | null | undefined);
105
+ /** Anything the marker/band props accept as a date */
106
+ export type GanttDateInput = string | Date | Dayjs;
107
+ /** The rendered timeline range, as reported by `onRangeChange` */
108
+ export interface GanttDateRange {
109
+ start: Dayjs;
110
+ end: Dayjs;
111
+ }
112
+ /**
113
+ * How many extra ticks the rendered range carries beyond the tasks' own span
114
+ *
115
+ * Grows as the user scrolls past an edge; measured in ticks of the current scale, so it
116
+ * is reset whenever the scale changes.
117
+ */
118
+ export interface GanttRangeExtension {
119
+ before: number;
120
+ after: number;
121
+ }
122
+ /** A labelled vertical line at one date - deadlines, releases, and the built-in today line */
123
+ export interface GanttMarker {
124
+ /** React key (default: the date) */
125
+ id?: string;
126
+ date: GanttDateInput;
127
+ /** Text shown at the top of the line - omitted, the line is drawn bare */
128
+ label?: string;
129
+ /** Extra class on the marker element */
130
+ className?: string;
131
+ /** Line color - any CSS color, overrides the class and the theme default */
132
+ color?: string;
133
+ /**
134
+ * Turn the marker into a warning (`data-warning="true"`) once a task ends past its date
135
+ *
136
+ * Checks every task, or only `taskIds` when that is given.
137
+ */
138
+ warnOnOverrun?: boolean;
139
+ /** Limits `warnOnOverrun` to these tasks */
140
+ taskIds?: string[];
141
+ }
142
+ /** A shaded band covering a date range - sprints, phases, freezes */
143
+ export interface GanttRangeBand {
144
+ /** React key (default: the start date) */
145
+ id?: string;
146
+ startDate: GanttDateInput;
147
+ endDate: GanttDateInput;
148
+ /** Text shown at the top of the band */
149
+ label?: string;
150
+ /** Extra class on the band element */
151
+ className?: string;
152
+ /** Fill color - any CSS color, overrides the class and the theme default */
153
+ color?: string;
154
+ }
155
+ /**
156
+ * What a row drag committed - everything needed to persist the move
157
+ *
158
+ * Returning `false` from the callback cancels the drop: nothing is written to the chart and
159
+ * `onTasksChange` does not fire.
160
+ */
161
+ export interface GanttReorderChange {
162
+ /** The moved task, already carrying its new parentId and sequence */
163
+ task: Task;
164
+ /** The new parent (null = root) */
165
+ parentId: string | null;
166
+ /** The parent the task had in the incoming data, untouched by normalization */
167
+ previousParentId: string | null;
168
+ /** Zero-based position among the new parent's children */
169
+ index: number;
170
+ /** The moved task's new dotted sequence */
171
+ sequence: string;
172
+ /** The whole updated array - the same one onTasksChange receives */
173
+ tasks: Task[];
174
+ }
175
+ export interface GanttDragOffset {
176
+ offsetX: number;
177
+ offsetWidth: number;
178
+ offsetStartDate: Dayjs;
179
+ offsetEndDate: Dayjs;
180
+ }
181
+ /** What a gesture is about to write */
182
+ export type GanttChangeType = 'move' | 'resize' | 'progress';
183
+ /**
184
+ * The mutation a finished gesture wants to commit
185
+ *
186
+ * Handed to `onBeforeTaskChange` before anything is written, so a host can send it to a
187
+ * server and answer with a veto.
188
+ */
189
+ export interface GanttTaskChange {
190
+ type: GanttChangeType;
191
+ /** The bar the user grabbed */
192
+ task: Task;
193
+ /** Only the tasks this gesture rewrites - dragging a summary bar carries its whole subtree */
194
+ changedTasks: Task[];
195
+ /** Those same tasks as they were before the gesture, in the same order */
196
+ previousTasks: Task[];
197
+ /** The full array the chart would hand to `onTasksChange` */
198
+ tasks: Task[];
199
+ /** Which edge moved - `resize` only */
200
+ edge?: 'start' | 'end';
201
+ }
202
+ /**
203
+ * Runs before a gesture is committed and can cancel it
204
+ *
205
+ * Returning `false`, a promise resolving to `false`, or a rejected promise rolls the bar
206
+ * back to where it started. Anything else commits. While the promise is pending the bar
207
+ * stays where it was dropped, so the UI never blocks on the round trip.
208
+ */
209
+ export type GanttBeforeChangeHandler = (change: GanttTaskChange) => boolean | void | Promise<boolean | void>;
210
+ /** Props handed to a `renderBar` override */
211
+ export interface GanttBarRenderProps {
212
+ task: TaskTransformed;
213
+ /** Left offset from the timeline origin in px, live drag offset included */
214
+ left: number;
215
+ /** Rendered bar width in px, live drag offset included */
216
+ width: number;
217
+ /** Row height available to the bar in px */
218
+ height: number;
219
+ /** Progress 0-100, or null when the task has none */
220
+ progress: number | null;
221
+ scale: GanttScaleKey;
222
+ isMilestone: boolean;
223
+ isSummary: boolean;
224
+ isDragging: boolean;
225
+ isSelected: boolean;
226
+ /**
227
+ * Spread onto the root node of the replacement
228
+ *
229
+ * Carries the positioning style plus the drag, click and double-click handlers, so a
230
+ * custom bar keeps behaving like the default one.
231
+ */
232
+ barProps: {
233
+ style: CSSProperties;
234
+ onPointerDown: PointerEventHandler<HTMLDivElement>;
235
+ onClick: MouseEventHandler<HTMLDivElement>;
236
+ onDoubleClick: MouseEventHandler<HTMLDivElement>;
237
+ };
238
+ }
239
+ export type GanttBarRenderer = (props: GanttBarRenderProps) => ReactNode;
240
+ /** Why a tooltip is showing */
241
+ export type GanttTooltipReason = 'hover' | 'move' | 'resize' | 'progress';
242
+ /** Props handed to a `renderTooltip` override */
243
+ export interface GanttTooltipRenderProps {
244
+ task: TaskTransformed;
245
+ reason: GanttTooltipReason;
246
+ /** Start being previewed - the live drag value while a gesture is running */
247
+ startDate: Dayjs;
248
+ /** End being previewed - equal to `startDate` for a milestone */
249
+ endDate: Dayjs;
250
+ /** End minus start in milliseconds */
251
+ durationMs: number;
252
+ /** Progress 0-100, or null when the task has none */
253
+ progress: number | null;
254
+ scale: GanttScaleKey;
255
+ }
256
+ export type GanttTooltipRenderer = (props: GanttTooltipRenderProps) => ReactNode;
257
+ /** Props handed to a `renderHeaderCell` override */
258
+ export interface GanttHeaderCellRenderProps {
259
+ /** `'top'` is a merged group label, `'bottom'` a single time tick */
260
+ row: 'top' | 'bottom';
261
+ date: Dayjs;
262
+ /** The label the default header would print */
263
+ label: string;
264
+ width: number;
265
+ scale: GanttScaleKey;
266
+ /** Spread onto the root node of the replacement to keep the header layout intact */
267
+ cellProps: {
268
+ className: string;
269
+ style: CSSProperties;
270
+ };
271
+ }
272
+ export type GanttHeaderCellRenderer = (props: GanttHeaderCellRenderProps) => ReactNode;
273
+ /** Everything a bar needs from the chart's props - passed as one object rather than eight */
274
+ export interface GanttBarOptions {
275
+ onTasksChange?: (updatedTasks: Task[]) => void;
276
+ onBeforeTaskChange?: GanttBeforeChangeHandler;
277
+ onTaskClick?: (task: TaskTransformed, event: ReactMouseEvent) => void;
278
+ onTaskDoubleClick?: (task: TaskTransformed, event: ReactMouseEvent) => void;
279
+ renderBar?: GanttBarRenderer;
280
+ renderTooltip?: GanttTooltipRenderer;
281
+ /** Hover and drag tooltips, on unless explicitly turned off */
282
+ showTooltip?: boolean;
283
+ }
@@ -0,0 +1,111 @@
1
+ import { Task, TaskDependency } from '../core/types';
2
+ export { isMilestoneTask, normalizeProgress } from '../core/types';
3
+ export type { DependencyType, Task, TaskDependency, TaskType, } from '../core/types';
4
+ /**
5
+ * Chart-wide interaction settings
6
+ *
7
+ * Every field is optional, and a task's own field of the same name wins over it.
8
+ * With nothing set, every gesture is allowed and there are no drag bounds - except
9
+ * drawing new tasks, which needs an `onTaskCreate` callback to go anywhere.
10
+ */
11
+ export interface GanttInteractionConfig {
12
+ readOnly?: boolean;
13
+ allowMove?: boolean;
14
+ allowResize?: boolean;
15
+ allowProgressChange?: boolean;
16
+ allowLinkCreate?: boolean;
17
+ allowLinkDelete?: boolean;
18
+ allowTaskCreate?: boolean;
19
+ minDate?: string;
20
+ maxDate?: string;
21
+ }
22
+ export interface ResolvedTaskInteraction {
23
+ canMove: boolean;
24
+ canResize: boolean;
25
+ canChangeProgress: boolean;
26
+ canCreateLink: boolean;
27
+ canDeleteLink: boolean;
28
+ minDate?: string;
29
+ maxDate?: string;
30
+ }
31
+ /**
32
+ * Resolves what one task allows, most specific setting first:
33
+ *
34
+ * `task.allowX` > `task.readOnly` > `config.allowX` > `config.readOnly` > allowed
35
+ *
36
+ * A capability flag always beats a blanket `readOnly` at the same level, so
37
+ * `readOnly` on the chart plus `allowProgressChange: true` on one task means
38
+ * "frozen except that one progress bar".
39
+ *
40
+ * Two structural rules are not flags and cannot be flagged back on, because the
41
+ * gesture has nowhere to write to: milestones are never resizable (they are a
42
+ * single point), and summary rows are never resizable and have no draggable
43
+ * progress (both are rolled up from their children, so an edit would snap back).
44
+ * Moving a summary is fine - it carries its whole subtree.
45
+ */
46
+ export declare function resolveTaskInteraction(task: Pick<Task, 'type' | 'readOnly' | 'allowMove' | 'allowResize' | 'allowProgressChange' | 'allowLinkCreate' | 'allowLinkDelete' | 'minDate' | 'maxDate'> & {
47
+ isSummary?: boolean;
48
+ }, config?: GanttInteractionConfig): ResolvedTaskInteraction;
49
+ /**
50
+ * Whether drawing a new task on empty timeline space is allowed
51
+ * Chart-wide only - the gesture starts on a row, not on a task
52
+ */
53
+ export declare function canCreateTasks(config?: GanttInteractionConfig): boolean;
54
+ /**
55
+ * CSS custom properties a colored bar sets
56
+ *
57
+ * Every value is a fallback for a theme token, so an empty object means the CSS
58
+ * defaults keep deciding - a task without a color renders exactly as before.
59
+ */
60
+ export interface TaskColorVars {
61
+ '--gantt-bar-color'?: string;
62
+ '--gantt-bar-color-hover'?: string;
63
+ '--gantt-progress-color'?: string;
64
+ }
65
+ /**
66
+ * Resolves a task's color into the variables the stylesheet reads
67
+ *
68
+ * Precedence: the task's own `color` wins; a missing or blank one resolves to nothing,
69
+ * which leaves `--gantt-bar-bg` / `--gantt-progress-bg` (and any host override of them)
70
+ * in charge. The progress fill and the hover shade are always derived from the bar color
71
+ * rather than read from a token, so a colored bar never mixes in the theme gray.
72
+ */
73
+ export declare function resolveTaskColors(color: string | undefined): TaskColorVars;
74
+ export interface TaskTransformed extends Task {
75
+ barLeft: number;
76
+ barWidth: number;
77
+ depth: number;
78
+ order: number;
79
+ originalOrder: number;
80
+ /**
81
+ * A summary row with children (true only when hierarchy is on)
82
+ *
83
+ * Its start/end are recomputed from the children, so resizing and progress editing are
84
+ * disabled and dragging the bar moves the whole subtree.
85
+ */
86
+ isSummary?: boolean;
87
+ dependencies?: TaskDependency[];
88
+ /** Baseline bar geometry - present only when the task carries baseline dates */
89
+ baselineLeft?: number;
90
+ baselineWidth?: number;
91
+ /** CPM outputs - present only while the `criticalPath` prop is on (read-only) */
92
+ earlyStart?: string;
93
+ earlyFinish?: string;
94
+ lateStart?: string;
95
+ lateFinish?: string;
96
+ totalSlack?: number;
97
+ freeSlack?: number;
98
+ critical?: boolean;
99
+ /** Duration in calendar days, or working days when the working-day calendar is on */
100
+ duration?: number;
101
+ }
102
+ export interface RenderedDependency extends TaskDependency {
103
+ /** Id of the successor that owns this dependency (`targetId` is the predecessor) */
104
+ sourceId: string;
105
+ fromX: number;
106
+ fromY: number;
107
+ toX: number;
108
+ toY: number;
109
+ /** True when this link sits on the critical path */
110
+ critical?: boolean;
111
+ }
@@ -0,0 +1,141 @@
1
+ import { Dayjs } from 'dayjs';
2
+ import { GanttDragMode, GanttScaleKey } from '../types/gantt';
3
+ import { GanttInteractionConfig, Task, TaskTransformed } from '../types/task';
4
+ import { GanttRow } from './grouping';
5
+ /** How much one `+`/`-` press moves the progress, in percentage points */
6
+ export declare const PROGRESS_STEP = 5;
7
+ /** Which cell of which row has the roving tabindex */
8
+ export interface GanttFocus {
9
+ row: number;
10
+ col: number;
11
+ }
12
+ /** What one row offers the keyboard - all the navigation math needs */
13
+ export interface GanttKeyboardRow {
14
+ /** Number of focusable cells in the row */
15
+ cells: number;
16
+ /** Cell index the bars start at (equal to `cells` when the row has none) */
17
+ firstBarCell: number;
18
+ /** Whether the row can be expanded/collapsed at all */
19
+ expandable: boolean;
20
+ /** Current state of an expandable row */
21
+ expanded: boolean;
22
+ }
23
+ export type GanttKeyAction =
24
+ /** Move the roving tabindex */
25
+ {
26
+ kind: "focus";
27
+ focus: GanttFocus;
28
+ }
29
+ /** Expand or collapse the row */
30
+ | {
31
+ kind: "toggle";
32
+ row: number;
33
+ col: number;
34
+ }
35
+ /** Enter/Space on a row that cannot be expanded */
36
+ | {
37
+ kind: "activate";
38
+ row: number;
39
+ col: number;
40
+ } | {
41
+ kind: "delete";
42
+ row: number;
43
+ col: number;
44
+ }
45
+ /** Move or resize by whole drag steps */
46
+ | {
47
+ kind: "nudge";
48
+ row: number;
49
+ col: number;
50
+ mode: GanttDragMode;
51
+ steps: number;
52
+ } | {
53
+ kind: "progress";
54
+ row: number;
55
+ col: number;
56
+ delta: number;
57
+ };
58
+ /** The parts of a keyboard event the resolver reads */
59
+ export interface GanttKeyEvent {
60
+ key: string;
61
+ altKey?: boolean;
62
+ shiftKey?: boolean;
63
+ ctrlKey?: boolean;
64
+ metaKey?: boolean;
65
+ }
66
+ /**
67
+ * The single keyboard map of the chart
68
+ *
69
+ * Pure on purpose: the component only has to run whatever comes back, so the
70
+ * whole map - including the editing shortcuts - is testable without a DOM.
71
+ *
72
+ * - arrows move between cells and rows, Home/End jump within the row and (with
73
+ * ctrl/meta) to the first/last row
74
+ * - on the first cell of an expandable row, Right expands and Left collapses
75
+ * before either moves, the usual treegrid behavior
76
+ * - `alt` turns the horizontal arrows into a move, `shift` into an end-edge
77
+ * resize and `alt+shift` into a start-edge resize, all in whole drag steps
78
+ *
79
+ * Returns null for a key the chart does not handle, so the event is left alone.
80
+ */
81
+ export declare function resolveKeyboardAction(event: GanttKeyEvent, focus: GanttFocus, rows: GanttKeyboardRow[]): GanttKeyAction | null;
82
+ /** The task a focused cell acts on - the row's first when the focus is on a list cell */
83
+ export declare function taskAtFocus(row: GanttRow | undefined, col: number, firstBarCell: number): TaskTransformed | undefined;
84
+ /**
85
+ * The bar's spoken name, e.g. "Design phase, Mar 3 to Mar 14, 40% complete"
86
+ *
87
+ * `format` is the scale's own tooltip formatter, so the label is localized by the
88
+ * same setting as everything else on the chart.
89
+ */
90
+ export declare function formatTaskAriaLabel(task: Pick<TaskTransformed, "name" | "startDate" | "endDate" | "type" | "isSummary">, format: (date: Dayjs) => string, progress?: number | null): string;
91
+ /** ARIA attributes of a treegrid row - spread straight onto the element */
92
+ export interface GanttRowAria {
93
+ role: "row";
94
+ "aria-level": number;
95
+ "aria-posinset": number;
96
+ "aria-setsize": number;
97
+ "aria-rowindex": number;
98
+ "aria-expanded"?: boolean;
99
+ /**
100
+ * The bars of the row
101
+ *
102
+ * They live in the timeline, a different DOM subtree from the task list, so
103
+ * `aria-owns` is what makes the two panes one widget instead of two.
104
+ */
105
+ "aria-owns"?: string;
106
+ }
107
+ export declare function rowAriaProps(row: GanttRow, rowIndex: number, options: {
108
+ /** 1 when a column header row sits above the data rows, otherwise 0 */
109
+ headerOffset: number;
110
+ expandable: boolean;
111
+ expanded: boolean;
112
+ /** Element ids of the bars belonging to this row */
113
+ ownedIds?: string[];
114
+ }): GanttRowAria;
115
+ /**
116
+ * Moves or resizes a task by whole drag steps, the keyboard equivalent of a drag
117
+ *
118
+ * Everything a drag enforces is enforced here: `resolveTaskInteraction` decides
119
+ * whether the gesture is allowed at all (so a read-only chart cannot be edited by
120
+ * keyboard either), a summary carries its subtree, and each moving task's own
121
+ * min/max window clamps the shared delta.
122
+ *
123
+ * Returns the updated task array, or null when nothing may or did change - the
124
+ * caller then fires no change event.
125
+ */
126
+ export declare function nudgeTaskDates(rawTasks: Task[], target: TaskTransformed, mode: GanttDragMode, steps: number, scaleKey: GanttScaleKey, interaction?: GanttInteractionConfig): Task[] | null;
127
+ /**
128
+ * Steps a task's progress, the keyboard equivalent of dragging the handle
129
+ *
130
+ * Returns null when progress editing is not allowed or the value would not move
131
+ * (already at 0 or 100).
132
+ */
133
+ export declare function stepTaskProgress(rawTasks: Task[], target: TaskTransformed, delta: number, interaction?: GanttInteractionConfig): Task[] | null;
134
+ /**
135
+ * Removes a task and everything under it
136
+ *
137
+ * The subtree goes too, hierarchy on or off - leaving orphans behind would move
138
+ * them to the root instead of deleting what the user asked for. Returns null
139
+ * when the task is read-only or is not in the data.
140
+ */
141
+ export declare function deleteTask(rawTasks: Task[], target: TaskTransformed, interaction?: GanttInteractionConfig): Task[] | null;
@@ -0,0 +1,66 @@
1
+ import { RenderedDependency, TaskTransformed } from '../types/task';
2
+ import { LinkAnchor } from './dependency';
3
+ /** Live offset of the task being dragged (0 when it is not being dragged) */
4
+ export interface DragOffset {
5
+ offsetX: number;
6
+ offsetWidth: number;
7
+ }
8
+ /**
9
+ * Returns the path string for an SVG <path> element for various dependency types.
10
+ * @param dependencyType - One of 'FS', 'FF', 'SF', 'SS' (with a fallback).
11
+ * @param startX - Starting X coordinate.
12
+ * @param startY - Starting Y coordinate.
13
+ * @param endX - Ending X coordinate.
14
+ * @param endY - Ending Y coordinate.
15
+ */
16
+ export declare function getSmartGanttPath(dependencyType: string, startX: number, startY: number, endX: number, endY: number): string;
17
+ /**
18
+ * Where one end of a task's bar sits, in timeline content coordinates
19
+ * The connector dots and the drag preview line have to land on the same points the
20
+ * committed arrow will use.
21
+ */
22
+ export declare function anchorPoint(task: TaskTransformed, anchor: LinkAnchor, offset?: DragOffset): {
23
+ x: number;
24
+ y: number;
25
+ };
26
+ /**
27
+ * Computes the arrow coordinates for a single dependency.
28
+ * targetTask is the predecessor, sourceTask the successor that owns the dependency.
29
+ * Returns null for an unknown dependency type (only that arrow is skipped).
30
+ */
31
+ export declare function calculateArrowCoords(sourceTask: TaskTransformed, targetTask: TaskTransformed, sourceOffset: DragOffset, targetOffset: DragOffset, depType: string): {
32
+ fromX: number;
33
+ fromY: number;
34
+ toX: number;
35
+ toY: number;
36
+ } | null;
37
+ /**
38
+ * Index for looking up tasks by id.
39
+ *
40
+ * Built once whenever the task array changes and then reused - scanning the array
41
+ * for every dependency costs time proportional to the square of the task count,
42
+ * and that cost would be paid again on every drag frame.
43
+ */
44
+ export declare function buildTaskIndex(transformedTasks: TaskTransformed[]): Map<string, TaskTransformed>;
45
+ /** Visible area used for arrow culling */
46
+ export interface ArrowViewport {
47
+ /** Vertical visible range (in row-virtualization terms, px) */
48
+ topPx: number;
49
+ bottomPx: number;
50
+ /** Horizontal visibility - reuses the column virtualization's bar visibility check */
51
+ isBarVisible: (left: number, width: number) => boolean;
52
+ }
53
+ /**
54
+ * Decides whether an arrow overlaps the visible area.
55
+ *
56
+ * It looks at the bounding box of the two endpoints, so a line is still drawn when
57
+ * both ends are off-screen but it crosses the viewport. The elbowed path runs a
58
+ * little past the endpoints, hence the slack.
59
+ */
60
+ export declare function isArrowVisible(dep: Pick<RenderedDependency, "fromX" | "fromY" | "toX" | "toY">, viewport: ArrowViewport): boolean;
61
+ /**
62
+ * Builds the dependency array.
63
+ * The index doubles as the iteration source and the lookup table
64
+ * (insertion order = task order).
65
+ */
66
+ export declare function buildDependencies(taskById: Map<string, TaskTransformed>, liveOffsets: Record<string, DragOffset>, criticalLinkIds?: Set<string>): RenderedDependency[];
@@ -0,0 +1,42 @@
1
+ import { DependencyType, Task, TaskDependency } from '../types/task';
2
+ /** Which end of a bar a link gesture grabbed */
3
+ export type LinkAnchor = "start" | "end";
4
+ /** Why a proposed link cannot be created */
5
+ export type LinkRejection = "self" | "duplicate" | "cycle";
6
+ /** The minimum a task needs for the dependency math - TaskTransformed fits as-is */
7
+ type DependencyNode = Pick<Task, "id"> & {
8
+ dependencies?: TaskDependency[];
9
+ };
10
+ /**
11
+ * Dependency type from the two ends a drag connected
12
+ *
13
+ * The bar the drag starts on is the predecessor and the bar it is dropped on the
14
+ * successor, so the first letter is the predecessor's end and the second the
15
+ * successor's: end -> start is FS, start -> start SS, end -> end FF, start -> end SF.
16
+ */
17
+ export declare function linkTypeFromAnchors(from: LinkAnchor, to: LinkAnchor): DependencyType;
18
+ /**
19
+ * Checks a proposed link before it is committed - null means it may be created
20
+ *
21
+ * `task.dependencies` lists that task's predecessors, so the new entry lands on the
22
+ * successor and points at the predecessor.
23
+ *
24
+ * The cycle check walks the predecessor chain up from the proposed predecessor:
25
+ * reaching the successor means the link would close a loop. Every node walked is
26
+ * recorded, so a cycle already present in the data ends the walk instead of spinning
27
+ * (the same guard `buildTaskTree` uses for the parentId chain).
28
+ */
29
+ export declare function validateDependency(tasks: DependencyNode[], predecessorId: string, successorId: string): LinkRejection | null;
30
+ /** Human-readable reason, shown on the drag preview */
31
+ export declare const LINK_REJECTION_LABEL: Record<LinkRejection, string>;
32
+ /**
33
+ * The tasks with one dependency added to the successor
34
+ * Returns the same array when the successor is not in the data
35
+ */
36
+ export declare function addDependency(tasks: Task[], predecessorId: string, successorId: string, type: DependencyType): Task[];
37
+ /**
38
+ * The tasks with one dependency removed from the successor
39
+ * Returns the same array when there is nothing to remove
40
+ */
41
+ export declare function removeDependency(tasks: Task[], predecessorId: string, successorId: string): Task[];
42
+ export {};