# Placing from outside

> Package `@umriss-ui/schedule`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/schedule/placing/>. 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).

Drag work that is not on the plan yet - from a backlog or a list of unassigned jobs - onto a lane, with the browser's own drag and drop. The drop reports where it should go; your application creates it.

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

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

The browser hands over dragged data only on the drop, so declare what is being dragged in `placing` on your `dragstart` and clear it on `dragend`. The schedule then shows the ghost at the snapped time, with the findings the drop would create.

A `place` intent carries your item's key, the task, the lane and the times. The id of the new subtask is yours; [`subtaskFromPlace`](https://romanhaendler.github.io/umriss-ui/schedule/api/#subtaskFromPlace) builds it from the intent.

## Examples

### Drag work in from the backlog

Set `placing` on your list's `dragstart` and clear it on `dragend`; the drop reports a `place` intent that [`subtaskFromPlace`](https://romanhaendler.github.io/umriss-ui/schedule/api/#subtaskFromPlace) turns into work.

```tsx
import { useState } from "react";
import { Card, Stack, Text } from "@umriss-ui/core";
import { applyIntent, subtaskFromPlace, useSchedule } from "@umriss-ui/schedule";
import type { Dependency, Intent, PlacingItem, Subtask, Task } from "@umriss-ui/schedule";

const at = (hours: number, minutes = 0) => new Date(2026, 2, 17, hours, minutes).getTime();
const HOUR = 3_600_000;

const PEOPLE = [
  { id: "arjun", label: "Arjun Mehta" },
  { id: "chloe", label: "Chloe Durand" },
  { id: "eva", label: "Eva Novak" },
];

const PROJECTS: readonly Task[] = [
  { id: "shop", name: "Online shop relaunch", color: "light-dark(#2563eb, #6b9bff)" },
  { id: "portal", name: "Member portal", color: "light-dark(#7c3aed, #a98bfa)" },
];

const TODAY: readonly Subtask[] = [
  { id: "w-101", task: "portal", lane: "arjun", from: at(8), to: at(9, 30), name: "Sign-in with e-mail code" },
  { id: "w-103", task: "portal", lane: "chloe", from: at(8), to: at(8, 45), name: "Session handling" },
  { id: "w-104", task: "shop", lane: "chloe", from: at(9, 30), to: at(10, 45), name: "Basket across devices" },
  { id: "w-108", task: "portal", lane: "eva", from: at(11, 30), to: at(12, 15), name: "Test plan for sign-in" },
];

const HANDOVERS: readonly Dependency[] = [{ id: "h-101", from: "w-101", to: "w-108", lag: 20 * 60_000 }];

/* The backlog: what the drag carries, and a label for the list. */
const BACKLOG: readonly (PlacingItem & { label: string })[] = [
  { item: "w-121", label: "Fix sign-in redirect · 2 h", task: "portal", duration: 2 * HOUR },
  { item: "w-122", label: "Update privacy text · 1 h", task: "shop", duration: HOUR },
  { item: "w-123", label: "Accessibility audit · 4 h", task: "shop", duration: 4 * HOUR },
];

export default function DragFromTheBacklog() {
  const { Schedule, Lane, Subtasks, Dependencies } = useSchedule({ initialView: { domain: [at(5, 30), at(18)] } });
  const [plan, setPlan] = useState<{ work: readonly Subtask[]; placed: number; last: string }>({
    work: TODAY,
    placed: 0,
    last: "Drag an item onto a person",
  });
  const [placing, setPlacing] = useState<PlacingItem | null>(null);

  /* One functional update, so that a second intent in the same tick - and the
     counter the new id is built from - starts from the plan the first left. */
  const onIntent = (intent: Intent) =>
    setPlan((current) => {
      if (intent.kind !== "place") {
        return { ...current, work: current.work.map((item) => applyIntent(item, intent)), last: `${intent.kind} ${intent.subtask}` };
      }
      const placed = current.placed + 1;
      const id = `${intent.item}-${placed}`;
      return { work: [...current.work, subtaskFromPlace(intent, id)], placed, last: `place ${intent.item} on ${intent.lane} as ${id}` };
    });

  return (
    <Stack gap={3}>
      <Stack direction="row" gap={2} wrap>
        {BACKLOG.map((entry) => (
          <Card key={entry.item}>
            <div
              draggable
              data-waiting={entry.item}
              onDragStart={(event) => {
                event.dataTransfer.setData("text/plain", entry.item);
                event.dataTransfer.effectAllowed = "copy";
                setPlacing(entry);
              }}
              onDragEnd={() => setPlacing(null)}
              style={{ cursor: "grab", padding: "var(--u-space-2) var(--u-space-3)" }}
            >
              <Text size="sm">{entry.label}</Text>
            </div>
          </Card>
        ))}
      </Stack>
      <Schedule
        ariaLabel="Today's plan of three people, with the backlog beside it"
        height={196}
        intents={["place", "move", "lane"]}
        placing={placing}
        onIntent={onIntent}
      >
        {PEOPLE.map((person) => (
          <Lane key={person.id} id={person.id} label={person.label} />
        ))}
        <Dependencies data={HANDOVERS} />
        <Subtasks data={plan.work} tasks={PROJECTS} />
      </Schedule>
      <Text size="sm" mono tone="secondary" data-last-place>
        {plan.last}
      </Text>
    </Stack>
  );
}
```

## When to use something else

- The work is already on the plan → Move and lane

## 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 |
|---|---|---|---|
| [`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. |
| [`intents`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-intents) | `readonly `[`IntentKind`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#type-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)). |
| [`placing`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-placing) | [`PlacingItem`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#type-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. |
| [`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 |
|---|---|---|---|
| [`onIntent`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#ScheduleProps-onIntent) | `(intent: `[`Intent`](https://romanhaendler.github.io/umriss-ui/schedule/schedule/#type-Intent)`) => void` | — | Called once per intent when a drag ends. The data changes only if the caller changes it. |

#### 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. |
| [`name`](https://romanhaendler.github.io/umriss-ui/schedule/subtasks/#Subtask-name) | `string` | — | What the subtask is called, where the schedule names it. |

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 `DependenciesProps` — the full table stands on [Dependencies](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#type-DependenciesProps).

| Prop | Type | Default | Description |
|---|---|---|---|
| [`data`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#DependenciesProps-data) *required* | `readonly `[`Dependency`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#type-Dependency)`[]` | — | The dependencies, as the caller holds them. |

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

| Prop | Type | Default | Description |
|---|---|---|---|
| [`id`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#Dependency-id) *required* | `string` | — | The caller's identity of the dependency. |
| [`from`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#Dependency-from) *required* | `string` | — | The id of the subtask it leaves. |
| [`to`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#Dependency-to) *required* | `string` | — | The id of the subtask it arrives at. |
| [`lag`](https://romanhaendler.github.io/umriss-ui/schedule/dependencies/#Dependency-lag) *required* | `number` | — | The least time between the two anchors; zero where the successor may begin as soon as the predecessor ends. |

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`. |
