{
  "name": "kanban",
  "type": "registry:ui",
  "dependencies": [
    "@dnd-kit/core",
    "@dnd-kit/modifiers",
    "@dnd-kit/sortable",
    "@dnd-kit/utilities",
    "radix-ui"
  ],
  "files": [
    {
      "path": "ui/kanban.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport {\n  type Announcements,\n  closestCenter,\n  closestCorners,\n  type CollisionDetection,\n  defaultDropAnimationSideEffects,\n  DndContext,\n  type DndContextProps,\n  type DragCancelEvent,\n  type DragEndEvent,\n  type DraggableAttributes,\n  type DraggableSyntheticListeners,\n  type DragOverEvent,\n  DragOverlay,\n  type DragStartEvent,\n  type DropAnimation,\n  type DroppableContainer,\n  getFirstCollision,\n  KeyboardCode,\n  type KeyboardCoordinateGetter,\n  KeyboardSensor,\n  MeasuringStrategy,\n  MouseSensor,\n  pointerWithin,\n  rectIntersection,\n  TouchSensor,\n  type UniqueIdentifier,\n  useSensor,\n  useSensors,\n} from \"@dnd-kit/core\";\nimport {\n  type AnimateLayoutChanges,\n  arrayMove,\n  defaultAnimateLayoutChanges,\n  horizontalListSortingStrategy,\n  SortableContext,\n  type SortableContextProps,\n  useSortable,\n  verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\n\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { cn } from \"@/lib/utils\";\n\nconst directions = new Set<string>([\n  KeyboardCode.Down,\n  KeyboardCode.Right,\n  KeyboardCode.Up,\n  KeyboardCode.Left,\n]);\n\nfunction getIsDirectionCode(code: string): code is KeyboardCode {\n  return directions.has(code);\n}\n\nconst coordinateGetter: KeyboardCoordinateGetter = (event, { context }) => {\n  const { active, droppableRects, droppableContainers, collisionRect } =\n    context;\n\n  if (getIsDirectionCode(event.code)) {\n    event.preventDefault();\n\n    if (!active || !collisionRect) return;\n\n    const filteredContainers: DroppableContainer[] = [];\n\n    for (const entry of droppableContainers.getEnabled()) {\n      if (!entry || entry?.disabled) return;\n\n      const rect = droppableRects.get(entry.id);\n\n      if (!rect) return;\n\n      const data = entry.data.current;\n\n      if (data) {\n        const { type, children } = data;\n\n        if (type === \"container\" && children?.length > 0) {\n          if (active.data.current?.type !== \"container\") {\n            return;\n          }\n        }\n      }\n\n      switch (event.code) {\n        case KeyboardCode.Down:\n          if (collisionRect.top < rect.top) {\n            filteredContainers.push(entry);\n          }\n          break;\n        case KeyboardCode.Up:\n          if (collisionRect.top > rect.top) {\n            filteredContainers.push(entry);\n          }\n          break;\n        case KeyboardCode.Left:\n          if (collisionRect.left >= rect.left + rect.width) {\n            filteredContainers.push(entry);\n          }\n          break;\n        case KeyboardCode.Right:\n          if (collisionRect.left + collisionRect.width <= rect.left) {\n            filteredContainers.push(entry);\n          }\n          break;\n      }\n    }\n\n    const collisions = closestCorners({\n      active,\n      collisionRect: collisionRect,\n      droppableRects,\n      droppableContainers: filteredContainers,\n      pointerCoordinates: null,\n    });\n    const closestId = getFirstCollision(collisions, \"id\");\n\n    if (closestId != null) {\n      const newDroppable = droppableContainers.get(closestId);\n      const newNode = newDroppable?.node.current;\n      const newRect = newDroppable?.rect.current;\n\n      if (newNode && newRect) {\n        if (newDroppable.id === \"placeholder\") {\n          return {\n            x: newRect.left + (newRect.width - collisionRect.width) / 2,\n            y: newRect.top + (newRect.height - collisionRect.height) / 2,\n          };\n        }\n\n        if (newDroppable.data.current?.type === \"container\") {\n          return {\n            x: newRect.left + 20,\n            y: newRect.top + 74,\n          };\n        }\n\n        return {\n          x: newRect.left,\n          y: newRect.top,\n        };\n      }\n    }\n  }\n\n  return undefined;\n};\n\nconst ROOT_NAME = \"Kanban\";\nconst BOARD_NAME = \"KanbanBoard\";\nconst COLUMN_NAME = \"KanbanColumn\";\nconst COLUMN_HANDLE_NAME = \"KanbanColumnHandle\";\nconst ITEM_NAME = \"KanbanItem\";\nconst ITEM_HANDLE_NAME = \"KanbanItemHandle\";\nconst OVERLAY_NAME = \"KanbanOverlay\";\n\ninterface KanbanContextValue<T> {\n  id: string;\n  items: Record<UniqueIdentifier, T[]>;\n  modifiers: DndContextProps[\"modifiers\"];\n  strategy: SortableContextProps[\"strategy\"];\n  orientation: \"horizontal\" | \"vertical\";\n  activeId: UniqueIdentifier | null;\n  setActiveId: (id: UniqueIdentifier | null) => void;\n  getItemValue: (item: T) => UniqueIdentifier;\n  flatCursor: boolean;\n}\n\nconst KanbanContext = React.createContext<KanbanContextValue<unknown> | null>(\n  null,\n);\n\nfunction useKanbanContext(consumerName: string) {\n  const context = React.useContext(KanbanContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n  }\n  return context;\n}\n\ninterface GetItemValue<T> {\n  /**\n   * Callback that returns a unique identifier for each kanban item. Required for array of objects.\n   * @example getItemValue={(item) => item.id}\n   */\n  getItemValue: (item: T) => UniqueIdentifier;\n}\n\ntype KanbanProps<T> = Omit<DndContextProps, \"collisionDetection\"> &\n  (T extends object ? GetItemValue<T> : Partial<GetItemValue<T>>) & {\n    value: Record<UniqueIdentifier, T[]>;\n    onValueChange?: (columns: Record<UniqueIdentifier, T[]>) => void;\n    onMove?: (\n      event: DragEndEvent & { activeIndex: number; overIndex: number },\n    ) => void;\n    strategy?: SortableContextProps[\"strategy\"];\n    orientation?: \"horizontal\" | \"vertical\";\n    flatCursor?: boolean;\n  };\n\nfunction Kanban<T>(props: KanbanProps<T>) {\n  const {\n    value,\n    onValueChange,\n    modifiers,\n    strategy = verticalListSortingStrategy,\n    orientation = \"horizontal\",\n    onMove,\n    getItemValue: getItemValueProp,\n    accessibility,\n    flatCursor = false,\n    ...kanbanProps\n  } = props;\n\n  const id = React.useId();\n  const [activeId, setActiveId] = React.useState<UniqueIdentifier | null>(null);\n  const lastOverIdRef = React.useRef<UniqueIdentifier | null>(null);\n  const hasMovedRef = React.useRef(false);\n  const sensors = useSensors(\n    useSensor(MouseSensor),\n    useSensor(TouchSensor),\n    useSensor(KeyboardSensor, {\n      coordinateGetter,\n    }),\n  );\n\n  const getItemValue = React.useCallback(\n    (item: T): UniqueIdentifier => {\n      if (typeof item === \"object\" && !getItemValueProp) {\n        throw new Error(\n          \"`getItemValue` is required when using array of objects\",\n        );\n      }\n      return getItemValueProp\n        ? getItemValueProp(item)\n        : (item as UniqueIdentifier);\n    },\n    [getItemValueProp],\n  );\n\n  const getColumn = React.useCallback(\n    (targetId: UniqueIdentifier) => {\n      if (targetId in value) return targetId;\n\n      for (const [columnId, columnItems] of Object.entries(value)) {\n        if (columnItems.some((item) => getItemValue(item) === targetId)) {\n          return columnId;\n        }\n      }\n\n      return null;\n    },\n    [value, getItemValue],\n  );\n\n  const collisionDetection: CollisionDetection = React.useCallback(\n    (args) => {\n      if (activeId && activeId in value) {\n        return closestCenter({\n          ...args,\n          droppableContainers: args.droppableContainers.filter(\n            (container) => container.id in value,\n          ),\n        });\n      }\n\n      const pointerIntersections = pointerWithin(args);\n      const intersections =\n        pointerIntersections.length > 0\n          ? pointerIntersections\n          : rectIntersection(args);\n      let overId = getFirstCollision(intersections, \"id\");\n\n      if (!overId) {\n        if (hasMovedRef.current) {\n          lastOverIdRef.current = activeId;\n        }\n        return lastOverIdRef.current ? [{ id: lastOverIdRef.current }] : [];\n      }\n\n      if (overId in value) {\n        const containerItems = value[overId];\n        if (containerItems && containerItems.length > 0) {\n          const closestItem = closestCenter({\n            ...args,\n            droppableContainers: args.droppableContainers.filter(\n              (container) =>\n                container.id !== overId &&\n                containerItems.some(\n                  (item) => getItemValue(item) === container.id,\n                ),\n            ),\n          });\n\n          if (closestItem.length > 0) {\n            overId = closestItem[0]?.id ?? overId;\n          }\n        }\n      }\n\n      lastOverIdRef.current = overId;\n      return [{ id: overId }];\n    },\n    [activeId, value, getItemValue],\n  );\n\n  const onDragStart = React.useCallback(\n    (event: DragStartEvent) => {\n      kanbanProps.onDragStart?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n      setActiveId(event.active.id);\n    },\n    [kanbanProps.onDragStart],\n  );\n\n  const onDragOver = React.useCallback(\n    (event: DragOverEvent) => {\n      kanbanProps.onDragOver?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      const { active, over } = event;\n      if (!over) return;\n\n      const activeColumn = getColumn(active.id);\n      const overColumn = getColumn(over.id);\n\n      if (!activeColumn || !overColumn) return;\n\n      if (activeColumn === overColumn) {\n        const items = value[activeColumn];\n        if (!items) return;\n\n        const activeIndex = items.findIndex(\n          (item) => getItemValue(item) === active.id,\n        );\n        const overIndex = items.findIndex(\n          (item) => getItemValue(item) === over.id,\n        );\n\n        if (activeIndex !== overIndex) {\n          const newColumns = { ...value };\n          newColumns[activeColumn] = arrayMove(items, activeIndex, overIndex);\n          onValueChange?.(newColumns);\n        }\n      } else {\n        const activeItems = value[activeColumn];\n        const overItems = value[overColumn];\n\n        if (!activeItems || !overItems) return;\n\n        const activeIndex = activeItems.findIndex(\n          (item) => getItemValue(item) === active.id,\n        );\n\n        if (activeIndex === -1) return;\n\n        const activeItem = activeItems[activeIndex];\n        if (!activeItem) return;\n\n        const updatedItems = {\n          ...value,\n          [activeColumn]: activeItems.filter(\n            (item) => getItemValue(item) !== active.id,\n          ),\n          [overColumn]: [...overItems, activeItem],\n        };\n\n        onValueChange?.(updatedItems);\n        hasMovedRef.current = true;\n      }\n    },\n    [value, getColumn, getItemValue, onValueChange, kanbanProps.onDragOver],\n  );\n\n  const onDragEnd = React.useCallback(\n    (event: DragEndEvent) => {\n      kanbanProps.onDragEnd?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      const { active, over } = event;\n\n      if (!over) {\n        setActiveId(null);\n        return;\n      }\n\n      if (active.id in value && over.id in value) {\n        const activeIndex = Object.keys(value).indexOf(active.id as string);\n        const overIndex = Object.keys(value).indexOf(over.id as string);\n\n        if (activeIndex !== overIndex) {\n          const orderedColumns = Object.keys(value);\n          const newOrder = arrayMove(orderedColumns, activeIndex, overIndex);\n\n          const newColumns: Record<UniqueIdentifier, T[]> = {};\n          for (const key of newOrder) {\n            const items = value[key];\n            if (items) {\n              newColumns[key] = items;\n            }\n          }\n\n          if (onMove) {\n            onMove({ ...event, activeIndex, overIndex });\n          } else {\n            onValueChange?.(newColumns);\n          }\n        }\n      } else {\n        const activeColumn = getColumn(active.id);\n        const overColumn = getColumn(over.id);\n\n        if (!activeColumn || !overColumn) {\n          setActiveId(null);\n          return;\n        }\n\n        if (activeColumn === overColumn) {\n          const items = value[activeColumn];\n          if (!items) {\n            setActiveId(null);\n            return;\n          }\n\n          const activeIndex = items.findIndex(\n            (item) => getItemValue(item) === active.id,\n          );\n          const overIndex = items.findIndex(\n            (item) => getItemValue(item) === over.id,\n          );\n\n          if (activeIndex !== overIndex) {\n            const newColumns = { ...value };\n            newColumns[activeColumn] = arrayMove(items, activeIndex, overIndex);\n            if (onMove) {\n              onMove({\n                ...event,\n                activeIndex,\n                overIndex,\n              });\n            } else {\n              onValueChange?.(newColumns);\n            }\n          }\n        }\n      }\n\n      setActiveId(null);\n      hasMovedRef.current = false;\n    },\n    [\n      value,\n      getColumn,\n      getItemValue,\n      onValueChange,\n      onMove,\n      kanbanProps.onDragEnd,\n    ],\n  );\n\n  const onDragCancel = React.useCallback(\n    (event: DragCancelEvent) => {\n      kanbanProps.onDragCancel?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      setActiveId(null);\n      hasMovedRef.current = false;\n    },\n    [kanbanProps.onDragCancel],\n  );\n\n  const announcements: Announcements = React.useMemo(\n    () => ({\n      onDragStart({ active }) {\n        const isColumn = active.id in value;\n        const itemType = isColumn ? \"column\" : \"item\";\n        const position = isColumn\n          ? Object.keys(value).indexOf(active.id as string) + 1\n          : (() => {\n              const column = getColumn(active.id);\n              if (!column || !value[column]) return 1;\n              return (\n                value[column].findIndex(\n                  (item) => getItemValue(item) === active.id,\n                ) + 1\n              );\n            })();\n        const total = isColumn\n          ? Object.keys(value).length\n          : (() => {\n              const column = getColumn(active.id);\n              return column ? (value[column]?.length ?? 0) : 0;\n            })();\n\n        return `Picked up ${itemType} at position ${position} of ${total}`;\n      },\n      onDragOver({ active, over }) {\n        if (!over) return;\n\n        const isColumn = active.id in value;\n        const itemType = isColumn ? \"column\" : \"item\";\n        const position = isColumn\n          ? Object.keys(value).indexOf(over.id as string) + 1\n          : (() => {\n              const column = getColumn(over.id);\n              if (!column || !value[column]) return 1;\n              return (\n                value[column].findIndex(\n                  (item) => getItemValue(item) === over.id,\n                ) + 1\n              );\n            })();\n        const total = isColumn\n          ? Object.keys(value).length\n          : (() => {\n              const column = getColumn(over.id);\n              return column ? (value[column]?.length ?? 0) : 0;\n            })();\n\n        const overColumn = getColumn(over.id);\n        const activeColumn = getColumn(active.id);\n\n        if (isColumn) {\n          return `${itemType} is now at position ${position} of ${total}`;\n        }\n\n        if (activeColumn !== overColumn) {\n          return `${itemType} is now at position ${position} of ${total} in ${overColumn}`;\n        }\n\n        return `${itemType} is now at position ${position} of ${total}`;\n      },\n      onDragEnd({ active, over }) {\n        if (!over) return;\n\n        const isColumn = active.id in value;\n        const itemType = isColumn ? \"column\" : \"item\";\n        const position = isColumn\n          ? Object.keys(value).indexOf(over.id as string) + 1\n          : (() => {\n              const column = getColumn(over.id);\n              if (!column || !value[column]) return 1;\n              return (\n                value[column].findIndex(\n                  (item) => getItemValue(item) === over.id,\n                ) + 1\n              );\n            })();\n        const total = isColumn\n          ? Object.keys(value).length\n          : (() => {\n              const column = getColumn(over.id);\n              return column ? (value[column]?.length ?? 0) : 0;\n            })();\n\n        const overColumn = getColumn(over.id);\n        const activeColumn = getColumn(active.id);\n\n        if (isColumn) {\n          return `${itemType} was dropped at position ${position} of ${total}`;\n        }\n\n        if (activeColumn !== overColumn) {\n          return `${itemType} was dropped at position ${position} of ${total} in ${overColumn}`;\n        }\n\n        return `${itemType} was dropped at position ${position} of ${total}`;\n      },\n      onDragCancel({ active }) {\n        const isColumn = active.id in value;\n        const itemType = isColumn ? \"column\" : \"item\";\n        return `Dragging was cancelled. ${itemType} was dropped.`;\n      },\n    }),\n    [value, getColumn, getItemValue],\n  );\n\n  const contextValue = React.useMemo<KanbanContextValue<T>>(\n    () => ({\n      id,\n      items: value,\n      modifiers,\n      strategy,\n      orientation,\n      activeId,\n      setActiveId,\n      getItemValue,\n      flatCursor,\n    }),\n    [\n      id,\n      value,\n      activeId,\n      modifiers,\n      strategy,\n      orientation,\n      getItemValue,\n      flatCursor,\n    ],\n  );\n\n  return (\n    <KanbanContext.Provider value={contextValue as KanbanContextValue<unknown>}>\n      <DndContext\n        collisionDetection={collisionDetection}\n        modifiers={modifiers}\n        sensors={sensors}\n        {...kanbanProps}\n        id={id}\n        measuring={{\n          droppable: {\n            strategy: MeasuringStrategy.Always,\n          },\n        }}\n        onDragStart={onDragStart}\n        onDragOver={onDragOver}\n        onDragEnd={onDragEnd}\n        onDragCancel={onDragCancel}\n        accessibility={{\n          announcements,\n          screenReaderInstructions: {\n            draggable: `\n            To pick up a kanban item or column, press space or enter.\n            While dragging, use the arrow keys to move the item.\n            Press space or enter again to drop the item in its new position, or press escape to cancel.\n          `,\n          },\n          ...accessibility,\n        }}\n      />\n    </KanbanContext.Provider>\n  );\n}\n\nconst KanbanBoardContext = React.createContext<boolean>(false);\n\ninterface KanbanBoardProps extends React.ComponentProps<\"div\"> {\n  children: React.ReactNode;\n  asChild?: boolean;\n}\n\nfunction KanbanBoard(props: KanbanBoardProps) {\n  const { asChild, className, ref, ...boardProps } = props;\n\n  const context = useKanbanContext(BOARD_NAME);\n\n  const columns = React.useMemo(() => {\n    return Object.keys(context.items);\n  }, [context.items]);\n\n  const BoardPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <KanbanBoardContext.Provider value={true}>\n      <SortableContext\n        items={columns}\n        strategy={\n          context.orientation === \"horizontal\"\n            ? horizontalListSortingStrategy\n            : verticalListSortingStrategy\n        }\n      >\n        <BoardPrimitive\n          aria-orientation={context.orientation}\n          data-orientation={context.orientation}\n          data-slot=\"kanban-board\"\n          {...boardProps}\n          ref={ref}\n          className={cn(\n            \"flex size-full gap-4\",\n            context.orientation === \"horizontal\" ? \"flex-row\" : \"flex-col\",\n            className,\n          )}\n        />\n      </SortableContext>\n    </KanbanBoardContext.Provider>\n  );\n}\n\ninterface KanbanColumnContextValue {\n  id: string;\n  attributes: DraggableAttributes;\n  listeners: DraggableSyntheticListeners | undefined;\n  setActivatorNodeRef: (node: HTMLElement | null) => void;\n  isDragging?: boolean;\n  disabled?: boolean;\n}\n\nconst KanbanColumnContext =\n  React.createContext<KanbanColumnContextValue | null>(null);\n\nfunction useKanbanColumnContext(consumerName: string) {\n  const context = React.useContext(KanbanColumnContext);\n  if (!context) {\n    throw new Error(\n      `\\`${consumerName}\\` must be used within \\`${COLUMN_NAME}\\``,\n    );\n  }\n  return context;\n}\n\nconst animateLayoutChanges: AnimateLayoutChanges = (args) =>\n  defaultAnimateLayoutChanges({ ...args, wasDragging: true });\n\ninterface KanbanColumnProps extends React.ComponentProps<\"div\"> {\n  value: UniqueIdentifier;\n  children: React.ReactNode;\n  asChild?: boolean;\n  asHandle?: boolean;\n  disabled?: boolean;\n}\n\nfunction KanbanColumn(props: KanbanColumnProps) {\n  const {\n    value,\n    asChild,\n    asHandle,\n    disabled,\n    className,\n    style,\n    ref,\n    ...columnProps\n  } = props;\n\n  const id = React.useId();\n  const context = useKanbanContext(COLUMN_NAME);\n  const inBoard = React.useContext(KanbanBoardContext);\n  const inOverlay = React.useContext(KanbanOverlayContext);\n\n  if (!inBoard && !inOverlay) {\n    throw new Error(\n      `\\`${COLUMN_NAME}\\` must be used within \\`${BOARD_NAME}\\` or \\`${OVERLAY_NAME}\\``,\n    );\n  }\n\n  if (value === \"\") {\n    throw new Error(`\\`${COLUMN_NAME}\\` value cannot be an empty string`);\n  }\n\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    setActivatorNodeRef,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({\n    id: value,\n    disabled,\n    animateLayoutChanges,\n  });\n\n  const onNodeRefChange = React.useCallback(\n    (node: HTMLElement | null) => {\n      if (disabled) return;\n      setNodeRef(node);\n    },\n    [disabled, setNodeRef],\n  );\n\n  const composedRef = useComposedRefs(ref, onNodeRefChange);\n\n  const composedStyle = React.useMemo<React.CSSProperties>(() => {\n    return {\n      transform: CSS.Transform.toString(transform),\n      transition,\n      ...style,\n    };\n  }, [transform, transition, style]);\n\n  const items = React.useMemo(() => {\n    const columnItems = context.items[value] ?? [];\n    return columnItems.map((item) => context.getItemValue(item));\n  }, [context.items, value, context.getItemValue]);\n\n  const columnContext = React.useMemo<KanbanColumnContextValue>(\n    () => ({\n      id,\n      attributes,\n      listeners,\n      setActivatorNodeRef,\n      isDragging,\n      disabled,\n    }),\n    [id, attributes, listeners, setActivatorNodeRef, isDragging, disabled],\n  );\n\n  const ColumnPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <KanbanColumnContext.Provider value={columnContext}>\n      <SortableContext\n        items={items}\n        strategy={\n          context.orientation === \"horizontal\"\n            ? horizontalListSortingStrategy\n            : verticalListSortingStrategy\n        }\n      >\n        <ColumnPrimitive\n          id={id}\n          data-disabled={disabled}\n          data-dragging={isDragging ? \"\" : undefined}\n          data-slot=\"kanban-column\"\n          {...columnProps}\n          {...(asHandle && !disabled ? attributes : {})}\n          {...(asHandle && !disabled ? listeners : {})}\n          ref={composedRef}\n          style={composedStyle}\n          className={cn(\n            \"flex size-full flex-col gap-2 rounded-lg border bg-zinc-100 p-2.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:bg-zinc-900\",\n            {\n              \"touch-none select-none\": asHandle,\n              \"cursor-default\": context.flatCursor,\n              \"data-dragging:cursor-grabbing\": !context.flatCursor,\n              \"cursor-grab\": !isDragging && asHandle && !context.flatCursor,\n              \"opacity-50\": isDragging,\n              \"pointer-events-none opacity-50\": disabled,\n            },\n            className,\n          )}\n        />\n      </SortableContext>\n    </KanbanColumnContext.Provider>\n  );\n}\n\ninterface KanbanColumnHandleProps extends React.ComponentProps<\"button\"> {\n  asChild?: boolean;\n}\n\nfunction KanbanColumnHandle(props: KanbanColumnHandleProps) {\n  const { asChild, disabled, className, ref, ...columnHandleProps } = props;\n\n  const context = useKanbanContext(COLUMN_NAME);\n  const columnContext = useKanbanColumnContext(COLUMN_HANDLE_NAME);\n\n  const isDisabled = disabled ?? columnContext.disabled;\n\n  const { setActivatorNodeRef } = columnContext;\n\n  const onActivatorNodeRef = React.useCallback(\n    (node: HTMLElement | null) => {\n      if (isDisabled) return;\n      setActivatorNodeRef(node);\n    },\n    [isDisabled, setActivatorNodeRef],\n  );\n\n  const composedRef = useComposedRefs(ref, onActivatorNodeRef);\n\n  const HandlePrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <HandlePrimitive\n      type=\"button\"\n      aria-controls={columnContext.id}\n      data-disabled={isDisabled}\n      data-dragging={columnContext.isDragging ? \"\" : undefined}\n      data-slot=\"kanban-column-handle\"\n      {...columnHandleProps}\n      {...(isDisabled ? {} : columnContext.attributes)}\n      {...(isDisabled ? {} : columnContext.listeners)}\n      ref={composedRef}\n      className={cn(\n        \"select-none disabled:pointer-events-none disabled:opacity-50\",\n        context.flatCursor\n          ? \"cursor-default\"\n          : \"cursor-grab data-dragging:cursor-grabbing\",\n        className,\n      )}\n      disabled={isDisabled}\n    />\n  );\n}\n\ninterface KanbanItemContextValue {\n  id: string;\n  attributes: DraggableAttributes;\n  listeners: DraggableSyntheticListeners | undefined;\n  setActivatorNodeRef: (node: HTMLElement | null) => void;\n  isDragging?: boolean;\n  disabled?: boolean;\n}\n\nconst KanbanItemContext = React.createContext<KanbanItemContextValue | null>(\n  null,\n);\n\nfunction useKanbanItemContext(consumerName: string) {\n  const context = React.useContext(KanbanItemContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\n  }\n  return context;\n}\n\ninterface KanbanItemProps extends React.ComponentProps<\"div\"> {\n  value: UniqueIdentifier;\n  asHandle?: boolean;\n  asChild?: boolean;\n  disabled?: boolean;\n}\n\nfunction KanbanItem(props: KanbanItemProps) {\n  const {\n    value,\n    style,\n    asHandle,\n    asChild,\n    disabled,\n    className,\n    ref,\n    ...itemProps\n  } = props;\n\n  const id = React.useId();\n  const context = useKanbanContext(ITEM_NAME);\n  const inBoard = React.useContext(KanbanBoardContext);\n  const inOverlay = React.useContext(KanbanOverlayContext);\n\n  if (!inBoard && !inOverlay) {\n    throw new Error(`\\`${ITEM_NAME}\\` must be used within \\`${BOARD_NAME}\\``);\n  }\n\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    setActivatorNodeRef,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({ id: value, disabled });\n\n  if (value === \"\") {\n    throw new Error(`\\`${ITEM_NAME}\\` value cannot be an empty string`);\n  }\n\n  // Kept stable so React doesn't detach and reattach dnd-kit's node ref on\n  // every render, which retriggers droppable measuring during a drag.\n  const onNodeRef = React.useCallback(\n    (node: HTMLElement | null) => {\n      if (disabled) return;\n      setNodeRef(node);\n    },\n    [disabled, setNodeRef],\n  );\n\n  const composedRef = useComposedRefs(ref, onNodeRef);\n\n  const composedStyle = React.useMemo<React.CSSProperties>(() => {\n    return {\n      transform: CSS.Transform.toString(transform),\n      transition,\n      ...style,\n    };\n  }, [transform, transition, style]);\n\n  const itemContext = React.useMemo<KanbanItemContextValue>(\n    () => ({\n      id,\n      attributes,\n      listeners,\n      setActivatorNodeRef,\n      isDragging,\n      disabled,\n    }),\n    [id, attributes, listeners, setActivatorNodeRef, isDragging, disabled],\n  );\n\n  const ItemPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <KanbanItemContext.Provider value={itemContext}>\n      <ItemPrimitive\n        id={id}\n        data-disabled={disabled}\n        data-dragging={isDragging ? \"\" : undefined}\n        data-slot=\"kanban-item\"\n        {...itemProps}\n        {...(asHandle && !disabled ? attributes : {})}\n        {...(asHandle && !disabled ? listeners : {})}\n        ref={composedRef}\n        style={composedStyle}\n        className={cn(\n          \"focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-hidden\",\n          {\n            \"touch-none select-none\": asHandle,\n            \"cursor-default\": context.flatCursor,\n            \"data-dragging:cursor-grabbing\": !context.flatCursor,\n            \"cursor-grab\": !isDragging && asHandle && !context.flatCursor,\n            \"opacity-50\": isDragging,\n            \"pointer-events-none opacity-50\": disabled,\n          },\n          className,\n        )}\n      />\n    </KanbanItemContext.Provider>\n  );\n}\n\ninterface KanbanItemHandleProps extends React.ComponentProps<\"button\"> {\n  asChild?: boolean;\n}\n\nfunction KanbanItemHandle(props: KanbanItemHandleProps) {\n  const { asChild, disabled, className, ref, ...itemHandleProps } = props;\n\n  const context = useKanbanContext(ITEM_HANDLE_NAME);\n  const itemContext = useKanbanItemContext(ITEM_HANDLE_NAME);\n\n  const isDisabled = disabled ?? itemContext.disabled;\n\n  const { setActivatorNodeRef } = itemContext;\n\n  const onActivatorNodeRef = React.useCallback(\n    (node: HTMLElement | null) => {\n      if (isDisabled) return;\n      setActivatorNodeRef(node);\n    },\n    [isDisabled, setActivatorNodeRef],\n  );\n\n  const composedRef = useComposedRefs(ref, onActivatorNodeRef);\n\n  const HandlePrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <HandlePrimitive\n      type=\"button\"\n      aria-controls={itemContext.id}\n      data-disabled={isDisabled}\n      data-dragging={itemContext.isDragging ? \"\" : undefined}\n      data-slot=\"kanban-item-handle\"\n      {...itemHandleProps}\n      {...(isDisabled ? {} : itemContext.attributes)}\n      {...(isDisabled ? {} : itemContext.listeners)}\n      ref={composedRef}\n      className={cn(\n        \"select-none disabled:pointer-events-none disabled:opacity-50\",\n        context.flatCursor\n          ? \"cursor-default\"\n          : \"cursor-grab data-dragging:cursor-grabbing\",\n        className,\n      )}\n      disabled={isDisabled}\n    />\n  );\n}\n\nconst KanbanOverlayContext = React.createContext(false);\n\nconst dropAnimation: DropAnimation = {\n  sideEffects: defaultDropAnimationSideEffects({\n    styles: {\n      active: {\n        opacity: \"0.4\",\n      },\n    },\n  }),\n};\n\ninterface KanbanOverlayProps extends Omit<\n  React.ComponentProps<typeof DragOverlay>,\n  \"children\"\n> {\n  container?: Element | DocumentFragment | null;\n  children?:\n    | React.ReactNode\n    | ((params: {\n        value: UniqueIdentifier;\n        variant: \"column\" | \"item\";\n      }) => React.ReactNode);\n}\n\nfunction KanbanOverlay(props: KanbanOverlayProps) {\n  const { container: containerProp, children, ...overlayProps } = props;\n\n  const context = useKanbanContext(OVERLAY_NAME);\n\n  const [mounted, setMounted] = React.useState(false);\n\n  React.useLayoutEffect(() => setMounted(true), []);\n\n  const container =\n    containerProp ?? (mounted ? globalThis.document?.body : null);\n\n  if (!container) return null;\n\n  const variant =\n    context.activeId && context.activeId in context.items ? \"column\" : \"item\";\n\n  return ReactDOM.createPortal(\n    <DragOverlay\n      dropAnimation={dropAnimation}\n      modifiers={context.modifiers}\n      className={cn(!context.flatCursor && \"cursor-grabbing\")}\n      {...overlayProps}\n    >\n      <KanbanOverlayContext.Provider value={true}>\n        {context.activeId && children\n          ? typeof children === \"function\"\n            ? children({\n                value: context.activeId,\n                variant,\n              })\n            : children\n          : null}\n      </KanbanOverlayContext.Provider>\n    </DragOverlay>,\n    container,\n  );\n}\n\nexport {\n  Kanban,\n  KanbanBoard,\n  KanbanColumn,\n  KanbanColumnHandle,\n  KanbanItem,\n  KanbanItemHandle,\n  KanbanOverlay,\n  type KanbanProps,\n};\n",
      "target": ""
    },
    {
      "path": "lib/compose-refs.ts",
      "type": "registry:lib",
      "content": "/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\nimport * as React from \"react\";\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n  if (typeof ref === \"function\") {\n    return ref(value);\n  }\n\n  if (ref !== null && ref !== undefined) {\n    ref.current = value;\n  }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  return (node) => {\n    let hasCleanup = false;\n    const cleanups = refs.map((ref) => {\n      const cleanup = setRef(ref, node);\n      if (!hasCleanup && typeof cleanup === \"function\") {\n        hasCleanup = true;\n      }\n      return cleanup;\n    });\n\n    // React <19 will log an error to the console if a callback ref returns a\n    // value. We don't use ref cleanups internally so this will only happen if a\n    // user's ref callback returns a value, which we only expect if they are\n    // using the cleanup functionality added in React 19.\n    if (hasCleanup) {\n      return () => {\n        for (let i = 0; i < cleanups.length; i++) {\n          const cleanup = cleanups[i];\n          if (typeof cleanup === \"function\") {\n            cleanup();\n          } else {\n            setRef(refs[i], null);\n          }\n        }\n      };\n    }\n  };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  // oxlint-disable-next-line react/exhaustive-deps -- we want to memoize by all values\n  return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n",
      "target": ""
    }
  ]
}