mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-25 06:18:45 +08:00
refactor: rewrite blocksuite dnd (#9595)
### Changed
- Refactored BlockSuite drag-and-drop using @atlaskit/pragmatic-drag-and-drop/element/adapter.
- Updated block dragging to use the new drag-and-drop infrastructure.
### BlockSuite DND API
Access the BlockSuite drag-and-drop API via `std.dnd`. This is a lightweight wrapper around pragmatic-drag-and-drop, offering convenient generic types and more intuitive option names.
#### Drag payload structure
There's some constrain about drag payload. The whole drag payload looks like this:
```typescript
type DragPayload = {
entity: {
type: string
},
from: {
at: 'blocksuite',
docId: string
}
}
```
- The `from` field is auto-generated—no need for manual handling.
- The `entity` field is customizable, but it must include a `type`.
All drag-and-drop methods accept a generic type for entity, ensuring more accurate payloads in event handlers.
```typescript
type BlockEntity = {
type: 'blocks',
blockIds: string[]
}
dnd.draggable<BlockEntity>({
element: someElement,
setDragData: () => {
// the return type must satisfy the generic type
// in this case, it's BlockEntity
return {
type: 'blocks',
blockIds: []
}
}
});
dnd.monitor<BlockEntity>({
// the arguments is same for other event handler
onDrag({ source }) {
// the type of this is BlockEntity
source.data.entity
}
})
```
#### Drop payload
When hover on droppable target. You can set drop payload as well. All drag-and-drop methods accept a second generic type for drop payload.
The drop payload is customizable. Additionally, the DND system will add an `edge` field to the final payload object, indicating the nearest edge of the drop target relative to the current drag position.
```typescript
type DropPayload = {
blockId: string;
}
dnd.dropTarget<BlockEntity, DropPayload>({
getData() {
// the type should be DropPayload
return {
blockId: 'someId'
}
}
});
dnd.monitor<BlockEntity, DropPayload>({
// drag over on drop target
onDrag({ location }) {
const target = location.current.dropTargets[0];
// the type is DropPayload
target.data;
// retrieve the nearest edge of the drop target relative to the current drop position.
target.data.edge;
}
})
```
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { BLOCK_ID_ATTR, type BlockComponent } from '@blocksuite/block-std';
|
||||
import type { Point, Rect } from '@blocksuite/global/utils';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
|
||||
import { BLOCK_CHILDREN_CONTAINER_PADDING_LEFT } from '../../consts/index.js';
|
||||
import { clamp } from '../math.js';
|
||||
@@ -272,18 +273,32 @@ export function getRectByBlockComponent(element: Element | BlockComponent) {
|
||||
* Only keep block elements of same level.
|
||||
*/
|
||||
export function getBlockComponentsExcludeSubtrees(
|
||||
elements: Element[] | BlockComponent[]
|
||||
elements: BlockComponent[]
|
||||
): BlockComponent[] {
|
||||
if (elements.length <= 1) return elements as BlockComponent[];
|
||||
let parent = elements[0];
|
||||
return elements.filter((node, index) => {
|
||||
if (index === 0) return true;
|
||||
if (contains(parent, node)) {
|
||||
return false;
|
||||
} else {
|
||||
parent = node;
|
||||
return true;
|
||||
|
||||
const getLevel = (element: BlockComponent) => {
|
||||
let level = 0;
|
||||
let model: BlockModel | null = element.model;
|
||||
|
||||
while (model && model.role !== 'root') {
|
||||
level++;
|
||||
model = model.parent;
|
||||
}
|
||||
|
||||
return level;
|
||||
};
|
||||
|
||||
let topMostLevel = Number.POSITIVE_INFINITY;
|
||||
const levels = elements.map(element => {
|
||||
const level = getLevel(element);
|
||||
|
||||
topMostLevel = Math.min(topMostLevel, level);
|
||||
return level;
|
||||
});
|
||||
|
||||
return elements.filter((_, index) => {
|
||||
return levels[index] === topMostLevel;
|
||||
}) as BlockComponent[];
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ export function requestConnectedFrame(
|
||||
* A wrapper around `requestConnectedFrame` that only calls at most once in one frame
|
||||
*/
|
||||
export function requestThrottledConnectedFrame<
|
||||
T extends (...args: unknown[]) => void,
|
||||
T extends (...args: any[]) => void,
|
||||
>(func: T, element?: HTMLElement): T {
|
||||
let raqId: number | undefined = undefined;
|
||||
let latestArgs: unknown[] = [];
|
||||
|
||||
Reference in New Issue
Block a user