# 機能設計書 74-ドラッグ＆ドロップ

## 概要

本ドキュメントは、Decap CMSで使用されるドラッグ＆ドロップ機能の設計を記述する。

### 本機能の処理概要

本機能は、react-dndライブラリをラップしたドラッグ＆ドロップ用のヘルパーコンポーネント群を提供する。リストウィジェットでのアイテム並べ替えやワークフローボードでのエントリカードのステータス変更に使用される。

**業務上の目的・背景**：CMSのコンテンツ管理において、リスト形式のデータの並べ替えやワークフローのステータス変更は頻繁に行われる操作である。ドラッグ＆ドロップによる直感的なUIを提供することで、ユーザーの操作効率を大幅に向上させる。

**機能の利用シーン**：リストウィジェットでのアイテム並べ替え、ワークフロー画面でのエントリカードのDraft/Pending Review/Pending Publishカラム間の移動に使用される。

**主要な処理内容**：
1. **DragSource**: react-dndのDragSourceをラップ。ドラッグ可能なコンポーネントを作成し、ドラッグ開始時にownPropsをドラッグアイテムデータとして渡す
2. **DropTarget**: react-dndのDropTargetをラップ。ドロップ可能な領域を作成し、ドロップ時にonDropコールバックを実行。ホバー状態（isHovered）も提供
3. **HTML5DragDrop**: react-dndのDndProviderをHTML5バックエンドで初期化するHOC（Higher-Order Component）。アプリケーションのルートレベルで使用

**関連システム・外部連携**：react-dnd（ドラッグ＆ドロップライブラリ）、react-dnd-html5-backend（HTML5 Drag and Drop APIバックエンド）と連携する。

**権限による制御**：ドラッグ＆ドロップ自体には権限制御はない。利用側のコンポーネントでallowReorder等のフラグにより制御される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 参照画面 | リストウィジェットでのアイテム並べ替え（DragSource/DropTarget） |
| 8 | ワークフロー画面 | 主画面 | ワークフローボード上でのエントリカードのドラッグ＆ドロップによるステータス変更 |

## 機能種別

UIインタラクション / ドラッグ＆ドロップ

## 入力仕様

### 入力パラメータ

**DragSource**

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| namespace | any | Yes | ドラッグ＆ドロップの名前空間（DragSource/DropTarget間で一致必要） | - |
| children | function | Yes | render prop関数。connectDragComponentを引数に受け取る | - |
| その他のprops | any | No | beginDrag時にドラッグアイテムデータとして渡される | - |

**DropTarget**

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| onDrop | function | Yes | ドロップ時のコールバック。monitor.getItem()の結果が引数 | - |
| namespace | any | Yes | ドラッグ＆ドロップの名前空間 | - |
| children | function | Yes | render prop関数。connectDropTarget, { isHovered }を引数に受け取る | - |

**HTML5DragDrop**

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| WrappedComponent | React.Component | Yes | DndProviderでラップするコンポーネント | - |

### 入力データソース

利用側のコンポーネント（リストウィジェット、ワークフローボード）からPropsとして渡される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| connectDragComponent | function | DOM要素をドラッグソースとして接続する関数 |
| connectDropTarget | function | DOM要素をドロップターゲットとして接続する関数 |
| isHovered | boolean | ドラッグアイテムがターゲット上にホバーしているか |

### 出力先

render prop経由で子コンポーネントに提供される。

## 処理フロー

### 処理シーケンス

```
1. HTML5DragDrop HOCでアプリケーションを初期化
   └─ ReactDNDProviderをHTML5Backendで設定
2. DragSourceコンポーネントの描画
   └─ render prop経由でconnectDragComponentを子に提供
3. ユーザーがドラッグ開始
   └─ beginDragでownProps（children, isDragging, connectDragComponent以外）をアイテムデータとして返す
4. DropTargetコンポーネントへのホバー
   └─ isHoveredがtrueになり、子コンポーネントがホバースタイルを適用
5. ユーザーがドロップ
   └─ DropTargetのdrop関数がonDrop(monitor.getItem())を呼び出す
```

### フローチャート

