apexgantt 3.10.3 → 3.11.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.
package/README.md CHANGED
@@ -70,8 +70,10 @@ The layout can be configured by passing a second argument to `ApexGantt` with th
70
70
  | `theme` | `'light' \| 'dark'` | `'light'` | Built-in color theme preset. |
71
71
  | `width` | `number \| string` | `'100%'` | Width of the chart container. |
72
72
  | `height` | `number \| string` | `500` | Height of the chart container. |
73
- | `viewMode` | `ViewMode` | `ViewMode.Month` | Timeline granularity: `Day`, `Week`, `Month`, `Quarter`, `Year`. |
74
- | `inputDateFormat` | `string` | `'MM-DD-YYYY'` | dayjs-compatible format used to parse `startTime` / `endTime` values. |
73
+ | `pixelsPerDay` | `number` | _auto-fit_ | Continuous zoom level. The header tier (year/quarter/month/week/day/hour/minute) is auto-picked from this value. Reference points: `0.5` year, `1.6` ≈ quarter, `4.9` ≈ month, `25.7` ≈ week, `80` = day. Bounds clamp to roughly `[0.25, 1280]`. **When omitted**, the chart auto-fits the data span into the visible timeline area on first render; supply an explicit value (or zoom via the toolbar) to take control. |
74
+ | `snapUnit` | `'day' \| 'hour' \| 'minute'` | `'day'` | Granularity at which task drag, resize, and inline edits snap. Independent of timeline header tier. |
75
+ | `snapValue` | `number` | `1` | Multiplier applied to `snapUnit`, e.g. `snapUnit: 'minute', snapValue: 15` → 15-min steps. |
76
+ | `inputDateFormat` | `string` | `'MM-DD-YYYY'` | dayjs-compatible format used to parse `startTime` / `endTime` values. Include time tokens (e.g. `'YYYY-MM-DD HH:mm'`) to enable sub-day editing — inline-edit inputs switch to `datetime-local` automatically. |
75
77
  | `canvasStyle` | `string` | `''` | Arbitrary CSS injected onto the root container element. |
76
78
  | `backgroundColor` | `string` | `'#FFFFFF'` | Background color of the chart container. |
77
79
  | `headerBackground` | `string` | `'#f3f3f3'` | Background color of the header row. |
@@ -79,26 +81,50 @@ The layout can be configured by passing a second argument to `ApexGantt` with th
79
81
  | `rowBackgroundColors` | `string[]` | `['#FFFFFF']` | Alternating row background colors; the pattern cycles automatically. |
80
82
  | `tasksContainerWidth` | `number` | `425` | Initial pixel width of the task-list panel. |
