@nocobase/flow-engine 2.0.0-alpha.3 → 2.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/dnd/findModelUidPosition.d.ts +13 -0
- package/lib/components/dnd/findModelUidPosition.js +50 -0
- package/lib/components/dnd/gridDragPlanner.d.ts +130 -0
- package/lib/components/dnd/gridDragPlanner.js +497 -0
- package/lib/components/dnd/index.d.ts +2 -2
- package/lib/components/dnd/index.js +5 -5
- package/lib/flowContext.js +4 -2
- package/package.json +2 -2
- package/src/components/__tests__/gridDragPlanner.test.ts +494 -0
- package/src/components/dnd/README.md +149 -0
- package/src/components/dnd/findModelUidPosition.ts +26 -0
- package/src/components/dnd/gridDragPlanner.ts +659 -0
- package/src/components/dnd/index.tsx +3 -3
- package/src/flowContext.ts +6 -2
- package/lib/components/dnd/getMousePositionOnElement.d.ts +0 -50
- package/lib/components/dnd/getMousePositionOnElement.js +0 -95
- package/lib/components/dnd/moveBlock.d.ts +0 -33
- package/lib/components/dnd/moveBlock.js +0 -302
- package/src/components/dnd/getMousePositionOnElement.ts +0 -115
- package/src/components/dnd/moveBlock.ts +0 -379
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Grid 拖拽设计 / Grid Drag Planner
|
|
2
|
+
|
|
3
|
+
> zh-CN 版本在前,English version follows after each section.
|
|
4
|
+
|
|
5
|
+
## 背景 / Background
|
|
6
|
+
|
|
7
|
+
- zh-CN:本模块负责区块栅格的拖拽预览与布局模拟,替换旧的基于方向推断的 `moveBlock` 实现。
|
|
8
|
+
- EN: This module powers grid block drag preview and layout simulation, replacing the legacy direction-based `moveBlock` helper.
|
|
9
|
+
|
|
10
|
+
## 核心概念 / Key Concepts
|
|
11
|
+
|
|
12
|
+
- zh-CN:
|
|
13
|
+
- **Snapshot**:在拖拽开始时记录 `rows`、`sizes` 以及 DOM slot 信息,保证预览可随时回滚。
|
|
14
|
+
- **Slots**:通过 DOM `data-grid-*` 标识生成的投放槽,涵盖列内部、列边缘、行间隙与空容器。
|
|
15
|
+
- **Simulation**:`simulateLayoutForSlot` 基于快照创建新布局,确保源节点先从原位置移除再插入目标 slot。
|
|
16
|
+
- EN:
|
|
17
|
+
- **Snapshot**: Captures `rows`, `sizes`, and DOM slots on drag start so previews can be reverted safely.
|
|
18
|
+
- **Slots**: Drop targets described by DOM `data-grid-*` attributes covering column, column-edge, row-gap and empty container.
|
|
19
|
+
- **Simulation**: `simulateLayoutForSlot` works on a cloned snapshot, removing the source first and inserting into the chosen slot.
|
|
20
|
+
|
|
21
|
+
## 事件流程 / Drag Lifecycle
|
|
22
|
+
|
|
23
|
+
- zh-CN:
|
|
24
|
+
1. `handleDragStart`:构建快照、初始化 overlay 状态并安排 slot 刷新。
|
|
25
|
+
2. `handleDragMove`:解析当前指针坐标,匹配最优 slot,调用 `simulateLayoutForSlot` 生成预览,并更新 overlay。
|
|
26
|
+
3. `handleDragEnd`:若存在有效 slot,提交 `rows/sizes` 并保存;否则回滚至快照。
|
|
27
|
+
4. `handleDragCancel`:直接恢复快照并清空 overlay。
|
|
28
|
+
- EN:
|
|
29
|
+
1. `handleDragStart`: Build the snapshot, reset overlay state, schedule slot refresh.
|
|
30
|
+
2. `handleDragMove`: Resolve pointer position, match the closest slot, call `simulateLayoutForSlot`, and update overlay.
|
|
31
|
+
3. `handleDragEnd`: Commit `rows/sizes` and persist when a valid slot exists, otherwise restore snapshot.
|
|
32
|
+
4. `handleDragCancel`: Restore snapshot immediately and clear the overlay.
|
|
33
|
+
|
|
34
|
+
## Slot 类型与数据标识 / Slot Types & Data Attributes
|
|
35
|
+
|
|
36
|
+
- zh-CN:`Grid` 组件为行、列、项生成 `data-grid-row-id`、`data-grid-column-index`、`data-grid-item-index` 等属性;
|
|
37
|
+
`buildLayoutSnapshot` 使用这些 DOM 节点计算 slot 区域。支持的 slot 类型包括:
|
|
38
|
+
- **ColumnSlot**:列内部插入位置,带 `position: 'before' | 'after'` 标识在元素上方或下方插入
|
|
39
|
+
- **ColumnEdgeSlot**:列边缘,带 `direction: 'left' | 'right'` 标识在左侧或右侧新建列
|
|
40
|
+
- **RowGapSlot**:行间隙,带 `position: 'above' | 'below'` 标识在行上方或下方插入新行
|
|
41
|
+
- **EmptyRowSlot**:空容器,当容器内没有任何元素时的插入位置
|
|
42
|
+
- **EmptyColumnSlot**:空列,当列内没有任何元素时的插入位置(不受配置影响)
|
|
43
|
+
- EN: The `Grid` component emits `data-grid-row-id`, `data-grid-column-index`, `data-grid-item-index`, etc.; `buildLayoutSnapshot` uses them to compute slot rectangles. Supported slot types:
|
|
44
|
+
- **ColumnSlot**: Column internal insert position with `position: 'before' | 'after'` indicating insertion above or below an element
|
|
45
|
+
- **ColumnEdgeSlot**: Column edge with `direction: 'left' | 'right'` indicating creating a new column on the left or right
|
|
46
|
+
- **RowGapSlot**: Row gap with `position: 'above' | 'below'` indicating inserting a new row above or below
|
|
47
|
+
- **EmptyRowSlot**: Empty container insertion position when no elements exist
|
|
48
|
+
- **EmptyColumnSlot**: Empty column insertion position when no items in the column (unaffected by config)
|
|
49
|
+
|
|
50
|
+
## 尺寸处理 / Size Handling
|
|
51
|
+
|
|
52
|
+
- zh-CN:
|
|
53
|
+
- 所有列宽保持 24 栅格,总和不变。
|
|
54
|
+
- `distributeSizesWithNewColumn` 负责为新增列分配宽度。
|
|
55
|
+
- `normalizeRowSizes` 保证在列增删后尺寸仍然稳定。
|
|
56
|
+
- EN:
|
|
57
|
+
- Column widths always sum to 24 units.
|
|
58
|
+
- `distributeSizesWithNewColumn` allocates width for newly inserted columns.
|
|
59
|
+
- `normalizeRowSizes` keeps sizes consistent after column add/remove operations.
|
|
60
|
+
|
|
61
|
+
## 拖拽高亮区域配置 / Drag Overlay Configuration
|
|
62
|
+
|
|
63
|
+
- zh-CN:
|
|
64
|
+
- `GridModel` 支持通过 `dragOverlayConfig` 属性配置拖拽时高亮区域的尺寸和偏移。
|
|
65
|
+
- 配置项包括:
|
|
66
|
+
- `columnInsert.before/after`:配置列内插入位置的 `height` 和 `offsetTop`
|
|
67
|
+
- `columnEdge.left/right`:配置列边缘位置的 `width` 和 `offsetLeft`
|
|
68
|
+
- `rowGap.above/below`:配置行间隙位置的 `height` 和 `offsetTop`
|
|
69
|
+
- 空行(EmptyRowSlot)和空列(EmptyColumnSlot)始终使用完整容器尺寸,不受配置影响。
|
|
70
|
+
- 示例:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
model.dragOverlayConfig = {
|
|
74
|
+
columnInsert: {
|
|
75
|
+
before: { height: 20, offsetTop: -2 },
|
|
76
|
+
after: { height: 20, offsetTop: -2 },
|
|
77
|
+
},
|
|
78
|
+
columnEdge: {
|
|
79
|
+
left: { width: 16, offsetLeft: -4 },
|
|
80
|
+
right: { width: 16, offsetLeft: 4 },
|
|
81
|
+
},
|
|
82
|
+
rowGap: {
|
|
83
|
+
above: { height: 32, offsetTop: -8 },
|
|
84
|
+
below: { height: 32, offsetTop: 8 },
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- EN:
|
|
90
|
+
- `GridModel` supports configuring drag overlay dimensions and offsets via the `dragOverlayConfig` property.
|
|
91
|
+
- Configuration options:
|
|
92
|
+
- `columnInsert.before/after`: Configure `height` and `offsetTop` for column insert positions
|
|
93
|
+
- `columnEdge.left/right`: Configure `width` and `offsetLeft` for column edge positions
|
|
94
|
+
- `rowGap.above/below`: Configure `height` and `offsetTop` for row gap positions
|
|
95
|
+
- Empty rows (EmptyRowSlot) and empty columns (EmptyColumnSlot) always use full container dimensions, unaffected by configuration.
|
|
96
|
+
- Example:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
model.dragOverlayConfig = {
|
|
100
|
+
columnInsert: {
|
|
101
|
+
before: { height: 20, offsetTop: -2 },
|
|
102
|
+
after: { height: 20, offsetTop: -2 },
|
|
103
|
+
},
|
|
104
|
+
columnEdge: {
|
|
105
|
+
left: { width: 16, offsetLeft: -4 },
|
|
106
|
+
right: { width: 16, offsetLeft: 4 },
|
|
107
|
+
},
|
|
108
|
+
rowGap: {
|
|
109
|
+
above: { height: 32, offsetTop: -8 },
|
|
110
|
+
below: { height: 32, offsetTop: 8 },
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 测试策略 / Testing Strategy
|
|
116
|
+
|
|
117
|
+
- zh-CN:
|
|
118
|
+
- `gridDragPlanner.test.ts` (22 个测试):覆盖 `getSlotKey`、`resolveDropIntent`、`simulateLayoutForSlot` 等核心函数,包括所有 slot 类型(column、column-edge、row-gap、empty-row、empty-column)的处理。
|
|
119
|
+
- `GridModel.dragOverlay.test.ts` (9 个测试):验证 `DragOverlayConfig` 接口的类型定义和配置结构。
|
|
120
|
+
- `GridModel.computeOverlayRect.test.ts` (15 个测试):测试各种配置下的 overlay 尺寸计算,确保所有 slot 类型和位置组合都正确应用配置,以及空行/空列不受配置影响。
|
|
121
|
+
- 后续如引入更多 slot 类型或行为,请同步补充测试用例。
|
|
122
|
+
- EN:
|
|
123
|
+
- `gridDragPlanner.test.ts` (22 tests): Covers core functions like `getSlotKey`, `resolveDropIntent`, `simulateLayoutForSlot`, including all slot types (column, column-edge, row-gap, empty-row, empty-column).
|
|
124
|
+
- `GridModel.dragOverlay.test.ts` (9 tests): Verifies `DragOverlayConfig` interface type definitions and configuration structure.
|
|
125
|
+
- `GridModel.computeOverlayRect.test.ts` (15 tests): Tests overlay dimension calculations under various configurations, ensuring all slot types and position combinations apply config correctly, and empty rows/columns remain unaffected.
|
|
126
|
+
- Add more test cases whenever new slot types or behaviours are introduced.
|
|
127
|
+
|
|
128
|
+
## 后续注意事项 / Future Notes
|
|
129
|
+
|
|
130
|
+
- zh-CN:
|
|
131
|
+
- DOM slot 解析依赖 `Grid` 组件结构,如需重构 UI,请同步更新 `buildLayoutSnapshot`。
|
|
132
|
+
- 拖拽 overlay 位置基于容器 scroll 偏移,滚动行为变化时需回归测试。
|
|
133
|
+
- 新增 slot 类型时,需要在以下位置同步更新:
|
|
134
|
+
1. 定义 slot 接口并添加到 `LayoutSlot` 联合类型
|
|
135
|
+
2. 更新 `getSlotKey` 函数处理新类型
|
|
136
|
+
3. 更新 `simulateLayoutForSlot` 函数添加布局逻辑
|
|
137
|
+
4. 更新 `GridModel.computeOverlayRect` 处理配置应用
|
|
138
|
+
5. 更新 `Grid/index.tsx` 的 `DragOverlayRect` 类型和样式
|
|
139
|
+
6. 添加相应的测试用例
|
|
140
|
+
- EN:
|
|
141
|
+
- DOM slot discovery depends on the current `Grid` structure; update `buildLayoutSnapshot` when UI markup changes.
|
|
142
|
+
- Overlay placement uses container scroll offsets; re-run drag tests if scroll handling changes.
|
|
143
|
+
- When adding new slot types, synchronize updates in:
|
|
144
|
+
1. Define slot interface and add to `LayoutSlot` union type
|
|
145
|
+
2. Update `getSlotKey` function to handle the new type
|
|
146
|
+
3. Update `simulateLayoutForSlot` function with layout logic
|
|
147
|
+
4. Update `GridModel.computeOverlayRect` for config application
|
|
148
|
+
5. Update `DragOverlayRect` type and styles in `Grid/index.tsx`
|
|
149
|
+
6. Add corresponding test cases
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const findModelUidPosition = (uid: string, rows: Record<string, string[][]>) => {
|
|
11
|
+
// 找到 sourceUid 和 targetUid 的位置
|
|
12
|
+
let result: { rowId: string; columnIndex: number; itemIndex: number } | null = null;
|
|
13
|
+
|
|
14
|
+
for (const [rowId, columns] of Object.entries(rows)) {
|
|
15
|
+
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
|
|
16
|
+
const column = columns[columnIndex];
|
|
17
|
+
for (let itemIndex = 0; itemIndex < column.length; itemIndex++) {
|
|
18
|
+
if (column[itemIndex] === uid) {
|
|
19
|
+
result = { rowId, columnIndex, itemIndex };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return result;
|
|
26
|
+
};
|