# Direct Editable Milestone Event Range

> Bidirectional drag handle and date picker event range adjuster.

A shadcn/ui block from the [@pacekit](https://ui.pacekit.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/pacekit/tanstack-charts-editable-event-range).

- Page, with a live preview: https://shadcn.exchange/r/pacekit/tanstack-charts-editable-event-range
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/pacekit/tanstack-charts-editable-event-range-light-0ae8edb9a1d40f88.png
- Registry item: https://ui.pacekit.dev/r/tanstack-charts-editable-event-range.json
- Listed: 2026-10-21, source last changed 2026-10-21

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/pacekit/tanstack-charts-editable-event-range.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@tanstack/charts`, `@tanstack/react-charts`, `d3-scale`
- Registry items: `card`, `button`, `calendar`, `popover`

## Files

### `components/tanstack-charts/line/editable-event-range.tsx`

```tsx
import { type ChartPoint, defineChart, rect, text } from "@tanstack/charts";
import { handleX } from "@tanstack/charts/interaction/handle";
import { controlledSignal } from "@tanstack/charts/interaction/signal";
import { motion } from "@tanstack/charts/motion";
import { tooltip } from "@tanstack/charts/tooltip";
import { Chart } from "@tanstack/react-charts/core";
import { scaleBand, scaleUtc } from "d3-scale";
import { useMemo, useState } from "react";

import { CalendarIcon, CalendarRange } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";

interface EditableEvent {
    id: string;
    lane: string;
    label: string;
    start: Date;
    end: Date;
}

const motionRenderer = motion({
    initial: "always",
    transition: { type: "spring", stiffness: 170, damping: 18, mass: 1 },
});

const lanes = ["Product", "Design", "Engineering"];

function generateInitialEvents(): EditableEvent[] {
    const baseDate = new Date(Date.UTC(2024, 8, 1)).getTime();
    const dayMs = 24 * 60 * 60 * 1000;

    return [
        {
            id: "discovery",
            lane: "Product",
            label: "Product Discovery",
            start: new Date(baseDate),
            end: new Date(baseDate + 10 * dayMs),
        },
        {
            id: "design",
            lane: "Design",
            label: "UI/UX Specs",
            start: new Date(baseDate + 5 * dayMs),
            end: new Date(baseDate + 18 * dayMs),
        },
        {
            id: "release",
            lane: "Engineering",
            label: "Production Release",
            start: new Date(baseDate + 12 * dayMs),
            end: new Date(baseDate + 28 * dayMs),
        },
    ];
}

function shadcnTooltipContent<TDatum>(points: readonly ChartPoint<TDatum>[]) {
    if (points.length === 0) return { title: "", rows: [] };
    const d = points[0].datum as EditableEvent;
    if (!d) return { title: "", rows: [] };
    const fmt = (dt: Date) => dt.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
    return {
        title: `${d.lane}: ${d.label}`,
        rows: [{ label: "Duration", value: `${fmt(d.start)} - ${fmt(d.end)}`, color: "var(--chart-1)" }],
    };
}

export function EditableEventRange() {
    const [releaseEnd, setReleaseEnd] = useState<Date>(new Date(Date.UTC(2024, 8, 29)));

    const data = useMemo(() => {
        const events = generateInitialEvents();
        const releaseEvt = events.find((e) => e.id === "release");
        if (releaseEvt) releaseEvt.end = releaseEnd;
        return events;
    }, [releaseEnd]);

    const fullDomain = useMemo(() => {
        return [new Date(Date.UTC(2024, 8, 1)), new Date(Date.UTC(2024, 9, 15))];
    }, []);

    const eventEndValues = useMemo(() => {
        const result: Date[] = [];
        const start = new Date(Date.UTC(2024, 8, 13)).getTime();
        const end = new Date(Date.UTC(2024, 9, 15)).getTime();
        const dayMs = 24 * 60 * 60 * 1000;
        for (let t = start; t <= end; t += dayMs) {
            result.push(new Date(t));
        }
        return result;
    }, []);

    const activeReleaseEnd = useMemo(() => {
        const releaseTime = releaseEnd.getTime();
        return eventEndValues.find((d) => d.getTime() === releaseTime) || eventEndValues[eventEndValues.length - 1];
    }, [releaseEnd, eventEndValues]);

    const calendarSelected = useMemo(() => {
        return new Date(releaseEnd.getUTCFullYear(), releaseEnd.getUTCMonth(), releaseEnd.getUTCDate());
    }, [releaseEnd]);

    const definition = useMemo(
        () =>
            defineChart(
                {
                    renderer: motionRenderer,
                    marks: [
                        rect(data, {
                            id: "event-ranges",
                            x1: "start",
                            x2: "end",
                            y: "lane",
                            color: "id",
                            radius: 4,
                            stroke: "var(--background)",
                            strokeWidth: 1.5,
                        }),
                        text(data, {
                            id: "event-labels",
                            x: "start",
                            y: "lane",
                            text: "label",
                            anchor: "start",
                            dx: 8,
                            fill: "var(--foreground)",
                            fontSize: 11,
                            fontWeight: 600,
                        }),
                    ],
                    scales: {
                        x: {
                            scale: scaleUtc().domain(fullDomain),
                            grid: true,
                            axis: {
                                ticks: {
                                    format: (v: Date) =>
                                        v.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" }),
                                },
                            },
                        },
                        y: {
                            scale: scaleBand<string>().domain(lanes).paddingInner(0.3),
                            grid: false,
                            axis: false,
                        },
                    },
                    color: {
                        domain: ["discovery", "design", "release"],
                        range: ["var(--chart-1)", "var(--chart-2)", "var(--chart-3)"],
                    },
                    controls: [
                        handleX({
                            id: "release-end",
                            value: controlledSignal<Date, any>(activeReleaseEnd, (next) =>
                                setReleaseEnd(next instanceof Date ? next : new Date(next)),
                            ),
                            values: eventEndValues,
                            cross: { value: "Engineering" as any },
                            ruleStyle: { fill: "var(--chart-3)" },
                            handleStyle: {
                                fill: "var(--chart-3)",
                                stroke: "var(--background)",
                                strokeWidth: 2,
                            },
                            ariaLabel: "Release end date handle scrubber",
                        } as any),
                    ],
                    margin: { top: 24, right: 24, bottom: 32, left: 90 },
                    theme: {
                        foreground: "var(--muted-foreground)",
                        grid: "var(--border)",
                        background: "transparent",
                    },
                },
                {
                    keyboard: true,
                    tooltip: {
                        use: tooltip,
                        className:
                            "font-sans shadow-none rounded-md bg-popover text-popover-foreground border p-2 text-xs",
                        anchor: "pointer",
                        placement: "auto",
                        content: (points) => shadcnTooltipContent(points),
                    },
                },
            ),
        [data, fullDomain, releaseEnd],
    );

    const fmtDate = (d: Date) => d.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });

    return (
        <Card className="w-full max-w-xl">
            <CardHeader>
                <CardTitle>Direct Editable Milestone Event Range</CardTitle>
                <CardDescription>
                    Bidirectional drag handle and date picker input synchronizing milestone durations
                </CardDescription>
            </CardHeader>
            <CardContent className="space-y-3">
                <div className="flex items-center justify-between gap-3">
                    <span className="text-muted-foreground font-mono text-xs">
                        Release End: <strong className="text-foreground">{fmtDate(releaseEnd)}</strong>
                    </span>
                    <div className="flex items-center gap-2">
                        <span className="text-muted-foreground text-xs font-medium">Edit Date:</span>
                        <Popover>
                            <PopoverTrigger
                                render={
                                    <Button
                                        variant="outline"
                                        size="sm"
                                        className="h-8 font-mono text-xs font-normal">
                                        <CalendarIcon className="mr-1.5 size-3.5 text-muted-foreground" />
                                        {fmtDate(releaseEnd)}
                                    </Button>
                                }
                            />
                            <PopoverContent className="w-auto p-0" align="end">
                                <Calendar
                                    mode="single"
                                    selected={calendarSelected}
                                    onSelect={(date) => {
                                        if (date) {
                                            setReleaseEnd(
                                                new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())),
                                            );
                                        }
                                    }}
                                    defaultMonth={calendarSelected}
                                    disabled={(date) => {
                                        const utcTime = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate());
                                        return (
                                            utcTime < Date.UTC(2024, 8, 13) ||
                                            utcTime > Date.UTC(2024, 9, 15)
                                        );
                                    }}
                                    autoFocus
                                />
                            </PopoverContent>
                        </Popover>
                    </div>
                </div>
                <div className="h-[210px] w-full">
                    <Chart
                        definition={definition}
                        renderer={motionRenderer}
                        height={210}
                        ariaLabel="Direct Editable Milestone Event Range Chart"
                    />
                </div>
            </CardContent>
            <CardFooter className="flex-col items-start gap-2 text-sm">
                <div className="flex items-center gap-2 leading-none font-medium">
                    Drag the orange handle on Engineering lane to adjust release date{" "}
                    <CalendarRange className="h-4 w-4 text-emerald-500" />
                </div>
                <div className="text-muted-foreground leading-none">
                    `handleX` provides interactive endpoint manipulation synchronized with form controls
                </div>
            </CardFooter>
        </Card>
    );
}

export default EditableEventRange;
```
