Files
huajishe-tts/docs/dev-log/05-drag-and-drop.md
Claude 24822d0d6a docs(dev-log): add chronological development log
Per user request: split the development process into per-topic docs under
docs/dev-log/. Each entry follows the same structure:
  What was asked → Problems → How solved

Files:
- README.md            index
- 01-initial-scope-and-design.md
- 02-ui-primitives-api-design.md
- 03-monorepo-scaffold.md
- 04-ui-primitive-impl.md
- 05-drag-and-drop.md
- 06-three-games.md
- 07-bugfixes-round-1.md
- lessons-learned.md   all gotchas consolidated
- decisions.md         locked decisions with dates

Total: 1071 lines across 10 files.
2026-08-17 16:49:09 +08:00

110 lines
3.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 05 · 拖拽 + 落点判定
## 做什么
实现 Card 拖出、Zone 接收、onDrop 触发游戏 move。验证端到端
- 拖单张牌
- 多选Shift+click后拖一组
- 跨 zone 拖动
## 遇到什么问题
### 问题 1Playwright 拖拽总 timeout — "subtree intercepts pointer events"
`dragstart` 不会从被遮挡的 SVG 子元素触发。`<rect>` / `<text>` 等会"吃掉"事件。
**解决**:把 SVG 包在 `<div style="pointer-events:none">` 里再 `dangerouslySetInnerHTML`
```tsx
dangerouslySetInnerHTML={{
__html: `<div style="pointer-events:none;width:100%;height:100%;">${svg}</div>`,
}}
```
### 问题 2React StrictMode + Client 双重挂载 → state 翻倍 + React duplicate key
`Client(...)` 内部用了全局 storeStrictMode 双重挂载让 store 注册两次state 出现两次(桌上有 4 张牌而非 2 张)。
**解决**
- `Client(...)``useMemo(() => Client({...}), [game])` 包裹,避免每次 render 重新创建
- `main.tsx` 暂时移除 `<StrictMode>`(在 boardgame.io 的 React client 升级前)
### 问题 3多选拖拽只拖了一张
Zone 没把 `selectedIds` 传给 CardCard dragstart 退化为只带自己的 id。
**解决**Zone 渲染 Card 时传 `cardIds={selected ? Array.from(selectedIds) : undefined}`Card 内部 `cardIds ?? [card.id]` 兜底。
### 问题 4拖拽的多张牌在桌面上重叠
DragTest 手牌 fan 默认 spacing=30 < 卡宽 63点击被上层卡遮挡。
**解决**DragTest 显式设 `layout: { fan: 0, spacing: 70 }`
### 问题 5拖拽到非 drop target zone 时被误判
例如拖到 `hand` zone没配 dropMove会被当作落到自身。
**解决**Zone 拖拽前先 `if (!isDropTarget) return;` 检查。
## 怎么解决
### HTML5 拖拽 + 自定义 MIME
```ts
const DRAG_MIME = 'application/x-tts-cards';
e.dataTransfer.setData(DRAG_MIME, JSON.stringify({
cardIds,
fromZoneId: props.fromZoneId,
}));
e.dataTransfer.effectAllowed = 'move';
```
### Zone 接收流程
```ts
const handleDragOver = (e) => {
if (!isDropTarget) return;
if (!e.dataTransfer.types.includes(DRAG_MIME)) return;
e.preventDefault(); // 关键:允许 drop
e.dataTransfer.dropEffect = 'move';
};
const handleDrop = (e) => {
e.preventDefault();
const raw = e.dataTransfer.getData(DRAG_MIME);
const payload = JSON.parse(raw);
onDrop?.(payload.cardIds, payload.fromZoneId, def.id);
};
```
### Board 协调
```ts
const handleDrop = (cardIds, fromZoneId, toZoneId) => {
const target = ui.zones.find(z => z.id === toZoneId);
if (!target?.dropMove || !onMove) return;
const args = target.dropArgs
? target.dropArgs(cardIds, fromZoneId, toZoneId)
: [cardIds, fromZoneId, toZoneId];
onMove(target.dropMove, args);
setSelectedIds(new Set()); // 拖完清选择
};
```
### 验证
- 拖 6♠ → 桌hand=4, table=1
- Shift+click 选 3 张2♠/4♠/6♠+ 拖 2♠ → 全部移到桌hand=3♠/5♠, table=2♠/4♠/6♠
## 成果
- Commit `2b492d2 feat(ui): HTML5 drag-and-drop + multi-select`
- Commit `8953554 fix(ui): drag pointer-events, StrictMode compat, multi-drag`(后续 bug 修复)
## 关联
- [06-three-games.md](./06-three-games.md) — 用 DragTest 验证拖拽
- [07-bugfixes-round-1.md](./07-bugfixes-round-1.md) — 拖拽 bug 修复
- [lessons-learned.md §7-12](./lessons-learned.md) — 拖拽相关踩坑