# How OpenScreen Integrates dnd-timeline for Drag-and-Drop Region Management

> Discover how OpenScreen integrates dnd timeline for drag-and-drop region management. Learn about global state, callbacks, and video constraints.

- Repository: [Sid/openscreen](https://github.com/siddharthvaddem/openscreen)
- Tags: deep-dive
- Published: 2026-04-03

---

**OpenScreen wraps the dnd-timeline library in a custom `TimelineWrapper` component that supplies global timeline state, delegates drag-and-drop callbacks, and enforces video-specific constraints like overlap prevention and duration clamping.**

OpenScreen is an open-source video editor built by siddharthvaddem/openscreen that leverages dnd-timeline to power its track-based editing interface. The integration centers on a three-layer React architecture—Context, Row, and Item—that bridges generic timeline primitives with domain-specific requirements for managing zoom, trim, speed, and annotation regions.

## Three-Layer Architecture for Timeline Integration

The integration strategy separates concerns across three distinct layers, each responsible for registering entities with the underlying dnd-timeline engine.

### Context Wrapper in TimelineWrapper.tsx

The foundation is established in [`src/components/video-editor/timeline/TimelineWrapper.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/src/components/video-editor/timeline/TimelineWrapper.tsx), where the `<TimelineContext>` from dnd-timeline is instantiated at lines 96-104. This provider forwards the current video range, handles range changes, and wires custom drag and resize end logic through callbacks including `onDragStart`, `onDragMove`, `onDragEnd`, `onResizeMove`, and `onResizeEnd`.

### Row Registration via useRow

Each track (video, audio, or annotation) is rendered using [`Row.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/Row.tsx), which calls the `useRow` hook at lines 13-20. This hook registers the row with the timeline context and returns a ref and style objects necessary for layout calculations.

### Item Definition via useItem

Draggable and resizable regions are created in [`Item.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/Item.tsx) through the `useItem` hook (lines 51-56). This hook provides drag listeners, attributes, and styling while storing each item's `span` (start/end milliseconds) and custom `rowId` data required for overlap detection.

## Implementing Drag and Resize Callbacks

OpenScreen implements a complete lifecycle for drag-and-resize interactions within [`TimelineWrapper.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/TimelineWrapper.tsx), intercepting dnd-timeline events to inject video-specific logic.

### Initiating Interactions

When a user begins dragging or resizing, the `onDragStart` callback (lines 21-23) extracts the current span via the item's data callbacks and triggers `showTooltip` to display a time indicator under the cursor.

### Real-time Position Updates

During movement, `onDragMove` (lines 28-35) and `onResizeMove` (lines 39-46) keep the tooltip positioned under the pointer using the delta values supplied by dnd-timeline. These callbacks update the UI without committing changes to state.

### Finalizing Region Changes

The validation and persistence logic resides in `onDragEnd` (lines 65-84) and `onResizeEnd` (lines 30-53). These handlers perform four critical operations:

1. **Clamp to video bounds** using `clampSpanToBounds` to ensure regions do not exceed total video duration
2. **Prevent overlap** by checking `hasOverlap` against `allRegionSpans`; if overlap exists, `clampToNeighbours` automatically adjusts the span to the nearest free space
3. **Enforce minimum duration** via `minItemDurationMs` to prevent regions from becoming invisible
4. **Update application state** through `onItemSpanChange` to persist the validated span

These callbacks are passed back to `<TimelineContext>` so the underlying dnd-timeline engine invokes them at the appropriate moments.

## Enforcing Video Constraints in the Timeline

While dnd-timeline provides generic span manipulation, OpenScreen must enforce constraints specific to video editing. The `handleRangeChange` function (lines 69-92) normalizes the visible time range using `clampRange`, ensuring it never exceeds the total video duration (`totalMs`) and respects `minVisibleRangeMs` to maintain a usable zoom level.

Because dnd-timeline has no intrinsic knowledge of video boundaries or region semantics, OpenScreen implements custom validation logic that prevents regions from extending beyond media bounds, enforces non-overlapping track constraints, and provides real-time tooltip feedback displaying precise "mm:ss.s – mm:ss.s" timestamps during drag operations.

## Code Implementation Examples

### Minimal Timeline Setup

```tsx
import TimelineWrapper from "@/components/video-editor/timeline/TimelineWrapper";
import Row from "@/components/video-editor/timeline/Row";
import Item from "@/components/video-editor/timeline/Item";

function VideoEditor({ videoDuration }) {
  const [range, setRange] = useState<Range>({ start: 0, end: 5000 });
  const [items, setItems] = useState([
    { id: "zoom1", rowId: "video", span: { start: 0, end: 2000 }, variant: "zoom" },
    // …more items
  ]);

  const hasOverlap = (span, excludeId) => {
    return items.some(i => i.id !== excludeId && spansOverlap(i.span, span));
  };

  const onItemSpanChange = (id, newSpan) => {
    setItems(prev => prev.map(i => (i.id === id ? { ...i, span: newSpan } : i)));
  };

  return (
    <TimelineWrapper
      range={range}
      videoDuration={videoDuration}
      hasOverlap={hasOverlap}
      onRangeChange={setRange}
      minItemDurationMs={100}
      minVisibleRangeMs={500}
      onItemSpanChange={onItemSpanChange}
    >
      <Row id="video" label="Video">
        {items
          .filter(i => i.rowId === "video")
          .map(i => (
            <Item key={i.id} {...i} />
          ))}
      </Row>
      {/* additional rows for audio, annotations, etc. */}
    </TimelineWrapper>
  );
}

```

### Custom Overlap Detection

```typescript
function spansOverlap(a: Span, b: Span) {
  return a.start < b.end && b.start < a.end;
}

```

Pass this function to `TimelineWrapper` via the `hasOverlap` prop to enable the built-in `clampToNeighbours` logic that resolves conflicts by sliding regions to the nearest available space.

### Tooltip Feedback Implementation

```tsx
<div
  ref={tooltipRef}
  className="absolute top-1 pointer-events-none z-[60] px-1.5 py-0.5 rounded bg-black/80 text-[10px] text-white/90 font-medium tabular-nums whitespace-nowrap border border-white/10 shadow-lg"
  style={{ opacity: 0, transition: "opacity 0.1s" }}
/>

```

The wrapper updates this element's position and text content during active drag or resize operations to provide real-time temporal feedback.

## Summary

- **[`TimelineWrapper.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/TimelineWrapper.tsx)** instantiates the dnd-timeline context at lines 96-104 and orchestrates drag, resize, and range change callbacks.
- **[`Row.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/Row.tsx)** registers track containers using the `useRow` hook (lines 13-20).
- **[`Item.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/Item.tsx)** enables draggable region behavior via the `useItem` hook (lines 51-56), storing span and row identification data.
- **Constraint enforcement** prevents region overlap through `hasOverlap` and `clampToNeighbours`, clamps spans to video bounds, and respects minimum durations.
- **Range management** ensures the visible timeline window stays within valid video limits using `clampRange` and `handleRangeChange`.

## Frequently Asked Questions

### What is dnd-timeline and why does OpenScreen use it?

dnd-timeline is a React library that provides primitives for building interactive timeline interfaces with drag-and-drop capabilities. OpenScreen uses it to handle the complex geometry calculations, pointer event handling, and collision detection required for track-based video editing, while layering custom logic for video-specific constraints on top.

### How does OpenScreen prevent regions from overlapping during drag operations?

When `onDragEnd` or `onResizeEnd` fires in [`TimelineWrapper.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/TimelineWrapper.tsx) (lines 30-53 and 65-84), the code checks for overlaps using the `hasOverlap` function against `allRegionSpans`. If an overlap is detected, `clampToNeighbours` automatically adjusts the region's start or end time to the nearest available gap rather than rejecting the drag operation entirely.

### Where is the timeline context initialized in the codebase?

The dnd-timeline context is initialized in [`src/components/video-editor/timeline/TimelineWrapper.tsx`](https://github.com/siddharthvaddem/openscreen/blob/main/src/components/video-editor/timeline/TimelineWrapper.tsx) at lines 96-104, where the `<TimelineContext>` component is rendered with callbacks for `onDragStart`, `onDragMove`, `onDragEnd`, `onResizeMove`, and `onResizeEnd`.

### What constraints does OpenScreen enforce beyond dnd-timeline's defaults?

OpenScreen implements four key constraints: regions cannot extend beyond the total video duration (`totalMs`), regions must maintain a minimum visible duration (`minItemDurationMs`), regions cannot overlap on the same track (`hasOverlap`/`clampToNeighbours`), and the visible range must stay within sane zoom limits (`minVisibleRangeMs`).