# The handle

> Package `@umriss-ui/schedule`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/schedule/handle/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/schedule/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/schedule/llms-full.txt).

Converts between a point on the screen and a time on a lane, so marks of your own - a cut-off, a delivery window, a pin - stand at the right place beside the plan.

```ts
import { useSchedule } from "@umriss-ui/schedule";
```

Demo page: https://romanhaendler.github.io/umriss-ui/schedule/handle/

The handle holds three functions and nothing else: `clientPointOf(time, lane?)`, `positionAt(clientX, clientY)` and `visibleDomain()`. Everything else the schedule does is props.

Place your marks again whenever `view.domain` changes.

## Examples

### Place a mark of your own

`clientPointOf` turns a time into a point for your own mark; `positionAt` turns a pointer back into a time and a lane.

```tsx
import { useEffect, useRef, useState } from "react";
import { Stack, Text } from "@umriss-ui/core";
import { useSchedule } from "@umriss-ui/schedule";
import type { ScheduleHandle, Subtask, Task } from "@umriss-ui/schedule";

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const min = (n: number) => n * 60_000;

const VEHICLES = [
  { id: "truck-118", label: "Truck FP 118 R" },
  { id: "van-402", label: "Van FP 402 R" },
  { id: "truck-520", label: "Truck FP 520 E" },
];

const CONSIGNMENTS: readonly Task[] = [
  { id: "c-2041", name: "C-2041 Holloway Garden Supplies", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "c-2043", name: "C-2043 Oakridge Pharmacy", color: "light-dark(#c2410c, #f08a52)" },
];

const LEGS: readonly Subtask[] = [
  { id: "c-2041-1", task: "c-2041", lane: "truck-118", from: at(6), to: at(7), leadIn: min(15), leadOut: min(10) },
  { id: "c-2041-2", task: "c-2041", lane: "van-402", from: at(8), to: at(10, 30), leadIn: min(30), leadOut: min(15) },
  { id: "c-2043-1", task: "c-2043", lane: "truck-520", from: at(6, 30), to: at(8), leadIn: min(30), leadOut: min(15) },
  { id: "c-2043-2", task: "c-2043", lane: "van-402", from: at(12), to: at(13, 30), leadIn: min(15) },
];

/** The last moment a parcel is taken for delivery today. */
const CUT_OFF = at(14);

const clock = (time: number) => new Date(time).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });

export default function PlaceAMark() {
  const { Schedule, Lane, Subtasks, view } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
  const plan = useRef<ScheduleHandle>(null);
  const host = useRef<HTMLDivElement>(null);
  const [pin, setPin] = useState<number | null>(null);
  const [under, setUnder] = useState("Move the pointer over the plan");

  /* The client point, turned into one inside this example - anew whenever the span moves. */
  useEffect(() => {
    const point = plan.current?.clientPointOf(CUT_OFF);
    const left = host.current?.getBoundingClientRect().left;
    setPin(point == null || left === undefined ? null : point.x - left);
  }, [view.domain]);

  return (
    <Stack gap={2}>
      <div ref={host} style={{ position: "relative", height: 12 }}>
        {pin !== null && (
          <Text size="xs" tone="muted" style={{ position: "absolute", top: -4, left: pin, transform: "translateX(-50%)", whiteSpace: "nowrap" }} data-pin>
            Same-day cut-off
          </Text>
        )}
      </div>
      <Schedule
        ref={plan}
        ariaLabel="Three vehicles with a cut-off of the application's own"
        height={196}
        onInteraction={(interaction) => {
          /* The interaction carries time and lane already; `positionAt` is for
             listeners of your own, which have only a point. */
          const there = plan.current?.positionAt(interaction.clientX, interaction.clientY);
          setUnder(there == null ? "off the plan" : `${clock(there.time)} on ${there.lane ?? "no lane"}`);
        }}
      >
        {VEHICLES.map((vehicle) => (
          <Lane key={vehicle.id} id={vehicle.id} label={vehicle.label} />
        ))}
        <Subtasks data={LEGS} tasks={CONSIGNMENTS} />
      </Schedule>
      <Text size="sm" mono tone="secondary" data-under-pointer>
        {under}
      </Text>
    </Stack>
  );
}
```

## When to use something else

- You need the time under the pointer during a hover or a click → Interactions

## Keyboard

