# 機能設計書 62-リストウィジェット

## 概要

本ドキュメントは、Decap CMSのリストウィジェット（List Widget）の機能設計を定義する。リストウィジェットは、繰り返し可能なデータ構造をエントリ編集画面で入力・管理するためのUIコンポーネントである。

### 本機能の処理概要

リストウィジェットは、CMSエントリ内でリスト形式（配列）のデータを管理するためのウィジェットである。単純な文字列リスト、単一フィールドのリスト、複数フィールドのオブジェクトリスト、型付きリスト（MIXED）の4種類のリスト形式に対応する。

**業務上の目的・背景**：ブログ記事のタグ一覧、FAQの質問・回答セット、ギャラリーのスライド一覧など、繰り返し構造のデータはCMSコンテンツ管理において頻繁に発生する。リストウィジェットを提供することで、コンテンツ編集者が直感的に複数項目を追加・削除・並べ替えできるようにする。

**機能の利用シーン**：エントリ編集画面でフィールドタイプが`list`に設定されているフィールドに対して利用される。タグ入力、FAQ一覧、チームメンバー一覧、ギャラリースライドなど多様なユースケースに使用される。

**主要な処理内容**：
1. リスト項目の追加・削除・並べ替え（ドラッグ＆ドロップ）
2. 3種類の値タイプに応じた入力UI表示（SINGLE/MULTIPLE/MIXED）
3. 項目の折りたたみ・展開制御
4. min/maxによるリストサイズのバリデーション
5. 型付きリスト（types定義）のサポート
6. summaryテンプレートによるラベル表示
7. ネストしたオブジェクトウィジェットの埋め込み

**関連システム・外部連携**：オブジェクトウィジェット（decap-cms-widget-object）と密接に連携し、複合フィールドのリスト表示を実現する。

**権限による制御**：allow_add、allow_remove、allow_reorderの各設定でリスト操作の権限を制御可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | 既存エントリのリストフィールド編集 |
| 7 | エントリ新規作成画面 | 主画面 | 新規エントリのリストフィールド入力 |

## 機能種別

CRUD操作（リスト項目の追加・更新・削除・並べ替え）/ UI入力コンポーネント

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| field | ImmutableMap | Yes | フィールド定義（fields/field/types, collapsed, min, max等） | - |
| value | ImmutableList | No | 現在のリスト値 | デフォルトはList() |
| onChange | function | Yes | 値変更コールバック | - |
| onChangeObject | function | Yes | オブジェクト値変更コールバック | - |
| onValidateObject | function | Yes | オブジェクトバリデーションコールバック | - |
| editorControl | elementType | Yes | ネストしたエディタコントロールコンポーネント | - |
| resolveWidget | function | Yes | ウィジェット解決関数 | - |
| t | function | No | 翻訳関数 | - |

### 入力データソース

- 画面入力: 各リスト項目のフィールド入力
- フィールド定義: field/fields/typesによるリスト構造定義

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| value | ImmutableList | リスト値（各項目はMap、string、またはprimitive） |
| metadata | object | メタデータ（リレーション等で使用） |

### 出力先

- Redux Store: エントリのフィールド値として保存

## 処理フロー

### 処理シーケンス

```
1. ウィジェット初期化
   └─ getValueType()でリスト種別（SINGLE/MULTIPLE/MIXED/null）を判定
2. リスト種別がnullの場合
   └─ シンプルなテキスト入力（カンマ区切り）を表示
3. リスト種別がSINGLE/MULTIPLE/MIXEDの場合
   └─ リストコントロールUIを表示（ObjectWidgetTopBar + SortableList）
4. 項目追加
   └─ handleAdd/handleAddTypeでデフォルト値を持つ新規項目を追加
5. 項目編集
   └─ handleChangeForでObjectControlのonChangeObjectを委譲
6. 項目削除
   └─ handleRemoveでvalue.delete(index)とstate更新
7. 並べ替え
   └─ onSortEndでvalue.delete→insertとstate同期
8. バリデーション
   └─ validate()で子ウィジェット検証 + validateSize()でmin/max検証
```

### フローチャート

