npm package: @hermes-solution/baekdu-dnd
Description: Lightweight, headless React tree drag-and-drop library with before/inside/after positioning. Zero CSS, full control.
Author: ISO 42001 ALM Team
Latest version: 0.1.0

# @aims/baekdu-dnd

> Lightweight, headless React tree drag-and-drop library with before/inside/after positioning. Zero CSS, full control.

## ✨ Features

- 🎯 **Headless** - Complete control over UI and styling
- 🎨 **Zero CSS** - Bring your own styles (Tailwind, CSS Modules, CSS-in-JS)
- 📍 **Precise Positioning** - Drop before/inside/after with visual indicators
- 🪶 **Lightweight** - No dependencies, native HTML5 Drag and Drop API
- 🎭 **Fully Typed** - Written in TypeScript with full type safety
- 🌲 **Tree Optimized** - Built specifically for hierarchical data structures

## 📦 Installation

```bash
npm install @aims/baekdu-dnd
```

## 🚀 Quick Start

The minimal import is now just the provider, item, and collection hook:

```tsx
import {TreeDndProvider, TreeItem, useTreeCollection} from '@aims/baekdu-dnd';

function FileExplorer({initialTree}) {
  const {flattened, handleDrop} = useTreeCollection(initialTree);

  return (
    <TreeDndProvider onDrop={handleDrop}>
      {flattened.map((item) => (
        <TreeItem key={item.id} id={item.id} parentId={item.parentId} depth={item.depth} index={item.index} value={item.node}>
          {({draggableProps, renderIndicator}) => (
            <div {...draggableProps}>
              {renderIndicator()}
              {item.node.name}
            </div>
          )}
        </TreeItem>
      ))}
    </TreeDndProvider>
  );
}
```

`flattened` already contains `depth`, `parentId`, `index`, and a stable `path`, so you can focus on rendering only.
Forward the `canDrop` handler returned by the hook to `TreeDndProvider` if you want the indicator to disappear immediately for disallowed gaps.

> `renderIndicator()` returns the built-in drop indicator. Omit it if you plan to render your own visuals based on `dropPosition`.
> `draggableProps` already includes `ref`, `onDragEnter`, and all necessary drag handlers—just spread it onto your focusable element.

> `renderIndicator()` returns the built-in drop indicator. Omit it if you plan to render your own visuals based on `dropPosition`.
> `draggableProps` already includes `ref`, `onDragEnter`, and all necessary drag handlers—just spread it onto your focusable element.

### Drop Event Payload

`onDrop` receives:
- `draggedId` – the id of the item being moved
- `targetId` – canonical drop target (after/before deduped)
- When you drop after the final root, `targetId` will be the virtual tail id (`__tree-dnd-root-tail__`); rely on `slot.parentId` (null) and `slot.index` for ordering there.
- `dropPosition` – `'before'`, `'inside'`, or `'after'`
- `slot` – stable slot metadata (`key`, `parentId`, `index`) for ordering
- `originalTargetId` – raw DOM target id that fired the drop
- `indicator` – full indicator state (rect, depth, `visualMode`, etc.)
- `draggedData` – metadata you registered with the dragged item (via `TreeItem`)
- `targetData` – metadata you registered with the canonical target

`indicator.visualMode === 'subtree'` means you are aiming at the gap after a branch; the default indicator now wraps the entire subtree in that case so users can see the full scope of the move. Use `indicator.visualRect` if you need screen-space measurements to draw custom overlays.

> The provider renders a tiny, invisible tail element (`data-tree-dnd-tail=""`) after your tree so users can drop below the final node. Feel free to restyle it (height, margin) to match your layout.

Use `slot.key` plus `slot.parentId` to decide where in your tree to insert without worrying about the “after vs before” overlap on adjacent nodes.

### Preventing Illegal Drops

Pass a `canDrop` guard to `TreeDndProvider` to veto specific slots before the user releases the mouse:

```tsx
<TreeDndProvider
  canDrop={({dropPosition, targetData}) => {
    if (dropPosition === 'inside' && targetData?.meta?.lockChildren) {
      return false; // indicator disappears immediately
    }
    return true;
  }}
/>
```

`canDrop` receives the same payload as `onDrop`, plus the metadata you registered via `TreeItem`. Returning `false` clears the indicator so the drop never fires.

### Built-in Motion

Nodes animate into their new positions by default using a lightweight FLIP transition. To tweak or disable the behaviour, pass `itemAnimations` to the provider:

```tsx
<TreeDndProvider itemAnimations={{ duration: 220, easing: 'cubic-bezier(.19,1,.22,1)' }}>
  {/* tree */}
</TreeDndProvider>

<TreeDndProvider itemAnimations={false}>
  {/* disable motion entirely */}
</TreeDndProvider>
```

Inside the `TreeItem` render prop you also receive the resolved `animation` config, making it easy to coordinate your own CSS transitions.

### Tree Utilities

Need simple path-based helpers while prototyping? The package now exports reusable utilities such as `cloneTree`, `flattenTree`, `removeNodeAtPath`, `insertInside`, `insertSibling`, `adjustTargetPathAfterRemoval`, and `reorderTreeByPath`, plus the shared `TreePath` and `FlattenedTreeItem` types. These are the same building blocks used by the dev playground, so you can reuse the exact reorder logic inside your app without re-implementing tree math.

## 📖 Core Concepts

### Headless Architecture

**What the library provides:**
- Drag and drop state management
- Drop position calculation (before/inside/after)
- Event handling and coordination

**What you control:**
- All styling (CSS, Tailwind, CSS-in-JS, etc.)
- UI rendering and animations

### Drop Positioning

```
┌─────────────────┐
│   BEFORE (25%)  │ ← Drop above
├─────────────────┤
│   INSIDE (50%)  │ ← Drop inside (make child)
├─────────────────┤
│   AFTER (25%)   │ ← Drop below
└─────────────────┘
```

Customize ratios:
```tsx
<TreeDndProvider dropZoneRatios={{ before: 20, inside: 60, after: 20 }} />
```

## 📄 License

MIT © ISO 42001 ALM Team