The keys of [First schedule](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#keyboard-schedule) apply here.

## Props on this page

From `ScheduleProps` — the full table stands on [First schedule](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#type-ScheduleProps).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`ref`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-ref) | `Ref<`[`ScheduleHandle`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#type-ScheduleHandle)`>` | — | Receives the `ScheduleHandle` - not an element. |
| [`height`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-height) | `number` | `400` | Height in CSS pixels, both bands included. The width is the host's. |
| [`children`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-children) | `ReactNode` | — | `Lane`, `Dependencies` and `Subtasks`, in the order they are to stand and be drawn. |

#### Events

| Prop | Type | Default | Description |
|---|---|---|---|
| [`onInteraction`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-onInteraction) | `(interaction: `[`ScheduleInteraction`](https://romanhaendler.github.io/umriss-ui/schedule/interactions/#type-ScheduleInteraction)`) => 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. |

#### Accessibility

| Prop | Type | Default | Description |
|---|---|---|---|
| [`ariaLabel`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-ariaLabel) *required* | `string` | — | What the schedule shows, for a screen reader: "Plan of week 38". |

From `LaneProps` — the full table stands on [Lanes](https://romanhaendler.github.io/umriss-ui/schedule/lane/#type-LaneProps).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`id`](https://romanhaendler.github.io/umriss-ui/schedule/lane/#LaneProps-id) *required* | `string` | — | The identity subtasks name in their `lane`. |
| [`label`](https://romanhaendler.github.io/umriss-ui/schedule/lane/#LaneProps-label) | `ReactNode` | `id` | What the lane header says - the person, vehicle or room. Real text, read by a screen reader. |

From `SubtasksProps` — the full table stands on [Subtasks](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-SubtasksProps).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`data`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#SubtasksProps-data) *required* | `readonly `[`Subtask`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-Subtask)`[]` | — | The subtasks, as the caller holds them. The schedule never changes one. |
| [`tasks`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#SubtasksProps-tasks) *required* | `readonly `[`Task`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-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. |

From `Subtask` — the full table stands on [Subtasks](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-Subtask).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`id`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-id) *required* | `string` | — | The caller's identity of the subtask. |
| [`task`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-task) *required* | `string` | — | The id of the task it belongs to. |
| [`lane`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-lane) *required* | `string` | — | The id of the lane it sits on. |
| [`from`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-from) *required* | `number` | — | Start of the main time. |
| [`to`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-to) *required* | `number` | — | End of the main time. |
| [`leadIn`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-leadIn) | `number` | — | Duration of the lead-in before the main time; none is zero. |
| [`leadOut`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-leadOut) | `number` | — | Duration of the lead-out after the main time; none is zero. |

From `Task` — the full table stands on [Subtasks](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#type-Task).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`id`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Task-id) *required* | `string` | — | The caller's identity of the task. |
| [`color`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Task-color) *required* | `string` | — | Any CSS colour - a literal, a token, `light-dark(…)`. The colour means what the caller's domain needs it to mean. |
| [`name`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Task-name) | `string` | — | What the task is called, where the schedule names it. |

From `ScheduleInteraction` — the full table stands on [Interactions](https://romanhaendler.github.io/umriss-ui/schedule/interactions/#type-ScheduleInteraction).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`clientX`](https://romanhaendler.github.io/umriss-ui/schedule/interactions/#ScheduleInteraction-clientX) *required* | `number` | — | The pointer's horizontal client coordinate - where a `ContextMenu` opens. |
| [`clientY`](https://romanhaendler.github.io/umriss-ui/schedule/interactions/#ScheduleInteraction-clientY) *required* | `number` | — | The pointer's vertical client coordinate. |

From `ScheduleOptions` — the full table stands on [View](https://romanhaendler.github.io/umriss-ui/schedule/view/#type-ScheduleOptions).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`initialView`](https://romanhaendler.github.io/umriss-ui/schedule/view/#ScheduleOptions-initialView) | [`ScheduleView`](https://romanhaendler.github.io/umriss-ui/schedule/view/#type-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. |

From `ScheduleView` — the full table stands on [View](https://romanhaendler.github.io/umriss-ui/schedule/view/#type-ScheduleView).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`domain`](https://romanhaendler.github.io/umriss-ui/schedule/view/#ScheduleView-domain) | `readonly [number, number]` | — | The span in view, as two wall-clock instants. Absent, the schedule shows the extent of its subtasks, within `zoomLimits`. |