```mermaid
flowchart TD
    A[ListControl.render] --> B{getValueType}
    B -->|null| C[renderInput - カンマ区切りテキスト]
    B -->|SINGLE/MULTIPLE/MIXED| D[renderListControl]
    D --> E[ObjectWidgetTopBar - 追加ボタン・折りたたみ]
    D --> F[SortableList]
    F --> G[renderItem - 各項目]
    G --> H[SortableListItem]
    H --> I[StyledListItemTopBar]
    H --> J[ObjectControl - ネスト入力]
    E -->|追加| K[handleAdd/handleAddType]
    K --> L[addItem - value.push/unshift]
    I -->|削除| M[handleRemove]
    I -->|折りたたみ| N[handleItemCollapseToggle]
    F -->|ドラッグ| O[onSortEnd]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-62-01 | 値タイプ判定 | field.fieldsがあればMULTIPLE、field.fieldがあればSINGLE、field.typesがあればMIXED、いずれもなければnull（テキスト入力） | 常時 |
| BR-62-02 | リストサイズバリデーション | min/max設定によりリスト項目数の範囲を検証 | min/maxが設定されている場合 |
| BR-62-03 | 追加位置制御 | add_to_topがtrueの場合、新規項目をリスト先頭に追加 | add_to_topがtrue |
| BR-62-04 | 操作制御 | allow_add（デフォルトtrue）、allow_remove（デフォルトtrue）、allow_reorder（デフォルトtrue）で各操作の可否を制御 | 各設定による |
| BR-62-05 | 折りたたみ制御 | collapsed（デフォルトtrue）で初期状態の折りたたみを制御。minimize_collapsedでリスト全体の最小化を制御 | 各設定による |
| BR-62-06 | summaryテンプレート | summaryフィールドにテンプレート文字列を指定すると、折りたたみ時の項目ラベルをカスタマイズ可能 | summaryが定義されている場合 |
| BR-62-07 | 型付きリスト | types定義により異なるフィールド構造を持つ項目を混在させられる。typeKeyで型判定キーをカスタマイズ可能 | typesが定義されている場合 |

### 計算ロジック

- summaryテンプレートの展開: `stringTemplate.compileStringTemplate(summary, null, '', data)`で処理

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

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

該当なし（クライアントサイドのみの処理）。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | サイズバリデーションエラー | リスト項目数がmin/max範囲外 | エラーメッセージを表示 |
| - | 型不一致エラー | MIXED型でtypeKeyに対応する型が見つからない | renderErroneousTypedItemでエラーメッセージ表示 |
| - | 値型警告 | リスト値がMap以外（MULTIPLE/MIXED時） | console.warnで警告 |

### リトライ仕様

リトライ処理は行わない。

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

該当なし（Redux Storeへの値更新はアトミック操作）。

## パフォーマンス要件

- shouldComponentUpdateは常にtrueを返す（ネストウィジェットの更新を保証）
- dnd-kitのMouseSensor/TouchSensorはdistance: 4のアクティベーション制約で誤操作を防止

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

- ユーザー入力値は直接ReactコンポーネントのJSX内で使用されるため、React標準のXSS対策が適用される

## 備考

- リストウィジェットのプレビューコンポーネントはオブジェクトウィジェットのpreviewComponentを再利用している
- 各リスト項目にはuuid()で生成される一意キーが付与され、並べ替え時もキーが維持される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `packages/decap-cms-widget-list/src/schema.js` | allow_add, allow_remove, allow_reorder, collapsed, summary, min, max等のスキーマ定義 |
| 1-2 | typedListHelpers.js | `packages/decap-cms-widget-list/src/typedListHelpers.js` | TYPES_KEY, TYPE_KEY, DEFAULT_TYPE_KEYの定数定義と型解決ロジック |

**読解のコツ**: リストウィジェットにはvalueTypes（SINGLE/MULTIPLE/MIXED）の概念があり、これがUI表示と値の扱いを決定する。field.field（単数）、field.fields（複数）、field.types（型付き）の違いを理解することが重要。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `packages/decap-cms-widget-list/src/index.js` | プレビューはDecapCmsWidgetObject.previewComponentを再利用している点に注目 |

**主要処理フロー**:
1. **1行目**: DecapCmsWidgetObjectからpreviewComponentをインポート
2. **3行目**: ListControlを制御コンポーネントとしてインポート
3. **8-16行目**: Widget関数でname='list'として登録

#### Step 3: 制御コンポーネント（ListControl）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ListControl.js | `packages/decap-cms-widget-list/src/ListControl.js` | 820行の大規模コンポーネント。valueTypesの分岐を中心に理解する |

**主要処理フロー**:
- **142-146行目**: valueTypes定義（SINGLE/MULTIPLE/MIXED）
- **244-255行目**: getValueType() - field定義からリスト種別を判定
- **295-303行目**: handleAdd - 新規項目追加
- **352-370行目**: addItem - value.push/unshiftで項目を追加
- **422-438行目**: handleChangeFor - インデックスベースの値変更
- **440-498行目**: handleRemove - 項目削除とエラークリア
- **572-594行目**: onSortEnd - ドラッグ＆ドロップ並べ替え
- **381-413行目**: validate/validateSize - バリデーション
- **534-570行目**: objectLabel - summaryテンプレート展開
- **748-793行目**: renderListControl - メインUI描画
- **812-818行目**: render - valueTypeによる分岐（リスト/テキスト入力）

#### Step 4: 型付きリストヘルパーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | typedListHelpers.js | `packages/decap-cms-widget-list/src/typedListHelpers.js` | MIXED型リストの型解決ロジック |

**主要処理フロー**:
- **5-10行目**: getTypedFieldForValue - 値のtypeKeyから対応する型定義を検索
- **21-23行目**: resolveFieldKeyType - typeKeyの解決（デフォルト'type'）
- **25-35行目**: getErrorMessageForTypedFieldAndValue - 型不一致時のエラーメッセージ生成

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

```
ListControl (ListControl.js)
    │
    ├─ render()
    │   ├─ getValueType() → SINGLE/MULTIPLE/MIXED/null
    │   ├─ renderInput() → テキスト入力（null時）
    │   └─ renderListControl()
    │       ├─ ObjectWidgetTopBar → handleAdd/handleAddType
    │       └─ SortableList → renderItem()
    │           ├─ SortableListItem
    │           ├─ StyledListItemTopBar → handleRemove/handleItemCollapseToggle
    │           ├─ NestedObjectLabel → objectLabel()
    │           └─ ObjectControl → handleChangeFor(index)
    │
    ├─ validate()
    │   ├─ childRefs[].validate() → 子ウィジェットバリデーション
    │   └─ validateSize() → min/maxチェック
    │
    └─ typedListHelpers
        ├─ getTypedFieldForValue() → 型解決
        ├─ resolveFieldKeyType() → typeKey解決
        └─ getErrorMessageForTypedFieldAndValue() → エラーメッセージ
```

### データフロー図

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

field定義 ────────▶ getValueType() ──▶ UI表示分岐
  (field/fields/types)      │
                           ▼
ユーザー操作 ──────▶ handleAdd/handleChangeFor ──▶ onChange(ImmutableList)
  (追加/編集/削除)    handleRemove/onSortEnd            │
                                                        ▼
                                                 Redux Store (value)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `packages/decap-cms-widget-list/src/index.js` | ソース | ウィジェット登録エントリーポイント |
| ListControl.js | `packages/decap-cms-widget-list/src/ListControl.js` | ソース | リスト制御コンポーネント（メインロジック） |
| typedListHelpers.js | `packages/decap-cms-widget-list/src/typedListHelpers.js` | ソース | 型付きリストヘルパー関数群 |
| schema.js | `packages/decap-cms-widget-list/src/schema.js` | ソース | ウィジェットスキーマ定義 |
| ObjectControl.js | `packages/decap-cms-widget-object/src/ObjectControl.js` | ソース | リスト項目のネスト入力に使用 |
| ObjectPreview.js | `packages/decap-cms-widget-object/src/ObjectPreview.js` | ソース | プレビューコンポーネント（再利用） |
