@umriss-ui/schedule 0.44.0 → 0.46.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/CHANGELOG.md +28 -0
- package/README.md +21 -3
- package/dist/index.d.ts +19 -5
- package/dist/schedule.css +1 -1
- package/dist/schedule.js +1382 -1258
- package/docs/llms-full.md +467 -68
- package/package.json +6 -6
package/docs/llms-full.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @umriss-ui/schedule 0.
|
|
1
|
+
# @umriss-ui/schedule 0.46.0
|
|
2
2
|
|
|
3
3
|
> React Gantt-style schedule for data-dense applications - work on lanes over time, with dependencies, blocked time, findings and controlled editing.
|
|
4
4
|
|
|
@@ -1433,38 +1433,38 @@ Under forced colours the plot paints itself in the system colours: the work in t
|
|
|
1433
1433
|
| Prop | Type | Default | Description |
|
|
1434
1434
|
|---|---|---|---|
|
|
1435
1435
|
| `ref` | `Ref<HTMLDivElement> \| undefined` | — | Receives the root element. |
|
|
1436
|
-
| `height` | `number` | fills the frame, at least 400 px | Height in CSS pixels, both bands included, fixed. Left out, the schedule fills its frame - a stretched grid cell, a card the grid stretches, a container of definite height - and never goes below 400 px, which is also its height in a frame without one. A different floor goes through `style`'s `minHeight` ([ADR-0050](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0050-a-chart-fills-its-frame-never-below-its-floor.md)). The width is the host's.<br>Shown in: [A day of tours](#first-schedule), [Start from an empty plan](#first-schedule), [Plan a day in the machine shop](#first-schedule) and
|
|
1437
|
-
| `laneHeight` | `number` | `44` | Height of one lane in pixels at the default font size of 16 px; the schedule multiplies it by the root font size over 16 px when that ratio is above 1, so lane names keep their room in a reader's larger font.<br>Shown in: [Scroll more lanes than fit (Lanes)](#lanes), [Measure a plan (Benchmark)](#benchmark) |
|
|
1438
|
-
| `headerWidth` | `number` | `160` | Width of the lane headers in pixels - at most 40 % of the schedule, so that on a phone the plot keeps the larger part.<br>Shown in: [What a lane header says (Lanes)](#lanes), [
|
|
1436
|
+
| `height` | `number` | fills the frame, at least 400 px | Height in CSS pixels, both bands included, fixed. Left out, the schedule fills its frame - a stretched grid cell, a card the grid stretches, a container of definite height - and never goes below 400 px, which is also its height in a frame without one. A different floor goes through `style`'s `minHeight` ([ADR-0050](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0050-a-chart-fills-its-frame-never-below-its-floor.md)). The width is the host's.<br>Shown in: [A day of tours](#first-schedule), [Start from an empty plan](#first-schedule), [Plan a day in the machine shop](#first-schedule) and 85 more |
|
|
1437
|
+
| `laneHeight` | `number` | `44` | Height of one lane in pixels at the default font size of 16 px; the schedule multiplies it by the root font size over 16 px when that ratio is above 1, so lane names keep their room in a reader's larger font.<br>Shown in: [Highlight a lane (Lanes)](#lanes), [Scroll more lanes than fit (Lanes)](#lanes), [Measure a plan (Benchmark)](#benchmark) |
|
|
1438
|
+
| `headerWidth` | `number` | `160` | Width of the lane headers in pixels - at most 40 % of the schedule, so that on a phone the plot keeps the larger part.<br>Shown in: [What a lane header says (Lanes)](#lanes), [Highlight a lane (Lanes)](#lanes), [Scroll more lanes than fit (Lanes)](#lanes) and 2 more |
|
|
1439
1439
|
| `calendar` | `CalendarInput` | `[]` | A working calendar: the intervals in which time counts. Nights and weekends outside them are cut out of the axis. Without intervals every hour counts, as on the wall clock.<br>Shown in: [Cut the nights out of the axis (Time axis and calendar)](#time-axis-and-calendar), [Plan in office hours only (Time axis and calendar)](#time-axis-and-calendar), [Plan the sprint around leave (Scenarios)](#plan-the-sprint-around-leave) |
|
|
1440
|
-
| `zoomLimits` | `ZoomLimits` | one hour to 28 days | The narrowest and widest time span zoom may reach, in milliseconds.<br>Shown in: [Move through the plan (Pan and zoom)](#pan-and-zoom) |
|
|
1440
|
+
| `zoomLimits` | `ZoomLimits` | one hour to 28 days | The narrowest and widest time span zoom may reach, in milliseconds.<br>Shown in: [Move through the plan (Pan and zoom)](#pan-and-zoom), [Zoom and fit with buttons (Pan and zoom)](#pan-and-zoom) |
|
|
1441
1441
|
| `snap` | `"ticks" \| number \| ``SnapRaster`` \| false` | `"ticks"` | The raster a drag lands on: `"ticks"` - the fine band's current step -, a step in milliseconds from local midnight, a step with an offset (shifts at 06:00, 14:00 and 22:00 are eight hours offset by six), or `false` for none. It shapes the ghost and the intent, never the data.<br>Shown in: [Snap to a duration (Snapping)](#snapping), [Snap to handover times (Snapping)](#snapping), [Place without snapping (Snapping)](#snapping) |
|
|
1442
|
-
| `intents` | `readonly ``IntentKind``[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)).<br>Shown in: [Show that a plan is on its way](#first-schedule), [Read a folded group (Lane groups)](#lane-groups), [Cut the nights out of the axis (Time axis and calendar)](#time-axis-and-calendar) and
|
|
1442
|
+
| `intents` | `readonly ``IntentKind``[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)).<br>Shown in: [Show that a plan is on its way](#first-schedule), [Read a folded group (Lane groups)](#lane-groups), [Cut the nights out of the axis (Time axis and calendar)](#time-axis-and-calendar) and 20 more |
|
|
1443
1443
|
| `canMoveTo` | `(subtask: `[`Subtask`](#subtasks)`, lane: string) => boolean` | every lane is open | Whether a subtask may go to a lane. It narrows `"lane"`, it does not enable it. Asked once per lane when a drag takes hold, and asked again at the drop. The lanes it turns down are marked from the first frame of the drag - a planner sees the refusal before meeting it - and over one of them the ghost stays on the last lane that was allowed, the cursor says no and a line ties the ghost to the pointer. A refused lane costs the lane and nothing else: a drop after it still reports the move in time. It is asked for work dragged in from outside as well, with the key and task the application declared in `placing`.<br>Shown in: [Keep work on the lanes that fit (Where a subtask may go)](#where-a-subtask-may-go), [Apply the same rule to work dragged in (Where a subtask may go)](#where-a-subtask-may-go), [Keep work out of someone's leave (Where a subtask may go)](#where-a-subtask-may-go) and 1 more |
|
|
1444
1444
|
| `selectedTask` | `string \| null` | — | The selected task, controlled. Leave it out and the schedule keeps the selection itself.<br>Shown in: [Control the selection (Selection)](#selection) |
|
|
1445
1445
|
| `onSelectedTaskChange` | `(task: string \| null, subtask: string \| null) => void` | — | Called when a click selects a task or clears the selection, with the subtask that was clicked - null where the click was on a dependency or on nothing. It is called again when another subtask of the same task is clicked.<br>Shown in: [Control the selection (Selection)](#selection) |
|
|
1446
1446
|
| `placing` | `PlacingItem`` \| null` | — | What the application is dragging in from outside while it drags it - its key, task, the length of its main time, its lead-in and lead-out. The browser hands the dragged data over only on the drop, so the ghost before it can only come from here: set it on your own `dragstart`, clear it on `dragend`. Without `"place"` in `intents` no drop is accepted.<br>Shown in: [Drag work in from the backlog (Placing from outside)](#placing-from-outside), [Apply the same rule to work dragged in (Where a subtask may go)](#where-a-subtask-may-go) |
|
|
1447
1447
|
| `route` | `DependencyRoute`<br>`"curve" \| "straight" \| "orthogonal"` | `"curve"` | How the dependencies are drawn: a `"curve"` that leaves and arrives forwards, a `"straight"` line, or `"orthogonal"` segments. A dependency may say otherwise for itself.<br>Shown in: [Straight (Routes)](#routes), [Orthogonal (Routes)](#routes), [Per dependency (Routes)](#routes) |
|
|
1448
|
-
| `attach` | `DependencyAttachment`<br>`"centre" \| "nearest"` | `"centre"` | Where a dependency's ends sit on their bars: the `"centre"` of both, or the `"nearest"` edge - which is the shortest line between two stops. Within one lane both mean the middle. A dependency may say otherwise for itself. It changes the picture and never a finding: whether a dependency is violated follows from its `leaves` and `arrives` alone.<br>Shown in: [Choose where a line touches its bars (Dependencies)](#dependencies), [Mark the ends with a dot (Dependencies)](#dependencies), [Draw one handover its own way (Dependencies)](#dependencies) and
|
|
1448
|
+
| `attach` | `DependencyAttachment`<br>`"centre" \| "nearest"` | `"centre"` | Where a dependency's ends sit on their bars: the `"centre"` of both, or the `"nearest"` edge - which is the shortest line between two stops. Within one lane both mean the middle. A dependency may say otherwise for itself. It changes the picture and never a finding: whether a dependency is violated follows from its `leaves` and `arrives` alone.<br>Shown in: [Choose where a line touches its bars (Dependencies)](#dependencies), [Mark the ends with a dot (Dependencies)](#dependencies), [Draw one handover its own way (Dependencies)](#dependencies) and 4 more |
|
|
1449
1449
|
| `ends` | `DependencyEnds`<br>`"dot" \| "none"` | `"dot"` | Whether a dependency's two ends carry a dot (`"dot"`) or the line stands alone (`"none"`). The dot says where the line is anchored - a help while a plan is being read, and noise in a plan full of short moves. A dependency may say otherwise for itself.<br>Shown in: [Mark the ends with a dot (Dependencies)](#dependencies), [Draw one handover its own way (Dependencies)](#dependencies) |
|
|
1450
1450
|
| `label` | `(subtask: `[`Subtask`](#subtasks)`) => string` | — | What stands written in a bar: a function from a subtask to a line of text, or nothing for bars without text. The text is cut off with an ellipsis where the bar is too narrow for it and left out where even that would say nothing; a bar that began before the view keeps its text at the view's edge.<br>Shown in: [Plan a day in the machine shop](#first-schedule), [Plan a sprint for a team (Subtasks)](#subtasks), [Write into the bars (Bar labels)](#bar-labels) and 7 more |
|
|
1451
1451
|
| `tooltip` | `false \| ((target: `[`ScheduleTooltipTarget`](#tooltip)`) => ReactNode)` | — | The tooltip on a hovered subtask or dependency: its order, its times, its parts and its findings. `false` switches it off; a function receives what the pointer rests on and returns content of the application's own.<br>Shown in: [Say what a milestone is in your own tooltip (Milestones)](#milestones), [Write your own tooltip content (Tooltip)](#tooltip), [Show a detail panel instead (Tooltip)](#tooltip) and 1 more |
|
|
1452
1452
|
| `now` | `boolean \| number` | `false` | A line marking the present across the lanes: `true` follows the clock by the minute, an instant fixes it there.<br>Shown in: [Plan a day in the machine shop](#first-schedule), [Mark the present (Now line)](#now-line), [Follow the clock (Now line)](#now-line) and 2 more |
|
|
1453
1453
|
| `loading` | `boolean` | `false` | The plan is on its way, as the chart's `loading` says ([ADR-0042](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0042-a-rows-height-never-follows-what-it-shows.md)). A plan already drawn stays: it dims after a moment and takes no pointer - no new drag, no tooltip, no intent by key - until the answer is in; a drag begun before finishes and reports. The keys still walk it. With no lanes, placeholder lines stand in the lane column. The plot is `aria-busy` meanwhile.<br>Shown in: [Show that a plan is on its way](#first-schedule) |
|
|
1454
|
-
| `children` | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn.<br>Shown in: [A day of tours](#first-schedule), [Start from an empty plan](#first-schedule), [Plan a day in the machine shop](#first-schedule) and
|
|
1454
|
+
| `children` | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn.<br>Shown in: [A day of tours](#first-schedule), [Start from an empty plan](#first-schedule), [Plan a day in the machine shop](#first-schedule) and 87 more |
|
|
1455
1455
|
|
|
1456
1456
|
###### Events
|
|
1457
1457
|
|
|
1458
1458
|
| Prop | Type | Default | Description |
|
|
1459
1459
|
|---|---|---|---|
|
|
1460
|
-
| `onIntent` | `(intent: ``Intent``) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it.<br>Shown in: [Read a folded group (Lane groups)](#lane-groups), [Cut the nights out of the axis (Time axis and calendar)](#time-axis-and-calendar), [Plan work around someone's leave (Blocked time)](#blocked-time) and
|
|
1460
|
+
| `onIntent` | `(intent: ``Intent``) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it.<br>Shown in: [Read a folded group (Lane groups)](#lane-groups), [Cut the nights out of the axis (Time axis and calendar)](#time-axis-and-calendar), [Plan work around someone's leave (Blocked time)](#blocked-time) and 19 more |
|
|
1461
1461
|
| `onInteraction` | `(interaction: `[`ScheduleInteraction`](#interactions)`) => void` | — | Click, context menu and hover, with what the pointer is on and where. While it is set, a right-click opens no browser menu.<br>Shown in: [Report what the pointer is on (Interactions)](#interactions), [Open a context menu (Interactions)](#interactions), [Show a detail panel instead (Tooltip)](#tooltip) and 3 more |
|
|
1462
1462
|
|
|
1463
1463
|
###### Accessibility
|
|
1464
1464
|
|
|
1465
1465
|
| Prop | Type | Default | Description |
|
|
1466
1466
|
|---|---|---|---|
|
|
1467
|
-
| `aria-label` *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38".<br>Shown in: [A day of tours](#first-schedule), [Start from an empty plan](#first-schedule), [Plan a day in the machine shop](#first-schedule) and
|
|
1467
|
+
| `aria-label` *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38".<br>Shown in: [A day of tours](#first-schedule), [Start from an empty plan](#first-schedule), [Plan a day in the machine shop](#first-schedule) and 87 more |
|
|
1468
1468
|
|
|
1469
1469
|
Also takes every attribute of `<div>` – without `aria-label`, `children`.
|
|
1470
1470
|
|
|
@@ -1666,11 +1666,11 @@ no subtask yet; the caller creates one, with its own identity, from
|
|
|
1666
1666
|
|
|
1667
1667
|
#### Known limits
|
|
1668
1668
|
|
|
1669
|
-
- No dependency types, critical path or auto-scheduling: the application decides where work goes, the schedule shows the result and its findings ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
1669
|
+
- No dependency types, critical path or auto-scheduling: the application decides where work goes, the schedule shows the result and its findings ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md)).
|
|
1670
1670
|
- No milestones yet: a point in time on a lane has no mark of its own.
|
|
1671
1671
|
- A dependency joins two subtasks of one task; dependencies across tasks are not yet possible.
|
|
1672
1672
|
|
|
1673
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
1673
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
1674
1674
|
|
|
1675
1675
|
## Plan
|
|
1676
1676
|
|
|
@@ -1741,6 +1741,79 @@ export default function LaneHeaders() {
|
|
|
1741
1741
|
}
|
|
1742
1742
|
```
|
|
1743
1743
|
|
|
1744
|
+
##### Highlight a lane
|
|
1745
|
+
|
|
1746
|
+
The application marks a lane by putting a `Badge` into its `label`, and `scrollToLane` brings it into view when it is picked from the list; the schedule has no highlight of its own.
|
|
1747
|
+
|
|
1748
|
+
```tsx
|
|
1749
|
+
import { useState } from "react";
|
|
1750
|
+
import { Badge, Button, Stack, Text } from "@umriss-ui/core";
|
|
1751
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
1752
|
+
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
1753
|
+
|
|
1754
|
+
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
1755
|
+
|
|
1756
|
+
const ROUNDS: Task[] = [{ id: "rounds", name: "Rounds", color: "light-dark(#0d9488, #3cc7b8)" }];
|
|
1757
|
+
|
|
1758
|
+
const CREWS = Array.from({ length: 12 }, (_, i) => ({ id: `crew-${i + 1}`, label: `Crew ${String(i + 1).padStart(2, "0")}` }));
|
|
1759
|
+
|
|
1760
|
+
const WORK: Subtask[] = CREWS.map((crew, i) => ({
|
|
1761
|
+
id: `${crew.id}-round`,
|
|
1762
|
+
task: "rounds",
|
|
1763
|
+
lane: crew.id,
|
|
1764
|
+
from: at(7, 30 * (i % 6)),
|
|
1765
|
+
to: at(9, 30 * (i % 6)),
|
|
1766
|
+
leadIn: 15 * 60_000,
|
|
1767
|
+
}));
|
|
1768
|
+
|
|
1769
|
+
export default function HighlightLane() {
|
|
1770
|
+
const [marked, setMarked] = useState<string | null>(null);
|
|
1771
|
+
const { Schedule, Lane, Subtasks, scrollToLane } = useSchedule({ initialView: { domain: [at(6), at(14)] } });
|
|
1772
|
+
|
|
1773
|
+
const pick = (id: string) => {
|
|
1774
|
+
setMarked(id);
|
|
1775
|
+
scrollToLane(id);
|
|
1776
|
+
};
|
|
1777
|
+
|
|
1778
|
+
return (
|
|
1779
|
+
<Stack direction="row" gap={3} align="flex-start">
|
|
1780
|
+
<Stack gap={1} style={{ flex: "none" }}>
|
|
1781
|
+
{CREWS.map((crew) => (
|
|
1782
|
+
<Button
|
|
1783
|
+
key={crew.id}
|
|
1784
|
+
size="sm"
|
|
1785
|
+
variant={marked === crew.id ? "secondary" : "ghost"}
|
|
1786
|
+
aria-pressed={marked === crew.id}
|
|
1787
|
+
onClick={() => pick(crew.id)}
|
|
1788
|
+
>
|
|
1789
|
+
{crew.label}
|
|
1790
|
+
</Button>
|
|
1791
|
+
))}
|
|
1792
|
+
</Stack>
|
|
1793
|
+
<Schedule aria-label="Twelve crews, one round each, a crew picked from the list" height={200} laneHeight={32} headerWidth={150}>
|
|
1794
|
+
{CREWS.map((crew) => (
|
|
1795
|
+
<Lane
|
|
1796
|
+
key={crew.id}
|
|
1797
|
+
id={crew.id}
|
|
1798
|
+
label={
|
|
1799
|
+
marked === crew.id ? (
|
|
1800
|
+
<Stack direction="row" gap={1} align="center">
|
|
1801
|
+
<Text size="sm">{crew.label}</Text>
|
|
1802
|
+
<Badge tone="accent">Picked</Badge>
|
|
1803
|
+
</Stack>
|
|
1804
|
+
) : (
|
|
1805
|
+
crew.label
|
|
1806
|
+
)
|
|
1807
|
+
}
|
|
1808
|
+
/>
|
|
1809
|
+
))}
|
|
1810
|
+
<Subtasks data={WORK} tasks={ROUNDS} />
|
|
1811
|
+
</Schedule>
|
|
1812
|
+
</Stack>
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
```
|
|
1816
|
+
|
|
1744
1817
|
##### Scroll more lanes than fit
|
|
1745
1818
|
|
|
1746
1819
|
Twenty vans in 300 pixels: the lanes scroll with their headers, while the time bands and the header column hold still.
|
|
@@ -1803,8 +1876,8 @@ The keys of [First schedule](#first-schedule) apply here.
|
|
|
1803
1876
|
|
|
1804
1877
|
| Prop | Type | Default | Description |
|
|
1805
1878
|
|---|---|---|---|
|
|
1806
|
-
| `id` *required* | `string` | — | The identity subtasks name in their `lane`.<br>Shown in: [What a lane header says](#lanes), [
|
|
1807
|
-
| `label` | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader.<br>Shown in: [What a lane header says](#lanes), [
|
|
1879
|
+
| `id` *required* | `string` | — | The identity subtasks name in their `lane`.<br>Shown in: [What a lane header says](#lanes), [Highlight a lane](#lanes), [Scroll more lanes than fit](#lanes) and 87 more |
|
|
1880
|
+
| `label` | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader.<br>Shown in: [What a lane header says](#lanes), [Highlight a lane](#lanes), [Scroll more lanes than fit](#lanes) and 86 more |
|
|
1808
1881
|
|
|
1809
1882
|
### Lane groups
|
|
1810
1883
|
|
|
@@ -2112,7 +2185,7 @@ The keys of [First schedule](#first-schedule) apply here.
|
|
|
2112
2185
|
|
|
2113
2186
|
- A folded group shows no summary - no utilisation band, no bar spanning its work: a computed claim would look like a drawn fact ([ADR-0025](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0025-a-lane-group-is-structure-over-lanes-never-a-lane.md)).
|
|
2114
2187
|
|
|
2115
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
2188
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
2116
2189
|
|
|
2117
2190
|
### Subtasks
|
|
2118
2191
|
|
|
@@ -2312,31 +2385,31 @@ The keys of [First schedule](#first-schedule) apply here.
|
|
|
2312
2385
|
|
|
2313
2386
|
| Prop | Type | Default | Description |
|
|
2314
2387
|
|---|---|---|---|
|
|
2315
|
-
| `data` *required* | `readonly ``Subtask``[]` | — | The subtasks, as the caller holds them. The schedule never changes one.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [Plan a sprint for a team](#subtasks) and
|
|
2316
|
-
| `tasks` *required* | `readonly ``Task``[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [Plan a sprint for a team](#subtasks) and
|
|
2388
|
+
| `data` *required* | `readonly ``Subtask``[]` | — | The subtasks, as the caller holds them. The schedule never changes one.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [Plan a sprint for a team](#subtasks) and 87 more |
|
|
2389
|
+
| `tasks` *required* | `readonly ``Task``[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [Plan a sprint for a team](#subtasks) and 87 more |
|
|
2317
2390
|
|
|
2318
2391
|
##### `Subtask`
|
|
2319
2392
|
|
|
2320
2393
|
| Prop | Type | Default | Description |
|
|
2321
2394
|
|---|---|---|---|
|
|
2322
|
-
| `id` *required* | `string` | — | The caller's identity of the subtask.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [Plan a sprint for a team](#subtasks) and
|
|
2323
|
-
| `task` *required* | `string` | — | The id of the task it belongs to.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and
|
|
2324
|
-
| `lane` *required* | `string` | — | The id of the lane it sits on.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and
|
|
2325
|
-
| `from` *required* | `number` | — | Start of the main time.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and
|
|
2326
|
-
| `to` *required* | `number` | — | End of the main time.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and
|
|
2327
|
-
| `leadIn` | `number` | — | Duration of the lead-in before the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule) and
|
|
2328
|
-
| `leadOut` | `number` | — | Duration of the lead-out after the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule) and
|
|
2329
|
-
| `name` | `string` | — | What the subtask is called, where the schedule names it.<br>Shown in: [Plan a sprint for a team](#subtasks), [Write into the bars (Bar labels)](#bar-labels), [Join the stops of a tour (Dependencies)](#dependencies) and
|
|
2330
|
-
| `appearance` | `readonly ``SubtaskAppearance``[]` | — | What the bar says besides its colour: `"provisional"`, `"fixed"`, `"muted"`, `"open"`. Several hold at once; `"provisional"` and `"fixed"` contradict, and the later one in the list wins.<br>Shown in: [Plan a sprint for a team](#subtasks), [Provisional (Appearances)](#appearances), [Fixed (Appearances)](#appearances) and
|
|
2395
|
+
| `id` *required* | `string` | — | The caller's identity of the subtask.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [Plan a sprint for a team](#subtasks) and 84 more |
|
|
2396
|
+
| `task` *required* | `string` | — | The id of the task it belongs to.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and 80 more |
|
|
2397
|
+
| `lane` *required* | `string` | — | The id of the lane it sits on.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and 80 more |
|
|
2398
|
+
| `from` *required* | `number` | — | Start of the main time.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and 80 more |
|
|
2399
|
+
| `to` *required* | `number` | — | End of the main time.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and 80 more |
|
|
2400
|
+
| `leadIn` | `number` | — | Duration of the lead-in before the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule) and 27 more |
|
|
2401
|
+
| `leadOut` | `number` | — | Duration of the lead-out after the main time; none is zero.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule) and 23 more |
|
|
2402
|
+
| `name` | `string` | — | What the subtask is called, where the schedule names it.<br>Shown in: [Plan a sprint for a team](#subtasks), [Write into the bars (Bar labels)](#bar-labels), [Join the stops of a tour (Dependencies)](#dependencies) and 31 more |
|
|
2403
|
+
| `appearance` | `readonly ``SubtaskAppearance``[]` | — | What the bar says besides its colour: `"provisional"`, `"fixed"`, `"muted"`, `"open"`. Several hold at once; `"provisional"` and `"fixed"` contradict, and the later one in the list wins.<br>Shown in: [Plan a sprint for a team](#subtasks), [Provisional (Appearances)](#appearances), [Fixed (Appearances)](#appearances) and 4 more |
|
|
2331
2404
|
| `progress` | `number` | — | The share of the work that is done, from 0 to 1, drawn as a filled part of the bar. Without it nothing is claimed - an empty bar and a bar at zero per cent are two different statements.<br>Shown in: [Progress (Appearances)](#appearances), [Several at once (Appearances)](#appearances) |
|
|
2332
2405
|
|
|
2333
2406
|
##### `Task`
|
|
2334
2407
|
|
|
2335
2408
|
| Prop | Type | Default | Description |
|
|
2336
2409
|
|---|---|---|---|
|
|
2337
|
-
| `id` *required* | `string` | — | The caller's identity of the task.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and
|
|
2338
|
-
| `color` *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and
|
|
2339
|
-
| `name` | `string` | — | What the task is called, where the schedule names it.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and
|
|
2410
|
+
| `id` *required* | `string` | — | The caller's identity of the task.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and 79 more |
|
|
2411
|
+
| `color` *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and 79 more |
|
|
2412
|
+
| `name` | `string` | — | What the task is called, where the schedule names it.<br>Shown in: [Main time, lead-in and lead-out](#subtasks), [Colour by what the task means](#subtasks), [The smallest schedule (Installation)](#installation) and 79 more |
|
|
2340
2413
|
|
|
2341
2414
|
##### Types on this page
|
|
2342
2415
|
|
|
@@ -2976,7 +3049,7 @@ From `ScheduleView` — the full table stands on [View](#view).
|
|
|
2976
3049
|
|
|
2977
3050
|
- No stacked layout on request yet: an overlap is always drawn offset, as the finding it is.
|
|
2978
3051
|
|
|
2979
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
3052
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
2980
3053
|
|
|
2981
3054
|
### Dependencies
|
|
2982
3055
|
|
|
@@ -3274,6 +3347,62 @@ export default function OneHandoverDrawnItsOwnWay() {
|
|
|
3274
3347
|
}
|
|
3275
3348
|
```
|
|
3276
3349
|
|
|
3350
|
+
##### Mark the critical path
|
|
3351
|
+
|
|
3352
|
+
The planning system works out the critical path and hands over its subtask ids; `muted` takes the rest back so the path reads first. The schedule computes nothing ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md)), so the switch only changes what the application marks.
|
|
3353
|
+
|
|
3354
|
+
```tsx
|
|
3355
|
+
import { useState } from "react";
|
|
3356
|
+
import { Stack, Switch } from "@umriss-ui/core";
|
|
3357
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
3358
|
+
import type { Dependency, Subtask, Task } from "@umriss-ui/schedule";
|
|
3359
|
+
|
|
3360
|
+
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
3361
|
+
const min = (n: number) => n * 60_000;
|
|
3362
|
+
|
|
3363
|
+
const PROJECTS: Task[] = [{ id: "portal", name: "Member portal", color: "light-dark(#2563eb, #6b9bff)" }];
|
|
3364
|
+
|
|
3365
|
+
const WORK: Subtask[] = [
|
|
3366
|
+
{ id: "design", task: "portal", lane: "noah", from: at(7), to: at(8), name: "Design" },
|
|
3367
|
+
{ id: "copy", task: "portal", lane: "mira", from: at(7), to: at(8, 30), name: "Copy" },
|
|
3368
|
+
{ id: "build", task: "portal", lane: "arjun", from: at(8, 30), to: at(9, 30), name: "Build" },
|
|
3369
|
+
{ id: "test", task: "portal", lane: "eva", from: at(10), to: at(11), name: "Test" },
|
|
3370
|
+
{ id: "docs", task: "portal", lane: "mira", from: at(9), to: at(10), name: "Docs" },
|
|
3371
|
+
];
|
|
3372
|
+
|
|
3373
|
+
const HANDOVERS: Dependency[] = [
|
|
3374
|
+
{ id: "design-build", from: "design", to: "build", lag: min(15) },
|
|
3375
|
+
{ id: "copy-docs", from: "copy", to: "docs", lag: min(15) },
|
|
3376
|
+
{ id: "build-test", from: "build", to: "test", lag: min(15) },
|
|
3377
|
+
];
|
|
3378
|
+
|
|
3379
|
+
/* As the planning system hands it over. */
|
|
3380
|
+
const CRITICAL_PATH = ["design", "build", "test"];
|
|
3381
|
+
|
|
3382
|
+
export default function CriticalPath() {
|
|
3383
|
+
const [marked, setMarked] = useState(true);
|
|
3384
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(6, 30), at(11, 30)] } });
|
|
3385
|
+
|
|
3386
|
+
const plan: Subtask[] = WORK.map((subtask): Subtask =>
|
|
3387
|
+
marked && !CRITICAL_PATH.includes(subtask.id) ? { ...subtask, appearance: ["muted"] } : subtask,
|
|
3388
|
+
);
|
|
3389
|
+
|
|
3390
|
+
return (
|
|
3391
|
+
<Stack gap={3}>
|
|
3392
|
+
<Switch label="Mark the critical path" checked={marked} onChange={(event) => setMarked(event.target.checked)} />
|
|
3393
|
+
<Schedule aria-label="Portal work, the critical path marked" height={188} attach="nearest">
|
|
3394
|
+
<Lane id="noah" label="Noah Fischer" />
|
|
3395
|
+
<Lane id="mira" label="Mira Stone" />
|
|
3396
|
+
<Lane id="arjun" label="Arjun Mehta" />
|
|
3397
|
+
<Lane id="eva" label="Eva Novak" />
|
|
3398
|
+
<Dependencies data={HANDOVERS} />
|
|
3399
|
+
<Subtasks data={plan} tasks={PROJECTS} />
|
|
3400
|
+
</Schedule>
|
|
3401
|
+
</Stack>
|
|
3402
|
+
);
|
|
3403
|
+
}
|
|
3404
|
+
```
|
|
3405
|
+
|
|
3277
3406
|
#### When to use something else
|
|
3278
3407
|
|
|
3279
3408
|
- The shape of the line → Routes
|
|
@@ -3293,21 +3422,21 @@ The keys of [First schedule](#first-schedule) apply here.
|
|
|
3293
3422
|
|
|
3294
3423
|
| Prop | Type | Default | Description |
|
|
3295
3424
|
|---|---|---|---|
|
|
3296
|
-
| `data` *required* | `readonly ``Dependency``[]` | — | The dependencies, as the caller holds them.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and
|
|
3425
|
+
| `data` *required* | `readonly ``Dependency``[]` | — | The dependencies, as the caller holds them.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and 30 more |
|
|
3297
3426
|
|
|
3298
3427
|
##### `Dependency`
|
|
3299
3428
|
|
|
3300
3429
|
| Prop | Type | Default | Description |
|
|
3301
3430
|
|---|---|---|---|
|
|
3302
|
-
| `id` *required* | `string` | — | The caller's identity of the dependency.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and
|
|
3303
|
-
| `from` *required* | `string` | — | The id of the subtask it leaves.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and
|
|
3304
|
-
| `to` *required* | `string` | — | The id of the subtask it arrives at.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and
|
|
3305
|
-
| `lag` *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and
|
|
3431
|
+
| `id` *required* | `string` | — | The caller's identity of the dependency.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and 29 more |
|
|
3432
|
+
| `from` *required* | `string` | — | The id of the subtask it leaves.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and 29 more |
|
|
3433
|
+
| `to` *required* | `string` | — | The id of the subtask it arrives at.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and 29 more |
|
|
3434
|
+
| `lag` *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends.<br>Shown in: [Join the stops of a tour](#dependencies), [Choose what a dependency connects](#dependencies), [Choose where a line touches its bars](#dependencies) and 29 more |
|
|
3306
3435
|
| `leaves` | `"main" \| "leadOut"` | `"leadOut"` | Where it leaves: at the end of the main time, or after the lead-out. The predecessor counts as ended once its lead-out is.<br>Shown in: [Choose what a dependency connects](#dependencies), [Plan a day in the machine shop (First schedule)](#first-schedule), [Replan the day on the line (Scenarios)](#replan-the-day-on-the-line) |
|
|
3307
3436
|
| `arrives` | `"main" \| "leadIn"` | `"leadIn"` | Where it arrives: at the start of the main time, or before the lead-in. The lag must have run out before the successor's lead-in begins.<br>Shown in: [Choose what a dependency connects](#dependencies), [A day of tours (First schedule)](#first-schedule), [Plan a day in the machine shop (First schedule)](#first-schedule) and 1 more |
|
|
3308
|
-
| `route` | `DependencyRoute`<br>`"curve" \| "straight" \| "orthogonal"` | — | How this one is drawn, where it is not drawn like the rest. It changes the picture and never the finding: whether a dependency is violated follows from `leaves` and `arrives` alone.<br>Shown in: [Per dependency (Routes)](#routes) |
|
|
3437
|
+
| `route` | `DependencyRoute`<br>`"curve" \| "straight" \| "orthogonal"` | — | How this one is drawn, where it is not drawn like the rest. It changes the picture and never the finding: whether a dependency is violated follows from `leaves` and `arrives` alone.<br>Shown in: [Per dependency (Routes)](#routes), [Undo the applied intents (Undo)](#undo) |
|
|
3309
3438
|
| `attach` | `DependencyAttachment`<br>`"centre" \| "nearest"` | — | Where on the bars this one's line attaches, where it does not attach like the rest. Also picture only, and not to be confused with the two anchors above: those say what the dependency connects, this says where the line touches.<br>Shown in: [Draw one handover its own way](#dependencies) |
|
|
3310
|
-
| `ends` | `DependencyEnds`<br>`"dot" \| "none"` | — | Whether this one's ends are marked with a dot, where it is not marked like the rest. Also picture only.<br>Shown in: [Draw one handover its own way](#dependencies) |
|
|
3439
|
+
| `ends` | `DependencyEnds`<br>`"dot" \| "none"` | — | Whether this one's ends are marked with a dot, where it is not marked like the rest. Also picture only.<br>Shown in: [Draw one handover its own way](#dependencies), [Undo the applied intents (Undo)](#undo) |
|
|
3311
3440
|
|
|
3312
3441
|
##### Types on this page
|
|
3313
3442
|
|
|
@@ -3346,9 +3475,9 @@ type DependencyEnds = "dot" | "none";
|
|
|
3346
3475
|
|
|
3347
3476
|
#### Known limits
|
|
3348
3477
|
|
|
3349
|
-
- Only finish-to-start within one task; no dependency types and no critical path ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
3478
|
+
- Only finish-to-start within one task; no dependency types and no critical path ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md)).
|
|
3350
3479
|
|
|
3351
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
3480
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
3352
3481
|
|
|
3353
3482
|
### Routes
|
|
3354
3483
|
|
|
@@ -4743,6 +4872,59 @@ export default function PanAndZoom() {
|
|
|
4743
4872
|
}
|
|
4744
4873
|
```
|
|
4745
4874
|
|
|
4875
|
+
##### Zoom and fit with buttons
|
|
4876
|
+
|
|
4877
|
+
The schedule has no zoom buttons of its own: `visibleDomain` reads the span in view, `setDomain` puts a new one in, and `setDomain(null)` fits the subtasks. The application keeps each zoom within the `zoomLimits` it passes.
|
|
4878
|
+
|
|
4879
|
+
```tsx
|
|
4880
|
+
import { Button, Stack } from "@umriss-ui/core";
|
|
4881
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
4882
|
+
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
4883
|
+
|
|
4884
|
+
const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
4885
|
+
const HOUR = 60 * 60_000;
|
|
4886
|
+
|
|
4887
|
+
const TASKS: Task[] = [{ id: "release", name: "Release 4.12", color: "light-dark(#2563eb, #6b9bff)" }];
|
|
4888
|
+
|
|
4889
|
+
const WORK: Subtask[] = [
|
|
4890
|
+
{ id: "build", task: "release", lane: "priya", from: at(6), to: at(7) },
|
|
4891
|
+
{ id: "rollout", task: "release", lane: "jonas", from: at(8), to: at(10, 30) },
|
|
4892
|
+
{ id: "verify", task: "release", lane: "ada", from: at(11), to: at(12) },
|
|
4893
|
+
];
|
|
4894
|
+
|
|
4895
|
+
const LIMITS = { min: 2 * HOUR, max: 48 * HOUR };
|
|
4896
|
+
|
|
4897
|
+
export default function ZoomAndFit() {
|
|
4898
|
+
const { Schedule, Lane, Subtasks, setDomain, visibleDomain } = useSchedule({
|
|
4899
|
+
initialView: { domain: [at(5, 30), at(18)] },
|
|
4900
|
+
});
|
|
4901
|
+
|
|
4902
|
+
/* Halves or doubles the span about its middle, clamped to the limits. */
|
|
4903
|
+
const zoom = (factor: number) => {
|
|
4904
|
+
const [from, to] = visibleDomain();
|
|
4905
|
+
const middle = (from + to) / 2;
|
|
4906
|
+
const span = Math.min(LIMITS.max, Math.max(LIMITS.min, (to - from) * factor));
|
|
4907
|
+
setDomain([middle - span / 2, middle + span / 2]);
|
|
4908
|
+
};
|
|
4909
|
+
|
|
4910
|
+
return (
|
|
4911
|
+
<Stack gap={3}>
|
|
4912
|
+
<Stack direction="row" gap={2}>
|
|
4913
|
+
<Button size="sm" onClick={() => zoom(0.5)}>Zoom in</Button>
|
|
4914
|
+
<Button size="sm" onClick={() => zoom(2)}>Zoom out</Button>
|
|
4915
|
+
<Button size="sm" onClick={() => setDomain(null)}>Fit</Button>
|
|
4916
|
+
</Stack>
|
|
4917
|
+
<Schedule aria-label="The release, with zoom and fit buttons" height={160} zoomLimits={LIMITS}>
|
|
4918
|
+
<Lane id="priya" label="Priya Raman" />
|
|
4919
|
+
<Lane id="jonas" label="Jonas Keller" />
|
|
4920
|
+
<Lane id="ada" label="Ada Mwangi" />
|
|
4921
|
+
<Subtasks data={WORK} tasks={TASKS} />
|
|
4922
|
+
</Schedule>
|
|
4923
|
+
</Stack>
|
|
4924
|
+
);
|
|
4925
|
+
}
|
|
4926
|
+
```
|
|
4927
|
+
|
|
4746
4928
|
#### Keyboard
|
|
4747
4929
|
|
|
4748
4930
|
| Key | Action |
|
|
@@ -5051,7 +5233,7 @@ From `ScheduleView` — the full table stands on [View](#view).
|
|
|
5051
5233
|
|
|
5052
5234
|
- One task at a time: there is no multiple selection.
|
|
5053
5235
|
|
|
5054
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
5236
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
5055
5237
|
|
|
5056
5238
|
### Interactions
|
|
5057
5239
|
|
|
@@ -5643,6 +5825,43 @@ export default function SayWhatABarCoversAndALineJoins() {
|
|
|
5643
5825
|
}
|
|
5644
5826
|
```
|
|
5645
5827
|
|
|
5828
|
+
##### Read the plan as a table
|
|
5829
|
+
|
|
5830
|
+
Place `DataTable` inside the schedule and a Show data key opens the subtasks in view as a table - lane, task, subtask, from and to with their dates, duration and findings. Press it: batch B-2 runs across midnight and overlaps B-1.
|
|
5831
|
+
|
|
5832
|
+
```tsx
|
|
5833
|
+
import { useSchedule } from "@umriss-ui/schedule";
|
|
5834
|
+
import type { Subtask, Task } from "@umriss-ui/schedule";
|
|
5835
|
+
|
|
5836
|
+
const at = (day: number, hours: number, minutes = 0) => new Date(2026, 2, day, hours, minutes).getTime();
|
|
5837
|
+
const min = (n: number) => n * 60_000;
|
|
5838
|
+
|
|
5839
|
+
const BATCHES: readonly Task[] = [
|
|
5840
|
+
{ id: "b-1", name: "Batch B-1 Oat drink", color: "light-dark(#2563eb, #6b9bff)" },
|
|
5841
|
+
{ id: "b-2", name: "Batch B-2 Almond drink", color: "light-dark(#c2410c, #f08a52)" },
|
|
5842
|
+
];
|
|
5843
|
+
|
|
5844
|
+
/* B-2 starts on the mixer before B-1 has left it, and runs on past midnight. */
|
|
5845
|
+
const STEPS: readonly Subtask[] = [
|
|
5846
|
+
{ id: "b-1-mix", task: "b-1", lane: "mixer", from: at(17, 21), to: at(18, 1), name: "Mix", leadIn: min(15) },
|
|
5847
|
+
{ id: "b-1-fill", task: "b-1", lane: "filler", from: at(18, 1, 30), to: at(18, 4), name: "Fill" },
|
|
5848
|
+
{ id: "b-2-mix", task: "b-2", lane: "mixer", from: at(17, 23, 30), to: at(18, 3), name: "Mix" },
|
|
5849
|
+
{ id: "b-2-fill", task: "b-2", lane: "filler", from: at(18, 4, 30), to: at(18, 6), name: "Fill", leadOut: min(30) },
|
|
5850
|
+
];
|
|
5851
|
+
|
|
5852
|
+
export default function PlanAsTable() {
|
|
5853
|
+
const { Schedule, Lane, Subtasks, DataTable } = useSchedule({ initialView: { domain: [at(17, 20), at(18, 7)] } });
|
|
5854
|
+
return (
|
|
5855
|
+
<Schedule aria-label="Two batches over the night shift, as a plan and as a table" height={300}>
|
|
5856
|
+
<Lane id="mixer" label="Mixer" />
|
|
5857
|
+
<Lane id="filler" label="Filling line" />
|
|
5858
|
+
<Subtasks data={STEPS} tasks={BATCHES} />
|
|
5859
|
+
<DataTable />
|
|
5860
|
+
</Schedule>
|
|
5861
|
+
);
|
|
5862
|
+
}
|
|
5863
|
+
```
|
|
5864
|
+
|
|
5646
5865
|
#### Keyboard
|
|
5647
5866
|
|
|
5648
5867
|
The keys of [First schedule](#first-schedule) apply here.
|
|
@@ -5793,7 +6012,7 @@ The keys of [First schedule](#first-schedule) apply here.
|
|
|
5793
6012
|
|
|
5794
6013
|
| Prop | Type | Default | Description |
|
|
5795
6014
|
|---|---|---|---|
|
|
5796
|
-
| `initialView` | `ScheduleView` | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The schedule remembers none: where a view is kept is the application's decision.<br>Shown in: [Keep and restore a view](#view), [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule) and
|
|
6015
|
+
| `initialView` | `ScheduleView` | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The schedule remembers none: where a view is kept is the application's decision.<br>Shown in: [Keep and restore a view](#view), [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule) and 87 more |
|
|
5797
6016
|
|
|
5798
6017
|
###### Events
|
|
5799
6018
|
|
|
@@ -5805,7 +6024,7 @@ The keys of [First schedule](#first-schedule) apply here.
|
|
|
5805
6024
|
|
|
5806
6025
|
| Prop | Type | Default | Description |
|
|
5807
6026
|
|---|---|---|---|
|
|
5808
|
-
| `domain` | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`.<br>Shown in: [Keep and restore a view](#view), [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule) and
|
|
6027
|
+
| `domain` | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`.<br>Shown in: [Keep and restore a view](#view), [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule) and 86 more |
|
|
5809
6028
|
| `folded` | `readonly string[]` | — | The **Lane group**s that are folded, by id. An id no group carries falls out. A folded outer group hides the inner ones without their ids leaving the list, so unfolding it gives back the view that was there.<br>Shown in: [Fold groups from outside (Lane groups)](#lane-groups), [Read a folded group (Lane groups)](#lane-groups), [Start with groups folded (Lane groups)](#lane-groups) |
|
|
5810
6029
|
|
|
5811
6030
|
### Linked schedules
|
|
@@ -6133,7 +6352,7 @@ From `ScheduleView` — the full table stands on [View](#view).
|
|
|
6133
6352
|
|
|
6134
6353
|
- Only the view is shared - the span and the folded groups, not the selection, the hover or the vertical scroll.
|
|
6135
6354
|
|
|
6136
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
6355
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
6137
6356
|
|
|
6138
6357
|
### Functions on the hook
|
|
6139
6358
|
|
|
@@ -6591,7 +6810,7 @@ The keys of [First schedule](#first-schedule) apply here.
|
|
|
6591
6810
|
|
|
6592
6811
|
- Overlapping work is marked, not packed into sub-lanes; stacked overlap is not built yet.
|
|
6593
6812
|
|
|
6594
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
6813
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
6595
6814
|
|
|
6596
6815
|
## Editing
|
|
6597
6816
|
|
|
@@ -6966,10 +7185,189 @@ From `Overlap` — the full table stands on [Findings as data](#findings-as-data
|
|
|
6966
7185
|
|
|
6967
7186
|
#### Known limits
|
|
6968
7187
|
|
|
6969
|
-
- No undo: your application owns the data, and with it the history ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
7188
|
+
- No undo: your application owns the data, and with it the history - see [Undo](#undo) ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md)).
|
|
6970
7189
|
- The keys move work in time only; a lane change needs the pointer.
|
|
6971
7190
|
|
|
6972
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
7191
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
7192
|
+
|
|
7193
|
+
### Undo
|
|
7194
|
+
|
|
7195
|
+
Taking back the last applied change: the application keeps the plan before each intent and pops it on Ctrl+Z, ⌘Z on a Mac, or an Undo button (also called history).
|
|
7196
|
+
|
|
7197
|
+
```ts
|
|
7198
|
+
import { useSchedule, applyIntent } from "@umriss-ui/schedule";
|
|
7199
|
+
```
|
|
7200
|
+
|
|
7201
|
+
The schedule keeps no history: it reports each intent and the application applies it ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)), so the stack of plans is the application's to keep ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md)). The key is the page's, not the plot's - the plot leaves Ctrl and ⌘ to it.
|
|
7202
|
+
|
|
7203
|
+
#### Examples
|
|
7204
|
+
|
|
7205
|
+
##### Undo the applied intents
|
|
7206
|
+
|
|
7207
|
+
The history is the application's, not the schedule's ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md), [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md)): it keeps the plan before each intent it applies, and Ctrl+Z, ⌘Z on a Mac, or the Undo button pops the last one. The schedule only knows end-to-start, so a start-to-start is one dependency with a negative lag: the lag is the delay less the spec's three hours, and the line is drawn apart.
|
|
7208
|
+
|
|
7209
|
+
```tsx
|
|
7210
|
+
import { useState, type KeyboardEvent } from "react";
|
|
7211
|
+
import { Button, Stack, Text } from "@umriss-ui/core";
|
|
7212
|
+
import { applyIntent, useSchedule } from "@umriss-ui/schedule";
|
|
7213
|
+
import type { Dependency, Intent, Subtask, Task } from "@umriss-ui/schedule";
|
|
7214
|
+
|
|
7215
|
+
const at = (hours: number) => new Date(2026, 2, 17, hours).getTime();
|
|
7216
|
+
const HOUR = 60 * 60_000;
|
|
7217
|
+
|
|
7218
|
+
const PROJECTS: Task[] = [{ id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" }];
|
|
7219
|
+
|
|
7220
|
+
const START: readonly Subtask[] = [
|
|
7221
|
+
{ id: "spec", task: "portal", lane: "arjun", from: at(9), to: at(12), name: "Specification" },
|
|
7222
|
+
{ id: "build", task: "portal", lane: "chloe", from: at(10), to: at(13), name: "Build" },
|
|
7223
|
+
];
|
|
7224
|
+
|
|
7225
|
+
/* Start-to-start: the build may begin an hour after the spec begins, which is
|
|
7226
|
+
a lag of one hour less the spec's three hours. */
|
|
7227
|
+
const LINKS: readonly Dependency[] = [
|
|
7228
|
+
{ id: "spec-starts-build", from: "spec", to: "build", lag: HOUR - 3 * HOUR, route: "orthogonal", ends: "dot" },
|
|
7229
|
+
];
|
|
7230
|
+
|
|
7231
|
+
export default function UndoIntents() {
|
|
7232
|
+
const [state, setState] = useState<{ plan: readonly Subtask[]; history: readonly (readonly Subtask[])[] }>({
|
|
7233
|
+
plan: START,
|
|
7234
|
+
history: [],
|
|
7235
|
+
});
|
|
7236
|
+
|
|
7237
|
+
const apply = (intent: Intent) =>
|
|
7238
|
+
setState((s) => ({ plan: s.plan.map((item) => applyIntent(item, intent)), history: [...s.history, s.plan] }));
|
|
7239
|
+
|
|
7240
|
+
const undo = () =>
|
|
7241
|
+
setState((s) => {
|
|
7242
|
+
const previous = s.history.at(-1);
|
|
7243
|
+
return previous === undefined ? s : { plan: previous, history: s.history.slice(0, -1) };
|
|
7244
|
+
});
|
|
7245
|
+
|
|
7246
|
+
/* The key belongs to the page: the plot leaves Ctrl and ⌘ to it. */
|
|
7247
|
+
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
7248
|
+
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey && event.key.toLowerCase() === "z") {
|
|
7249
|
+
event.preventDefault();
|
|
7250
|
+
undo();
|
|
7251
|
+
}
|
|
7252
|
+
};
|
|
7253
|
+
|
|
7254
|
+
const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(8), at(15)] } });
|
|
7255
|
+
|
|
7256
|
+
return (
|
|
7257
|
+
<Stack gap={3}>
|
|
7258
|
+
<Stack direction="row" gap={2} align="center">
|
|
7259
|
+
<Button size="sm" variant="secondary" disabled={state.history.length === 0} onClick={undo}>
|
|
7260
|
+
Undo
|
|
7261
|
+
</Button>
|
|
7262
|
+
<Text size="xs" tone="muted">
|
|
7263
|
+
{state.history.length} to undo
|
|
7264
|
+
</Text>
|
|
7265
|
+
</Stack>
|
|
7266
|
+
<div onKeyDown={onKeyDown}>
|
|
7267
|
+
<Schedule
|
|
7268
|
+
aria-label="Specification and build, the build starting an hour after the spec starts"
|
|
7269
|
+
height={150}
|
|
7270
|
+
intents={["move", "lane"]}
|
|
7271
|
+
onIntent={apply}
|
|
7272
|
+
>
|
|
7273
|
+
<Lane id="arjun" label="Arjun Mehta" />
|
|
7274
|
+
<Lane id="chloe" label="Chloe Durand" />
|
|
7275
|
+
<Dependencies data={LINKS} />
|
|
7276
|
+
<Subtasks data={state.plan} tasks={PROJECTS} />
|
|
7277
|
+
</Schedule>
|
|
7278
|
+
</div>
|
|
7279
|
+
</Stack>
|
|
7280
|
+
);
|
|
7281
|
+
}
|
|
7282
|
+
```
|
|
7283
|
+
|
|
7284
|
+
#### Keyboard
|
|
7285
|
+
|
|
7286
|
+
The keys of [First schedule](#first-schedule) apply here.
|
|
7287
|
+
|
|
7288
|
+
#### Props on this page
|
|
7289
|
+
|
|
7290
|
+
From `ScheduleProps` — the full table stands on [First schedule](#first-schedule).
|
|
7291
|
+
|
|
7292
|
+
| Prop | Type | Default | Description |
|
|
7293
|
+
|---|---|---|---|
|
|
7294
|
+
| [`height`](#first-schedule) | `number` | fills the frame, at least 400 px | Height in CSS pixels, both bands included, fixed. Left out, the schedule fills its frame - a stretched grid cell, a card the grid stretches, a container of definite height - and never goes below 400 px, which is also its height in a frame without one. A different floor goes through `style`'s `minHeight` ([ADR-0050](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0050-a-chart-fills-its-frame-never-below-its-floor.md)). The width is the host's. |
|
|
7295
|
+
| [`intents`](#first-schedule) | `readonly `[`IntentKind`](#first-schedule)`[]` | `[]` | The intents the caller handles. Each one enables its interaction; none leaves a read-only schedule ([ADR-0023](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0023-editing-is-controlled-the-schedule-reports-intents.md)). |
|
|
7296
|
+
| [`children`](#first-schedule) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |
|
|
7297
|
+
|
|
7298
|
+
###### Events
|
|
7299
|
+
|
|
7300
|
+
| Prop | Type | Default | Description |
|
|
7301
|
+
|---|---|---|---|
|
|
7302
|
+
| [`onIntent`](#first-schedule) | `(intent: `[`Intent`](#first-schedule)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |
|
|
7303
|
+
|
|
7304
|
+
###### Accessibility
|
|
7305
|
+
|
|
7306
|
+
| Prop | Type | Default | Description |
|
|
7307
|
+
|---|---|---|---|
|
|
7308
|
+
| [`aria-label`](#first-schedule) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |
|
|
7309
|
+
|
|
7310
|
+
From `LaneProps` — the full table stands on [Lanes](#lanes).
|
|
7311
|
+
|
|
7312
|
+
| Prop | Type | Default | Description |
|
|
7313
|
+
|---|---|---|---|
|
|
7314
|
+
| [`id`](#lanes) *required* | `string` | — | The identity subtasks name in their `lane`. |
|
|
7315
|
+
| [`label`](#lanes) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |
|
|
7316
|
+
|
|
7317
|
+
From `SubtasksProps` — the full table stands on [Subtasks](#subtasks).
|
|
7318
|
+
|
|
7319
|
+
| Prop | Type | Default | Description |
|
|
7320
|
+
|---|---|---|---|
|
|
7321
|
+
| [`data`](#subtasks) *required* | `readonly `[`Subtask`](#subtasks)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
|
|
7322
|
+
| [`tasks`](#subtasks) *required* | `readonly `[`Task`](#subtasks)`[]` | — | The tasks the subtasks belong to - their identity and their colour. A subtask whose task is not among them is drawn in a muted colour. |
|
|
7323
|
+
|
|
7324
|
+
From `Subtask` — the full table stands on [Subtasks](#subtasks).
|
|
7325
|
+
|
|
7326
|
+
| Prop | Type | Default | Description |
|
|
7327
|
+
|---|---|---|---|
|
|
7328
|
+
| [`id`](#subtasks) *required* | `string` | — | The caller's identity of the subtask. |
|
|
7329
|
+
| [`task`](#subtasks) *required* | `string` | — | The id of the task it belongs to. |
|
|
7330
|
+
| [`lane`](#subtasks) *required* | `string` | — | The id of the lane it sits on. |
|
|
7331
|
+
| [`from`](#subtasks) *required* | `number` | — | Start of the main time. |
|
|
7332
|
+
| [`to`](#subtasks) *required* | `number` | — | End of the main time. |
|
|
7333
|
+
| [`name`](#subtasks) | `string` | — | What the subtask is called, where the schedule names it. |
|
|
7334
|
+
|
|
7335
|
+
From `Task` — the full table stands on [Subtasks](#subtasks).
|
|
7336
|
+
|
|
7337
|
+
| Prop | Type | Default | Description |
|
|
7338
|
+
|---|---|---|---|
|
|
7339
|
+
| [`id`](#subtasks) *required* | `string` | — | The caller's identity of the task. |
|
|
7340
|
+
| [`color`](#subtasks) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
|
|
7341
|
+
| [`name`](#subtasks) | `string` | — | What the task is called, where the schedule names it. |
|
|
7342
|
+
|
|
7343
|
+
From `DependenciesProps` — the full table stands on [Dependencies](#dependencies).
|
|
7344
|
+
|
|
7345
|
+
| Prop | Type | Default | Description |
|
|
7346
|
+
|---|---|---|---|
|
|
7347
|
+
| [`data`](#dependencies) *required* | `readonly `[`Dependency`](#dependencies)`[]` | — | The dependencies, as the caller holds them. |
|
|
7348
|
+
|
|
7349
|
+
From `Dependency` — the full table stands on [Dependencies](#dependencies).
|
|
7350
|
+
|
|
7351
|
+
| Prop | Type | Default | Description |
|
|
7352
|
+
|---|---|---|---|
|
|
7353
|
+
| [`id`](#dependencies) *required* | `string` | — | The caller's identity of the dependency. |
|
|
7354
|
+
| [`from`](#dependencies) *required* | `string` | — | The id of the subtask it leaves. |
|
|
7355
|
+
| [`to`](#dependencies) *required* | `string` | — | The id of the subtask it arrives at. |
|
|
7356
|
+
| [`lag`](#dependencies) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |
|
|
7357
|
+
| [`route`](#dependencies) | [`DependencyRoute`](#dependencies)<br>`"curve" \| "straight" \| "orthogonal"` | — | How this one is drawn, where it is not drawn like the rest. It changes the picture and never the finding: whether a dependency is violated follows from `leaves` and `arrives` alone. |
|
|
7358
|
+
| [`ends`](#dependencies) | [`DependencyEnds`](#dependencies)<br>`"dot" \| "none"` | — | Whether this one's ends are marked with a dot, where it is not marked like the rest. Also picture only. |
|
|
7359
|
+
|
|
7360
|
+
From `ScheduleOptions` — the full table stands on [View](#view).
|
|
7361
|
+
|
|
7362
|
+
| Prop | Type | Default | Description |
|
|
7363
|
+
|---|---|---|---|
|
|
7364
|
+
| [`initialView`](#view) | [`ScheduleView`](#view) | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The schedule remembers none: where a view is kept is the application's decision. |
|
|
7365
|
+
|
|
7366
|
+
From `ScheduleView` — the full table stands on [View](#view).
|
|
7367
|
+
|
|
7368
|
+
| Prop | Type | Default | Description |
|
|
7369
|
+
|---|---|---|---|
|
|
7370
|
+
| [`domain`](#view) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
|
|
6973
7371
|
|
|
6974
7372
|
### Stretch, lead-in, lead-out
|
|
6975
7373
|
|
|
@@ -7141,7 +7539,7 @@ From `ScheduleView` — the full table stands on [View](#view).
|
|
|
7141
7539
|
|
|
7142
7540
|
- A main time never becomes shorter than one raster step, or a minute without a raster.
|
|
7143
7541
|
|
|
7144
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
7542
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
7145
7543
|
|
|
7146
7544
|
### Snapping
|
|
7147
7545
|
|
|
@@ -8000,7 +8398,7 @@ From `ScheduleView` — the full table stands on [View](#view).
|
|
|
8000
8398
|
|
|
8001
8399
|
- A rule that changes while a drag runs is noticed at the drop, not before.
|
|
8002
8400
|
|
|
8003
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
8401
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
8004
8402
|
|
|
8005
8403
|
### Ripple
|
|
8006
8404
|
|
|
@@ -8285,10 +8683,10 @@ From `ScheduleView` — the full table stands on [View](#view).
|
|
|
8285
8683
|
|
|
8286
8684
|
#### Known limits
|
|
8287
8685
|
|
|
8288
|
-
- No dependency types, slack or critical path: the schedule is no project-planning engine ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
8686
|
+
- No dependency types, slack or critical path: the schedule is no project-planning engine ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md)).
|
|
8289
8687
|
- A dependency joins subtasks of one task, so work of other tasks is never pushed.
|
|
8290
8688
|
|
|
8291
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.
|
|
8689
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/schedule-v0.46.0/docs/adr/0032-what-umriss-is-not.md).
|
|
8292
8690
|
|
|
8293
8691
|
## Measuring
|
|
8294
8692
|
|
|
@@ -8714,7 +9112,7 @@ Everything `@umriss-ui/schedule` exports, with its signature. A component's prop
|
|
|
8714
9112
|
##### `useSchedule`
|
|
8715
9113
|
|
|
8716
9114
|
Declares a schedule: hands back `Schedule` and the parts declared inside it
|
|
8717
|
-
- `Lane`, `LaneGroup`, `Subtasks`, `Dependencies`, `BlockedTimes`, `Milestones` -, its
|
|
9115
|
+
- `Lane`, `LaneGroup`, `Subtasks`, `Dependencies`, `BlockedTimes`, `Milestones`, `DataTable` -, its
|
|
8718
9116
|
`view` and the setters `setView`, `setDomain`, `toggleGroup`, `foldAll` and
|
|
8719
9117
|
`unfoldAll`, `positionAt`, `clientPointOf` and `visibleDomain`, and `scrollToLane`, `scrollToSubtask` and
|
|
8720
9118
|
`focusSubtask`.
|
|
@@ -8723,7 +9121,7 @@ Declares a schedule: hands back `Schedule` and the parts declared inside it
|
|
|
8723
9121
|
function useSchedule(options?: ScheduleOptions): ScheduleParts;
|
|
8724
9122
|
```
|
|
8725
9123
|
|
|
8726
|
-
Used on [Scenarios](#scenarios), [Installation](#installation), [First schedule](#first-schedule), [Lanes](#lanes), [Lane groups](#lane-groups), [Subtasks](#subtasks), [Bar labels](#bar-labels), [Appearances](#appearances), [Overlap](#overlap), [Dependencies](#dependencies), [Routes](#routes), [Time axis and calendar](#time-axis-and-calendar), [Blocked time](#blocked-time), [Milestones](#milestones), [Now line](#now-line), [Pan and zoom](#pan-and-zoom), [Selection](#selection), [Interactions](#interactions), [Tooltip](#tooltip), [View](#view), [Linked schedules](#linked-schedules), [Functions on the hook](#functions-on-the-hook), [Findings as data](#findings-as-data), [Move and lane](#move-and-lane), [Stretch, lead-in, lead-out](#stretch-lead-in-lead-out), [Snapping](#snapping), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go), [Ripple](#ripple) and [Benchmark](#benchmark).
|
|
9124
|
+
Used on [Scenarios](#scenarios), [Installation](#installation), [First schedule](#first-schedule), [Lanes](#lanes), [Lane groups](#lane-groups), [Subtasks](#subtasks), [Bar labels](#bar-labels), [Appearances](#appearances), [Overlap](#overlap), [Dependencies](#dependencies), [Routes](#routes), [Time axis and calendar](#time-axis-and-calendar), [Blocked time](#blocked-time), [Milestones](#milestones), [Now line](#now-line), [Pan and zoom](#pan-and-zoom), [Selection](#selection), [Interactions](#interactions), [Tooltip](#tooltip), [View](#view), [Linked schedules](#linked-schedules), [Functions on the hook](#functions-on-the-hook), [Findings as data](#findings-as-data), [Move and lane](#move-and-lane), [Undo](#undo), [Stretch, lead-in, lead-out](#stretch-lead-in-lead-out), [Snapping](#snapping), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go), [Ripple](#ripple) and [Benchmark](#benchmark).
|
|
8727
9125
|
|
|
8728
9126
|
#### Functions
|
|
8729
9127
|
|
|
@@ -8736,7 +9134,7 @@ it as it is.
|
|
|
8736
9134
|
function applyIntent<S extends Subtask>(subtask: S, intent: Intent): S;
|
|
8737
9135
|
```
|
|
8738
9136
|
|
|
8739
|
-
Used on [Scenarios](#scenarios), [Lane groups](#lane-groups), [Time axis and calendar](#time-axis-and-calendar), [Blocked time](#blocked-time), [Interactions](#interactions), [Move and lane](#move-and-lane), [Stretch, lead-in, lead-out](#stretch-lead-in-lead-out), [Snapping](#snapping), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go) and [Ripple](#ripple).
|
|
9137
|
+
Used on [Scenarios](#scenarios), [Lane groups](#lane-groups), [Time axis and calendar](#time-axis-and-calendar), [Blocked time](#blocked-time), [Interactions](#interactions), [Move and lane](#move-and-lane), [Undo](#undo), [Stretch, lead-in, lead-out](#stretch-lead-in-lead-out), [Snapping](#snapping), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go) and [Ripple](#ripple).
|
|
8740
9138
|
|
|
8741
9139
|
##### `arrival`
|
|
8742
9140
|
|
|
@@ -8851,12 +9249,12 @@ Used on [Scenarios](#scenarios) and [Ripple](#ripple).
|
|
|
8851
9249
|
|
|
8852
9250
|
##### `snapTime`
|
|
8853
9251
|
|
|
8854
|
-
`time` onto the nearest line of the raster in
|
|
8855
|
-
|
|
8856
|
-
stays as it is.
|
|
9252
|
+
`time` onto the nearest line of the raster in the zone's time, the reader's
|
|
9253
|
+
unless one is named - a step alone starts at the zone's midnight. A step of
|
|
9254
|
+
zero or less is no raster: the time stays as it is.
|
|
8857
9255
|
|
|
8858
9256
|
```ts
|
|
8859
|
-
function snapTime(time: number, raster: number | SnapRaster): number;
|
|
9257
|
+
function snapTime(time: number, raster: number | SnapRaster, timeZone?: string): number;
|
|
8860
9258
|
```
|
|
8861
9259
|
|
|
8862
9260
|
Used on [Snapping](#snapping).
|
|
@@ -8903,7 +9301,7 @@ Its table stands on [Dependencies](#dependencies).
|
|
|
8903
9301
|
|
|
8904
9302
|
Its table stands on [Dependencies](#dependencies).
|
|
8905
9303
|
|
|
8906
|
-
Used on [Scenarios](#scenarios), [Installation](#installation), [First schedule](#first-schedule), [Lane groups](#lane-groups), [Dependencies](#dependencies), [Routes](#routes), [Time axis and calendar](#time-axis-and-calendar), [Selection](#selection), [Interactions](#interactions), [Tooltip](#tooltip), [Findings as data](#findings-as-data), [Move and lane](#move-and-lane), [Placing from outside](#placing-from-outside), [Ripple](#ripple) and [Benchmark](#benchmark).
|
|
9304
|
+
Used on [Scenarios](#scenarios), [Installation](#installation), [First schedule](#first-schedule), [Lane groups](#lane-groups), [Dependencies](#dependencies), [Routes](#routes), [Time axis and calendar](#time-axis-and-calendar), [Selection](#selection), [Interactions](#interactions), [Tooltip](#tooltip), [Findings as data](#findings-as-data), [Move and lane](#move-and-lane), [Undo](#undo), [Placing from outside](#placing-from-outside), [Ripple](#ripple) and [Benchmark](#benchmark).
|
|
8907
9305
|
|
|
8908
9306
|
##### `DependencyAttachment`
|
|
8909
9307
|
|
|
@@ -8966,7 +9364,7 @@ drop - a move and a lane - give the same data in either order.
|
|
|
8966
9364
|
type Intent = MoveIntent | StretchIntent | LeadInIntent | LeadOutIntent | LaneIntent | PlaceIntent;
|
|
8967
9365
|
```
|
|
8968
9366
|
|
|
8969
|
-
Used on [Scenarios](#scenarios), [Blocked time](#blocked-time), [Move and lane](#move-and-lane), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go) and [Ripple](#ripple).
|
|
9367
|
+
Used on [Scenarios](#scenarios), [Blocked time](#blocked-time), [Move and lane](#move-and-lane), [Undo](#undo), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go) and [Ripple](#ripple).
|
|
8970
9368
|
|
|
8971
9369
|
##### `IntentKind`
|
|
8972
9370
|
|
|
@@ -9119,21 +9517,22 @@ it, its view and the setters that change it.
|
|
|
9119
9517
|
|
|
9120
9518
|
| Prop | Type | Default | Description |
|
|
9121
9519
|
|---|---|---|---|
|
|
9122
|
-
| `Schedule` *required* | `(props: `[`ScheduleProps`](#first-schedule)`) => ReactNode` | — | The schedule. One per `useSchedule`: a second schedule is a second call.<br>Shown in: [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule), [Start from an empty plan (First schedule)](#first-schedule) and
|
|
9123
|
-
| `Lane` *required* | `typeof Lane` | — | <br>Shown in: [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule), [Start from an empty plan (First schedule)](#first-schedule) and
|
|
9520
|
+
| `Schedule` *required* | `(props: `[`ScheduleProps`](#first-schedule)`) => ReactNode` | — | The schedule. One per `useSchedule`: a second schedule is a second call.<br>Shown in: [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule), [Start from an empty plan (First schedule)](#first-schedule) and 87 more |
|
|
9521
|
+
| `Lane` *required* | `typeof Lane` | — | <br>Shown in: [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule), [Start from an empty plan (First schedule)](#first-schedule) and 87 more |
|
|
9124
9522
|
| `LaneGroup` *required* | `typeof LaneGroup` | — | <br>Shown in: [Group lanes under a head (Lane groups)](#lane-groups), [Nest groups to any depth (Lane groups)](#lane-groups), [Fold groups from outside (Lane groups)](#lane-groups) and 5 more |
|
|
9125
|
-
| `Subtasks` *required* | `typeof Subtasks` | — | <br>Shown in: [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule), [Start from an empty plan (First schedule)](#first-schedule) and
|
|
9126
|
-
| `Dependencies` *required* | `typeof Dependencies` | — | <br>Shown in: [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule), [Plan a day in the machine shop (First schedule)](#first-schedule) and
|
|
9523
|
+
| `Subtasks` *required* | `typeof Subtasks` | — | <br>Shown in: [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule), [Start from an empty plan (First schedule)](#first-schedule) and 87 more |
|
|
9524
|
+
| `Dependencies` *required* | `typeof Dependencies` | — | <br>Shown in: [The smallest schedule (Installation)](#installation), [A day of tours (First schedule)](#first-schedule), [Plan a day in the machine shop (First schedule)](#first-schedule) and 30 more |
|
|
9127
9525
|
| `BlockedTimes` *required* | `typeof BlockedTimes` | — | <br>Shown in: [Block out leave and maintenance (Blocked time)](#blocked-time), [Plan work around someone's leave (Blocked time)](#blocked-time), [Check a sprint against the leave calendar (Blocked time)](#blocked-time) and 5 more |
|
|
9128
9526
|
| `Milestones` *required* | `typeof Milestones` | — | <br>Shown in: [Mark a handover and a release (Milestones)](#milestones), [Say what a milestone is in your own tooltip (Milestones)](#milestones) |
|
|
9527
|
+
| `DataTable` *required* | `typeof DataTable` | — | The plan as a table, on demand: placed inside the `Schedule`, it gives the schedule a "Show data" key beside the bottom time band, and the key opens a table of the subtasks in view - lane, task, subtask, from and to with their dates, duration and findings in words. For a reader who wants every value at once. It takes no props and renders nothing outside a `Schedule`.<br>Shown in: [Read the plan as a table (Tooltip)](#tooltip) |
|
|
9129
9528
|
| `view` *required* | [`ScheduleView`](#view) | — | How the planner is looking at the plan: the span and the folded groups.<br>Shown in: [Fold groups from outside (Lane groups)](#lane-groups), [Keep and restore a view (View)](#view), [Keep two schedules in step (Linked schedules)](#linked-schedules) and 1 more |
|
|
9130
9529
|
| `setView` *required* | `(view: `[`ScheduleView`](#view)`) => void` | — | Goes to a whole view; what it leaves out is shown whole again. Not reported through `onViewChange`: two schedules kept in step by `onViewChange: (v) => other.setView(v)` in both directions do not answer each other. A view with the content already shown changes nothing.<br>Shown in: [Keep and restore a view (View)](#view), [Keep two schedules in step (Linked schedules)](#linked-schedules), [Read a rota beside the incidents (Linked schedules)](#linked-schedules) |
|
|
9131
|
-
| `setDomain` *required* | `(span: readonly [number, number] \| null) => void` | — | Puts a span of two wall-clock instants in view; `null` shows the subtasks' extent.<br>Shown in: [Keep and restore a view (View)](#view) |
|
|
9530
|
+
| `setDomain` *required* | `(span: readonly [number, number] \| null) => void` | — | Puts a span of two wall-clock instants in view; `null` shows the subtasks' extent.<br>Shown in: [Zoom and fit with buttons (Pan and zoom)](#pan-and-zoom), [Keep and restore a view (View)](#view) |
|
|
9132
9531
|
| `toggleGroup` *required* | `(id: string) => void` | — | Folds or unfolds one **Lane group**. |
|
|
9133
9532
|
| `positionAt` *required* | `(clientX: number, clientY: number) => { time: number; lane: string \| null } \| null` | — | The time and the lane at a client point, or null outside the plot - and null while no schedule is mounted.<br>Shown in: [Place a mark of your own (Functions on the hook)](#functions-on-the-hook) |
|
|
9134
9533
|
| `clientPointOf` *required* | `(time: number, lane?: string) => { x: number; y: number } \| null` | — | The client point of a time - on the middle of a lane where one is named - or null while the schedule is not on screen.<br>Shown in: [Place a mark of your own (Functions on the hook)](#functions-on-the-hook) |
|
|
9135
|
-
| `visibleDomain` *required* | `() => readonly [number, number]` | — | The visible span, as two wall-clock instants
|
|
9136
|
-
| `scrollToLane` *required* | `(lane: string) => boolean` | — | Scrolls the lanes until the lane's row is in view, and returns `true`; `false` and nothing changed for an unknown lane, one inside a folded **Lane group** (none is unfolded), or while no schedule is mounted
|
|
9534
|
+
| `visibleDomain` *required* | `() => readonly [number, number]` | — | The visible span, as two wall-clock instants.<br>Shown in: [Zoom and fit with buttons (Pan and zoom)](#pan-and-zoom) |
|
|
9535
|
+
| `scrollToLane` *required* | `(lane: string) => boolean` | — | Scrolls the lanes until the lane's row is in view, and returns `true`; `false` and nothing changed for an unknown lane, one inside a folded **Lane group** (none is unfolded), or while no schedule is mounted.<br>Shown in: [Highlight a lane (Lanes)](#lanes) |
|
|
9137
9536
|
| `scrollToSubtask` *required* | `(id: string) => boolean` | — | Brings the subtask's lane and time into view, the span moving to it and keeping its width as `setDomain` would, and leaves the focus where it is; `false` and nothing changed in the cases of `scrollToLane`.<br>Shown in: [Show a finding (Functions on the hook)](#functions-on-the-hook) |
|
|
9138
9537
|
| `focusSubtask` *required* | `(id: string) => boolean` | — | Does what `scrollToSubtask` does, then makes the subtask the **Active subtask** and moves the focus to the plot, where the live region reads it out as the arrow keys do; `false` and nothing changed in the cases of `scrollToLane`.<br>Shown in: [Show a finding (Functions on the hook)](#functions-on-the-hook) |
|
|
9139
9538
|
| `foldAll` *required* | `() => void` | — | Folds every group.<br>Shown in: [Fold groups from outside (Lane groups)](#lane-groups) |
|
|
@@ -9183,7 +9582,7 @@ Stretch the main time at one or both of its edges.
|
|
|
9183
9582
|
|
|
9184
9583
|
Its table stands on [Subtasks](#subtasks).
|
|
9185
9584
|
|
|
9186
|
-
Used on [Scenarios](#scenarios), [Installation](#installation), [First schedule](#first-schedule), [Lanes](#lanes), [Lane groups](#lane-groups), [Subtasks](#subtasks), [Bar labels](#bar-labels), [Appearances](#appearances), [Overlap](#overlap), [Dependencies](#dependencies), [Routes](#routes), [Time axis and calendar](#time-axis-and-calendar), [Blocked time](#blocked-time), [Milestones](#milestones), [Now line](#now-line), [Pan and zoom](#pan-and-zoom), [Selection](#selection), [Interactions](#interactions), [Tooltip](#tooltip), [View](#view), [Linked schedules](#linked-schedules), [Functions on the hook](#functions-on-the-hook), [Findings as data](#findings-as-data), [Move and lane](#move-and-lane), [Stretch, lead-in, lead-out](#stretch-lead-in-lead-out), [Snapping](#snapping), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go), [Ripple](#ripple) and [Benchmark](#benchmark).
|
|
9585
|
+
Used on [Scenarios](#scenarios), [Installation](#installation), [First schedule](#first-schedule), [Lanes](#lanes), [Lane groups](#lane-groups), [Subtasks](#subtasks), [Bar labels](#bar-labels), [Appearances](#appearances), [Overlap](#overlap), [Dependencies](#dependencies), [Routes](#routes), [Time axis and calendar](#time-axis-and-calendar), [Blocked time](#blocked-time), [Milestones](#milestones), [Now line](#now-line), [Pan and zoom](#pan-and-zoom), [Selection](#selection), [Interactions](#interactions), [Tooltip](#tooltip), [View](#view), [Linked schedules](#linked-schedules), [Functions on the hook](#functions-on-the-hook), [Findings as data](#findings-as-data), [Move and lane](#move-and-lane), [Undo](#undo), [Stretch, lead-in, lead-out](#stretch-lead-in-lead-out), [Snapping](#snapping), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go), [Ripple](#ripple) and [Benchmark](#benchmark).
|
|
9187
9586
|
|
|
9188
9587
|
##### `SubtaskAppearance`
|
|
9189
9588
|
|
|
@@ -9201,7 +9600,7 @@ Its table stands on [Subtasks](#subtasks).
|
|
|
9201
9600
|
|
|
9202
9601
|
Its table stands on [Subtasks](#subtasks).
|
|
9203
9602
|
|
|
9204
|
-
Used on [Scenarios](#scenarios), [Installation](#installation), [First schedule](#first-schedule), [Lanes](#lanes), [Lane groups](#lane-groups), [Subtasks](#subtasks), [Bar labels](#bar-labels), [Appearances](#appearances), [Overlap](#overlap), [Dependencies](#dependencies), [Routes](#routes), [Time axis and calendar](#time-axis-and-calendar), [Blocked time](#blocked-time), [Milestones](#milestones), [Now line](#now-line), [Pan and zoom](#pan-and-zoom), [Selection](#selection), [Interactions](#interactions), [Tooltip](#tooltip), [View](#view), [Linked schedules](#linked-schedules), [Functions on the hook](#functions-on-the-hook), [Findings as data](#findings-as-data), [Move and lane](#move-and-lane), [Stretch, lead-in, lead-out](#stretch-lead-in-lead-out), [Snapping](#snapping), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go), [Ripple](#ripple) and [Benchmark](#benchmark).
|
|
9603
|
+
Used on [Scenarios](#scenarios), [Installation](#installation), [First schedule](#first-schedule), [Lanes](#lanes), [Lane groups](#lane-groups), [Subtasks](#subtasks), [Bar labels](#bar-labels), [Appearances](#appearances), [Overlap](#overlap), [Dependencies](#dependencies), [Routes](#routes), [Time axis and calendar](#time-axis-and-calendar), [Blocked time](#blocked-time), [Milestones](#milestones), [Now line](#now-line), [Pan and zoom](#pan-and-zoom), [Selection](#selection), [Interactions](#interactions), [Tooltip](#tooltip), [View](#view), [Linked schedules](#linked-schedules), [Functions on the hook](#functions-on-the-hook), [Findings as data](#findings-as-data), [Move and lane](#move-and-lane), [Undo](#undo), [Stretch, lead-in, lead-out](#stretch-lead-in-lead-out), [Snapping](#snapping), [Placing from outside](#placing-from-outside), [Where a subtask may go](#where-a-subtask-may-go), [Ripple](#ripple) and [Benchmark](#benchmark).
|
|
9205
9604
|
|
|
9206
9605
|
##### `ViolatedDependency`
|
|
9207
9606
|
|