Doramagic 项目包 · 项目说明书

dnd 项目

💅 为 React 列表提供美观且易用的拖拽排序功能。⭐️ 点 Star 支持我们的工作!

Core API: DragDropContext, Draggable, and Droppable

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

章节 相关页面

继续阅读本节完整说明和来源证据。

概述与设计目标

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

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

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

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 ...

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 lift 中间件

继续阅读本节完整说明和来源证据。

章节 auto-scroll 中间件

继续阅读本节完整说明和来源证据。

章节 焦点与 DOM 协调

继续阅读本节完整说明和来源证据。

概述与设计目标

@hello-pangea/dnd 内部以 Redux 作为核心状态引擎,借助 react-redux v9 将状态分发给拖拽上下文中的子组件(Droppable / Draggable)。从 package.json 可看到运行依赖中显式声明了 react-redux ^9.2.0redux ^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 中导出,其中 AnnounceDragStartDragUpdateDropResultMovementModePreDragActionsSnapDragActions 等类型决定了与用户代码之间的契约面。

状态管理架构

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

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 中定义,包括 BeforeInitialCaptureActionLiftActionInitialPublishActionPublishWhileDraggingActionDropActionDropPendingActionDropCompleteActionDropAnimationFinishedAction 等。其中 cancel()drop({ reason: 'CANCEL' }) 的语法糖,对应 DropReason = 'DROP' | 'CANCEL'(见 src/types.ts)。

核心中间件

lift 中间件

lift 中间件位于 src/state/middleware/lift.ts,负责把 LIFT 动作转化为可发布的拖拽状态。其关键步骤包括:

  • 若当前处于 DROP_ANIMATING,先 dispatch completeDrop 以收尾上一次拖拽;
  • 使用 invariant 断言新阶段为 IDLE(来自 src/invariant.tsRbdInvariant 错误类在生产环境会剥除消息);
  • dispatch(flush()) 清空占位符;
  • dispatch(beforeInitialCapture(...)) 触发 onBeforeCapture 回调;
  • 最终根据 movementMode 决定是否立即发布(SNAP 模式 → shouldPublishImmediately: true)。

auto-scroll 中间件

auto-scroll 中间件(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,仅暴露 start(state: DraggingState)stop()scroll(state: State) 三个方法。

焦点与 DOM 协调

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

拖拽阶段与移动模式

状态机把一次拖拽拆分为四个阶段:IDLEDRAGGINGDROP_PENDINGDROP_ANIMATINGIDLEMovementModesrc/types.ts 中定义为 'FLUID' | 'SNAP'

  • FLUID:由高粒度输入(鼠标、触摸)驱动,位置随指针实时更新;
  • SNAP:由离散命令(键盘空格、回车)驱动,需要在收到指令后才发布一次。

进入 LIFT 时会同时携带 clientSelectionmovementModelift 中间件据此决定立即发布还是等待后续 PUBLISH_WHILE_DRAGGINGDimensionMarshal 通过 src/state/dimension-marshal/dimension-marshal-types.ts 中描述的 CallbackscollectionStartingpublishWhileDraggingupdateDroppableScrollupdateDroppableIsEnabledupdateDroppableIsCombineEnabled 等动作回送至 store。

错误处理与初始化守护

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

See Also

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

Sensors and Input Handling

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

章节 相关页面

继续阅读本节完整说明和来源证据。

概述

@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

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 / pageDownend / home 以及四个方向键,便于在不同传感器间复用同一份常量。资料来源:src/view/key-codes.ts

useKeyboardSensorkeydown 回调里把这些按键映射到拖放动作: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

启动期检查

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

这两个检查与 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

常见失败模式

公共入口

See Also

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

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

Advanced Features: Auto-Scrolling, Accessibility, and Patterns

@hello-pangea/dnd 旨在为 React 应用提供"漂亮且自然"的列表拖拽体验。本页聚焦三大高级主题:自动滚动(Auto-Scrolling)、可访问性(Accessibility) 与常见模式(Patterns)。这些能力共同支撑了项目"漂亮、可访问、高性能"的核心理念——正如 README.md 所宣称的"Beautiful and natural mov...

章节 相关页面

继续阅读本节完整说明和来源证据。

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

概述

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

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

失败模式与踩坑日记

保留 Doramagic 在发现、验证和编译中沉淀的项目专属风险,不把社区讨论只当作装饰信息。

medium 能力判断依赖假设

假设不成立时,用户拿不到承诺的能力。

medium 维护活跃度未知

新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。

medium 存在评分风险

风险会影响是否适合普通用户安装。

medium 来源证据:Dependency Dashboard

可能影响升级、迁移或版本选择。

Pitfall Log / 踩坑日志

项目: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

来源:Doramagic 发现、验证与编译记录