```mermaid
flowchart TD
    A[HTML5DragDrop: DndProvider初期化] --> B[DragSource描画]
    A --> C[DropTarget描画]
    B --> D[ユーザーがドラッグ開始]
    D --> E[beginDrag: ownPropsをアイテムデータとして返す]
    E --> F{DropTarget上にホバー?}
    F -->|Yes| G[isHovered = true]
    F -->|No| H[ドラッグ継続]
    G --> I{ドロップ実行?}
    H --> F
    I -->|Yes| J[onDrop callback実行]
    I -->|No| F
    J --> K[利用側で状態更新]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-74-01 | 名前空間一致 | DragSourceとDropTargetは同じnamespaceを持つもの同士でのみドラッグ＆ドロップが可能 | 常時 |
| BR-74-02 | ドラッグデータ | beginDrag時にchildren, isDragging, connectDragComponent以外の全propsがドラッグアイテムデータとして渡される | ドラッグ開始時 |
| BR-74-03 | ドロップコールバック | ドロップ時にmonitor.getItem()の結果がonDropに渡される | ドロップ時 |
| BR-74-04 | ホバー状態 | DropTargetはmonitor.isOver()でホバー状態を検知し、childrenのisHoveredパラメータとして提供する | ホバー時 |

### 計算ロジック

特になし。

## データベース操作仕様

### 操作別データベース影響一覧

本機能はUIインタラクション層であり、直接的なデータベース操作は行わない。onDropコールバック経由で利用側がReduxアクションをディスパッチする。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | PropTypes警告 | namespace, children, onDropが未指定の場合 | コンソール警告を出力 |

### リトライ仕様

リトライは不要（同期的なUI操作のみ）。

## トランザクション仕様

トランザクションは存在しない。

## パフォーマンス要件

- ドラッグ操作中のフレームレートは60fpsを維持すること（HTML5 Drag and Drop APIにより実現）
- ホバー状態の更新は即座にUIに反映されること

## セキュリティ考慮事項

- ドラッグ＆ドロップはブラウザ内の操作に限定されるため、セキュリティリスクは低い

## 備考

- react-dndのDragSource/DropTarget APIはlegacy APIであり、最新版ではuseDrag/useDropフックが推奨されている
- HTML5DragDropはクラスコンポーネントのHOCパターンで実装されている
- DragSourceとDropTargetはrender propパターンを使用し、childrenに関数を渡す

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

ドラッグ＆ドロップの3つのコンポーネントの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | DragDrop.js | `packages/decap-cms-core/src/components/UI/DragDrop.js` | DragSource, DropTarget, HTML5DragDropの3つのエクスポートを理解する |

**読解のコツ**: react-dndのlegacy API（DragSource, DropTarget HOC）を使用している。各HOCは (型, spec, collect) の3引数パターンで呼ばれる。

#### Step 2: DragSourceを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | DragDrop.js | `packages/decap-cms-core/src/components/UI/DragDrop.js` | DragSourceコンポーネントのbeginDragロジック |

**主要処理フロー**:
- **10行目**: namespaceをpropsから分離。残りはDragComponentのpropsとなる
- **11-18行目**: ReactDNDDragSource HOCの生成
  - **15-17行目**: beginDrag - children, isDragging, connectDragComponentを除外した残りのpropsをドラッグデータとして返す
  - **20-22行目**: collect - connect.dragSource()をconnectDragComponentとして提供
- **23行目**: DragComponentの子コンポーネント描画（children(connectDragComponent)）
- **25行目**: React.createElementでDragComponentを生成

#### Step 3: DropTargetを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | DragDrop.js | `packages/decap-cms-core/src/components/UI/DragDrop.js` | DropTargetコンポーネントのdropとisHoveredロジック |

**主要処理フロー**:
- **33行目**: onDrop, namespaceをpropsから分離
- **34-44行目**: ReactDNDDropTarget HOCの生成
  - **37-38行目**: drop - onDrop(monitor.getItem())でドロップ時コールバック実行
  - **41-43行目**: collect - connectDropTargetとisHovered（monitor.isOver()）を提供
- **45行目**: DropComponentの子コンポーネント描画（children(connectDropTarget, { isHovered })）

#### Step 4: HTML5DragDrop HOCを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | DragDrop.js | `packages/decap-cms-core/src/components/UI/DragDrop.js` | HTML5DragDrop HOCのDndProvider初期化 |

**主要処理フロー**:
- **56-66行目**: HTML5DragDrop - WrappedComponentをReactDNDProviderでラップするHOC
  - **60行目**: ReactDNDProviderにbackend={ReactDNDHTML5Backend}を設定
  - **61行目**: WrappedComponentにthis.propsを透過的に渡す

### プログラム呼び出し階層図

```
HTML5DragDrop (HOC)
    |
    +-- ReactDNDProvider (backend=HTML5Backend)
            |
            +-- WrappedComponent (Workflow, ListControl等)
                    |
                    +-- DragSource (namespace, children render prop)
                    |       +-- ReactDNDDragSource
                    |               +-- beginDrag: ownPropsをアイテムデータ化
                    |               +-- collect: connectDragComponent提供
                    |
                    +-- DropTarget (namespace, onDrop, children render prop)
                            +-- ReactDNDDropTarget
                                    +-- drop: onDrop(monitor.getItem())
                                    +-- collect: connectDropTarget, isHovered提供
```

### データフロー図

```
[入力]                          [処理]                              [出力]

ドラッグ操作開始           -->  DragSource.beginDrag            -->  ドラッグアイテムデータ
  (ownProps)                    (children等を除外)                   (monitor経由)
                                      |
ドラッグアイテム            -->  DropTarget.drop                 -->  onDrop コールバック実行
  (monitor.getItem())           (利用側のハンドラ呼び出し)
                                      |
ホバー状態                 -->  monitor.isOver()                -->  isHovered 提供
                                                                    (UIスタイル変更)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| DragDrop.js | `packages/decap-cms-core/src/components/UI/DragDrop.js` | ソース | DragSource, DropTarget, HTML5DragDrop定義 |
| index.js | `packages/decap-cms-core/src/components/UI/index.js` | ソース | UIコンポーネントのエクスポート |
| ListItemTopBar.js | `packages/decap-cms-ui-default/src/ListItemTopBar.js` | ソース | DragHandleを含むリストアイテムUI |