81
83
  | `columnConfig` | `ColumnListItem[]` | `undefined` | Custom column definitions for the task-list panel. Controls which columns are shown, their order, titles, and widths. See [Column Configuration](#column-configuration). |
82
- | `barBackgroundColor` | `string` | `'#537CFA'` | Default background fill color for task bars. |
84
+ | `barBackgroundColor` | `string` | `'#318CE7'` (light) / `'#818CF8'` (dark) | Default background fill color for task bars. |
83
85
  | `barBorderRadius` | `string` | `'5px'` | CSS border-radius applied to task bars. |
84
86
  | `barMargin` | `number` | `4` | Top and bottom margin inside each row for the task bar. |
85
87
  | `barTextColor` | `string` | `'#FFFFFF'` | Text color rendered inside task bars. |
86
- | `arrowColor` | `string` | `'#0D6EFD'` | Color of dependency arrows between tasks. |
87
- | `borderColor` | `string` | `'#eff0f0'` | Color of cell and row divider lines. |
88
- | `cellBorderColor` | `string` | `'#eff0f0'` | Border color for all cells in the task table and timeline grid. |
88
+ | `barLabel` | `BarLabelOptions` | `{ position: 'right', field: 'name' }` | Per-task bar label. Defaults to `position: 'right'` so labels stay visible regardless of bar width. Set `position: 'inside'` to render centered inside the bar, `'left'` to render before it, or `'auto'` to pick between `'inside'` and `'right'` based on bar width; supply `render: (task) => string \| HTMLElement` to compose the name with chips, icons, or owner info. When `position: 'left'`, the timeline auto-reserves `120px` of leading space so labels don't disappear under the task-list panel — tune via `barLabel.leadingPadding`. Outside labels are not currently rendered for milestones. |
89
+ | `summaryBarColor` | `string` | `'#B9CECE'` (light) / `'#8FBCBC'` (dark) | Fill color for summary (group) bars. |
90
+ | `milestoneColor` | `string` | `'#7C3AED'` (light) / `'#A78BFA'` (dark) | Fill color for milestone diamonds. |
91
+ | `arrowColor` | `string` | `'#94A3B8'` (light) / `'#94A3B8'` (dark) | Color of dependency arrows between tasks. Subtle slate-gray by default so links don't compete with bars. |
92
+ | `dependencies` | `DependencyOptions` | `{}` | Polish + editing for dependency arrows: `cornerRadius` (rounded joints), `hitWidth` (invisible thicker stroke for hover/click targeting), `tooltipTemplate: (ctx) => string` (HTML on hover — requires non-zero `hitWidth`), `classBuilder: (ctx) => string \| string[]` (per-arrow CSS class for cross-team / blocked / etc), `editable: boolean` (hover darkens the arrow, click selects it and reveals a "✕" affordance — click the ✕ or press <kbd>Delete</kbd> to remove via the undoable command path; on bar hover two anchor circles appear at the bar's start/finish edges, and dragging from one to another bar draws a dashed preview that commits the new dependency on release — the (source-anchor, target-anchor) pair determines the type: right→left FS, left→left SS, right→right FF, left→right SF), `allowSummaryDescendantLinks: boolean` (allow drawing edges between a summary task and its descendants; off by default since such edges confuse downstream date math). When `editable: true`, `hitWidth` is auto-bumped to at least `12` if unset. The `ctx` carries `fromTask`, `toTask`, `type`, and `lag`. |
93
+ | `calendar` | `CalendarOptions` | `undefined` | Working-calendar config. When set, weekends + holidays drive duration math, summary aggregation, and timeline stripes — durations stop counting calendar days and start counting working days. Fields: `workingWeekdays: number[]` (0 = Sunday … 6 = Saturday; default `[1, 2, 3, 4, 5]`), `holidays: Array<string \| Date \| { date, label? }>` (non-working dates regardless of weekday; mixed forms allowed), `showNonWorkingStripes: boolean` (render hatched bands over weekend/holiday columns; default `true`), `holidayTooltip: (ctx) => string` (optional HTML rendered when the user hovers a holiday stripe — `ctx.date` is the Date, `ctx.label` is whichever string was supplied in the matching entry), `dragSnapMode: 'next' \| 'previous' \| 'allow'` (what happens when a drag/resize commits onto a non-working day — `'next'` (default) snaps forward, `'previous'` snaps backward, `'allow'` permits non-working start/end. Drag preserves the source task's working-day duration across the snap; resize snaps the moving endpoint only. No effect when `snapUnit !== 'day'` or the input format carries time tokens). Absent calendar = today's behavior (every day is a working day, no stripes, no snap). |
94
+ | `borderColor` | `string` | `'#DFE0E1'` | Color of cell and row divider lines. |
95
+ | `cellBorderColor` | `string` | `'#D0D7DE'` | Border color for all cells in the task table and timeline grid. |
89
96
  | `cellBorderWidth` | `string` | `'1px'` | CSS border-width for all cell lines. |
97
+ | `columnLines` | `boolean` | `true` | Whether to draw vertical lines between timeline columns. Set to `false` for a cleaner look that keeps only the horizontal row dividers. |
98
+ | `enableProjectBoundary` | `boolean` | `false` | When `true`, two vertical lines mark the project's earliest start and latest end across all rows. Auto-recomputes when tasks are added, removed, or rescheduled. |
99
+ | `projectBoundaryColor` | `string` | `'#7C3AED'` | Stroke colour for the project-boundary lines. Falls back to `annotationBorderColor` when omitted. |
100
+ | `enableRollups` | `boolean` | `false` | When `true`, summary (parent) rows display thin rollup markers below the summary bar at each leaf descendant's date range. Useful for keeping children visible at a glance even when the parent is collapsed. |
90
101
  | `enableResize` | `boolean` | `true` | Allow the task-list panel to be resized by dragging the divider. |
91
102
  | `enableExport` | `boolean` | `true` | Show the SVG export button in the toolbar. |
103
+ | `enableInlineEdit` | `boolean` | `false` | Allow editing task fields directly in the task-list cells (double-click to edit `name`, `startTime`, `endTime`, `duration`, `progress`). Auto-enabled when `enableTaskEdit` is `true`; set explicitly to `false` to opt out. See [Inline Editing](#inline-editing). |
92
104
  | `enableTaskDrag` | `boolean` | `true` | Allow tasks to be reordered by dragging rows in the task list. |
93
105
  | `enableTaskEdit` | `boolean` | `false` | Show the inline task-edit form when a task row is clicked. |
94
106
  | `enableTaskResize` | `boolean` | `true` | Allow task bars to be resized by dragging their handles. |
107
+ | `enableProgressDrag` | `boolean` | `true` | Allow editing task progress by dragging the small handle that appears at the bottom of the bar on hover. Snaps to whole percent on commit and emits a `taskProgressChanged` event. |
108
+ | `enableTaskEditingShortcuts` | `boolean` | `false` | Enable keyboard shortcuts on the task-list panel: `Delete` / `Backspace` removes the focused task (cascade: children), `Tab` indents under the previous sibling, `Shift+Tab` outdents to the grandparent. Off by default; opt in once you've decided the shortcuts fit your app. All operations are recorded in the undo history and respect `beforeTaskDelete` / `beforeTaskMove` hooks. |
109
+ | `enableTaskCRUDToolbar` | `boolean` | `false` | Show built-in `+ Add task` and trash-icon `Delete` buttons in the toolbar. Delete is auto-disabled when nothing is selected and deletes every selected task (cascade: children) on click; Add inserts a root-level "New task" using placeholder dates derived from the current project span. Combine with `enableSelection: true` for the delete button to be useful. |
110
+ | `enableContextMenu` | `boolean` | `false` | Show a built-in right-click menu on task bars and task-list rows with entries for Edit, Add child / sibling task, Indent, Outdent, and Delete (cascade). Entries are gated by capability — Edit only when `enableTaskEdit` is on; Indent/Outdent only when the move is legal. Off by default so consumers can ship their own menu without competing. |
111
+ | `enableAddTaskRow` | `boolean` | `false` | Render a single-line `+ Add task` row at the bottom of the task list that inserts a new root-level placeholder task on click (or Enter / Space). Disabled while row virtualisation is active (dataset ≥ 50 rows). |
112
+ | `history` | `{ enabled?: boolean; maxSize?: number }` | `{ enabled: true, maxSize: 100 }` | Configures the undo/redo history. Every mutating call (drag, resize, inline / dialog edit, add, delete, move, dependency change) is recorded unless `enabled: false`. Use `gantt.undo()` / `gantt.redo()` to traverse, `gantt.canUndo()` / `gantt.canRedo()` to gate UI affordances, and the `historyChange` event to react to changes. The toolbar shows Undo/Redo buttons when `enableTaskCRUDToolbar` is on and history is enabled; Ctrl/Cmd+Z and Ctrl+Y / Ctrl+Shift+Z trigger undo/redo from anywhere inside the chart (except text inputs). |
95
113
  | `enableTooltip` | `boolean` | `true` | Show a tooltip on task-bar hover. |
96
114
  | `enableSelection` | `boolean` | `false` | Enable row selection (click, Ctrl+Click, Shift+Click, keyboard). |
115
+ | `beforeTaskAdd` | `(ctx) => boolean \| void` | `undefined` | Veto hook called immediately before `gantt.addTask()` inserts a task. Return `false` to cancel the insertion. `ctx` is `{ input, parentId? }`. |
116
+ | `beforeTaskUpdate` | `(ctx) => boolean \| void` | `undefined` | Veto hook called before every update path (drag, resize, progress drag, inline / dialog edit, `gantt.updateTask()`). Return `false` to reject. `ctx` is `{ task, updates }`. |
117
+ | `beforeTaskMove` | `(ctx) => boolean \| void` | `undefined` | Veto hook called immediately before `gantt.moveTask()` re-parents a task. Return `false` to cancel. `ctx` is `{ task, oldParentId?, newParentId? }`. |
118
+ | `beforeTaskDelete` | `(ctx) => boolean \| void` | `undefined` | Veto hook called immediately before `gantt.deleteTask()` removes a task. Return `false` to cancel. `ctx` is `{ task, descendantIds, cascade: 'forbid' \| 'children' \| 'orphan' }`. `descendantIds` is empty for `'orphan'` since children survive. |
119
+ | `beforeDependencyChange` | `(ctx) => boolean \| void` | `undefined` | Veto hook called immediately before `gantt.addDependency()` / `gantt.removeDependency()` mutate an edge. Return `false` to cancel. `ctx` is `{ change: 'add'\|'remove', fromId, toId, type, lag }`. |
97
120
  | `showCheckboxColumn` | `boolean` | `true` | Show a checkbox column for multi-select. Only applies when `enableSelection` is true. |
98
121
  | `enableCriticalPath` | `boolean` | `false` | Calculate and highlight the critical path through dependent tasks. |
99
- | `criticalBarColor` | `string` | `'#e53935'` | Fill color for task bars on the critical path. |
100
- | `criticalArrowColor` | `string` | `'#e53935'` | Stroke color for dependency arrows on the critical path. |
101
- | `baseline` | `Partial<BaselineOptions>` | `undefined` | When `enabled: true`, renders a thin baseline bar below each task bar. |
122
+ | `criticalBarColor` | `string` | `'#E53935'` (light) / `'#F87171'` (dark) | Fill color for task bars on the critical path. |
123
+ | `criticalArrowColor` | `string` | `'#E53935'` (light) / `'#F87171'` (dark) | Stroke color for dependency arrows on the critical path. |
124
+ | `enableCrosshair` | `boolean` | `false` | Show a vertical crosshair line that follows the cursor across the timeline, with a label showing the precise date/time at the pointer position. |
125
+ | `crosshairColor` | `string` | `'#318CE7'` (light) / `'#818CF8'` (dark) | Color of the crosshair line and the label background. |
126
+ | `crosshairLabelFormat` | `(date, tier) => string` | _auto_ | Custom formatter for the crosshair label. Receives the date under the cursor and the active sub-tier (`'minute' \| 'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'`). When omitted, the label auto-adapts to the active tier — `'ddd MM/DD/YYYY'` for day-and-coarser tiers, `'MM/DD HH:mm'` for hour/minute tiers. |
127
+ | `baseline` | `Partial<BaselineOptions>` | `undefined` | When `enabled: true`, renders a thin baseline bar below each task bar. Hovering the baseline shows a tooltip with its planned start/end dates. When `rowHeight` isn't explicitly set, the default rowHeight is bumped to make room for the baseline without squeezing the actual bar. |
102
128
  | `tooltipId` | `string` | `'apexgantt-tooltip-container'` | HTML `id` for the tooltip container element. |
103
129
  | `tooltipTemplate` | `(task, dateFormat) => string` | built-in | Custom function returning an HTML string for the task tooltip. |
104
130
  | `tooltipBorderColor` | `string` | `'#BCBCBC'` | Border color of the tooltip. |
@@ -191,6 +217,24 @@ Each tasks should be in below format
191
217
  ];
192
218
  ```
193
219
 
220
+ #### Per-task fields
221
+
222
+ | Field | Type | Default | Description |
223
+ | --- | --- | --- | --- |
224
+ | `id` | `string` | **required** | Unique task identifier. Must be stable across renders. |
225
+ | `name` | `string` | **required** | Display name shown in the task list and inside the bar. |
226
+ | `startTime` | `string` | **required** | Task start date, parsed with `inputDateFormat`. Not required when `showSummaryBar` is `true`. |
227
+ | `endTime` | `string` | `startTime` | Task end date. When omitted the task renders as a milestone. Not required when `showSummaryBar` is `true`. |
228
+ | `progress` | `number` | `0` | Completion percentage (0–100). |
229
+ | `type` | `'task' \| 'milestone'` | `'task'` | Visual type of the task. |
230
+ | `parentId` | `string` | `undefined` | ID of the parent task; creates a hierarchical (indented) relationship. |
231
+ | `dependency` | `string \| TaskDependency` | `undefined` | Declares a dependency on another task. A plain string is treated as a Finish-to-Start dependency. `TaskDependency.lagUnit: 'working' \| 'calendar'` controls whether `lag` is interpreted as working days (the default when a calendar is configured) or raw calendar days. |
232
+ | `barBackgroundColor` | `string` | `undefined` | Overrides the chart-level `barBackgroundColor` for this task only. |
233
+ | `rowBackgroundColor` | `string` | `undefined` | Overrides the row background color for this task only. |
234
+ | `collapsed` | `boolean` | `false` | Whether this task's children are initially collapsed. |
235
+ | `showSummaryBar` | `boolean` | `true` | Renders this task as a summary (group) bar when it has children. Its date range is computed automatically from its descendants — `startTime`/`endTime` are ignored (a warning is logged if provided). The bar is read-only: drag, resize, and progress are disabled. Set to `false` to opt out and render the parent as a normal task bar. |
236
+ | `baseline` | `BaselineInput` | `undefined` | Planned (baseline) dates rendered as a thin bar beneath the actual bar when `baseline.enabled` is `true`. |
237
+
194
238
  ### Expected annotation format to set as Options.annotations
195
239
 
196
240
  Each tasks should be in below format
@@ -215,7 +259,7 @@ Each tasks should be in below format
215
259
 
216
260
  Customize which columns appear in the task-list panel, their order, titles, and widths. When `columnConfig` is provided, **it is authoritative** — only the columns you list are rendered, in the order you specify. Omitted columns are hidden. Each entry is merged with defaults, so you only need to specify overrides.
217
261
 
218
- Available column keys: `Name`, `StartTime`, `EndTime`, `Duration`, `Progress`.
262
+ Available column keys: `Name`, `StartTime`, `EndTime`, `Duration`, `Progress`, `ProgressRing`, `Wbs`.
219
263
 
220
264
  ### Customize column widths
221
265
 
@@ -304,15 +348,257 @@ columnConfig: [
304
348
  ],
305
349
  ```
306
350
 
351
+ ### Show progress as a ring instead of text
352
+
353
+ Swap the default text Progress column for the built-in SVG ring variant — same data (`task.progress`), no setup needed:
354
+
355
+ ```js
356
+ columnConfig: [
357
+ {key: ColumnKey.Name, title: 'Task Name'},
358
+ {key: ColumnKey.StartTime, title: 'Start'},
359
+ {key: ColumnKey.Duration, title: 'Duration'},
360
+ {key: ColumnKey.ProgressRing, title: 'Progress', minWidth: '60px', flexGrow: 0.6},
361
+ ],
362
+ ```
363
+
364
+ `ColumnKey.ProgressRing` renders with sensible defaults (28px ring, blue arc). For full customisation (size, colours, accessor, per-task colour function) use the `renderers.progressRing()` factory with a custom column key — see [Built-in renderer presets](#built-in-renderer-presets).
365
+
366
+ ### Include the WBS column
367
+
368
+ The `Wbs` column shows an auto-numbered Work Breakdown Structure code derived from each task's position in the parent/child tree (`1`, `1.1`, `1.1.2`, `2`, …). Codes recompute automatically when tasks are added, removed, or moved. Opt in by listing it explicitly:
369
+
370
+ ```js
371
+ columnConfig: [
372
+ {key: ColumnKey.Wbs, title: 'WBS', minWidth: '60px', flexGrow: 0.6},
373
+ {key: ColumnKey.Name, title: 'Task Name'},
374
+ {key: ColumnKey.StartTime, title: 'Start'},
375
+ {key: ColumnKey.Duration, title: 'Duration'},
376
+ {key: ColumnKey.Progress, title: 'Progress'},
377
+ ],
378
+ ```
379
+
380
+ The code is also available on each `Task` as `task.wbs` if you need to read it from event handlers, custom column renderers, or your own UI.
381
+
307
382
  ### ColumnListItem properties
308
383
 
309
- | Property | Type | Default | Description |
310
- | ---------- | ----------- | ------------- | ------------------------------------------------- |
311
- | `key` | `ColumnKey` | — | Which data field this column shows (required). |
312
- | `title` | `string` | from defaults | Header text displayed for the column. |
313
- | `minWidth` | `string` | `'30px'` | Minimum CSS width (used in `minmax()`). |
314
- | `flexGrow` | `number` | `1` | Flex proportion (used as `fr` units in CSS Grid). |
315
- | `visible` | `boolean` | `true` | Set to `false` to hide the column. |
384
+ | Property | Type | Default | Description |
385
+ | --- | --- | --- | --- |
386
+ | `key` | `ColumnKey \| string` | — | Built-in column identifier or any custom string id when `render` is supplied (required). |
387
+ | `title` | `string` | from defaults | Header text displayed for the column. |
388
+ | `minWidth` | `string` | `'30px'` | Minimum CSS width (used in `minmax()`). |
389
+ | `flexGrow` | `number` | `1` | Flex proportion (used as `fr` units in CSS Grid). |
390
+ | `visible` | `boolean` | `true` | Set to `false` to hide the column. |
391
+ | `render` | `ColumnRenderer` | — | Custom cell renderer. Required for custom columns; ignored for built-in keys. |
392
+ | `accessor` | `(task) => unknown` | — | Extracts the cell's underlying value (used by SVG export and future sort/filter features). |
393
+
394
+ ## Custom column renderers
395
+
396
+ You can add columns that aren't part of the built-in set by giving them a string `key` and a `render` function. Two renderer presets ship with the library — `renderers.avatars` for assignee/resource columns and `renderers.progressRing` for completion-percentage columns. Both are tree-shakeable; only the ones you import get bundled.
397
+
398
+ ```js
399
+ import {ApexGantt, ColumnKey, renderers} from '@apexcharts/apexgantt';
400
+
401
+ const gantt = new ApexGantt(element, {
402
+ series: tasks, // each task may include `assignees: Assignee[]`
403
+ columnConfig: [
404
+ {key: ColumnKey.Name, title: 'Task'},
405
+ {
406
+ key: 'assignees',
407
+ title: 'Assigned',
408
+ render: renderers.avatars({
409
+ accessor: (task) => task.assignees,
410
+ max: 4,
411
+ size: 24,
412
+ }),
413
+ },
414
+ {
415
+ key: 'progressRing',
416
+ title: '%',
417
+ render: renderers.progressRing({size: 28, strokeWidth: 3}),
418
+ },
419
+ ],
420
+ });
421
+ ```
422
+
423
+ ### Built-in renderer presets
424
+
425
+ | Preset | Source | Options |
426
+ | --- | --- | --- |
427
+ | `renderers.avatars` | An assignee-stack with `+N` overflow and initials fallback. | `accessor`, `max`, `size`, `overlap`, `borderColor`, `fallbackColor` |
428
+ | `renderers.progressRing` | An SVG ring with optional centered numeric label. | `accessor`, `size`, `strokeWidth`, `progressColor`, `trackColor`, `showLabel`, `labelColor` |
429
+
430
+ ### `Assignee` type
431
+
432
+ The avatar preset reads from `Assignee[]`. It is a public type you can import:
433
+
434
+ ```ts
435
+ import type {Assignee} from '@apexcharts/apexgantt';
436
+
437
+ interface Assignee {
438
+ name: string; // required
439
+ avatarUrl?: string; // when omitted, initials are rendered
440
+ initials?: string; // override the auto-derived initials
441
+ color?: string; // background color for the initials fallback
442
+ }
443
+ ```
444
+
445
+ ### Writing your own renderer
446
+
447
+ A renderer is `(ctx, el) => string | void | (() => void)`. Return one of three things depending on how you build the cell content:
448
+
449
+ | Return | Meaning | Best for |
450
+ | --- | --- | --- |
451
+ | `string` | The library treats it as HTML and writes it to the cell via `innerHTML`. Always escape user-supplied text with the exported `escapeHtml` helper. | Vanilla JS, the built-in presets |
452
+ | `void` | You have already mounted content into `el` yourself. The library will not modify the cell's contents. | Frameworks that own their own lifecycle |
453
+ | `() => void` | Same as void, plus a cleanup function the library will invoke before the cell is discarded (row removal, full re-render, or `destroy()`). | React `createRoot`, Angular `ComponentRef`, Vue `createApp().mount()` |
454
+
455
+ ```ts
456
+ import {escapeHtml, type ColumnRenderer} from '@apexcharts/apexgantt';
457
+
458
+ const statusPill: ColumnRenderer = (ctx) => {
459
+ const status = ctx.task.status ?? 'unknown';
460
+ return `<span class="pill pill-${escapeHtml(status)}">${escapeHtml(status)}</span>`;
461
+ };
462
+ ```
463
+
464
+ The `ctx` argument provides:
465
+
466
+ | Field | Type | Description |
467
+ | ------------- | -------------- | --------------------------------------------------------- |
468
+ | `task` | `Task` | The task being rendered. |
469
+ | `options` | `GanttOptions` | Resolved chart options — useful for theme-aware coloring. |
470
+ | `rowIndex` | `number` | Zero-based index in the visible task list. |
471
+ | `isSummary` | `boolean` | True when the row is a summary (group) bar. |
472
+ | `isMilestone` | `boolean` | True when the task is a milestone. |
473
+
474
+ ### Using with React, Angular, or Vue
475
+
476
+ Use the cleanup return so the framework can unmount and free resources when a row is evicted (during virtualization scroll) or when the chart is destroyed. The library guarantees the cleanup function runs synchronously before the cell is reused or removed.
477
+
478
+ ```tsx
479
+ // React
480
+ import {createRoot} from 'react-dom/client';
481
+
482
+ const render: ColumnRenderer = (ctx, el) => {
483
+ const root = createRoot(el);
484
+ root.render(<AvatarStack assignees={ctx.task.assignees} />);
485
+ return () => root.unmount();
486
+ };
487
+ ```
488
+
489
+ ```ts
490
+ // Angular — assuming you have a ViewContainerRef + a component to mount
491
+ const render: ColumnRenderer = (ctx, el) => {
492
+ const ref = vcr.createComponent(AssigneesComponent);
493
+ ref.setInput('task', ctx.task);
494
+ el.appendChild(ref.location.nativeElement);
495
+ return () => ref.destroy();
496
+ };
497
+ ```
498
+
499
+ ```ts
500
+ // Vue 3
501
+ import {createApp, h} from 'vue';
502
+ import AssigneesCell from './AssigneesCell.vue';
503
+
504
+ const render: ColumnRenderer = (ctx, el) => {
505
+ const app = createApp({render: () => h(AssigneesCell, {task: ctx.task})});
506
+ app.mount(el);
507
+ return () => app.unmount();
508
+ };
509
+ ```
510
+
511
+ ### Lifecycle contract
512
+
513
+ The library invokes the cleanup function in **all three** of these cases:
514
+
515
+ 1. The chart is destroyed (`gantt.destroy()`).
516
+ 2. A full re-render replaces the cell (e.g. `gantt.update()`, or any `gantt.render()` call).
517
+ 3. The row is evicted because the user scrolled it out of the virtualization window.
518
+
519
+ You do not need to listen for DOM mutations yourself. If your renderer returns a function, the library will call it.
520
+
521
+ ## Sub-day scheduling
522
+
523
+ Set `inputDateFormat` to a format that includes time tokens (e.g. `'YYYY-MM-DD HH:mm'`) and choose a finer `snapUnit` to schedule tasks at the hour or minute level:
524
+
525
+ ```js
526
+ const gantt = new ApexGantt(element, {
527
+ series: tasks,
528
+ inputDateFormat: 'YYYY-MM-DD HH:mm',
529
+ snapUnit: 'minute',
530
+ snapValue: 15, // drags/resizes snap in 15-minute steps
531
+ pixelsPerDay: 600, // zoom in enough that hour cells show in the header
532
+ });
533
+ ```
534
+
535
+ Behavior changes when `inputDateFormat` includes time:
536
+
537
+ - The end-time of a task is treated as the **exclusive end timestamp** (a task from `10:00` to `10:30` is 30 minutes wide). Day-only formats keep the existing inclusive-end behavior (a task from `Jan 10` to `Jan 15` spans 6 cells).
538
+ - Inline-edit `startTime` / `endTime` cells switch to `datetime-local` inputs.
539
+ - The Duration column reports in the configured `snapUnit` suffix: `d` / `h` / `m`.
540
+
541
+ > **Working time / non-working hours** (skipping weekends, after-hours, etc.) is **not yet supported** and is planned for a future major release. For now, the timeline tiles continuously through every hour and day.
542
+
543
+ ## Inline Editing
544
+
545
+ Set `enableInlineEdit: true` to let users edit task fields directly in the task-list cells without opening a form.
546
+
547
+ ```js
548
+ const gantt = new ApexGantt(element, {
549
+ series: tasks,
550
+ enableInlineEdit: true,
551
+ });
552
+ ```
553
+
554
+ Inline editing is also auto-enabled by `enableTaskEdit: true` (which opens a dialog when a task bar is clicked), so the two surfaces ship together by default. Pass `enableInlineEdit: false` explicitly to keep the dialog without inline cell editing:
555
+
556
+ ```js
557
+ new ApexGantt(element, {
558
+ series: tasks,
559
+ enableTaskEdit: true,
560
+ enableInlineEdit: false, // dialog only, cells stay read-only
561
+ });
562
+ ```
563
+
564
+ ### How it works
565
+
566
+ - **Activate**: double-click any editable cell.
567
+ - **Commit**: press `Enter` or click outside the input (`blur`).
568
+ - **Cancel**: press `Escape` to revert without saving.
569
+
570
+ ### Editable columns
571
+
572
+ | Column | Editor | Notes |
573
+ | --- | --- | --- |
574
+ | `name` | text input | Empty values are rejected (cancelled silently). |
575
+ | `startTime` | native `<input type="date">` | For tasks (not milestones), the duration is preserved — `endTime` shifts by the same delta. |
576
+ | `endTime` | native `<input type="date">` | Rejected if before `startTime`. |
577
+ | `duration` | number input (min `1`) | Edits update `endTime = startTime + duration - 1` day. `startTime` stays fixed. |
578
+ | `progress` | number input (`0`–`100`) | Values clamp to the `0–100` range. |
579
+
580
+ Hours are not currently supported — all dates are day-precision.
581
+
582
+ ### Cells that are not editable
583
+
584
+ - **Summary rows** (parents with `showSummaryBar`) — only `name` is editable; dates/duration/progress are derived from descendants.
585
+ - **Milestones** — only `name` and `startTime` are editable (no end / duration / progress).
586
+ - **Empty filler rows** at the bottom of the task-list panel.
587
+
588
+ ### Events
589
+
590
+ Inline edits emit the same events as the inline `TaskForm` and the public `updateTask()` method, so consumers can listen on a single event regardless of which surface produced the change:
591
+
592
+ - `taskUpdate` — fires before the change is applied.
593
+ - `taskUpdateSuccess` — fires after a successful update; `detail.updatedTask` contains the resolved task.
594
+ - `taskUpdateError` — fires if the update throws.
595
+
596
+ ```js
597
+ container.addEventListener('taskUpdateSuccess', (e) => {
598
+ const {taskId, updatedTask} = e.detail;
599
+ // persist to backend
600
+ });
601
+ ```
316
602
 
317
603
  ## Data Parsing
318
604
 
@@ -371,7 +657,7 @@ const gantt = new ApexGantt(document.getElementById('gantt'), {
371
657
  });
372
658
  ```
373
659
 
374
- **Supported fields:** `id`, `name`, `startTime`, `endTime`, `progress`, `type`, `parentId`, `dependency`, `barBackgroundColor`, `rowBackgroundColor`, `collapsed`
660
+ **Supported fields:** `id`, `name`, `startTime`, `endTime`, `progress`, `type`, `parentId`, `dependency`, `barBackgroundColor`, `rowBackgroundColor`, `collapsed`, `showSummaryBar`
375
661
 
376
662
  ## 📘 Public API
377
663
 
@@ -399,7 +685,7 @@ ganttInstance.update({
399
685
  },
400
686
  // more tasks...
401
687
  ],
402
- viewMode: 'Week',
688
+ pixelsPerDay: 25.7, // week density
403
689
  });
404
690
  ```
405
691
 
@@ -425,7 +711,201 @@ ganttInstance.updateTask('task-1', {
425
711
  });
426
712
  ```
427
713
 
428
- ### 3. `zoomIn()`
714
+ ### 3. `addTask(input, options?)`
715
+
716
+ Inserts a new task and re-renders the chart. The operation is recorded in the undo history. Emits a `taskAdded` event on success. If a `beforeTaskAdd` hook is configured and it returns `false`, the insertion is cancelled and the method returns `null`. Throws when `input.id` is missing or already exists.
717
+
718
+ #### Parameters
719
+
720
+ | Name | Type | Description |
721
+ | --------- | ----------------------- | ------------------------------------------------------------------------ |
722
+ | `input` | `TaskInput` | Task data; `id` is required. |
723
+ | `options` | `{ parentId?: string }` | Optional parent id to insert the task under. Omit for a root-level task. |
724
+
725
+ Returns the inserted `Task`, or `null` when the `beforeTaskAdd` hook cancels.
726
+
727
+ #### Example
728
+
729
+ ```js
730
+ ganttInstance.addTask({
731
+ id: 'task-9',
732
+ name: 'Review',
733
+ startTime: '2026-08-01',
734
+ endTime: '2026-08-05',
735
+ });
736
+
737
+ ganttInstance.addTask(
738
+ {id: 'subA', name: 'Subtask', startTime: '2026-08-01', endTime: '2026-08-03'},
739
+ {parentId: 'task-9'}
740
+ );
741
+ ```
742
+
743
+ ### 4. `deleteTask(taskId, options?)`
744
+
745
+ Removes a task and re-renders. Recorded in the undo history. Emits a `taskDeleted` event on success. Dependency edges that reference the removed task(s) are auto-cleaned in the same transaction, so undo restores both tasks and edges atomically; one `dependencyRemoved` event fires per auto-removed edge before `taskDeleted`.
746
+
747
+ Cascade modes:
748
+
749
+ - `'forbid'` (default) — throws when the task has children. Safe default to prevent accidental subtree loss.
750
+ - `'children'` — deletes the task plus every descendant in a single undoable transaction.
751
+ - `'orphan'` — reparents the immediate children to the deleted task's parent (or root if the deleted task was a root), then removes just the task. A `taskMoved` event fires for each reparented child. Dependency edges between surviving (reparented) children are preserved; only edges that reference the deleted task itself are cleaned up.
752
+
753
+ If a `beforeTaskDelete` hook is configured and it returns `false`, the removal is cancelled and the method returns `false`. Throws when the task id is not found, or when `cascade: 'forbid'` and the task has children.
754
+
755
+ #### Parameters
756
+
757
+ | Name | Type | Description |
758
+ | --------- | -------------------------------------------------- | --------------------------------- |
759
+ | `taskId` | `string` | Id of the task to remove. |
760
+ | `options` | `{ cascade?: 'forbid' \| 'children' \| 'orphan' }` | Cascade mode. Default `'forbid'`. |
761
+
762
+ Returns `true` if the task was removed, `false` if the hook cancelled.
763
+
764
+ #### Example
765
+
766
+ ```js
767
+ // leaf task — forbids by default but the task has no children so it's removed
768
+ ganttInstance.deleteTask('task-3');
769
+
770
+ // summary task — opt in to cascade
771
+ ganttInstance.deleteTask('task-1', {cascade: 'children'});
772
+
773
+ // remove a summary but keep its children, moving them to its parent
774
+ ganttInstance.deleteTask('task-1', {cascade: 'orphan'});
775
+ ```
776
+
777
+ ### 5. `moveTask(taskId, options?)`
778
+
779
+ Re-parents a task and re-renders. Recorded in the undo history. Emits a `taskMoved` event. If `beforeTaskMove` returns `false`, the move is cancelled and the method returns `false`.
780
+
781
+ Throws when `taskId` doesn't exist, when `newParentId` doesn't exist, when moving onto itself, or when the move would create a cycle.
782
+
783
+ Sibling reordering within the same parent is not modelled yet — that lands with Phase-4 cascade work.
784
+
785
+ #### Parameters
786
+
787
+ | Name | Type | Description |
788
+ | --------- | ---------------------------------- | ------------------------------------------------------------------ |
789
+ | `taskId` | `string` | Id of the task to move. |
790
+ | `options` | `{ newParentId?: string \| null }` | New parent id. Pass `null` (or omit) to move the task to the root. |
791
+
792
+ Returns `true` if the move was applied (or was a no-op), `false` if the hook cancelled.
793
+
794
+ #### Example
795
+
796
+ ```js
797
+ ganttInstance.moveTask('subA', {newParentId: 'p2'});
798
+ ganttInstance.moveTask('subA', {newParentId: null}); // promote to root
799
+ ```
800
+
801
+ ### 6. `addDependency(fromId, toId, options?)`
802
+
803
+ Creates a dependency edge between two tasks and re-renders the arrow. Recorded in undo history. Emits `dependencyAdded`. Runs through `beforeDependencyChange`.
804
+
805
+ Throws when either task doesn't exist, or when the edge already exists.
806
+
807
+ #### Parameters
808
+
809
+ | Name | Type | Description |
810
+ | --------- | ------------------------------------------------------- | --------------------------------- |
811
+ | `fromId` | `string` | Source task id (predecessor). |
812
+ | `toId` | `string` | Target task id (successor). |
813
+ | `options` | `{ type?: 'FS' \| 'FF' \| 'SS' \| 'SF'; lag?: number }` | Defaults: `type: 'FS'`, `lag: 0`. |
814
+
815
+ Returns `true` on success, `false` if the hook cancelled.
816
+
817
+ #### Example
818
+
819
+ ```js
820
+ ganttInstance.addDependency('t1', 't2'); // FS, lag 0
821
+ ganttInstance.addDependency('t1', 't2', {type: 'SS', lag: 2});
822
+ ```
823
+
824
+ ### 7. `removeDependency(fromId, toId)`
825
+
826
+ Removes a dependency edge and re-renders. Recorded in undo history. Emits `dependencyRemoved` with the captured `type` and `lag`. Runs through `beforeDependencyChange`.
827
+
828
+ Throws when no edge exists between the two tasks.
829
+
830
+ #### Example
831
+
832
+ ```js
833
+ ganttInstance.removeDependency('t1', 't2');
834
+ ```
835
+
836
+ ### 7a. `canAddDependency(fromId, toId, options?)`
837
+
838
+ Tells whether a new edge `fromId → toId` would be accepted right now, without committing it. Shared by the programmatic API and the interactive draw UI so both apply the same rules.
839
+
840
+ Returns `{ ok: true }` when the edge would be accepted, or `{ ok: false, reason }` where `reason` is one of:
841
+
842
+ | reason | meaning |
843
+ | -------------------- | --------------------------------------------------------------------------------- |
844
+ | `self` | `fromId === toId` |
845
+ | `task-missing` | either id is not in the data model |
846
+ | `duplicate` | an edge `fromId → toId` already exists |
847
+ | `cycle` | the new edge would close a cycle in the dependency graph |
848
+ | `summary-descendant` | one endpoint is an ancestor of the other (gated by `allowSummaryDescendantLinks`) |
849
+ | `hook-veto` | `beforeDependencyChange` returned `false` |
850
+
851
+ #### Example
852
+
853
+ ```js
854
+ const verdict = ganttInstance.canAddDependency('t1', 't2');
855
+ if (verdict.ok) ganttInstance.addDependency('t1', 't2');
856
+ else console.warn(verdict.reason);
857
+ ```
858
+
859
+ ### 8. `undo()`
860
+
861
+ Rolls back the most recent recorded transaction through the same command bridge that forward operations use; data and DOM return to the pre-operation state. Emits `historyChange` with `kind: 'undo'`.
862
+
863
+ Returns `true` if a transaction was undone, `false` when the undo stack is empty or `history.enabled` is `false`.
864
+
865
+ #### Example
866
+
867
+ ```js
868
+ ganttInstance.updateTask('t1', {progress: 90});
869
+ ganttInstance.undo(); // restores the previous progress
870
+ ```
871
+
872
+ ### 9. `redo()`
873
+
874
+ Replays the most recently undone transaction. Pops from the redo stack and re-executes through the command bridge; emits `historyChange` with `kind: 'redo'`.
875
+
876
+ Any new mutating call between `undo()` and `redo()` discards the redo stack — once a fresh transaction is recorded, future redos are no longer reachable.
877
+
878
+ Returns `true` if a transaction was redone, `false` otherwise.
879
+
880
+ #### Example
881
+
882
+ ```js
883
+ ganttInstance.undo();
884
+ ganttInstance.redo(); // reapplies the rolled-back change
885
+ ```
886
+
887
+ ### 10. `canUndo()` / `canRedo()`
888
+
889
+ Returns whether an undoable / redoable transaction is currently on top of the stack. Use these to gate custom Undo/Redo UI affordances.
890
+
891
+ ```js
892
+ myUndoButton.disabled = !ganttInstance.canUndo();
893
+ ```
894
+
895
+ ### 11. `clearHistory()`
896
+
897
+ Drops every recorded transaction and emits `historyChange` with `kind: 'clear'`. Useful after loading a fresh dataset where rolling back to a previous tree state would be incoherent.
898
+
899
+ ```js
900
+ ganttInstance.update({series: freshTasks});
901
+ ganttInstance.clearHistory();
902
+ ```
903
+
904
+ ### 12. `getHistorySize()`
905
+
906
+ Returns the current undo/redo stack sizes as `{ undo: number, redo: number }`. Handy for debugging or surfacing a "you have N undo steps left" hint.
907
+
908
+ ### 13. `zoomIn()`
429
909
 
430
910
  Zooms in the gantt based on current view mode. View mode direction for zoom in year -> quarter -> month -> week -> day
431
911
 
@@ -435,7 +915,7 @@ Zooms in the gantt based on current view mode. View mode direction for zoom in y
435
915
  ganttInstance.zoomOut();
436
916
  ```
437
917
 
438
- ### 4. `zoomOut()`
918
+ ### 14. `zoomOut()`
439
919
 
440
920
  Zooms out the gantt based on current view mode. View mode direction for zoom in day -> week -> month -> quarter -> year
441
921
 
@@ -457,8 +937,15 @@ ApexGantt emits CustomEvents on the container element for various user interacti
457
937
  | `taskUpdateSuccess` | Update completed successfully | `{ taskId, updatedTask, timestamp }` |
458
938
  | `taskValidationError` | Form validation failed | `{ taskId, errors, timestamp }` |
459
939
  | `taskUpdateError` | Update failed | `{ taskId, error, timestamp }` |
940
+ | `taskAdded` | Task inserted via `gantt.addTask()` | `{ taskId, task, parentId?, timestamp }` |
941
+ | `taskDeleted` | Task removed via `gantt.deleteTask()` | `{ taskId, task, removedDescendantIds, timestamp }` |
942
+ | `taskMoved` | Task re-parented via `gantt.moveTask()` | `{ taskId, oldParentId?, newParentId?, timestamp }` |
943
+ | `dependencyAdded` | Edge created via `gantt.addDependency()` | `{ fromId, toId, type, lag, timestamp }` |
944
+ | `dependencyRemoved` | Edge removed via `gantt.removeDependency()` | `{ fromId, toId, type, lag, timestamp }` |
460
945
  | `taskDragged` | Task bar is dragged | `{ taskId, oldStartTime, oldEndTime, newStartTime, newEndTime, daysMoved, affectedChildTasks, timestamp }` |
461
946
  | `taskResized` | Task bar is resized | `{ taskId, resizeHandle, oldStartTime, oldEndTime, newStartTime, newEndTime, durationChange, timestamp }` |
947
+ | `taskProgressChanged` | In-bar progress handle is dragged to a new value | `{ taskId, oldProgress, newProgress, timestamp }` |
948
+ | `historyChange` | The undo/redo stack changed — `kind` is `'record'`, `'undo'`, `'redo'`, or `'clear'` | `{ kind, canUndo, canRedo, undoSize, redoSize, topUndoLabel?, topRedoLabel?, timestamp }` |
462
949
 
463
950
  ### Events Usage
464
951