Task list
The task list is the left-hand table of a classic gantt tool: a row for every row of the chart, at the same height and place, indented by depth, with a toggle on the groups, the columns you choose and a splitter on its edge. It is a component beside the chart, built on the chart’s public API, and an application that does not use it does not pay for it.
See it working in the task list example.
A first list
Section titled “A first list”import '@ganttlane/dom/styles.css';import { createGantt } from '@ganttlane/dom';import { createTaskList } from '@ganttlane/dom/task-list';
const gantt = createGantt(chart, { tasks, layout: { height: 500 }, // A click on a bar selects its row in the list. selection: { onClick: true },});
const list = createTaskList(side, gantt, { columns: [ { id: 'name', header: 'Task' }, { id: 'done', header: '%', width: 48, align: 'end', value: (row) => String(row.record.progress), }, ],});
// When you are done with them, in either order:list.destroy();gantt.destroy();import '@ganttlane/react/styles.css';import { TaskList, useGantt } from '@ganttlane/react';
const columns = [ { id: 'name', header: 'Task' }, { id: 'done', header: '%', width: 48, align: 'end', value: (row) => String(row.record.progress), },];
function Plan() { const gantt = useGantt({ defaultTasks: tasks, layout: { height: 500 }, selection: { onClick: true }, }); const { setContainer, slots } = gantt; return ( <div className="plan"> <TaskList gantt={gantt} columns={columns} /> <div ref={setContainer} className="chart" /> {slots} </div> );}<TaskList> takes the chart from useGantt, which exists while your component renders, so the list
also renders on a server. A ref of <Gantt> would be null until the commit.
Side by side, tops aligned. The list has its own width, and the chart takes the rest:
.plan { display: flex;}
.chart { flex: 1 1 0; /* Without this a flex item is as wide as its content, and the chart would widen the page. */ min-width: 0;}What is kept in sync
Section titled “What is kept in sync”| In the chart | In the list |
|---|---|
| The rows and what is expanded | The same rows, in the same order, from any expand, collapse or scrollToTask |
| The row height and header sizes | The same sizes, also when layout changes |
| The vertical position | The same position, see scrolling |
| The selected tasks | aria-selected on their rows, wherever the selection was made |
| The row under the pointer | The row lights, and the other way round |
| The focused task | The keyboard focus, and the chart tints that row |
| The theme and the row stripes | The same theme and stripes |
| Names and committed dates | The cells draw again, only those of the rows that changed |
A click on a row selects the task and scrolls the chart to its bar, moving nothing that is already
in view. The chevron opens and shuts the group, and selects nothing. To do something else on a click,
use onRowClick and call event.preventDefault(). With
selection.multiple on the chart, Ctrl or Cmd + click adds a row to the
selected ones or takes it out, and the grid says aria-multiselectable.
Dates in the list follow a commit, not the drag in progress: a task’s record changes when the change is committed.
Scrolling
Section titled “Scrolling”How the two scroll depends on the chart’s layout.height:
layout.height |
What scrolls the rows | In sync |
|---|---|---|
'auto' |
One box around both (the nearest ancestor that scrolls, or the page), headers sticky | Always: they cannot differ |
| A number, or fills | The chart’s scroller and the list’s body, which hides its scrollbar | Within a pixel at rest; up to a frame behind during a fast fling |
For 'auto', the box needs a height, and nothing between it and the chart may clip:
.plan { display: flex; height: 500px; overflow-y: auto;}The sideways scrollbar belongs to the chart’s scroller, so with 'auto' it is under the last row. A
sideways wheel and touch work anywhere over the chart.
Columns
Section titled “Columns”A column is { id, header, width, align, tree, value, render }:
| Field | Type | Default | What it does |
|---|---|---|---|
id |
string |
required | Its identity; data-column of its cells |
header |
string |
none | The text of its header |
width |
number (px) | 'fill' |
'fill' for the tree column, else 96 |
'fill' shares what the other columns leave |
align |
'start' | 'center' | 'end' |
'start' |
How its cells are aligned |
tree |
boolean |
the first column | Whether it has the indent and the toggle |
value |
(row) => string |
the name in the tree column | The text of a cell |
render |
(row) => Node | string | null |
none | A cell’s content; null falls back to value. In React it returns a ReactNode |
row is a GanttRow: id, index, name, depth, hasChildren, isExpanded, setSize,
position and record, the task’s TaskRecord. A column’s
functions are called again only when its row changes, or when you give new columns, so in React
memoize them if your component renders often.
A link or a form control drawn in a cell keeps its own clicks and keys.
A column of dates, with Intl:
const dates = new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'short' });
const column = { id: 'dates', header: 'Dates', width: 110, align: 'end', value: ({ record }) => record.start && record.end ? dates.formatRange(record.start, record.end) : '',};Options
Section titled “Options”createTaskList(container, gantt, options), and list.setOptions(patch) later.
| Option | Type | Default | What it does |
|---|---|---|---|
columns |
TaskListColumn[] |
the task’s name | See columns |
width, minWidth, maxWidth |
number (px) |
280, 120, 640 |
Its width and what the splitter may do |
splitter |
boolean |
true |
The splitter on its end edge |
indent |
number (px) |
16 |
How far each level is indented |
cellNavigation |
boolean |
false |
The keys walk the cells of a row, see the keyboard |
theme |
ThemeOption |
the chart’s | Its data-theme; without one it follows the chart |
strings |
{ label, name, resize } |
English | Its name, the default header, the splitter’s name |
onRowClick(record, event) |
function | A click, Enter or Space. preventDefault() keeps the default |
|
onRowDoubleClick(record, event) |
function | A double click | |
onWidthChange(width) |
function | The splitter moved, or setWidth was called |
The result has element, setOptions(patch), getWidth(), setWidth(px) and destroy().
<TaskList gantt={instance} … />; inside a GanttRoot the gantt prop can be left
out, and the list takes the root’s chart. The same options as props, with the width as a controlled or
uncontrolled pair, and id, className and style for its root:
| Prop | What it does |
|---|---|
gantt |
The chart, from useGantt |
width, defaultWidth, onWidthChange |
The width, controlled or uncontrolled |
minWidth, maxWidth |
What the splitter may do |
columns, splitter, indent, cellNavigation, theme, strings, onRowClick, onRowDoubleClick |
As in the vanilla list |
id, className, style |
Of its root |
The keyboard and assistive technology
Section titled “The keyboard and assistive technology”The list is a role="treegrid" of role="row"s, with aria-level, aria-expanded, aria-selected,
aria-rowindex, aria-setsize and aria-posinset, and one tab stop.
| Key | What it does |
|---|---|
| Up, Down, Page Up, Page Down, Home, End | Move to another row |
| Right | Opens a group, and once it is open steps into it |
| Left | Shuts a group, and once it is shut steps out to its parent |
| Enter, Space | Activates the row: onRowClick, then select and scroll |
| Ctrl or Cmd + Enter, Space | With selection.multiple, adds the task or takes it out |
| Left, Right, Home, End on the splitter | Resize the list |
Walking the cells
Section titled “Walking the cells”With cellNavigation on, the focus can also be in a cell of the row, as in the ARIA treegrid pattern.
The row stays the one tab stop, so Tab does not walk the cells, and each cell and column header gets
an aria-colindex. Off, which is the default, nothing about the list changes.
| Key (focus on a row) | What it does |
|---|---|
| Right | Opens a shut group; else steps into the first cell |
| Key (focus on a cell) | What it does |
|---|---|
| Left, Right | The cell before or after; in the tree column they shut or open a group first. Left from the first cell steps back to the row |
| Up, Down, Page Up, Page Down | The cell of the same column in another row |
| Home, End | The first or last cell of the row |
| Ctrl or Cmd + Home, End | The same column in the first or last row |
| Enter, Space | Activates the row, as on the row itself |
Keys typed into something drawn in a cell, such as an input, stay its own.
Bars that take the focus
Section titled “Bars that take the focus”With keyboard.barFocus on the chart, the bars and the rows share one
focused task, and the surface that holds the browser’s focus keeps it:
- A bar with the focus stays focused, and the list moves its tab stop to that task’s row. A row with the focus works the same way the other way round.
- The keys walk the surface they were pressed on, and the other one follows.
focusTask(id)stays on the surface that holds the focus. With the focus on neither it goes to the row, as it does without the option.focusBar(id)always goes to the bar.- Moving between a bar and a row reports the new task to
onFocusChange, with nonullbetween.
Styling
Section titled “Styling”The default stylesheet styles the list, with no second import. Its classes and attributes are part of the unstyled contract, the same in vanilla and React.
The list has the class gantt-scope, which declares every --gantt-* token and honours data-theme.
A sibling cannot inherit the tokens from .gantt, so put that class on your own panels too, to use
them there:
<aside class="gantt-scope" data-theme="dark">…</aside>Three tokens are for rows: --gantt-row-hover-color, --gantt-row-highlight-color and
--gantt-row-selected-color (see Theming).
The row hover
Section titled “The row hover”A chart on its own lights no row: --gantt-row-hover-color is transparent, so a chart without a
list looks as it always did. A task list turns on display.rowHover while it is attached, which
gives that token the colour of the theme. To have a row hover without a list, set
display: { rowHover: true }, or set the token yourself.
A list of your own
Section titled “A list of your own”Everything the list uses is public, so you can draw your own markup on the same API (the task list beside the chart example does):
| What | API |
|---|---|
| The rows, and when to draw | getRows(), the same array until a row changes; onRowsChange |
| Which rows to draw | getRowWindow() |
| The sizes | getLayoutMetrics(), getRowMetrics(id) |
| Theme, height, stripes | getAppearance(), on('appearanceChange', …) |
| Scrolling the chart | scrollTo({ top }), scrollToTask(id, { align: 'nearest', alignY: 'nearest' }) |
| Opening and shutting | toggle(id), onExpandedChange |
| The lit row | setHoveredRow(id), snapshot.hoveredRow, onRowHoverChange |
| The keyboard focus | focusTask(id), snapshot.focusedTask, onFocusChange |
@ganttlane/dom/task-list also exports the pieces that createTaskList and <TaskList> share:
mediateTaskList, which wires the events and the scrolling to your elements, and the helpers
treeAction, rowsToDraw, rowAttributes, columnTracks and createTabStop.