diff --git a/blocksuite/affine/blocks/frame/src/frame-block.ts b/blocksuite/affine/blocks/frame/src/frame-block.ts index 142a72841..066a0b6e6 100644 --- a/blocksuite/affine/blocks/frame/src/frame-block.ts +++ b/blocksuite/affine/blocks/frame/src/frame-block.ts @@ -2,7 +2,7 @@ import { DefaultTheme, type FrameBlockModel } from '@blocksuite/affine-model'; import { ThemeProvider } from '@blocksuite/affine-shared/services'; import { Bound } from '@blocksuite/global/gfx'; import { GfxBlockComponent } from '@blocksuite/std'; -import type { SelectedContext } from '@blocksuite/std/gfx'; +import type { BoxSelectionContext, SelectedContext } from '@blocksuite/std/gfx'; import { cssVarV2 } from '@toeverything/theme/v2'; import { html } from 'lit'; import { state } from 'lit/decorators.js'; @@ -69,6 +69,16 @@ export class FrameBlockComponent extends GfxBlockComponent { return super.onSelected(context); } + override onBoxSelected(context: BoxSelectionContext) { + const { box } = context; + const bound = new Bound(box.x, box.y, box.w, box.h); + const elementBound = this.model.elementBound; + + return ( + this.model.childElements.length === 0 || bound.contains(elementBound) + ); + } + override renderGfxBlock() { const { model, showBorder, std } = this; const backgroundColor = std diff --git a/blocksuite/affine/blocks/note/src/note-edgeless-block.ts b/blocksuite/affine/blocks/note/src/note-edgeless-block.ts index 1b78ff8da..8fe95bba2 100644 --- a/blocksuite/affine/blocks/note/src/note-edgeless-block.ts +++ b/blocksuite/affine/blocks/note/src/note-edgeless-block.ts @@ -10,7 +10,7 @@ import { } from '@blocksuite/affine-shared/utils'; import { Bound } from '@blocksuite/global/gfx'; import { toGfxBlockComponent } from '@blocksuite/std'; -import type { SelectedContext } from '@blocksuite/std/gfx'; +import type { BoxSelectionContext, SelectedContext } from '@blocksuite/std/gfx'; import { html, nothing, type PropertyValues } from 'lit'; import { query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; @@ -372,6 +372,10 @@ export class EdgelessNoteBlockComponent extends toGfxBlockComponent( } } + override onBoxSelected(_: BoxSelectionContext) { + return this.model.props.displayMode !== NoteDisplayMode.DocOnly; + } + @state() private accessor _editing = false; diff --git a/blocksuite/affine/blocks/root/src/edgeless/components/rects/edgeless-dragging-area-rect.ts b/blocksuite/affine/blocks/root/src/edgeless/components/rects/edgeless-dragging-area-rect.ts index 0724a308a..3bd1857c2 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/components/rects/edgeless-dragging-area-rect.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/components/rects/edgeless-dragging-area-rect.ts @@ -5,8 +5,10 @@ import { css, html, nothing, unsafeCSS } from 'lit'; import { styleMap } from 'lit/directives/style-map.js'; import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js'; -import { DefaultTool } from '../../gfx-tool/default-tool.js'; -import { DefaultModeDragType } from '../../gfx-tool/default-tool-ext/ext.js'; +import { + DefaultModeDragType, + DefaultTool, +} from '../../gfx-tool/default-tool.js'; export const EDGELESS_DRAGGING_AREA_WIDGET = 'edgeless-dragging-area-rect'; diff --git a/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool-ext/ext.ts b/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool-ext/ext.ts deleted file mode 100644 index aa3bb9a6a..000000000 --- a/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool-ext/ext.ts +++ /dev/null @@ -1,10 +0,0 @@ -export enum DefaultModeDragType { - /** Moving selected contents */ - ContentMoving = 'content-moving', - /** Native range dragging inside active note block */ - NativeEditing = 'native-editing', - /** Default void state */ - None = 'none', - /** Expanding the dragging area, select the content covered inside */ - Selecting = 'selecting', -} diff --git a/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool.ts b/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool.ts index 8fe02abe4..ec4ea4b37 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/gfx-tool/default-tool.ts @@ -1,27 +1,27 @@ -import { isFrameBlock } from '@blocksuite/affine-block-frame'; -import { - GroupElementModel, - MindmapElementModel, - NoteBlockModel, - NoteDisplayMode, -} from '@blocksuite/affine-model'; import { resetNativeSelection } from '@blocksuite/affine-shared/utils'; import { DisposableGroup } from '@blocksuite/global/disposable'; import type { IVec } from '@blocksuite/global/gfx'; -import { Bound } from '@blocksuite/global/gfx'; import type { PointerEventState } from '@blocksuite/std'; import { BaseTool, - getTopElements, type GfxModel, InteractivityIdentifier, isGfxGroupCompatibleModel, - type PointTestOptions, } from '@blocksuite/std/gfx'; import { effect } from '@preact/signals-core'; import { calPanDelta } from '../utils/panning-utils.js'; -import { DefaultModeDragType } from './default-tool-ext/ext.js'; + +export enum DefaultModeDragType { + /** Moving selected contents */ + ContentMoving = 'content-moving', + /** Native range dragging inside active note block */ + NativeEditing = 'native-editing', + /** Default void state */ + None = 'none', + /** Expanding the dragging area, select the content covered inside */ + Selecting = 'selecting', +} export class DefaultTool extends BaseTool { static override toolName: string = 'default'; @@ -44,11 +44,11 @@ export class DefaultTool extends BaseTool { private _disposables: DisposableGroup | null = null; - private readonly _panViewport = (delta: IVec) => { + private _panViewport(delta: IVec) { this._accumulateDelta[0] += delta[0]; this._accumulateDelta[1] += delta[1]; this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]); - }; + } private _selectionRectTransition: null | { w: number; @@ -117,44 +117,21 @@ export class DefaultTool extends BaseTool { }; } - const { x, y, w, h } = this.controller.draggingArea$.peek(); - const bound = new Bound(x, y, w, h); - - let elements = gfx.getElementsByBound(bound).filter(el => { - if (isFrameBlock(el)) { - return el.childElements.length === 0 || bound.contains(el.elementBound); - } - if (el instanceof MindmapElementModel) { - return bound.contains(el.elementBound); - } - if ( - el instanceof NoteBlockModel && - el.props.displayMode === NoteDisplayMode.DocOnly - ) { - return false; - } - return true; + const elements = this.interactivity?.handleBoxSelection({ + box: this.controller.draggingArea$.peek(), }); - elements = getTopElements(elements).filter(el => !el.isLocked()); + if (!elements) return; - const set = new Set( - gfx.keyboard.shiftKey$.peek() - ? [...elements, ...gfx.selection.selectedElements] - : elements - ); - - this.edgelessSelectionManager.set({ - elements: Array.from(set).map(element => element.id), + this.selection.set({ + elements: elements.map(el => el.id), editing: false, }); }; dragType = DefaultModeDragType.None; - enableHover = true; - - dragging = false; + movementDragging = false; /** * Get the end position of the dragging area in the model coordinate @@ -174,7 +151,7 @@ export class DefaultTool extends BaseTool { return [startX, startY] as IVec; } - get edgelessSelectionManager() { + get selection() { return this.gfx.selection; } @@ -183,52 +160,55 @@ export class DefaultTool extends BaseTool { } private async _cloneContent() { - const clonedResult = await this.interactivity?.requestElementsClone({ + const clonedResult = await this.interactivity?.requestElementClone({ elements: this._toBeMoved, }); if (!clonedResult) return; this._toBeMoved = clonedResult.elements; - this.edgelessSelectionManager.set({ + this.selection.set({ elements: this._toBeMoved.map(e => e.id), editing: false, }); } - private _determineDragType(e: PointerEventState): DefaultModeDragType { - const { x, y } = e; - // Is dragging started from current selected rect - if (this.edgelessSelectionManager.isInSelectedRect(x, y)) { - if (this.edgelessSelectionManager.selectedElements.length === 1) { - let selected = this.edgelessSelectionManager.selectedElements[0]; - // double check - const currentSelected = this._pick(x, y); - if ( - !isFrameBlock(selected) && - !(selected instanceof GroupElementModel) && - currentSelected && - currentSelected !== selected - ) { - selected = currentSelected; - this.edgelessSelectionManager.set({ - elements: [selected.id], + private _determineDragType(evt: PointerEventState): DefaultModeDragType { + const { x, y } = this.controller.lastMouseModelPos$.peek(); + if (this.selection.isInSelectedRect(x, y)) { + if (this.selection.selectedElements.length === 1) { + const currentHoveredElem = this._getElementInGroup(x, y); + let curSelected = this.selection.selectedElements[0]; + + // If one of the following condition is true, keep the selection: + // 1. if group is currently selected + // 2. if the selected element is descendant of the hovered element + // 3. not hovering any element or hovering the same element + // + // Otherwise, we update the selection to the current hovered element + const shouldKeepSelection = + isGfxGroupCompatibleModel(curSelected) || + (isGfxGroupCompatibleModel(currentHoveredElem) && + currentHoveredElem.hasDescendant(curSelected)) || + !currentHoveredElem || + currentHoveredElem === curSelected; + + if (!shouldKeepSelection) { + curSelected = currentHoveredElem; + this.selection.set({ + elements: [curSelected.id], editing: false, }); } } - return this.edgelessSelectionManager.editing + return this.selection.editing ? DefaultModeDragType.NativeEditing : DefaultModeDragType.ContentMoving; } else { - const selected = this._pick(x, y); - if (selected) { - this.edgelessSelectionManager.set({ - elements: [selected.id], - editing: false, - }); + const checked = this.interactivity?.handleElementSelection(evt); + if (checked) { return DefaultModeDragType.ContentMoving; } else { return DefaultModeDragType.Selecting; @@ -236,9 +216,7 @@ export class DefaultTool extends BaseTool { } } - private _pick(x: number, y: number, options?: PointTestOptions) { - const modelPos = this.gfx.viewport.toModelCoord(x, y); - + private _getElementInGroup(modelX: number, modelY: number) { const tryGetLockedAncestor = (e: GfxModel | null) => { if (e?.isLockedByAncestor()) { return e.groups.findLast(group => group.isLocked()); @@ -246,34 +224,7 @@ export class DefaultTool extends BaseTool { return e; }; - const result = this.gfx.getElementInGroup( - modelPos[0], - modelPos[1], - options - ); - - if (result instanceof MindmapElementModel) { - const picked = this.gfx.getElementByPoint(modelPos[0], modelPos[1], { - ...((options ?? {}) as PointTestOptions), - all: true, - }); - - let pickedIdx = picked.length - 1; - - while (pickedIdx >= 0) { - const element = picked[pickedIdx]; - if (element === result) { - pickedIdx -= 1; - continue; - } - - break; - } - - return tryGetLockedAncestor(picked[pickedIdx]) ?? null; - } - - return tryGetLockedAncestor(result); + return tryGetLockedAncestor(this.gfx.getElementInGroup(modelX, modelY)); } private initializeDragState( @@ -305,7 +256,7 @@ export class DefaultTool extends BaseTool { if (this.dragType === DefaultModeDragType.ContentMoving) { if (this.interactivity) { this.doc.captureSync(); - this.interactivity.initializeDrag({ + this.interactivity.handleElementMove({ movingElements: this._toBeMoved, event: event.raw, onDragEnd: () => { @@ -320,8 +271,8 @@ export class DefaultTool extends BaseTool { override click(e: PointerEventState) { if (this.doc.readonly) return; - if (!this.interactivity?.dispatchOnSelected(e)) { - this.edgelessSelectionManager.clear(); + if (!this.interactivity?.handleElementSelection(e)) { + this.selection.clear(); resetNativeSelection(null); } @@ -353,9 +304,9 @@ export class DefaultTool extends BaseTool { override dragEnd(e: PointerEventState) { this.interactivity?.dispatchEvent('dragend', e); - if (this.edgelessSelectionManager.editing || !this.dragging) return; + if (this.selection.editing || !this.movementDragging) return; - this.dragging = false; + this.movementDragging = false; this._toBeMoved = []; this._clearSelectingState(); this.dragType = DefaultModeDragType.None; @@ -364,7 +315,7 @@ export class DefaultTool extends BaseTool { override dragMove(e: PointerEventState) { this.interactivity?.dispatchEvent('dragmove', e); - if (!this.dragging) { + if (!this.movementDragging) { return; } @@ -397,19 +348,14 @@ export class DefaultTool extends BaseTool { const { preventDefaultState, handledByView } = this.interactivity?.dispatchEvent('dragstart', e) ?? {}; - if ( - this.edgelessSelectionManager.editing || - preventDefaultState || - handledByView - ) - return; + if (this.selection.editing || preventDefaultState || handledByView) return; - this.dragging = true; + this.movementDragging = true; // Determine the drag type based on the current state and event let dragType = this._determineDragType(e); - const elements = this.edgelessSelectionManager.selectedElements; + const elements = this.selection.selectedElements; if (elements.some(e => e.isLocked())) return; const toBeMoved = new Set(elements); @@ -468,8 +414,6 @@ export class DefaultTool extends BaseTool { this.interactivity?.dispatchEvent('pointerup', e); } - override tripleClick() {} - override unmounted(): void {} } diff --git a/blocksuite/affine/gfx/mindmap/src/view/view.ts b/blocksuite/affine/gfx/mindmap/src/view/view.ts index 4090fb1fe..40c0d1042 100644 --- a/blocksuite/affine/gfx/mindmap/src/view/view.ts +++ b/blocksuite/affine/gfx/mindmap/src/view/view.ts @@ -7,8 +7,13 @@ import { } from '@blocksuite/affine-model'; import { TelemetryProvider } from '@blocksuite/affine-shared/services'; import { requestThrottledConnectedFrame } from '@blocksuite/affine-shared/utils'; +import { Bound } from '@blocksuite/global/gfx'; import type { PointerEventState } from '@blocksuite/std'; -import { GfxElementModelView } from '@blocksuite/std/gfx'; +import { + type BoxSelectionContext, + GfxElementModelView, + type SelectedContext, +} from '@blocksuite/std/gfx'; import { handleLayout } from './utils.js'; @@ -329,6 +334,40 @@ export class MindMapView extends GfxElementModelView { return collapseButton; } + override onSelected(context: SelectedContext): void | boolean { + const { position } = context; + const target = this.model.childElements.find(child => { + if (child.elementBound.containsPoint([position.x, position.y])) { + return true; + } + + return false; + }); + + if (target) { + if (this.model.isLocked()) { + return super.onSelected(context); + } + + if (context.multiSelect) { + this.gfx.selection.toggle(target); + } else { + this.gfx.selection.set({ elements: [target.id] }); + } + + return true; + } + + return false; + } + + override onBoxSelected(context: BoxSelectionContext) { + const { box } = context; + const bound = new Bound(box.x, box.y, box.w, box.h); + + return bound.contains(this.model.elementBound); + } + override onCreated(): void { this._setLayoutMethod(); this._initCollapseButtons(); diff --git a/blocksuite/framework/std/src/gfx/index.ts b/blocksuite/framework/std/src/gfx/index.ts index 46a2e730e..67278ee23 100644 --- a/blocksuite/framework/std/src/gfx/index.ts +++ b/blocksuite/framework/std/src/gfx/index.ts @@ -16,6 +16,7 @@ export { GfxExtension, GfxExtensionIdentifier } from './extension.js'; export { GridManager } from './grid.js'; export { GfxControllerIdentifier } from './identifiers.js'; export type { + BoxSelectionContext, DragEndContext, DragExtensionInitializeContext, DragInitializationOption, diff --git a/blocksuite/framework/std/src/gfx/interactivity/index.ts b/blocksuite/framework/std/src/gfx/interactivity/index.ts index 3cc87a2cf..a1f936c2f 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/index.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/index.ts @@ -10,6 +10,7 @@ export type { ExtensionDragStartContext, } from './types/drag.js'; export type { + BoxSelectionContext, DragEndContext, DragMoveContext, DragStartContext, diff --git a/blocksuite/framework/std/src/gfx/interactivity/manager.ts b/blocksuite/framework/std/src/gfx/interactivity/manager.ts index b518edfe2..cf2142cd3 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/manager.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/manager.ts @@ -3,6 +3,7 @@ import { DisposableGroup } from '@blocksuite/global/disposable'; import { Bound, Point } from '@blocksuite/global/gfx'; import type { PointerEventState } from '../../event/state/pointer.js'; +import { getTopElements } from '../../utils/tree.js'; import { GfxExtension, GfxExtensionIdentifier } from '../extension.js'; import type { GfxModel } from '../model/model.js'; import { createInteractionContext, type SupportedEvents } from './event.js'; @@ -20,6 +21,7 @@ import type { ExtensionDragMoveContext, ExtensionDragStartContext, } from './types/drag.js'; +import type { BoxSelectionContext } from './types/view.js'; type ExtensionPointerHandler = Exclude< SupportedEvents, @@ -90,7 +92,12 @@ export class InteractivityManager extends GfxExtension { }; } - dispatchOnSelected(evt: PointerEventState) { + /** + * Handle element selection. + * @param evt The pointer event that triggered the selection. + * @returns True if the element was selected, false otherwise. + */ + handleElementSelection(evt: PointerEventState) { const { raw } = evt; const { gfx } = this; const [x, y] = gfx.viewport.toModelCoordFromClientCoord([raw.x, raw.y]); @@ -122,15 +129,30 @@ export class InteractivityManager extends GfxExtension { return false; } + handleBoxSelection(context: { box: BoxSelectionContext['box'] }) { + const elements = this.gfx.getElementsByBound(context.box).filter(model => { + const view = this.gfx.view.get(model); + + if ( + !view || + view.onBoxSelected({ + box: context.box, + }) === false + ) + return false; + + return true; + }); + + return getTopElements(elements).filter(elm => !elm.isLocked()); + } + /** - * Initialize drag operation for elements. - * Handles drag start, move and end events automatically. + * Initialize elements movements. + * It will handle drag start, move and end events automatically. * Note: Call this when mouse is already down. - * - * @param options - * @returns */ - initializeDrag(options: DragInitializationOption) { + handleElementMove(options: DragInitializationOption) { let cancelledByExt = false; const context: DragExtensionInitializeContext = { @@ -294,7 +316,7 @@ export class InteractivityManager extends GfxExtension { dragStart(); } - requestElementsClone(options: RequestElementsCloneContext) { + requestElementClone(options: RequestElementsCloneContext) { const extensions = this.interactExtensions; for (let ext of extensions.values()) { diff --git a/blocksuite/framework/std/src/gfx/interactivity/types/view.ts b/blocksuite/framework/std/src/gfx/interactivity/types/view.ts index 01611b6b3..8f7e52ee5 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/types/view.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/types/view.ts @@ -1,4 +1,4 @@ -import type { Bound, IPoint } from '@blocksuite/global/gfx'; +import type { Bound, IBound, IPoint } from '@blocksuite/global/gfx'; import type { GfxBlockComponent } from '../../../view/element/gfx-block-component.js'; import type { GfxModel } from '../../model/model.js'; @@ -56,11 +56,25 @@ export type SelectedContext = { position: IPoint; /** - * If the current selection is a fallback selection, like selecting the element inside a group, the group will be selected instead + * If the current selection is a fallback selection. + * + * E.g., if selecting a child element inside a group, the `onSelected` method will be executed on group, and + * the fallback is true because the it's not the original target(the child element). */ fallback: boolean; }; +export type BoxSelectionContext = { + box: Readonly< + IBound & { + startX: number; + startY: number; + endX: number; + endY: number; + } + >; +}; + export type GfxViewTransformInterface = { onDragStart: (context: DragStartContext) => void; onDragMove: (context: DragMoveContext) => void; @@ -70,8 +84,11 @@ export type GfxViewTransformInterface = { /** * When the element is selected by the pointer - * @param context - * @returns */ onSelected: (context: SelectedContext) => void; + + /** + * When the element is selected by box selection, return false to prevent the default selection behavior. + */ + onBoxSelected: (context: BoxSelectionContext) => boolean | void; }; diff --git a/blocksuite/framework/std/src/gfx/selection.ts b/blocksuite/framework/std/src/gfx/selection.ts index ecb2b8736..79e8b6b8e 100644 --- a/blocksuite/framework/std/src/gfx/selection.ts +++ b/blocksuite/framework/std/src/gfx/selection.ts @@ -204,12 +204,11 @@ export class GfxSelectionManager extends GfxExtension { return selections.every(sel => sel.elements.length === 0); } - isInSelectedRect(viewX: number, viewY: number) { + isInSelectedRect(modelX: number, modelY: number) { const selected = this.selectedElements; if (!selected.length) return false; const commonBound = getCommonBoundWithRotation(selected); - const [modelX, modelY] = this.gfx.viewport.toModelCoord(viewX, viewY); if (commonBound && commonBound.isPointInBound([modelX, modelY])) { return true; } diff --git a/blocksuite/framework/std/src/gfx/tool/tool-controller.ts b/blocksuite/framework/std/src/gfx/tool/tool-controller.ts index ed33af89a..19e8fac37 100644 --- a/blocksuite/framework/std/src/gfx/tool/tool-controller.ts +++ b/blocksuite/framework/std/src/gfx/tool/tool-controller.ts @@ -2,7 +2,7 @@ import type { ServiceIdentifier } from '@blocksuite/global/di'; import { DisposableGroup } from '@blocksuite/global/disposable'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import type { IBound, IPoint } from '@blocksuite/global/gfx'; -import { Signal } from '@preact/signals-core'; +import { computed, Signal } from '@preact/signals-core'; import { Subject } from 'rxjs'; import type { PointerEventState } from '../../event/index.js'; @@ -125,6 +125,18 @@ export class ToolController extends GfxExtension { y: 0, }); + readonly lastMouseModelPos$ = computed(() => { + const [x, y] = this.gfx.viewport.toModelCoord( + this.lastMousePos$.value.x, + this.lastMousePos$.value.y + ); + + return { + x, + y, + }; + }); + get currentTool$() { // oxlint-disable-next-line typescript/no-this-alias const self = this; @@ -330,6 +342,10 @@ export class ToolController extends GfxExtension { w: 0, h: 0, }; + this.lastMousePos$.value = { + x: evt.x, + y: evt.y, + }; // this means the dragEnd event is not even fired // so we need to manually call the dragEnd method @@ -372,6 +388,11 @@ export class ToolController extends GfxExtension { endY: evt.y, }; + this.lastMousePos$.value = { + x: evt.x, + y: evt.y, + }; + invokeToolHandler('dragMove', evt, dragContext?.tool); }) ); diff --git a/blocksuite/framework/std/src/gfx/view/view.ts b/blocksuite/framework/std/src/gfx/view/view.ts index 4d2d83283..84f755527 100644 --- a/blocksuite/framework/std/src/gfx/view/view.ts +++ b/blocksuite/framework/std/src/gfx/view/view.ts @@ -1,13 +1,14 @@ import { type Container, createIdentifier } from '@blocksuite/global/di'; import { DisposableGroup } from '@blocksuite/global/disposable'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; -import type { Bound, IVec } from '@blocksuite/global/gfx'; +import { type Bound, type IVec } from '@blocksuite/global/gfx'; import type { Extension } from '@blocksuite/store'; import type { PointerEventState } from '../../event/index.js'; import type { EditorHost } from '../../view/index.js'; import type { GfxController } from '../index.js'; import type { + BoxSelectionContext, DragEndContext, DragMoveContext, DragStartContext, @@ -221,6 +222,8 @@ export class GfxElementModelView< } } + onBoxSelected(_: BoxSelectionContext): boolean | void {} + onResize = () => {}; onRotate = () => {}; diff --git a/blocksuite/framework/std/src/view/element/gfx-block-component.ts b/blocksuite/framework/std/src/view/element/gfx-block-component.ts index 660957d0a..e3bebaf09 100644 --- a/blocksuite/framework/std/src/view/element/gfx-block-component.ts +++ b/blocksuite/framework/std/src/view/element/gfx-block-component.ts @@ -6,6 +6,7 @@ import { nothing } from 'lit'; import type { BlockService } from '../../extension/index.js'; import { GfxControllerIdentifier } from '../../gfx/identifiers.js'; import type { + BoxSelectionContext, DragMoveContext, GfxViewTransformInterface, SelectedContext, @@ -113,6 +114,8 @@ export abstract class GfxBlockComponent< return true; } + onBoxSelected(_: BoxSelectionContext) {} + onRotate() {} onResize() {} @@ -231,6 +234,8 @@ export function toGfxBlockComponent< return true; } + onBoxSelected(_: BoxSelectionContext) {} + onRotate() {} onResize() {} diff --git a/tests/blocksuite/e2e/edgeless/frame/frame-mindmap.spec.ts b/tests/blocksuite/e2e/edgeless/frame/frame-mindmap.spec.ts index 05fe01545..3cb41587b 100644 --- a/tests/blocksuite/e2e/edgeless/frame/frame-mindmap.spec.ts +++ b/tests/blocksuite/e2e/edgeless/frame/frame-mindmap.spec.ts @@ -22,7 +22,7 @@ const dragBetweenViewCoords = async ( end: number[] ) => { // dragging slowly may drop frame if mindmap is existed, so for test we drag quickly - await _dragBetweenViewCoords(page, start, end, { steps: 2 }); + await _dragBetweenViewCoords(page, start, end, { steps: 10 }); await waitNextFrame(page); }; diff --git a/tests/blocksuite/e2e/edgeless/mindmap.spec.ts b/tests/blocksuite/e2e/edgeless/mindmap.spec.ts index 262348324..a442e23f4 100644 --- a/tests/blocksuite/e2e/edgeless/mindmap.spec.ts +++ b/tests/blocksuite/e2e/edgeless/mindmap.spec.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test'; -import { clickView } from '../utils/actions/click.js'; +import { click, clickView } from '../utils/actions/click.js'; import { dragBetweenCoords } from '../utils/actions/drag.js'; import { addBasicRectShapeElement, @@ -272,7 +272,7 @@ test('drag root node should layout in real time', async ({ page }) => { // assert when dragging is in progress await waitNextFrame(page, 500); - await assertMindMapNodesPosition(50, 50); + await assertMindMapNodesPosition(54, 54); await page.mouse.up(); }); @@ -297,6 +297,7 @@ test('drag node out of mind map should detach the node and create a new mind map }); const { rect } = await getMindMapNode(page, mindmapId, [0, 1]); + await click(page, { x: rect.x + rect.w / 2, y: rect.y + rect.h / 2 }); await dragBetweenCoords( page, {