{
  "name": "sortable",
  "type": "registry:ui",
  "dependencies": [
    "@dnd-kit/core",
    "@dnd-kit/modifiers",
    "@dnd-kit/sortable",
    "@dnd-kit/utilities",
    "radix-ui"
  ],
  "files": [
    {
      "path": "ui/sortable.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport {\n  type Announcements,\n  closestCenter,\n  closestCorners,\n  defaultDropAnimationSideEffects,\n  DndContext,\n  type DndContextProps,\n  type DragEndEvent,\n  type DraggableAttributes,\n  type DraggableSyntheticListeners,\n  DragOverlay,\n  type DragStartEvent,\n  type DropAnimation,\n  KeyboardSensor,\n  MouseSensor,\n  type ScreenReaderInstructions,\n  TouchSensor,\n  type UniqueIdentifier,\n  useSensor,\n  useSensors,\n} from \"@dnd-kit/core\";\nimport {\n  restrictToHorizontalAxis,\n  restrictToParentElement,\n  restrictToVerticalAxis,\n} from \"@dnd-kit/modifiers\";\nimport {\n  arrayMove,\n  horizontalListSortingStrategy,\n  SortableContext,\n  type SortableContextProps,\n  sortableKeyboardCoordinates,\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 orientationConfig = {\n  vertical: {\n    modifiers: [restrictToVerticalAxis, restrictToParentElement],\n    strategy: verticalListSortingStrategy,\n    collisionDetection: closestCenter,\n  },\n  horizontal: {\n    modifiers: [restrictToHorizontalAxis, restrictToParentElement],\n    strategy: horizontalListSortingStrategy,\n    collisionDetection: closestCenter,\n  },\n  mixed: {\n    modifiers: [restrictToParentElement],\n    strategy: undefined,\n    collisionDetection: closestCorners,\n  },\n};\n\nconst ROOT_NAME = \"Sortable\";\nconst CONTENT_NAME = \"SortableContent\";\nconst ITEM_NAME = \"SortableItem\";\nconst ITEM_HANDLE_NAME = \"SortableItemHandle\";\nconst OVERLAY_NAME = \"SortableOverlay\";\n\ninterface SortableRootContextValue<T> {\n  id: string;\n  items: UniqueIdentifier[];\n  modifiers: DndContextProps[\"modifiers\"];\n  strategy: SortableContextProps[\"strategy\"];\n  activeId: UniqueIdentifier | null;\n  setActiveId: (id: UniqueIdentifier | null) => void;\n  getItemValue: (item: T) => UniqueIdentifier;\n  flatCursor: boolean;\n}\n\nconst SortableRootContext =\n  React.createContext<SortableRootContextValue<unknown> | null>(null);\n\nfunction useSortableContext(consumerName: string) {\n  const context = React.useContext(SortableRootContext);\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 sortable item. Required for array of objects.\n   * @example getItemValue={(item) => item.id}\n   */\n  getItemValue: (item: T) => UniqueIdentifier;\n}\n\ntype SortableProps<T> = DndContextProps &\n  (T extends object ? GetItemValue<T> : Partial<GetItemValue<T>>) & {\n    value: T[];\n    onValueChange?: (items: T[]) => void;\n    onMove?: (\n      event: DragEndEvent & { activeIndex: number; overIndex: number },\n    ) => void;\n    strategy?: SortableContextProps[\"strategy\"];\n    orientation?: \"vertical\" | \"horizontal\" | \"mixed\";\n    flatCursor?: boolean;\n  };\n\nfunction Sortable<T>(props: SortableProps<T>) {\n  const {\n    value,\n    onValueChange,\n    collisionDetection,\n    modifiers,\n    strategy,\n    onMove,\n    orientation = \"vertical\",\n    flatCursor = false,\n    getItemValue: getItemValueProp,\n    accessibility,\n    ...sortableProps\n  } = props;\n\n  const id = React.useId();\n  const [activeId, setActiveId] = React.useState<UniqueIdentifier | null>(null);\n\n  const sensors = useSensors(\n    useSensor(MouseSensor),\n    useSensor(TouchSensor),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates,\n    }),\n  );\n  const config = React.useMemo(\n    () => orientationConfig[orientation],\n    [orientation],\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 items = React.useMemo(() => {\n    return value.map((item) => getItemValue(item));\n  }, [value, getItemValue]);\n\n  const onDragStart = React.useCallback(\n    (event: DragStartEvent) => {\n      sortableProps.onDragStart?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      setActiveId(event.active.id);\n    },\n    [sortableProps.onDragStart],\n  );\n\n  const onDragEnd = React.useCallback(\n    (event: DragEndEvent) => {\n      sortableProps.onDragEnd?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      const { active, over } = event;\n      if (over && active.id !== over?.id) {\n        const activeIndex = value.findIndex(\n          (item) => getItemValue(item) === active.id,\n        );\n        const overIndex = value.findIndex(\n          (item) => getItemValue(item) === over.id,\n        );\n\n        if (onMove) {\n          onMove({ ...event, activeIndex, overIndex });\n        } else {\n          onValueChange?.(arrayMove(value, activeIndex, overIndex));\n        }\n      }\n      setActiveId(null);\n    },\n    [value, onValueChange, onMove, getItemValue, sortableProps.onDragEnd],\n  );\n\n  const onDragCancel = React.useCallback(\n    (event: DragEndEvent) => {\n      sortableProps.onDragCancel?.(event);\n\n      if (event.activatorEvent.defaultPrevented) return;\n\n      setActiveId(null);\n    },\n    [sortableProps.onDragCancel],\n  );\n\n  const announcements: Announcements = React.useMemo(\n    () => ({\n      onDragStart({ active }) {\n        const activeValue = active.id.toString();\n        return `Grabbed sortable item \"${activeValue}\". Current position is ${active.data.current?.sortable.index + 1} of ${value.length}. Use arrow keys to move, space to drop.`;\n      },\n      onDragOver({ active, over }) {\n        if (over) {\n          const overIndex = over.data.current?.sortable.index ?? 0;\n          const activeIndex = active.data.current?.sortable.index ?? 0;\n          const moveDirection = overIndex > activeIndex ? \"down\" : \"up\";\n          const activeValue = active.id.toString();\n          return `Sortable item \"${activeValue}\" moved ${moveDirection} to position ${overIndex + 1} of ${value.length}.`;\n        }\n        return \"Sortable item is no longer over a droppable area. Press escape to cancel.\";\n      },\n      onDragEnd({ active, over }) {\n        const activeValue = active.id.toString();\n        if (over) {\n          const overIndex = over.data.current?.sortable.index ?? 0;\n          return `Sortable item \"${activeValue}\" dropped at position ${overIndex + 1} of ${value.length}.`;\n        }\n        return `Sortable item \"${activeValue}\" dropped. No changes were made.`;\n      },\n      onDragCancel({ active }) {\n        const activeIndex = active.data.current?.sortable.index ?? 0;\n        const activeValue = active.id.toString();\n        return `Sorting cancelled. Sortable item \"${activeValue}\" returned to position ${activeIndex + 1} of ${value.length}.`;\n      },\n      onDragMove({ active, over }) {\n        if (over) {\n          const overIndex = over.data.current?.sortable.index ?? 0;\n          const activeIndex = active.data.current?.sortable.index ?? 0;\n          const moveDirection = overIndex > activeIndex ? \"down\" : \"up\";\n          const activeValue = active.id.toString();\n          return `Sortable item \"${activeValue}\" is moving ${moveDirection} to position ${overIndex + 1} of ${value.length}.`;\n        }\n        return \"Sortable item is no longer over a droppable area. Press escape to cancel.\";\n      },\n    }),\n    [value],\n  );\n\n  const screenReaderInstructions: ScreenReaderInstructions = React.useMemo(\n    () => ({\n      draggable: `\n        To pick up a sortable item, press space or enter.\n        While dragging, use the ${orientation === \"vertical\" ? \"up and down\" : orientation === \"horizontal\" ? \"left and right\" : \"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    [orientation],\n  );\n\n  const contextValue = React.useMemo(\n    () => ({\n      id,\n      items,\n      modifiers: modifiers ?? config.modifiers,\n      strategy: strategy ?? config.strategy,\n      activeId,\n      setActiveId,\n      getItemValue,\n      flatCursor,\n    }),\n    [\n      id,\n      items,\n      modifiers,\n      strategy,\n      config.modifiers,\n      config.strategy,\n      activeId,\n      getItemValue,\n      flatCursor,\n    ],\n  );\n\n  return (\n    <SortableRootContext.Provider\n      value={contextValue as SortableRootContextValue<unknown>}\n    >\n      <DndContext\n        collisionDetection={collisionDetection ?? config.collisionDetection}\n        modifiers={modifiers ?? config.modifiers}\n        sensors={sensors}\n        {...sortableProps}\n        id={id}\n        onDragStart={onDragStart}\n        onDragEnd={onDragEnd}\n        onDragCancel={onDragCancel}\n        accessibility={{\n          announcements,\n          screenReaderInstructions,\n          ...accessibility,\n        }}\n      />\n    </SortableRootContext.Provider>\n  );\n}\n\nconst SortableContentContext = React.createContext<boolean>(false);\n\ninterface SortableContentProps extends React.ComponentProps<\"div\"> {\n  strategy?: SortableContextProps[\"strategy\"];\n  children: React.ReactNode;\n  asChild?: boolean;\n  withoutSlot?: boolean;\n}\n\nfunction SortableContent(props: SortableContentProps) {\n  const {\n    strategy: strategyProp,\n    asChild,\n    withoutSlot,\n    children,\n    ref,\n    ...contentProps\n  } = props;\n\n  const context = useSortableContext(CONTENT_NAME);\n\n  const ContentPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <SortableContentContext.Provider value={true}>\n      <SortableContext\n        items={context.items}\n        strategy={strategyProp ?? context.strategy}\n      >\n        {withoutSlot ? (\n          children\n        ) : (\n          <ContentPrimitive\n            data-slot=\"sortable-content\"\n            {...contentProps}\n            ref={ref}\n          >\n            {children}\n          </ContentPrimitive>\n        )}\n      </SortableContext>\n    </SortableContentContext.Provider>\n  );\n}\n\ninterface SortableItemContextValue {\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 SortableItemContext =\n  React.createContext<SortableItemContextValue | null>(null);\n\nfunction useSortableItemContext(consumerName: string) {\n  const context = React.useContext(SortableItemContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\n  }\n  return context;\n}\n\ninterface SortableItemProps extends React.ComponentProps<\"div\"> {\n  value: UniqueIdentifier;\n  asHandle?: boolean;\n  asChild?: boolean;\n  disabled?: boolean;\n}\n\nfunction SortableItem(props: SortableItemProps) {\n  const {\n    value,\n    style,\n    asHandle,\n    asChild,\n    disabled,\n    className,\n    ref,\n    ...itemProps\n  } = props;\n\n  const inSortableContent = React.useContext(SortableContentContext);\n  const inSortableOverlay = React.useContext(SortableOverlayContext);\n\n  if (!inSortableContent && !inSortableOverlay) {\n    throw new Error(\n      `\\`${ITEM_NAME}\\` must be used within \\`${CONTENT_NAME}\\` or \\`${OVERLAY_NAME}\\``,\n    );\n  }\n\n  if (value === \"\") {\n    throw new Error(`\\`${ITEM_NAME}\\` value cannot be an empty string`);\n  }\n\n  const context = useSortableContext(ITEM_NAME);\n  const id = React.useId();\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    setActivatorNodeRef,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({ id: value, disabled });\n\n  const onNodeRefChange = React.useCallback(\n    (node: HTMLElement | null) => {\n      if (disabled) return;\n      setNodeRef(node);\n      if (asHandle) setActivatorNodeRef(node);\n    },\n    [disabled, asHandle, setNodeRef, setActivatorNodeRef],\n  );\n\n  const composedRef = useComposedRefs(ref, onNodeRefChange);\n\n  const composedStyle = React.useMemo<React.CSSProperties>(() => {\n    return {\n      transform: CSS.Translate.toString(transform),\n      transition,\n      ...style,\n    };\n  }, [transform, transition, style]);\n\n  const itemContext = React.useMemo<SortableItemContextValue>(\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    <SortableItemContext.Provider value={itemContext}>\n      <ItemPrimitive\n        id={id}\n        data-disabled={disabled}\n        data-dragging={isDragging ? \"\" : undefined}\n        data-slot=\"sortable-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    </SortableItemContext.Provider>\n  );\n}\n\ninterface SortableItemHandleProps extends React.ComponentProps<\"button\"> {\n  asChild?: boolean;\n}\n\nfunction SortableItemHandle(props: SortableItemHandleProps) {\n  const { asChild, disabled, className, ref, ...itemHandleProps } = props;\n\n  const context = useSortableContext(ITEM_HANDLE_NAME);\n  const itemContext = useSortableItemContext(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=\"sortable-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 SortableOverlayContext = React.createContext(false);\n\nconst dropAnimation: DropAnimation = {\n  sideEffects: defaultDropAnimationSideEffects({\n    styles: {\n      active: {\n        opacity: \"0.4\",\n      },\n    },\n  }),\n};\n\ninterface SortableOverlayProps extends Omit<\n  React.ComponentProps<typeof DragOverlay>,\n  \"children\"\n> {\n  container?: Element | DocumentFragment | null;\n  children?:\n    | ((params: { value: UniqueIdentifier }) => React.ReactNode)\n    | React.ReactNode;\n}\n\nfunction SortableOverlay(props: SortableOverlayProps) {\n  const { container: containerProp, children, ...overlayProps } = props;\n\n  const context = useSortableContext(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  return ReactDOM.createPortal(\n    <DragOverlay\n      dropAnimation={dropAnimation}\n      modifiers={context.modifiers}\n      className={cn(!context.flatCursor && \"cursor-grabbing\")}\n      {...overlayProps}\n    >\n      <SortableOverlayContext.Provider value={true}>\n        {context.activeId\n          ? typeof children === \"function\"\n            ? children({ value: context.activeId })\n            : children\n          : null}\n      </SortableOverlayContext.Provider>\n    </DragOverlay>,\n    container,\n  );\n}\n\nexport {\n  Sortable,\n  SortableContent,\n  SortableItem,\n  SortableItemHandle,\n  SortableOverlay,\n  type SortableProps,\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": ""
    }
  ]
}