Skip to content

Commit

Permalink
refactor: rewrite blocksuite dnd (#9595)
Browse files Browse the repository at this point in the history
### 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;
  }
})
```
  • Loading branch information
doouding committed Jan 16, 2025
1 parent 3828144 commit 9971719
Show file tree
Hide file tree
Showing 30 changed files with 1,203 additions and 557 deletions.
1 change: 0 additions & 1 deletion blocksuite/affine/block-attachment/src/attachment-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<
<div
${this._whenHover ? ref(this._whenHover.setReference) : nothing}
class="affine-attachment-container"
draggable="${this.blockDraggable ? 'true' : 'false'}"
style=${this.containerStyleMap}
>
${embedView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export class FileDropExtension extends LifeCycleWatcher {

point$ = signal<Point | null>(null);

private _disableIndicator = false;

closestElement$ = signal<BlockComponent | null>(null);

dropTarget$ = computed<DropTarget | null>(() => {
Expand Down Expand Up @@ -196,7 +198,9 @@ export class FileDropExtension extends LifeCycleWatcher {

std.event.disposables.add(
this.dropTarget$.subscribe(target => {
FileDropExtension.indicator.rect = target?.rect ?? null;
FileDropExtension.indicator.rect = this._disableIndicator
? null
: (target?.rect ?? null);
})
);

Expand All @@ -210,6 +214,17 @@ export class FileDropExtension extends LifeCycleWatcher {
this.dragging$.value = false;
})
);
std.event.disposables.add(
std.dnd.monitor({
onDragStart: () => {
this._disableIndicator = true;
},
onDrop: () => {
this._disableIndicator = false;
},
})
);

std.event.disposables.add(
std.event.add('nativeDragOver', context => {
const event = context.get('dndState').raw;
Expand Down
33 changes: 24 additions & 9 deletions blocksuite/affine/shared/src/utils/dom/point-to-block.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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[];
}

Expand Down
2 changes: 1 addition & 1 deletion blocksuite/affine/shared/src/utils/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ export class DropIndicator extends LitElement {
if (!this.rect) {
return null;
}

const parentRect = this.parentElement!.getBoundingClientRect();
const { left, top, width, height } = this.rect;

const style = styleMap({
width: `${width}px`,
height: `${height}px`,
top: `${top}px`,
left: `${left}px`,
top: `${top - parentRect.y}px`,
left: `${left - parentRect.x}px`,
});

return html`<div class="affine-drop-indicator" style=${style}></div>`;
}

Expand Down
Loading

0 comments on commit 9971719

Please sign in to comment.