# https://github.com/hello-pangea/dnd 项目说明书

生成时间：2026-06-15 01:07:26 UTC

## 目录

- [Core API: DragDropContext, Draggable, and Droppable](#page-1)
- [State Management and Internal Architecture](#page-2)
- [Sensors and Input Handling](#page-3)
- [Advanced Features: Auto-Scrolling, Accessibility, and Patterns](#page-4)

<a id='page-1'></a>

## Core API: DragDropContext, Draggable, and Droppable

### 相关页面

相关主题：[State Management and Internal Architecture](#page-2), [Advanced Features: Auto-Scrolling, Accessibility, and Patterns](#page-4)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/hello-pangea/dnd/blob/main/README.md)
- [src/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/index.ts)
- [src/view/drag-drop-context/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/drag-drop-context/index.ts)
- [src/view/draggable/draggable.tsx](https://github.com/hello-pangea/dnd/blob/main/src/view/draggable/draggable.tsx)
- [src/view/droppable/droppable.tsx](https://github.com/hello-pangea/dnd/blob/main/src/view/droppable/droppable.tsx)
- [src/state/get-home-location.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/get-home-location.ts)
- [src/state/droppable/is-home-of.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/droppable/is-home-of.ts)
- [src/state/droppable/should-use-placeholder.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/droppable/should-use-placeholder.ts)
- [src/state/action-creators.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/action-creators.ts)
- [src/state/middleware/drop/drop-middleware.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/middleware/drop/drop-middleware.ts)
- [stories/src/table/with-portal.tsx](https://github.com/hello-pangea/dnd/blob/main/stories/src/table/with-portal.tsx)

</details>

# Core API: DragDropContext, Draggable, and Droppable

## 概述与设计目标

`@hello-pangea/dnd` 是一个面向 React 应用的高级拖拽与放置（Drag and Drop）库，专门为「列表式」交互（垂直、水平、跨列表、嵌套列表）设计，并强调可访问性与键盘支持。该库对外暴露的核心 API 由三个组件构成——`DragDropContext`、`Droppable`、`Draggable`——它们构成嵌套的容器结构，缺一不可。

资料来源：[README.md]() 中说明 `<DragDropContext />` 用于包裹整个拖拽区域，`<Droppable />` 是可放置区域，其内部嵌套若干 `<Draggable />`。这种三层结构清晰地划分了职责：上下文负责状态与传感器注册，Droppable 负责放置区几何信息，Draggable 负责被拖拽元素本身。

---

## DragDropContext：拖拽上下文根容器

`DragDropContext` 是整个拖拽系统的根组件。它向子树注入拖拽状态机、传感器（Mouse / Touch / Keyboard）、协调器（marshal）以及样式管理器等基础设施。库的主入口在 [src/index.ts]() 中导出 `DragDropContext` 及其 props 类型 `DragDropContextProps`。

典型职责包括：

- **响应器（Responders）**：通过 `onDragStart`、`onDragUpdate`、`onDragEnd`、`onBeforeDragStart` 回调让父组件感知拖拽生命周期。
- **传感器注册**：通过 `useMouseSensor`、`useTouchSensor`、`useKeyboardSensor` 等 Hook 启用输入源。
- **跨列表协调**：内部维护统一的 Redux 风格 store，使多个 `Droppable` 之间能够正确交换位置。

资料来源：[src/view/drag-drop-context/index.ts]() 显示该组件的默认导出与 props 类型来自 `drag-drop-context` 模块。

---

## Droppable：可放置区域

`Droppable` 表示「一个可以接受被拖拽元素落入的容器」，例如看板中的一列、列表中的一组条目。库在 [src/index.ts]() 中导出 `Droppable` 与配套的 `DroppableProps`、`DroppableProvided`、`DroppableStateSnapshot` 等类型。

每个 `Droppable` 在挂载时向中心化注册表（registry）发布自身几何尺寸 `DroppableDimension`，包括 `subject`、`axis`、`container`、`viewport`、`client`、`page` 等多个 `Rect`。这些信息在 `is-home-of.ts` 中用于判断「某个 Draggable 是否属于此 Droppable 的『家乡』」：

```ts
资料来源：[src/state/droppable/is-home-of.ts]() 中：
draggable.descriptor.droppableId === destination.descriptor.id
```

`Droppable` 使用 render-prop 形式提供 `provided`，其中包含 `droppableProps`、`placeholder` 与 `innerRef`。`placeholder` 是一块占位元素，在用户拖动时填充被移出元素的空隙，使列表布局不会坍塌。`should-use-placeholder.ts` 中明确说明占位策略：

```ts
资料来源：[src/state/droppable/should-use-placeholder.ts]() 中：
whatIsDraggedOver(impact) === descriptor.droppableId
```

---

## Draggable：可拖拽元素

`Draggable` 是真正随光标移动的单元，每个实例对应一行列表项或一个卡片。库在 [src/index.ts]() 中导出 `Draggable` 与 `DraggableProps`、`DraggableProvided`、`DraggableStateSnapshot` 等类型。

每个 `Draggable` 在挂载时构造 `DraggableDescriptor`（包含 `id`、`index`、`type`、`droppableId`），并向中心化注册表发布几何尺寸。`get-home-location.ts` 定义了「起始位置」的概念：

```ts
资料来源：[src/state/get-home-location.ts]() 中：
return { index: descriptor.index, droppableId: descriptor.droppableId };
```

`Draggable` 通过 render-prop 向子组件注入 `provided`，其中包含 `draggableProps`（应用至根元素）、`dragHandleProps`（应用至拖拽手柄，可分离主体与触发区）、`innerRef`。组件还支持 `isClone` 用于克隆渲染（portal 场景），并强制约束「整个生命周期内 isClone 不可变化」以避免 hooks 规则违规。

资料来源：[src/view/draggable/draggable.tsx]() 中 `useClonePropValidation(isClone)` 调用以及随后 `if (!isClone)` 条件分支。

---

## 三者协同工作机制

下图为拖拽过程中三类组件的典型协作流程：

```mermaid
sequenceDiagram
    participant U as 用户
    participant D as Draggable
    participant DP as Droppable
    participant CTX as DragDropContext

    U->>D: 在 dragHandleProps 处按下/键入
    D->>CTX: lift(draggableId, clientSelection)
    CTX->>DP: 计算各 Droppable 的 DragImpact
    CTX-->>U: 派发 onDragStart
    U->>D: 移动光标/按键
    D->>CTX: move / moveByWindowScroll
    CTX->>DP: 重新计算 DragImpact
    DP-->>CTX: shouldUsePlaceholder?
    U->>D: 释放 / 取消键
    D->>CTX: drop(reason)
    CTX-->>U: 派发 onDragEnd(dropResult)
```

关键点：

- **状态相位**：`IDLE` → `COLLECTING` → `DRAGGING` → `DROP_PENDING` → `IDLE`，由 [src/state/middleware/drop/drop-middleware.ts]() 中的 `dropMiddleware` 处理，例如收到 `DROP` 动作但仍处于 `COLLECTING` 时会派发 `dropPending`。
- **放置结果**：`onDragEnd` 收到的 `DropResult` 形如 `{ draggableId, type, source, destination, reason }`，用户需根据此对象手动更新列表状态。

---

## 使用模式与示例

下面是最小可运行的集成示例（简化自 [stories/src/table/with-portal.tsx]() 中渲染 `Droppable` 包裹 `Draggable` 列表的模式）：

```tsx
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';

<DragDropContext onDragEnd={(result) => {
  // 根据 result.source / result.destination 更新你的状态
}}>
  <Droppable droppableId="column-1">
    {(provided) => (
      <div ref={provided.innerRef} {...provided.droppableProps}>
        {items.map((item, index) => (
          <Draggable draggableId={item.id} index={index} key={item.id}>
            {(p, snapshot) => (
              <div ref={p.innerRef} {...p.draggableProps}
                   {...p.dragHandleProps}>
                {item.content}
              </div>
            )}
          </Draggable>
        ))}
        {provided.placeholder}
      </div>
    )}
  </Droppable>
</DragDropContext>
```

资料来源：[README.md]() 中对三类组件关系的描述。

---

## 常见失败模式

1. **缺少 `placeholder`**：若 `Droppable` 渲染时忘记 `provided.placeholder`，列表在拖拽时会出现「跳行」或高度坍塌。
2. **缺少 `innerRef`**：未把 `provided.innerRef` 挂到外层 DOM 会导致库无法测量容器尺寸，进而无法正确计算落点。
3. **父组件未响应 `onDragEnd`**：库的拖拽是「无状态」的，必须由消费者在 `onDragEnd` 中同步修改数据，否则视觉位置与真实顺序不一致。
4. **跨 React 版本兼容问题**：v17.0.0 起不再支持 React 16/17；v16.2.0 起在 React 18 下使用 `useId` 实现 SSR 水合，无需再调用 `resetServerContext`。

---

## See Also

- [Sensors: Mouse / Touch / Keyboard](/docs/sensors/keyboard.md)
- [Responders 指南](/docs/guides/responders.md)
- [Combining `<Draggable />`s](/docs/guides/combining.md)
- [Reparenting your `<Draggable />`](/docs/guides/reparenting.md)
- [Common setup issues](/docs/guides/common-setup-issues.md)

---

<a id='page-2'></a>

## State Management and Internal Architecture

### 相关页面

相关主题：[Core API: DragDropContext, Draggable, and Droppable](#page-1), [Sensors and Input Handling](#page-3)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/index.ts)
- [src/types.ts](https://github.com/hello-pangea/dnd/blob/main/src/types.ts)
- [src/state/action-creators.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/action-creators.ts)
- [src/state/middleware/lift.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/middleware/lift.ts)
- [src/state/middleware/auto-scroll.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/middleware/auto-scroll.ts)
- [src/state/auto-scroller/auto-scroller-types.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/auto-scroller/auto-scroller-types.ts)
- [src/state/dimension-marshal/dimension-marshal-types.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/dimension-marshal/dimension-marshal-types.ts)
- [src/view/drag-drop-context/check-doctype.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/drag-drop-context/check-doctype.ts)
- [src/view/key-codes.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/key-codes.ts)
- [src/invariant.ts](https://github.com/hello-pangea/dnd/blob/main/src/invariant.ts)
- [package.json](https://github.com/hello-pangea/dnd/blob/main/package.json)
</details>

# State Management and Internal Architecture

## 概述与设计目标

`@hello-pangea/dnd` 内部以 Redux 作为核心状态引擎，借助 `react-redux` v9 将状态分发给拖拽上下文中的子组件（`Droppable` / `Draggable`）。从 `package.json` 可看到运行依赖中显式声明了 `react-redux ^9.2.0` 与 `redux ^5.0.1`（v17 起升级到 redux v5、react-redux v9，是当前主版本），这意味着整个拖拽生命周期都由单一可预测的状态机驱动。

状态层主要负责四件事：
1. 集中维护拖拽阶段（`IDLE` / `DRAGGING` / `DROP_ANIMATING` 等）；
2. 调度传感器（Sensors）上报的原始输入为统一动作（Actions）；
3. 通过中间件链（Middleware）触发副作用，例如自动滚动与尺寸采集；
4. 把派生数据回传给消费者（responders），供 `onDragStart` / `onDragUpdate` / `onDragEnd` 使用。

公共类型与组件入口在 [src/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/index.ts) 中导出，其中 `Announce`、`DragStart`、`DragUpdate`、`DropResult`、`MovementMode`、`PreDragActions`、`SnapDragActions` 等类型决定了与用户代码之间的契约面。

## 状态管理架构

整体架构遵循「View → Sensor → Store → Middleware → View」的闭环：传感器把指针或键盘事件翻译为动作，动作经过中间件预处理后被 reducer 处理，中间件再据此驱动 `AutoScroller`、`DimensionMarshal` 等子系统。

```mermaid
flowchart LR
    A[View 组件<br/>Draggable / Droppable] -->|用户输入| B[Sensor<br/>mouse / touch / keyboard]
    B -->|Lift / Drop / Publish| C[Redux Store]
    C --> D[lift 中间件]
    C --> E[auto-scroll 中间件]
    C --> F[focus 中间件]
    D --> G[DimensionMarshal<br/>采集尺寸]
    E --> H[AutoScroller<br/>自动滚动]
    F --> I[DOM 焦点恢复]
    G --> C
    H --> C
    C -->|订阅| A
```

核心动作类型在 [src/state/action-creators.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/action-creators.ts) 中定义，包括 `BeforeInitialCaptureAction`、`LiftAction`、`InitialPublishAction`、`PublishWhileDraggingAction`、`DropAction`、`DropPendingAction`、`DropCompleteAction`、`DropAnimationFinishedAction` 等。其中 `cancel()` 是 `drop({ reason: 'CANCEL' })` 的语法糖，对应 `DropReason = 'DROP' | 'CANCEL'`（见 [src/types.ts](https://github.com/hello-pangea/dnd/blob/main/src/types.ts)）。

## 核心中间件

### lift 中间件

`lift` 中间件位于 [src/state/middleware/lift.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/middleware/lift.ts)，负责把 `LIFT` 动作转化为可发布的拖拽状态。其关键步骤包括：
- 若当前处于 `DROP_ANIMATING`，先 dispatch `completeDrop` 以收尾上一次拖拽；
- 使用 `invariant` 断言新阶段为 `IDLE`（来自 [src/invariant.ts](https://github.com/hello-pangea/dnd/blob/main/src/invariant.ts) 的 `RbdInvariant` 错误类在生产环境会剥除消息）；
- `dispatch(flush())` 清空占位符；
- `dispatch(beforeInitialCapture(...))` 触发 `onBeforeCapture` 回调；
- 最终根据 `movementMode` 决定是否立即发布（`SNAP` 模式 → `shouldPublishImmediately: true`）。

### auto-scroll 中间件

`auto-scroll` 中间件（[src/state/middleware/auto-scroll.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/middleware/auto-scroll.ts)）通过 `guard(action, 'DROP_COMPLETE' | 'DROP_ANIMATE' | 'FLUSH')` 终止自动滚动，并在 `INITIAL_PUBLISH` 时启动自动滚动器。它直接持有 `AutoScroller` 实例，并在每次 reducer 更新后调用 `autoScroller.scroll(store.getState())`，与状态机保持严格同步。`AutoScroller` 接口定义在 [src/state/auto-scroller/auto-scroller-types.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/auto-scroller/auto-scroller-types.ts)，仅暴露 `start(state: DraggingState)`、`stop()`、`scroll(state: State)` 三个方法。

### 焦点与 DOM 协调

键盘传感器依赖 [src/view/key-codes.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/key-codes.ts) 中定义的 `tab`/`enter`/`space`/`escape`/`arrowLeft` 等常量，把按键翻译为方向指令。焦点恢复则由独立的 focus 中间件负责，并通过 [src/view/use-sensor-marshal/closest.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/closest.ts) 中的 `closest()` 工具向上回溯 DOM，确保拖拽手柄始终可定位。

## 拖拽阶段与移动模式

状态机把一次拖拽拆分为四个阶段：`IDLE` → `DRAGGING` → `DROP_PENDING` → `DROP_ANIMATING` → `IDLE`。`MovementMode` 在 [src/types.ts](https://github.com/hello-pangea/dnd/blob/main/src/types.ts) 中定义为 `'FLUID' | 'SNAP'`：
- **FLUID**：由高粒度输入（鼠标、触摸）驱动，位置随指针实时更新；
- **SNAP**：由离散命令（键盘空格、回车）驱动，需要在收到指令后才发布一次。

进入 `LIFT` 时会同时携带 `clientSelection` 与 `movementMode`，`lift` 中间件据此决定立即发布还是等待后续 `PUBLISH_WHILE_DRAGGING`。`DimensionMarshal` 通过 [src/state/dimension-marshal/dimension-marshal-types.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/dimension-marshal/dimension-marshal-types.ts) 中描述的 `Callbacks` 把 `collectionStarting`、`publishWhileDragging`、`updateDroppableScroll`、`updateDroppableIsEnabled`、`updateDroppableIsCombineEnabled` 等动作回送至 store。

## 错误处理与初始化守护

库内部对异常路径做了显式守护。`invariant(condition, message)` 在开发环境抛出带消息的 `RbdInvariant`，在生产环境仅保留前缀以避免泄漏。`DragDropContext` 在挂载时调用 [src/view/drag-drop-context/check-doctype.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/drag-drop-context/check-doctype.ts) 检查 `document.doctype` 是否为 HTML5，缺失或带 `publicId` 时发出 `warning`，这关系到后续尺寸测量与定位算法的一致性。

## See Also

- Sensors 与键盘协议：[src/view/key-codes.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/key-codes.ts)
- 公开 API 契约：[src/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/index.ts)
- 升级到 redux v5 / react-redux v9 的变更说明：[Release 17.0.0](https://github.com/hello-pangea/dnd/releases/tag/v17.0.0)
- 自定义 `AutoScroller` 配置：[Release 16.1.0](https://github.com/hello-pangea/dnd/releases/tag/v16.1.0)

---

<a id='page-3'></a>

## Sensors and Input Handling

### 相关页面

相关主题：[Core API: DragDropContext, Draggable, and Droppable](#page-1), [Advanced Features: Auto-Scrolling, Accessibility, and Patterns](#page-4)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/view/use-sensor-marshal/use-sensor-marshal.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/use-sensor-marshal.ts)
- [src/view/use-sensor-marshal/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/index.ts)
- [src/view/use-sensor-marshal/sensors/use-mouse-sensor.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/sensors/use-mouse-sensor.ts)
- [src/view/use-sensor-marshal/sensors/use-keyboard-sensor.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/sensors/use-keyboard-sensor.ts)
- [src/view/use-sensor-marshal/sensors/use-touch-sensor.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/sensors/use-touch-sensor.ts)
- [src/view/use-sensor-marshal/sensors/util/prevent-standard-key-events.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/sensors/util/prevent-standard-key-events.ts)
- [src/view/use-sensor-marshal/closest.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/closest.ts)
- [src/view/use-sensor-marshal/use-validate-sensor-hooks.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/use-validate-sensor-hooks.ts)
- [src/view/key-codes.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/key-codes.ts)
- [src/view/drag-drop-context/drag-drop-context.tsx](https://github.com/hello-pangea/dnd/blob/main/src/view/drag-drop-context/drag-drop-context.tsx)
- [src/view/drag-drop-context/check-doctype.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/drag-drop-context/check-doctype.ts)
- [src/view/drag-drop-context/check-react-version.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/drag-drop-context/check-react-version.ts)
- [src/state/middleware/lift.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/middleware/lift.ts)
- [src/state/action-creators.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/action-creators.ts)
- [src/invariant.ts](https://github.com/hello-pangea/dnd/blob/main/src/invariant.ts)
- [src/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/index.ts)
- [README.md](https://github.com/hello-pangea/dnd/blob/main/README.md)
- [package.json](https://github.com/hello-pangea/dnd/blob/main/package.json)
</details>

# Sensors and Input Handling

## 概述

`@hello-pangea/dnd` 将鼠标、键盘、触屏等不同输入设备统一抽象为一组**传感器（Sensors）**，由它们负责把浏览器事件翻译成对拖放状态机的指令。这一层抽象既保证了多设备交互的一致性，也允许通过自定义传感器接入任意输入源（例如游戏手柄或脚本化测试）。资料来源：[README.md]()

`<DragDropContext />` 默认启用鼠标、键盘、触屏三类传感器；通过 `enableDefaultSensors` 可以禁用默认集合，再以 `sensors` 属性完全接管。资料来源：[src/view/drag-drop-context/drag-drop-context.tsx]()

## 架构与组件

整个传感器子系统围绕 `useSensorMarshal` 协调器构建。它统一管理：动作锁（action lock）、传感器生命周期、当前拖放阶段（`LockPhase` 取值为 `PRE_DRAG` / `DRAGGING` / `COMPLETED`），并在阶段不匹配时通过 `isActive` 帮助函数决定是否触发开发模式告警。资料来源：[src/view/use-sensor-marshal/use-sensor-marshal.ts]()

```mermaid
flowchart TB
    DOM[DOM 事件源] --> Marshal[useSensorMarshal<br/>协调器与动作锁]
    Marshal --> Mouse[useMouseSensor]
    Marshal --> Touch[useTouchSensor]
    Marshal --> Kbd[useKeyboardSensor]
    Kbd --> KC[key-codes.ts<br/>按键常量]
    Mouse --> Lift[lift 中间件]
    Kbd --> Lift
    Touch --> Lift
    Lift --> Store[Redux Store<br/>状态机]
    Marshal -.校验.-> Validate[useValidateSensorHooks]
```

三个传感器 Hook 都从 `src/view/use-sensor-marshal/index.ts` 统一导出，并在 `src/index.ts` 中再次对外暴露，因此使用者可以单独 import 它们并自行组合。资料来源：[src/view/use-sensor-marshal/index.ts](), [src/index.ts]()

## 键盘传感器详解

键盘传感器是整套交互中职责最明确的一类。所有按键码集中在 `src/view/key-codes.ts`，包括 `tab`（9）、`enter`（13）、`escape`（27）、`space`（32）、`pageUp` / `pageDown`、`end` / `home` 以及四个方向键，便于在不同传感器间复用同一份常量。资料来源：[src/view/key-codes.ts]()

`useKeyboardSensor` 在 `keydown` 回调里把这些按键映射到拖放动作：`space` 触发落放（`actions.drop()`），`escape` 触发取消（`actions.cancel()`），`pageUp` / `pageDown` / `home` / `end` 则被列入 `scrollJumpKeys`，由传感器触发滚动跳转逻辑。资料来源：[src/view/use-sensor-marshal/sensors/use-keyboard-sensor.ts]()

为了避免 `space` 引起页面滚动或 `tab` 引发焦点跳动，传感器在拖动期间会通过 `preventStandardKeyEvents` 阻止浏览器默认行为，并在拖动开始时绑定、在 `stop()` 时解绑全部键盘事件。资料来源：[src/view/use-sensor-marshal/sensors/util/prevent-standard-key-events.ts]()

## 输入事件绑定与校验

协调器在事件回调中需要把指针或焦点位置映射到具体的 `<Draggable />`。`findClosestDraggableIdFromEvent` 借助 `closest` ponyfill 在 DOM 树中向上回溯；该 ponyfill 兼容 IE11 的 `msMatchesSelector` / `webkitMatchesSelector` 命名差异。资料来源：[src/view/use-sensor-marshal/closest.ts]()

为了避免误触发，`isEventInInteractiveElement` 会判断事件目标是否位于按钮、链接等可交互元素中；当标签页切换到后台时，协调器还会通过 `supportedPageVisibilityEventName` 注册可见性事件，从而安全停止拖动。资料来源：[src/view/use-sensor-marshal/use-sensor-marshal.ts]()

开发模式下，`useValidateSensorHooks` 利用 `invariant` 强制约束：传感器 Hook 的数量在组件挂载后必须保持不变。任何动态增删都会抛错以避免状态机错乱；非生产环境会附带完整 message，生产环境则仅抛出 `RbdInvariant` 并剥离文本。资料来源：[src/view/use-sensor-marshal/use-validate-sensor-hooks.ts](), [src/invariant.ts]()

## 启动期检查

除了传感器自身，库在挂载时还会执行两类静态校验：

- **Doctype 检查**：若文档未声明 HTML5 doctype，`check-doctype` 会通过 `warning` 发出提示，确保布局与测量结果一致。资料来源：[src/view/drag-drop-context/check-doctype.ts]()
- **React 版本检查**：`check-react-version` 通过正则解析 `react` 的 peerDependency 与运行时 `React.version`，使用 `isSatisfied` 对主/次/补丁号逐级比较。资料来源：[src/view/drag-drop-context/check-react-version.ts]()

这两个检查与 v17.0.0 起的 React 18/19 适配、`useId` 支持（v16.2.0）等历史变更紧密相关；v17.0.0 之后官方只支持 React 18+，并在 v18.0.0-beta.0 中将 Node 引擎升级到 20.17.0。资料来源：[package.json]()

## 常见失败模式

- **在 `<DragDropContext />` 中动态增减 `sensors` 数量**：会触发 `useValidateSensorHooks` 的 invariant 错误。资料来源：[src/view/use-sensor-marshal/use-validate-sensor-hooks.ts]()
- **在错误阶段发起 `LIFT` action**：`lift` 中间件要求 `getState().phase === 'IDLE'`，若处于 `DROP_ANIMATING` 必须先 `completeDrop`，否则会抛错。资料来源：[src/state/middleware/lift.ts]()
- **动作锁过期后调用处理器**：`actions.drop()` / `actions.cancel()` 等若在 lock 失效后被调用，协调器会通过 `warning` 提示丢弃旧回调。资料来源：[src/view/use-sensor-marshal/use-sensor-marshal.ts]()

## 公共入口

- 传感器 Hook：`useMouseSensor`、`useTouchSensor`、`useKeyboardSensor` 来自 `src/view/use-sensor-marshal`，并在 `src/index.ts` 中对外导出。资料来源：[src/index.ts]()
- Redux 动作：`drop` / `dropPending` / `completeDrop` 等定义于 `src/state/action-creators.ts`，由 `lift` 中间件在传感器派发 `LIFT` / `DROP` 时调用。资料来源：[src/state/action-creators.ts]()

## See Also

- `<DragDropContext />` 响应者（`onDragStart` / `onDragUpdate` / `onDragEnd` / `onBeforeDragStart`）
- 自定义传感器 API（`Sensor` / `SensorAPI`）
- 自动滚动与键盘可达性指南
- 多设备拖放模式（虚拟列表、表格、多选拖拽）

---

<a id='page-4'></a>

## Advanced Features: Auto-Scrolling, Accessibility, and Patterns

### 相关页面

相关主题：[Core API: DragDropContext, Draggable, and Droppable](#page-1), [State Management and Internal Architecture](#page-2)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/state/auto-scroller/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/auto-scroller/index.ts)
- [src/state/auto-scroller/auto-scroller-types.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/auto-scroller/auto-scroller-types.ts)
- [src/state/auto-scroller/fluid-scroller/auto-scroller-options-types.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/auto-scroller/fluid-scroller/auto-scroller-options-types.ts)
- [src/view/key-codes.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/key-codes.ts)
- [src/view/drag-drop-context/check-doctype.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/drag-drop-context/check-doctype.ts)
- [src/view/window/get-window-scroll.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/window/get-window-scroll.ts)
- [src/view/use-sensor-marshal/closest.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/closest.ts)
- [src/types.ts](https://github.com/hello-pangea/dnd/blob/main/src/types.ts)
- [src/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/index.ts)
- [README.md](https://github.com/hello-pangea/dnd/blob/main/README.md)
- [stories/src/customize-autoscroll/customize-autoscroll-app.tsx](https://github.com/hello-pangea/dnd/blob/main/stories/src/customize-autoscroll/customize-autoscroll-app.tsx)
</details>

# 高级功能：自动滚动、可访问性与模式

## 概述

`@hello-pangea/dnd` 旨在为 React 应用提供"漂亮且自然"的列表拖拽体验。本页聚焦三大高级主题：**自动滚动（Auto-Scrolling）**、**可访问性（Accessibility）** 与**常见模式（Patterns）**。这些能力共同支撑了项目"漂亮、可访问、高性能"的核心理念——正如 [README.md](https://github.com/hello-pangea/dnd/blob/main/README.md) 所宣称的"Beautiful and natural movement of items 💐"与"powerful keyboard and screen reader support ♿️"。

---

## 自动滚动（Auto-Scrolling）

### 模块组成

自动滚动器由两个并行的滚动器构成，统一通过 `scroll(state)` 调度：

```mermaid
graph LR
  A[DragDropContext<br/>State] --> B{phase === 'DRAGGING'?}
  B -- 否 --> Z[跳过滚动]
  B -- 是 --> C[disabled?]
  C -- 是 --> Z
  C -- 否 --> D[FluidScroller<br/>连续滚动]
  C -- 否 --> E[JumpScroller<br/>键盘/瞬移滚动]
  D --> F[scrollWindow / scrollDroppable]
  E --> F
```

> 资料来源：[src/state/auto-scroller/index.ts:20-58]()

- `FluidScroller` 处理鼠标/触摸产生的细粒度（`FLUID`）滚动；`JumpScroller` 处理键盘（`SNAP`）或离散位移。两种 `MovementMode` 在 [src/types.ts](https://github.com/hello-pangea/dnd/blob/main/src/types.ts) 中定义。
- 只有当 `state.phase === 'DRAGGING'` 且 `disabled` 为 `false` 时才会真正触发滚动。

### 配置选项

自 16.1.0 版本起（见 [Release 16.1.0](https://github.com/hello-pangea/dnd/releases/tag/v16.1.0)），可通过 `autoScrollerOptions` 自定义行为。完整字段定义在 [src/state/auto-scroller/fluid-scroller/auto-scroller-options-types.ts](https://github.com/hello-pangea/dnd/blob/main/src/state/auto-scroller/fluid-scroller/auto-scroller-options-types.ts)：

| 字段 | 默认值 / 类型 | 含义 |
| --- | --- | --- |
| `startFromPercentage` | `number` | 距边缘达到该百分比时开始自动滚动（如 `0.1`） |
| `maxScrollAtPercentage` | `number` | 达到最大滚动速度的百分比，需小于 `startFromPercentage` |
| `maxPixelScroll` | `number` | 每帧最大滚动像素数 |
| `ease` | `(p: number) => number` | 缓动函数，输入输出均为 `[0,1]` |
| `durationDampening.stopDampeningAt` | `number`（ms） | 从拖拽开始到完全停止阻尼的时间 |
| `durationDampening.accelerateAt` | `number`（ms） | 开始加速衰减阻尼的时间 |
| `disabled` | `boolean` | 完全关闭自动滚动 |

类型导出支持递归部分覆盖（`RecursivePartial`），便于只覆盖需要调整的字段。

### 实战示例

在 [stories/src/customize-autoscroll/customize-autoscroll-app.tsx](https://github.com/hello-pangea/dnd/blob/main/stories/src/customize-autoscroll/customize-autoscroll-app.tsx) 中演示了传入 `autoScrollerOptions` 调整不同列表（含嵌套列表）的滚动行为，可使用线性、二次或三次缓动函数，并能在运行时关闭。

---

## 可访问性（Accessibility）

### 键盘交互

库依赖一组标准键盘常量，常量集中定义于 [src/view/key-codes.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/key-codes.ts)：

| 键 | 码值 |
| --- | --- |
| `tab` | 9 |
| `enter` | 13 |
| `escape` | 27 |
| `space` | 32 |
| `pageUp` / `pageDown` | 33 / 34 |
| `end` / `home` | 35 / 36 |
| `arrowLeft/Up/Right/Down` | 37 / 38 / 39 / 40 |

`useKeyboardSensor` 通过这些常量识别用户的键盘意图，从而实现键盘拖拽、`JumpScroller` 离散位移以及屏幕阅读器友好行为（README 中强调"powerful keyboard and screen reader support"）。

### 文档类型校验

为保证布局与测量跨浏览器一致，[src/view/drag-drop-context/check-doctype.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/drag-drop-context/check-doctype.ts) 在挂载时检查文档声明：

- 不存在 `<!doctype>` 时发出 `warning`；
- 名称非 `html` 或 `publicId` 非空时同样警告。

这是可访问性前置条件之一，避免不同浏览器对盒模型产生差异。

### 跨浏览器滚动测量

不同浏览器更新 `documentElement.scrollTop` 与 `window.pageYOffset` 的方式不同——WebKit 不更新前者，Chrome 同时更新并保留小数，Firefox/IE11 仅整数。库通过 [src/view/window/get-window-scroll.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/window/get-window-scroll.ts) 统一使用 `window.pageXOffset`/`pageYOffset` 来规避差异。

### 元素匹配兼容性

[src/view/use-sensor-marshal/closest.ts](https://github.com/hello-pangea/dnd/blob/main/src/view/use-sensor-marshal/closest.ts) 提供 `Element.closest` 的 IE11 兼容垫片，自动检测 `matches`/`msMatchesSelector`/`webkitMatchesSelector` 中可用的方法，确保旧浏览器下仍可正确解析拖拽把手层级。

---

## 常用模式（Patterns）

README 中列出的库能力即代表了官方认可的常见模式：

- **方向与跨列表**：垂直列表 ↕、水平列表 ↔、列表间移动（▤ ↔ ▤）。
- **大数据量**：通过 `virtual-lists` 模式在 60fps 下支持 10,000 项。
- **合并（Combining）**：在 [stories/src/dynamic/with-controls.tsx](https://github.com/hello-pangea/dnd/blob/main/stories/src/dynamic/with-controls.tsx) 中演示了 `isCombineEnabled` 切换。
- **多选拖拽（Multi drag）**：同时拖动多个 `Draggable`。
- **条件拖放**：`Draggable` 的 `isDragDisabled`、`Droppable` 的 `isDropDisabled`。
- **语义表（`<table>`）**：表格行重排序专用模式。
- **自定义拖拽把手**：仅拖拽元素局部即可移动整体。
- **重新挂载（Reparenting）**：拖拽过程中可将被拖动项渲染到另一元素（如克隆、Portal）。
- **动态变更**：在拖拽过程中增删 `Draggable`，配合 `onBeforeCapture` 协调。
- **服务器端渲染**：自 16.2.0 起基于 `useId` 解决水合问题（[Release 16.2.0](https://github.com/hello-pangea/dnd/releases/tag/v16.2.0)）。

公共 API 通过 [src/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/index.ts) 统一暴露 `DragDropContext`、`Droppable`、`Draggable`、`useMouseSensor`、`useTouchSensor`、`useKeyboardSensor` 等。

---

## 故障排查提示

- 自动滚动"卡住"或过快：检查 `autoScrollerOptions` 中 `disabled`、`ease` 与 `maxPixelScroll`。
- 元素测量偏移：确认页面包含 `<!doctype html>`；旧项目可通过 `check-doctype.ts` 触发警告排查。
- SSR 下 `useId` 行为异常：升级至 ≥ 16.2.0 版本，并避免使用旧版 `resetServerContext`。
- 浏览器更新策略：16.5.0 起仅支持近一年内发布的主流浏览器版本。

---

## See Also

- [README.md](https://github.com/hello-pangea/dnd/blob/main/README.md) —— 功能全景
- [src/index.ts](https://github.com/hello-pangea/dnd/blob/main/src/index.ts) —— 公共 API 与类型导出
- [src/types.ts](https://github.com/hello-pangea/dnd/blob/main/src/types.ts) —— `FLUID`/`SNAP` 模式与事件类型
- [Release 16.1.0](https://github.com/hello-pangea/dnd/releases/tag/v16.1.0) —— 引入 `autoScrollerOptions`
- [Release 16.2.0](https://github.com/hello-pangea/dnd/releases/tag/v16.2.0) —— SSR `useId` 修复

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Doramagic 踩坑日志

项目：hello-pangea/dnd

摘要：发现 7 个潜在踩坑项，其中 0 个为 high/blocking；最高优先级：能力坑 - 能力判断依赖假设。

## 1. 能力坑 · 能力判断依赖假设

- 严重度：medium
- 证据强度：source_linked
- 发现：README/documentation is current enough for a first validation pass.
- 对用户的影响：假设不成立时，用户拿不到承诺的能力。
- 证据：capability.assumptions | github_repo:321362240 | https://github.com/hello-pangea/dnd | README/documentation is current enough for a first validation pass.

## 2. 维护坑 · 维护活跃度未知

- 严重度：medium
- 证据强度：source_linked
- 发现：未记录 last_activity_observed。
- 对用户的影响：新项目、停更项目和活跃项目会被混在一起，推荐信任度下降。
- 证据：evidence.maintainer_signals | github_repo:321362240 | https://github.com/hello-pangea/dnd | last_activity_observed missing

- 严重度：medium
- 证据强度：source_linked
- 发现：no_demo
- 证据：downstream_validation.risk_items | github_repo:321362240 | https://github.com/hello-pangea/dnd | no_demo; severity=medium

## 4. 安全/权限坑 · 存在评分风险

- 严重度：medium
- 证据强度：source_linked
- 发现：no_demo
- 对用户的影响：风险会影响是否适合普通用户安装。
- 证据：risks.scoring_risks | github_repo:321362240 | https://github.com/hello-pangea/dnd | no_demo; severity=medium

## 5. 安全/权限坑 · 来源证据：Dependency Dashboard

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：Dependency Dashboard
- 对用户的影响：可能影响升级、迁移或版本选择。
- 证据：community_evidence:github | https://github.com/hello-pangea/dnd/issues/210 | 来源讨论提到 node 相关条件，需在安装/试用前复核。

## 6. 维护坑 · issue/PR 响应质量未知

- 严重度：low
- 证据强度：source_linked
- 发现：issue_or_pr_quality=unknown。
- 对用户的影响：用户无法判断遇到问题后是否有人维护。
- 证据：evidence.maintainer_signals | github_repo:321362240 | https://github.com/hello-pangea/dnd | issue_or_pr_quality=unknown

## 7. 维护坑 · 发布节奏不明确

- 严重度：low
- 证据强度：source_linked
- 发现：release_recency=unknown。
- 对用户的影响：安装命令和文档可能落后于代码，用户踩坑概率升高。
- 证据：evidence.maintainer_signals | github_repo:321362240 | https://github.com/hello-pangea/dnd | release_recency=unknown

<!-- canonical_name: hello-pangea/dnd; human_manual_source: deepwiki_human_wiki -->
