{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "gantt",
  "title": "Gantt",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/gantt/Gantt.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { GanttScale, GanttTask } from './types'\n\n  export interface GanttProps extends HTMLAttributes<HTMLDivElement> {\n    tasks: GanttTask[]\n    scale?: GanttScale\n    startDate?: string\n    endDate?: string\n    rowHeight?: number\n    headerHeight?: number\n    treeWidth?: number\n    onscalechange?: (scale: GanttScale) => void\n    ontaskclick?: (task: GanttTask) => void\n    ontaskchange?: (task: GanttTask) => void\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { setContext, untrack } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { GANTT_CONTEXT_KEY } from './context'\n\n  let {\n    class: className,\n    tasks,\n    scale = 'day',\n    startDate,\n    endDate,\n    rowHeight = 40,\n    headerHeight = 48,\n    treeWidth = 280,\n    onscalechange,\n    ontaskclick,\n    ontaskchange,\n    children,\n    ref = $bindable(null),\n    ...restProps\n  }: GanttProps = $props()\n\n  // `untrack` — mirrors the Vue twin: the prop seeds initial state only,\n  // later scale changes flow through context + `onscalechange`.\n  let currentScale = $state<GanttScale>(untrack(() => scale))\n\n  // Compute start and end dates from tasks if not explicitly provided\n  const resolvedStartDate = $derived.by(() => {\n    if (startDate) return new Date(startDate)\n    if (tasks.length === 0) return new Date()\n    const dates = tasks.map((t) => new Date(t.startDate).getTime())\n    const min = Math.min(...dates)\n    const d = new Date(min)\n    d.setDate(d.getDate() - 3) // padding\n    return d\n  })\n\n  const resolvedEndDate = $derived.by(() => {\n    if (endDate) return new Date(endDate)\n    if (tasks.length === 0) {\n      const d = new Date()\n      d.setDate(d.getDate() + 30)\n      return d\n    }\n    const dates = tasks.map((t) => new Date(t.endDate).getTime())\n    const max = Math.max(...dates)\n    const d = new Date(max)\n    d.setDate(d.getDate() + 7) // padding\n    return d\n  })\n\n  const totalDays = $derived.by(() => {\n    const diff = resolvedEndDate.getTime() - resolvedStartDate.getTime()\n    return Math.max(1, Math.ceil(diff / (1000 * 60 * 60 * 24)))\n  })\n\n  const columnWidth = $derived.by(() => {\n    switch (currentScale) {\n      case 'day':\n        return 44\n      case 'week':\n        return 120\n      case 'month':\n        return 180\n      case 'year':\n        return 240\n      default:\n        return 44\n    }\n  })\n\n  function applyScale(v: GanttScale) {\n    currentScale = v\n    onscalechange?.(v)\n  }\n\n  setContext(GANTT_CONTEXT_KEY, {\n    get scale() {\n      return currentScale\n    },\n    set scale(v: GanttScale) {\n      applyScale(v)\n    },\n    setScale: applyScale,\n    get startDate() {\n      return resolvedStartDate\n    },\n    get endDate() {\n      return resolvedEndDate\n    },\n    get totalDays() {\n      return totalDays\n    },\n    get columnWidth() {\n      return columnWidth\n    },\n    get rowHeight() {\n      return rowHeight\n    },\n    get headerHeight() {\n      return headerHeight\n    },\n    get treeWidth() {\n      return treeWidth\n    },\n    get tasks() {\n      return tasks\n    },\n    onTaskClick: (task: GanttTask) => ontaskclick?.(task),\n    onTaskChange: (task: GanttTask) => ontaskchange?.(task),\n  })\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"gantt\"\n  class={cn(\n    'border-border bg-card text-card-foreground relative flex w-full flex-col overflow-hidden rounded-xl border shadow-xs',\n    className,\n  )}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/Gantt.svelte"
    },
    {
      "path": "packages/registry-svelte/components/gantt/GanttHeader.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { GanttScale } from './types'\n\n  export interface GanttHeaderProps extends HTMLAttributes<HTMLDivElement> {\n    title?: string\n    showScaleSwitcher?: boolean\n    showNavigation?: boolean\n    /** Extra header actions (replaces the Vue `actions` slot). */\n    actions?: Snippet\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Calendar } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { Button, ButtonGroup } from '$lib/components/ui/button'\n  import { getGanttContext } from './context'\n\n  let {\n    class: className,\n    title = 'Project Timeline',\n    showScaleSwitcher = true,\n    showNavigation = false,\n    actions,\n    ref = $bindable(null),\n    ...restProps\n  }: GanttHeaderProps = $props()\n\n  // `showNavigation` is accepted for Vue-twin parity but unused (also unused\n  // there). Destructured so it stays out of `...restProps`.\n  const ctx = getGanttContext()\n\n  function setScale(scale: GanttScale) {\n    if (ctx) {\n      ctx.scale = scale\n    }\n  }\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"gantt-header\"\n  class={cn('border-border bg-muted/30 flex items-center justify-between border-b px-4 py-2.5', className)}\n  {...restProps}\n>\n  <div class=\"flex items-center gap-2\">\n    <Calendar class=\"text-primary size-4\" />\n    <span class=\"text-foreground text-sm font-semibold\">{title}</span>\n  </div>\n\n  <div class=\"flex items-center gap-3\">\n    {@render actions?.()}\n\n    {#if showScaleSwitcher && ctx}\n      <ButtonGroup>\n        <Button size=\"xs\" variant={ctx.scale === 'day' ? 'default' : 'outline'} onclick={() => setScale('day')}>\n          Day\n        </Button>\n        <Button size=\"xs\" variant={ctx.scale === 'week' ? 'default' : 'outline'} onclick={() => setScale('week')}>\n          Week\n        </Button>\n        <Button size=\"xs\" variant={ctx.scale === 'month' ? 'default' : 'outline'} onclick={() => setScale('month')}>\n          Month\n        </Button>\n        <Button size=\"xs\" variant={ctx.scale === 'year' ? 'default' : 'outline'} onclick={() => setScale('year')}>\n          Year\n        </Button>\n      </ButtonGroup>\n    {/if}\n  </div>\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/GanttHeader.svelte"
    },
    {
      "path": "packages/registry-svelte/components/gantt/GanttTree.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface GanttTreeProps extends HTMLAttributes<HTMLDivElement> {\n    showAssignee?: boolean\n    showPriority?: boolean\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { ChevronDown, ChevronRight, Flag } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { getGanttContext } from './context'\n\n  let {\n    class: className,\n    showAssignee = true,\n    showPriority = true,\n    ref = $bindable(null),\n    ...restProps\n  }: GanttTreeProps = $props()\n\n  // `showAssignee` is accepted for Vue-twin parity but unused (also unused\n  // there). Destructured so it stays out of `...restProps`.\n  const ctx = getGanttContext()\n\n  const priorityColors: Record<string, string> = {\n    urgent: 'text-destructive',\n    high: 'text-amber-500',\n    medium: 'text-primary',\n    low: 'text-muted-foreground/60',\n  }\n\n  function calculateDays(startDate: string, endDate: string) {\n    const diff = new Date(endDate).getTime() - new Date(startDate).getTime()\n    const days = Math.max(1, Math.ceil(diff / (1000 * 60 * 60 * 24)))\n    return `${days}d`\n  }\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"gantt-tree\"\n  style={`width: ${ctx?.treeWidth ?? 300}px`}\n  class={cn('border-border bg-card flex shrink-0 flex-col border-r transition-[width] select-none', className)}\n  {...restProps}\n>\n  <!-- Column Headers -->\n  <div\n    style={`height: ${ctx?.headerHeight ?? 48}px`}\n    class=\"border-border bg-muted/20 text-muted-foreground flex items-center justify-between border-b px-3 text-xs font-semibold tracking-wider uppercase\"\n  >\n    <span class=\"flex-1 truncate\">Deliverable</span>\n    {#if showPriority}\n      <span class=\"w-12 shrink-0 text-center\">Pri</span>\n    {/if}\n    <span class=\"w-16 shrink-0 text-right\">Duration</span>\n  </div>\n\n  <!-- Rows List -->\n  <div class=\"divide-border/40 flex-1 divide-y overflow-y-auto\">\n    {#each (ctx?.tasks ?? []) as task (task.id)}\n      <div\n        style={`height: ${ctx?.rowHeight ?? 40}px`}\n        class={cn(\n          'group/row text-foreground hover:bg-muted/40 flex cursor-pointer items-center justify-between px-3 text-xs transition-colors',\n          task.isGroup && 'bg-muted/10 font-semibold',\n        )}\n        onclick={() => ctx?.onTaskClick(task)}\n        onkeydown={(e) => {\n          if (e.key === 'Enter' || e.key === ' ') {\n            e.preventDefault()\n            ctx?.onTaskClick(task)\n          }\n        }}\n        role=\"button\"\n        tabindex=\"0\"\n      >\n        <!-- Task Name & Chevron / Indent -->\n        <div class=\"flex min-w-0 flex-1 items-center gap-1.5 pr-2\">\n          {#if task.isGroup || (task.children && task.children.length > 0)}\n            <button\n              type=\"button\"\n              class=\"text-muted-foreground hover:text-foreground flex size-4 shrink-0 items-center justify-center rounded-xs p-0.5\"\n              onclick={(e) => {\n                e.stopPropagation()\n                ctx?.toggleExpand?.(task.id)\n              }}\n            >\n              {#if task.isExpanded !== false}\n                <ChevronDown class=\"size-3\" />\n              {:else}\n                <ChevronRight class=\"size-3\" />\n              {/if}\n            </button>\n          {:else if task.parentId}\n            <span class=\"w-4 shrink-0\"></span>\n          {/if}\n\n          <!-- Status Indicator Dot -->\n          {#if task.status}\n            <span\n              class={cn(\n                'size-2 shrink-0 rounded-full',\n                task.status === 'done' && 'bg-emerald-500 ring-2 ring-emerald-500/20',\n                task.status === 'in-progress' && 'bg-primary ring-primary/20 ring-2',\n                task.status === 'at-risk' && 'bg-amber-500 ring-2 ring-amber-500/20',\n                task.status === 'todo' && 'bg-muted-foreground/40',\n                task.status === 'blocked' && 'bg-destructive ring-destructive/20 ring-2',\n              )}\n            ></span>\n          {/if}\n\n          <span class=\"truncate font-medium\">{task.name}</span>\n        </div>\n\n        <!-- Priority Flag -->\n        {#if showPriority}\n          <div class=\"flex w-12 shrink-0 items-center justify-center\">\n            {#if task.priority}\n              <Flag class={cn('size-3', priorityColors[task.priority])} />\n            {/if}\n          </div>\n        {/if}\n\n        <!-- Duration / Due Date Tag -->\n        <div class=\"text-muted-foreground w-16 shrink-0 text-right font-mono text-xs\">\n          {#if task.isMilestone}\n            <span class=\"text-xs font-semibold text-amber-500\"> Milestone </span>\n          {:else}\n            {calculateDays(task.startDate, task.endDate)}\n          {/if}\n        </div>\n      </div>\n    {/each}\n  </div>\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/GanttTree.svelte"
    },
    {
      "path": "packages/registry-svelte/components/gantt/GanttTimeline.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { GanttTask } from './types'\n\n  export interface GanttTimelineProps extends HTMLAttributes<HTMLDivElement> {\n    showTodayLine?: boolean\n    showDependencies?: boolean\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import GanttBar from './GanttBar.svelte'\n  import GanttMilestone from './GanttMilestone.svelte'\n  import { getGanttContext } from './context'\n\n  let {\n    class: className,\n    showTodayLine = true,\n    showDependencies = true,\n    ref = $bindable(null),\n    ...restProps\n  }: GanttTimelineProps = $props()\n\n  const ctx = getGanttContext()\n\n  const timelineTasks = $derived<GanttTask[]>(ctx?.tasks ?? [])\n\n  // Generate header date columns\n  const columns = $derived.by(() => {\n    if (!ctx) return []\n    const list: { date: Date; label: string; subLabel: string; isWeekend: boolean }[] = []\n    const start = new Date(ctx.startDate)\n    const total = ctx.totalDays\n\n    for (let i = 0; i < total; i++) {\n      const d = new Date(start)\n      d.setDate(d.getDate() + i)\n      const dayOfWeek = d.getDay()\n      const isWeekend = dayOfWeek === 0 || dayOfWeek === 6\n\n      list.push({\n        date: d,\n        label: d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),\n        subLabel: d.toLocaleDateString(undefined, { weekday: 'narrow' }),\n        isWeekend,\n      })\n    }\n    return list\n  })\n\n  const timelineWidth = $derived.by(() => {\n    if (!ctx) return 800\n    return columns.length * ctx.columnWidth\n  })\n\n  function getTaskCoordinates(task: GanttTask, index: number) {\n    if (!ctx) return { left: 0, width: 100, top: 0, height: 28 }\n    const start = new Date(ctx.startDate).getTime()\n    const taskStart = new Date(task.startDate).getTime()\n    const taskEnd = new Date(task.endDate).getTime()\n    const oneDay = 1000 * 60 * 60 * 24\n\n    const startDiffDays = Math.max(0, (taskStart - start) / oneDay)\n    const durationDays = Math.max(1, (taskEnd - taskStart) / oneDay)\n\n    const left = startDiffDays * ctx.columnWidth\n    const width = durationDays * ctx.columnWidth\n    const rowHeight = ctx.rowHeight\n    const top = index * rowHeight + (rowHeight - 28) / 2\n\n    return { left, width, top, height: 28 }\n  }\n\n  const todayPosition = $derived.by(() => {\n    if (!ctx) return null\n    const start = new Date(ctx.startDate).getTime()\n    const today = new Date().setHours(0, 0, 0, 0)\n    const oneDay = 1000 * 60 * 60 * 24\n    const diffDays = (today - start) / oneDay\n\n    if (diffDays < 0 || diffDays > ctx.totalDays) return null\n    return diffDays * ctx.columnWidth + ctx.columnWidth / 2\n  })\n\n  // Compute SVG Dependency curves between tasks\n  const dependencyPaths = $derived.by(() => {\n    if (!ctx || !showDependencies) return []\n    const tasksList: GanttTask[] = ctx.tasks ?? []\n    const taskMap = new Map<string, { task: GanttTask; index: number }>()\n    tasksList.forEach((t, i) => taskMap.set(t.id, { task: t, index: i }))\n\n    const paths: { d: string; fromId: string; toId: string }[] = []\n\n    tasksList.forEach((toTask, toIdx) => {\n      if (!toTask.dependencies || toTask.dependencies.length === 0) return\n      toTask.dependencies.forEach((fromId) => {\n        const fromEntry = taskMap.get(fromId)\n        if (!fromEntry) return\n\n        const fromCoords = getTaskCoordinates(fromEntry.task, fromEntry.index)\n        const toCoords = getTaskCoordinates(toTask, toIdx)\n\n        const startX = fromEntry.task.isMilestone ? fromCoords.left : fromCoords.left + fromCoords.width\n        const startY = fromCoords.top + 14\n\n        const endX = toCoords.left\n        const endY = toCoords.top + 14\n\n        const deltaX = Math.max(16, (endX - startX) / 2)\n        const d = `M ${startX} ${startY} C ${startX + deltaX} ${startY}, ${endX - deltaX} ${endY}, ${endX} ${endY}`\n        paths.push({ d, fromId, toId: toTask.id })\n      })\n    })\n\n    return paths\n  })\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"gantt-timeline\"\n  class={cn('bg-background relative flex-1 overflow-x-auto overflow-y-hidden select-none', className)}\n  {...restProps}\n>\n  <div style={`width: ${timelineWidth}px`} class=\"relative\">\n    <!-- Header Dates -->\n    <div\n      style={`height: ${ctx?.headerHeight ?? 48}px`}\n      class=\"border-border bg-muted/10 sticky top-0 z-20 flex border-b\"\n    >\n      {#each columns as col, i (i)}\n        <div\n          style={`width: ${ctx?.columnWidth ?? 44}px`}\n          class={cn(\n            'border-border/50 text-muted-foreground flex flex-col items-center justify-center border-r text-[10px]',\n            col.isWeekend && 'bg-muted/20 text-muted-foreground/60',\n          )}\n        >\n          <span class=\"text-foreground font-medium\">{col.label}</span>\n          <span class=\"text-[9px]\">{col.subLabel}</span>\n        </div>\n      {/each}\n    </div>\n\n    <!-- Grid Background & Task Bars -->\n    <div class=\"relative\">\n      <!-- Column grid lines -->\n      <div class=\"pointer-events-none absolute inset-0 flex\">\n        {#each columns as col, i (i)}\n          <div\n            style={`width: ${ctx?.columnWidth ?? 44}px`}\n            class={cn('border-border/30 h-full border-r', col.isWeekend && 'bg-muted/15')}\n          ></div>\n        {/each}\n      </div>\n\n      <!-- Today Marker Line -->\n      {#if showTodayLine && todayPosition != null}\n        <div\n          style={`left: ${todayPosition}px`}\n          class=\"pointer-events-none absolute inset-y-0 z-30 flex flex-col items-center\"\n        >\n          <div class=\"bg-destructive text-destructive-foreground rounded-full px-1.5 py-0.5 text-[9px] font-bold shadow-xs\">\n            Today\n          </div>\n          <div class=\"bg-destructive/60 h-full w-[1.5px] border-r border-dashed\"></div>\n        </div>\n      {/if}\n\n      <!-- SVG Dependencies Overlay -->\n      {#if dependencyPaths.length > 0}\n        <svg\n          width={timelineWidth}\n          height={(ctx?.tasks.length ?? 0) * (ctx?.rowHeight ?? 40)}\n          class=\"pointer-events-none absolute inset-0 z-10\"\n        >\n          <defs>\n            <marker id=\"gantt-arrow\" viewBox=\"0 0 6 6\" refX=\"5\" refY=\"3\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto\">\n              <path d=\"M 0 0 L 6 3 L 0 6 z\" class=\"fill-primary/60\" />\n            </marker>\n          </defs>\n          {#each dependencyPaths as p, i (i)}\n            <path\n              d={p.d}\n              fill=\"none\"\n              class=\"stroke-primary/50\"\n              stroke-width=\"1.5\"\n              stroke-dasharray=\"3,3\"\n              marker-end=\"url(#gantt-arrow)\"\n            />\n          {/each}\n        </svg>\n      {/if}\n\n      <!-- Task Rows & Bars -->\n      {#each timelineTasks as task, idx (task.id)}\n        {@const coords = getTaskCoordinates(task, idx)}\n        {@const rowHeight = ctx?.rowHeight ?? 40}\n        <div style={`height: ${rowHeight}px`} class=\"border-border/40 hover:bg-muted/10 relative border-b transition-colors\">\n          {#if task.isMilestone}\n            <GanttMilestone\n              {task}\n              left={coords.left}\n              top={rowHeight / 2}\n              onclick={(t) => ctx?.onTaskClick(t)}\n            />\n          {:else}\n            <GanttBar\n              {task}\n              left={coords.left}\n              width={coords.width}\n              top={(coords.height - 28) / 2 + 6}\n              height={28}\n              onclick={(t) => ctx?.onTaskClick(t)}\n            />\n          {/if}\n        </div>\n      {/each}\n    </div>\n  </div>\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/GanttTimeline.svelte"
    },
    {
      "path": "packages/registry-svelte/components/gantt/GanttBar.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { GanttTask } from './types'\n\n  // `onclick` carries the clicked task (not the MouseEvent), so it replaces\n  // the DOM handler from HTMLAttributes.\n  export interface GanttBarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onclick'> {\n    task: GanttTask\n    left: number\n    width: number\n    top: number\n    height: number\n    onclick?: (task: GanttTask) => void\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    task,\n    left,\n    width,\n    top,\n    height,\n    onclick,\n    ref = $bindable(null),\n    ...restProps\n  }: GanttBarProps = $props()\n\n  const statusColors: Record<string, string> = {\n    done: 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-300 border-emerald-500/40',\n    'in-progress': 'bg-primary/20 text-primary border-primary/40',\n    'at-risk': 'bg-amber-500/20 text-amber-700 dark:text-amber-300 border-amber-500/40',\n    todo: 'bg-muted/80 text-muted-foreground border-border',\n    blocked: 'bg-destructive/20 text-destructive border-destructive/40',\n  }\n\n  const progressColors: Record<string, string> = {\n    done: 'bg-emerald-500/40',\n    'in-progress': 'bg-primary/40',\n    'at-risk': 'bg-amber-500/40',\n    todo: 'bg-muted-foreground/20',\n    blocked: 'bg-destructive/40',\n  }\n\n  function handleActivate() {\n    onclick?.(task)\n  }\n</script>\n\n{#if task.isGroup}\n  <!-- Group Parent Task Bracket Bar -->\n  <div\n    bind:this={ref}\n    data-uipkge\n    data-slot=\"gantt-group-bar\"\n    role=\"button\"\n    tabindex=\"0\"\n    style={`left: ${left}px; width: ${Math.max(24, width)}px; top: ${top + 4}px; height: ${height - 8}px`}\n    class={cn(\n      'group/bar bg-foreground/80 text-background hover:bg-foreground absolute z-10 flex cursor-pointer items-center justify-between rounded-xs px-2 text-xs font-semibold shadow-xs select-none',\n      className,\n    )}\n    onclick={handleActivate}\n    onkeydown={(e) => {\n      if (e.key === 'Enter' || e.key === ' ') {\n        e.preventDefault()\n        handleActivate()\n      }\n    }}\n    {...restProps}\n  >\n    <span class=\"truncate\">{task.name}</span>\n    {#if task.progress != null}\n      <span class=\"font-mono text-xs opacity-80\"> {task.progress}% </span>\n    {/if}\n  </div>\n{:else}\n  <!-- Standard Deliverable Bar -->\n  <div\n    bind:this={ref}\n    data-uipkge\n    data-slot=\"gantt-bar\"\n    role=\"button\"\n    tabindex=\"0\"\n    style={`left: ${left}px; width: ${Math.max(24, width)}px; top: ${top}px; height: ${height}px`}\n    class={cn(\n      'group/bar absolute z-10 flex cursor-pointer items-center overflow-hidden rounded-md border text-xs font-medium shadow-xs transition-[box-shadow,transform] select-none hover:scale-[1.01] hover:shadow-md',\n      task.color ? task.color : statusColors[task.status ?? 'in-progress'],\n      className,\n    )}\n    onclick={handleActivate}\n    onkeydown={(e) => {\n      if (e.key === 'Enter' || e.key === ' ') {\n        e.preventDefault()\n        handleActivate()\n      }\n    }}\n    {...restProps}\n  >\n    <!-- Progress fill -->\n    {#if task.progress != null && task.progress > 0}\n      <div\n        style={`width: ${task.progress}%`}\n        class={cn('absolute inset-y-0 left-0 transition-[width]', progressColors[task.status ?? 'in-progress'])}\n      ></div>\n    {/if}\n\n    <!-- Content -->\n    <div class=\"relative z-10 flex w-full min-w-0 items-center justify-between px-2\">\n      <span class=\"truncate font-medium\">{task.name}</span>\n      {#if task.progress != null}\n        <span class=\"ml-1 shrink-0 font-mono text-xs opacity-80\">\n          {task.progress}%\n        </span>\n      {/if}\n    </div>\n\n    <!-- Left / Right Resize Handles -->\n    <div\n      aria-hidden=\"true\"\n      class=\"bg-foreground/20 absolute inset-y-0 left-0 w-1.5 cursor-ew-resize opacity-0 transition-opacity group-hover/bar:opacity-100\"\n    ></div>\n    <div\n      aria-hidden=\"true\"\n      class=\"bg-foreground/20 absolute inset-y-0 right-0 w-1.5 cursor-ew-resize opacity-0 transition-opacity group-hover/bar:opacity-100\"\n    ></div>\n  </div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/GanttBar.svelte"
    },
    {
      "path": "packages/registry-svelte/components/gantt/GanttMilestone.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { GanttTask } from './types'\n\n  // `onclick` carries the clicked task (not the MouseEvent), so it replaces\n  // the DOM handler from HTMLAttributes.\n  export interface GanttMilestoneProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onclick'> {\n    task: GanttTask\n    left: number\n    top: number\n    size?: number\n    onclick?: (task: GanttTask) => void\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    task,\n    left,\n    top,\n    size = 16,\n    onclick,\n    ref = $bindable(null),\n    ...restProps\n  }: GanttMilestoneProps = $props()\n\n  function handleActivate() {\n    onclick?.(task)\n  }\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"gantt-milestone\"\n  role=\"button\"\n  tabindex=\"0\"\n  style={`left: ${left - size / 2}px; top: ${top - size / 2}px; width: ${size}px; height: ${size}px`}\n  class={cn(\n    'border-primary bg-primary absolute z-20 rotate-45 cursor-pointer rounded-xs border-2 shadow-sm transition-transform hover:scale-125',\n    className,\n  )}\n  title={`${task.name} (${task.startDate})`}\n  onclick={handleActivate}\n  onkeydown={(e) => {\n    if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault()\n      handleActivate()\n    }\n  }}\n  {...restProps}\n></div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/GanttMilestone.svelte"
    },
    {
      "path": "packages/registry-svelte/components/gantt/GanttContextMenu.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { GanttTask, GanttTaskPriority, GanttTaskStatus } from './types'\n\n  export interface GanttContextMenuProps {\n    task: GanttTask\n    children?: Snippet\n    onedit?: (task: GanttTask) => void\n    onstatuschange?: (task: GanttTask, status: GanttTaskStatus) => void\n    onprioritychange?: (task: GanttTask, priority: GanttTaskPriority) => void\n    onduplicate?: (task: GanttTask) => void\n    ondelete?: (task: GanttTask) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import {\n    ContextMenu,\n    ContextMenuContent,\n    ContextMenuItem,\n    ContextMenuLabel,\n    ContextMenuRadioGroup,\n    ContextMenuRadioItem,\n    ContextMenuSeparator,\n    ContextMenuShortcut,\n    ContextMenuSub,\n    ContextMenuSubContent,\n    ContextMenuSubTrigger,\n    ContextMenuTrigger,\n  } from '$lib/components/ui/context-menu'\n  import { Clock, Copy, Edit2, Flag, Layers, Trash2 } from '@lucide/svelte'\n\n  let {\n    task,\n    onedit,\n    onstatuschange,\n    onprioritychange,\n    onduplicate,\n    ondelete,\n    children,\n  }: GanttContextMenuProps = $props()\n\n  function copyTaskId() {\n    if (typeof navigator !== 'undefined' && navigator.clipboard) {\n      navigator.clipboard.writeText(task.id)\n    }\n  }\n</script>\n\n<ContextMenu>\n  <!-- The Vue twin uses as-child here; without a render-prop contract on the\n       Svelte Trigger yet, nest the trigger content in the default element. -->\n  <ContextMenuTrigger>\n    {@render children?.()}\n  </ContextMenuTrigger>\n  <ContextMenuContent class=\"w-56\">\n    <ContextMenuLabel class=\"flex items-center justify-between text-xs\">\n      <span class=\"truncate font-semibold\">{task.name}</span>\n      <span class=\"text-muted-foreground font-mono text-xs\">{task.id}</span>\n    </ContextMenuLabel>\n    <ContextMenuSeparator />\n\n    <!-- Edit / Inspect -->\n    <ContextMenuItem onSelect={() => onedit?.(task)}>\n      <Edit2 class=\"mr-2 size-3.5\" />\n      <span>View Details</span>\n      <ContextMenuShortcut>↵</ContextMenuShortcut>\n    </ContextMenuItem>\n\n    <!-- Status Submenu -->\n    <ContextMenuSub>\n      <ContextMenuSubTrigger>\n        <Clock class=\"text-primary mr-2 size-3.5\" />\n        <span>Change Status</span>\n      </ContextMenuSubTrigger>\n      <ContextMenuSubContent class=\"w-44\">\n        <ContextMenuRadioGroup\n          value={task.status ?? 'todo'}\n          onValueChange={(v) => onstatuschange?.(task, v as GanttTaskStatus)}\n        >\n          <ContextMenuRadioItem value=\"done\">\n            <span class=\"mr-2 size-2 rounded-full bg-emerald-500\"></span>\n            <span>Completed</span>\n          </ContextMenuRadioItem>\n          <ContextMenuRadioItem value=\"in-progress\">\n            <span class=\"bg-primary mr-2 size-2 rounded-full\"></span>\n            <span>In Progress</span>\n          </ContextMenuRadioItem>\n          <ContextMenuRadioItem value=\"at-risk\">\n            <span class=\"mr-2 size-2 rounded-full bg-amber-500\"></span>\n            <span>At Risk</span>\n          </ContextMenuRadioItem>\n          <ContextMenuRadioItem value=\"blocked\">\n            <span class=\"bg-destructive mr-2 size-2 rounded-full\"></span>\n            <span>Blocked</span>\n          </ContextMenuRadioItem>\n          <ContextMenuRadioItem value=\"todo\">\n            <span class=\"bg-muted-foreground/40 mr-2 size-2 rounded-full\"></span>\n            <span>To Do</span>\n          </ContextMenuRadioItem>\n        </ContextMenuRadioGroup>\n      </ContextMenuSubContent>\n    </ContextMenuSub>\n\n    <!-- Priority Submenu -->\n    <ContextMenuSub>\n      <ContextMenuSubTrigger>\n        <Flag class=\"mr-2 size-3.5 text-amber-500\" />\n        <span>Set Priority</span>\n      </ContextMenuSubTrigger>\n      <ContextMenuSubContent class=\"w-40\">\n        <ContextMenuRadioGroup\n          value={task.priority ?? 'medium'}\n          onValueChange={(v) => onprioritychange?.(task, v as GanttTaskPriority)}\n        >\n          <ContextMenuRadioItem value=\"urgent\">\n            <Flag class=\"text-destructive mr-2 size-3\" />\n            <span>Urgent</span>\n          </ContextMenuRadioItem>\n          <ContextMenuRadioItem value=\"high\">\n            <Flag class=\"mr-2 size-3 text-amber-500\" />\n            <span>High</span>\n          </ContextMenuRadioItem>\n          <ContextMenuRadioItem value=\"medium\">\n            <Flag class=\"text-primary mr-2 size-3\" />\n            <span>Medium</span>\n          </ContextMenuRadioItem>\n          <ContextMenuRadioItem value=\"low\">\n            <Flag class=\"text-muted-foreground mr-2 size-3\" />\n            <span>Low</span>\n          </ContextMenuRadioItem>\n        </ContextMenuRadioGroup>\n      </ContextMenuSubContent>\n    </ContextMenuSub>\n\n    <ContextMenuSeparator />\n\n    <!-- Copy ID -->\n    <ContextMenuItem onSelect={copyTaskId}>\n      <Copy class=\"mr-2 size-3.5\" />\n      <span>Copy Task ID</span>\n      <ContextMenuShortcut>⌘C</ContextMenuShortcut>\n    </ContextMenuItem>\n\n    <!-- Duplicate -->\n    <ContextMenuItem onSelect={() => onduplicate?.(task)}>\n      <Layers class=\"mr-2 size-3.5\" />\n      <span>Duplicate</span>\n      <ContextMenuShortcut>⌘D</ContextMenuShortcut>\n    </ContextMenuItem>\n\n    <ContextMenuSeparator />\n\n    <!-- Delete -->\n    <ContextMenuItem class=\"text-destructive focus:text-destructive\" onSelect={() => ondelete?.(task)}>\n      <Trash2 class=\"mr-2 size-3.5\" />\n      <span>Delete Deliverable</span>\n      <ContextMenuShortcut>⌫</ContextMenuShortcut>\n    </ContextMenuItem>\n  </ContextMenuContent>\n</ContextMenu>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/GanttContextMenu.svelte"
    },
    {
      "path": "packages/registry-svelte/components/gantt/context.ts",
      "content": "import { getContext } from 'svelte'\nimport type { GanttScale, GanttTask } from './types'\n\nexport const GANTT_CONTEXT_KEY = Symbol('gantt')\n\nexport interface GanttContext {\n  scale: GanttScale\n  /** React-parity setter (the Svelte-idiomatic path is assigning `scale`). */\n  setScale: (scale: GanttScale) => void\n  startDate: Date\n  endDate: Date\n  totalDays: number\n  columnWidth: number\n  rowHeight: number\n  headerHeight: number\n  treeWidth: number\n  tasks: GanttTask[]\n  onTaskClick: (task: GanttTask) => void\n  onTaskChange: (task: GanttTask) => void\n  toggleExpand?: (id: string) => void\n}\n\n/** React's context-value name, kept as an alias (shapes match: Svelte's\n *  `onTaskClick`/`onTaskChange` are required because `<Gantt>` always\n *  provides them; React's are optional). */\nexport type GanttContextValue = GanttContext\n\nexport function getGanttContext(): GanttContext | undefined {\n  return getContext<GanttContext | undefined>(GANTT_CONTEXT_KEY)\n}\n\n/** React's `useGantt` name — like React, throws outside `<Gantt>`.\n *  Prefer `getGanttContext()` when a missing provider is not an error. */\nexport function useGantt(): GanttContext {\n  const ctx = getGanttContext()\n  if (!ctx) throw new Error('useGantt must be used within a <Gantt /> component')\n  return ctx\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/context.ts"
    },
    {
      "path": "packages/registry-svelte/components/gantt/types.ts",
      "content": "export type GanttScale = 'day' | 'week' | 'month' | 'quarter' | 'year'\n\nexport type GanttTaskStatus = 'todo' | 'in-progress' | 'done' | 'blocked' | 'at-risk'\n\nexport type GanttTaskPriority = 'low' | 'medium' | 'high' | 'urgent'\n\nexport interface GanttDependency {\n  fromId: string\n  toId: string\n  type?: 'finish-to-start' | 'start-to-start' | 'finish-to-finish'\n}\n\nexport interface GanttAssignee {\n  name: string\n  avatar?: string\n  initials?: string\n  role?: string\n}\n\nexport interface GanttTask {\n  id: string\n  name: string\n  startDate: string // YYYY-MM-DD\n  endDate: string // YYYY-MM-DD\n  progress?: number // 0 to 100\n  color?: string\n  status?: GanttTaskStatus\n  priority?: GanttTaskPriority\n  assignee?: GanttAssignee\n  isMilestone?: boolean\n  isGroup?: boolean\n  parentId?: string | null\n  isExpanded?: boolean\n  dependencies?: string[] // task IDs\n  children?: GanttTask[]\n}\n\nexport interface GanttColumn {\n  key: string\n  label: string\n  width?: string\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/types.ts"
    },
    {
      "path": "packages/registry-svelte/components/gantt/index.ts",
      "content": "export { default as Gantt, type GanttProps } from './Gantt.svelte'\nexport { default as GanttHeader, type GanttHeaderProps } from './GanttHeader.svelte'\nexport { default as GanttTree, type GanttTreeProps } from './GanttTree.svelte'\nexport { default as GanttTimeline, type GanttTimelineProps } from './GanttTimeline.svelte'\nexport { default as GanttBar, type GanttBarProps } from './GanttBar.svelte'\nexport { default as GanttMilestone, type GanttMilestoneProps } from './GanttMilestone.svelte'\nexport { default as GanttContextMenu, type GanttContextMenuProps } from './GanttContextMenu.svelte'\nexport { GANTT_CONTEXT_KEY, getGanttContext, useGantt, type GanttContext, type GanttContextValue } from './context'\nexport * from './types'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/gantt/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/button.json",
    "https://uipkge.dev/r/svelte/context-menu.json"
  ],
  "description": "Interactive Gantt chart primitive with multi-scale timeline (day/week/month/year), collapsible task tree, progress fill, milestone markers, dependency lines, and right-click GanttContextMenu.",
  "categories": [
    "data-display"
  ]
}