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 index c9766d87a..aa3bb9a6a 100644 --- 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 @@ -1,14 +1,10 @@ export enum DefaultModeDragType { - /** Moving connector label */ - ConnectorLabelMoving = 'connector-label-moving', /** Moving selected contents */ ContentMoving = 'content-moving', /** Native range dragging inside active note block */ NativeEditing = 'native-editing', /** Default void state */ None = 'none', - /** Dragging preview */ - PreviewDragging = 'preview-dragging', /** 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 d2a828c3c..0e66d66d2 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 @@ -2,12 +2,8 @@ import { type FrameOverlay, isFrameBlock, } from '@blocksuite/affine-block-frame'; +import { OverlayIdentifier } from '@blocksuite/affine-block-surface'; import { - ConnectorUtils, - OverlayIdentifier, -} from '@blocksuite/affine-block-surface'; -import { - type ConnectorElementModel, GroupElementModel, MindmapElementModel, NoteBlockModel, @@ -16,7 +12,7 @@ import { import { resetNativeSelection } from '@blocksuite/affine-shared/utils'; import { DisposableGroup } from '@blocksuite/global/disposable'; import type { IVec } from '@blocksuite/global/gfx'; -import { Bound, Vec } from '@blocksuite/global/gfx'; +import { Bound } from '@blocksuite/global/gfx'; import type { PointerEventState } from '@blocksuite/std'; import { BaseTool, @@ -29,7 +25,6 @@ import { import { effect } from '@preact/signals-core'; import { calPanDelta } from '../utils/panning-utils.js'; -import { isCanvasElement } from '../utils/query.js'; import { DefaultModeDragType } from './default-tool-ext/ext.js'; export class DefaultTool extends BaseTool { @@ -59,11 +54,6 @@ export class DefaultTool extends BaseTool { this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]); }; - // For moving the connector label - private _selectedConnector: ConnectorElementModel | null = null; - - private _selectedConnectorLabelBounds: Bound | null = null; - private _selectionRectTransition: null | { w: number; h: number; @@ -168,6 +158,8 @@ export class DefaultTool extends BaseTool { enableHover = true; + dragging = false; + /** * Get the end position of the dragging area in the model coordinate */ @@ -232,20 +224,6 @@ export class DefaultTool extends BaseTool { editing: false, }); } - - if ( - isCanvasElement(selected) && - ConnectorUtils.isConnectorWithLabel(selected) && - (selected as ConnectorElementModel).labelIncludesPoint( - this.gfx.viewport.toModelCoord(x, y) - ) - ) { - this._selectedConnector = selected as ConnectorElementModel; - this._selectedConnectorLabelBounds = Bound.fromXYWH( - this._selectedConnector.labelXYWH! - ); - return DefaultModeDragType.ConnectorLabelMoving; - } } return this.edgelessSelectionManager.editing @@ -259,20 +237,6 @@ export class DefaultTool extends BaseTool { editing: false, }); - if ( - isCanvasElement(selected) && - ConnectorUtils.isConnectorWithLabel(selected) && - (selected as ConnectorElementModel).labelIncludesPoint( - this.gfx.viewport.toModelCoord(x, y) - ) - ) { - this._selectedConnector = selected as ConnectorElementModel; - this._selectedConnectorLabelBounds = Bound.fromXYWH( - this._selectedConnector.labelXYWH! - ); - return DefaultModeDragType.ConnectorLabelMoving; - } - return DefaultModeDragType.ContentMoving; } else { return DefaultModeDragType.Selecting; @@ -280,24 +244,6 @@ export class DefaultTool extends BaseTool { } } - private _moveLabel(delta: IVec) { - const connector = this._selectedConnector; - let bounds = this._selectedConnectorLabelBounds; - if (!connector || !bounds) return; - bounds = bounds.clone(); - const center = connector.getNearestPoint( - Vec.add(bounds.center, delta) as IVec - ); - const distance = connector.getOffsetDistanceByPoint(center as IVec); - bounds.center = center; - this.gfx.updateElement(connector, { - labelXYWH: bounds.toXYWH(), - labelOffset: { - distance, - }, - }); - } - private _pick(x: number, y: number, options?: PointTestOptions) { const modelPos = this.gfx.viewport.toModelCoord(x, y); @@ -387,7 +333,7 @@ export class DefaultTool extends BaseTool { resetNativeSelection(null); } - this.interactivity?.dispatch('click', e); + this.interactivity?.dispatchEvent('click', e); } override deactivate() { @@ -409,21 +355,28 @@ export class DefaultTool extends BaseTool { return; } - this.interactivity?.dispatch('dblclick', e); + this.interactivity?.dispatchEvent('dblclick', e); } - override dragEnd() { - if (this.edgelessSelectionManager.editing) return; + override dragEnd(e: PointerEventState) { + this.interactivity?.dispatchEvent('dragend', e); + if (this.edgelessSelectionManager.editing || !this.dragging) return; + + this.dragging = false; this.frameOverlay.clear(); this._toBeMoved = []; - this._selectedConnector = null; - this._selectedConnectorLabelBounds = null; this._clearSelectingState(); this.dragType = DefaultModeDragType.None; } override dragMove(e: PointerEventState) { + this.interactivity?.dispatchEvent('dragmove', e); + + if (!this.dragging) { + return; + } + const { viewport } = this.gfx; switch (this.dragType) { case DefaultModeDragType.Selecting: { @@ -441,12 +394,6 @@ export class DefaultTool extends BaseTool { case DefaultModeDragType.ContentMoving: { break; } - case DefaultModeDragType.ConnectorLabelMoving: { - const dx = this.dragLastPos[0] - this.dragStartPos[0]; - const dy = this.dragLastPos[1] - this.dragStartPos[1]; - this._moveLabel([dx, dy]); - break; - } case DefaultModeDragType.NativeEditing: { // TODO reset if drag out of note break; @@ -456,7 +403,18 @@ export class DefaultTool extends BaseTool { // eslint-disable-next-line @typescript-eslint/no-misused-promises override async dragStart(e: PointerEventState) { - if (this.edgelessSelectionManager.editing) return; + const { preventDefaultState, handledByView } = + this.interactivity?.dispatchEvent('dragstart', e) ?? {}; + + if ( + this.edgelessSelectionManager.editing || + preventDefaultState || + handledByView + ) + return; + + this.dragging = true; + // Determine the drag type based on the current state and event let dragType = this._determineDragType(e); @@ -508,7 +466,7 @@ export class DefaultTool extends BaseTool { } override pointerDown(e: PointerEventState): void { - this.interactivity?.dispatch('pointerdown', e); + this.interactivity?.dispatchEvent('pointerdown', e); } override pointerMove(e: PointerEventState) { @@ -527,11 +485,11 @@ export class DefaultTool extends BaseTool { this.frameOverlay.clear(); } - this.interactivity?.dispatch('pointermove', e); + this.interactivity?.dispatchEvent('pointermove', e); } override pointerUp(e: PointerEventState) { - this.interactivity?.dispatch('pointerup', e); + this.interactivity?.dispatchEvent('pointerup', e); } override tripleClick() {} diff --git a/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/dblclick-add-edgeless-text.ts b/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/dblclick-add-edgeless-text.ts index e0ac099c4..a8e57f4d9 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/dblclick-add-edgeless-text.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/interact-extensions/dblclick-add-edgeless-text.ts @@ -6,14 +6,17 @@ import { FeatureFlagService, TelemetryProvider, } from '@blocksuite/affine-shared/services'; -import type { PointerEventState } from '@blocksuite/std'; -import { InteractivityExtension } from '@blocksuite/std/gfx'; +import { + type GfxInteractivityContext, + InteractivityExtension, +} from '@blocksuite/std/gfx'; export class DblClickAddEdgelessText extends InteractivityExtension { static override key = 'dbl-click-add-edgeless-text'; override mounted() { - this.event.on('dblclick', (e: PointerEventState) => { + this.event.on('dblclick', (ctx: GfxInteractivityContext) => { + const { event: e } = ctx; const textFlag = this.std.store .get(FeatureFlagService) .getFlag('enable_edgeless_text'); diff --git a/blocksuite/affine/gfx/connector/src/view/view.ts b/blocksuite/affine/gfx/connector/src/view/view.ts index 2da5a4fcb..830776629 100644 --- a/blocksuite/affine/gfx/connector/src/view/view.ts +++ b/blocksuite/affine/gfx/connector/src/view/view.ts @@ -1,8 +1,14 @@ -import type { ConnectorElementModel } from '@blocksuite/affine-model'; +import { + type ConnectorElementModel, + LocalShapeElementModel, +} from '@blocksuite/affine-model'; +import { Bound, serializeXYWH, Vec } from '@blocksuite/global/gfx'; +import type { PointerEventState } from '@blocksuite/std'; import { type DragEndContext, type DragMoveContext, type DragStartContext, + generateKeyBetween, GfxElementModelView, } from '@blocksuite/std/gfx'; @@ -30,11 +36,17 @@ export class ConnectorElementView extends GfxElementModelView { + private _initLabelMoving(): void { + let curLabelElement: LocalShapeElementModel | null = null; + + if (this.model.isLocked()) { + return; + } + + const enterLabelEditor = (evt: PointerEventState) => { const edgeless = this.std.view.getBlock(this.std.store.root!.id); if (edgeless && !this.model.isLocked()) { @@ -44,6 +56,121 @@ export class ConnectorElementView extends GfxElementModelView { + const [x, y] = this.gfx.viewport.toModelCoord(evt.x, evt.y); + return { + x, + y, + clientX: evt.raw.clientX, + clientY: evt.raw.clientY, + }; + }; + const watchEvent = (labelModel: LocalShapeElementModel) => { + const view = this.gfx.view.get(labelModel) as GfxElementModelView; + const connectorModel = this.model; + + let labelBound: Bound | null = null; + let startPoint = { + x: 0, + y: 0, + clientX: 0, + clientY: 0, + }; + let lastPoint = { + x: 0, + y: 0, + clientX: 0, + clientY: 0, + }; + + view.on('dblclick', evt => { + enterLabelEditor(evt); + }); + view.on('dragstart', evt => { + startPoint = getCurrentPosition(evt); + labelBound = Bound.deserialize(labelModel.xywh); + + connectorModel.stash('labelXYWH'); + connectorModel.stash('labelOffset'); + }); + + view.on('dragmove', evt => { + if (!labelBound) { + return; + } + + lastPoint = getCurrentPosition(evt); + const newBound = labelBound.clone(); + const delta = [lastPoint.x - startPoint.x, lastPoint.y - startPoint.y]; + const center = connectorModel.getNearestPoint( + Vec.add(newBound.center, delta) + ); + const distance = connectorModel.getOffsetDistanceByPoint(center); + newBound.center = center; + + connectorModel.labelXYWH = newBound.toXYWH(); + connectorModel.labelOffset = { + distance, + }; + }); + + view.on('dragend', () => { + if (labelBound) { + labelBound = null; + connectorModel.pop('labelXYWH'); + connectorModel.pop('labelOffset'); + } + }); + }; + const updateLabelElement = () => { + if (!this.model.labelXYWH || !this.model.text) { + // Clean up existing label element if conditions are no longer met + if (curLabelElement) { + this.surface.deleteLocalElement(curLabelElement); + curLabelElement = null; + } + return; + } + + const labelElement = + curLabelElement || new LocalShapeElementModel(this.surface); + labelElement.xywh = serializeXYWH(...this.model.labelXYWH); + labelElement.index = generateKeyBetween(this.model.index, null); + + if (!curLabelElement) { + curLabelElement = labelElement; + + labelElement.fillColor = 'transparent'; + labelElement.strokeColor = 'transparent'; + labelElement.strokeWidth = 0; + + this.surface.addLocalElement(labelElement); + this.disposable.add(() => { + this.surface.deleteLocalElement(labelElement); + }); + watchEvent(labelElement); + } + }; + + this.disposable.add( + this.model.propsUpdated.subscribe(payload => { + if ( + payload.key === 'labelXYWH' || + payload.key === 'text' || + payload.key === 'index' + ) { + updateLabelElement(); + } + }) + ); + + updateLabelElement(); + + this.on('dblclick', evt => { + if (!curLabelElement) { + enterLabelEditor(evt); + } }); } } diff --git a/blocksuite/affine/gfx/shape/src/view.ts b/blocksuite/affine/gfx/shape/src/view.ts index e4ed7e7e8..03b2ed678 100644 --- a/blocksuite/affine/gfx/shape/src/view.ts +++ b/blocksuite/affine/gfx/shape/src/view.ts @@ -1,4 +1,4 @@ -import type { ShapeElementModel } from '@blocksuite/affine-model'; +import { ShapeElementModel } from '@blocksuite/affine-model'; import { GfxElementModelView } from '@blocksuite/std/gfx'; import { mountShapeTextEditor } from './text/edgeless-shape-text-editor'; @@ -16,7 +16,11 @@ export class ShapeElementView extends GfxElementModelView { this.on('dblclick', () => { const edgeless = this.std.view.getBlock(this.std.store.root!.id); - if (edgeless && !this.model.isLocked()) { + if ( + edgeless && + !this.model.isLocked() && + this.model instanceof ShapeElementModel + ) { mountShapeTextEditor(this.model, edgeless); } }); diff --git a/blocksuite/framework/std/src/gfx/index.ts b/blocksuite/framework/std/src/gfx/index.ts index eec03db9b..46a2e730e 100644 --- a/blocksuite/framework/std/src/gfx/index.ts +++ b/blocksuite/framework/std/src/gfx/index.ts @@ -24,6 +24,7 @@ export type { ExtensionDragEndContext, ExtensionDragMoveContext, ExtensionDragStartContext, + GfxInteractivityContext, SelectedContext, } from './interactivity/index.js'; export { diff --git a/blocksuite/framework/std/src/gfx/interactivity/event.ts b/blocksuite/framework/std/src/gfx/interactivity/event.ts index dbbf79a0d..e143f213b 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/event.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/event.ts @@ -1,3 +1,5 @@ +import type { PointerEventState, UIEventState } from '../../event'; + export type SupportedEvents = | 'click' | 'dblclick' @@ -5,4 +7,42 @@ export type SupportedEvents = | 'pointerenter' | 'pointerleave' | 'pointermove' - | 'pointerup'; + | 'pointerup' + | 'dragstart' + | 'dragmove' + | 'dragend'; + +export type GfxInteractivityContext< + EventState extends UIEventState = PointerEventState, + RawEvent extends Event = EventState['event'], +> = { + event: EventState; + + /** + * The raw dom event. + */ + raw: RawEvent; + + /** + * Prevent the default gfx interaction + */ + preventDefault: () => void; +}; + +export const createInteractionContext = (event: PointerEventState) => { + let preventDefaultState = false; + + return { + context: { + event, + raw: event.raw, + preventDefault: () => { + preventDefaultState = true; + event.raw.preventDefault(); + }, + }, + get preventDefaultState() { + return preventDefaultState; + }, + }; +}; diff --git a/blocksuite/framework/std/src/gfx/interactivity/extension/base.ts b/blocksuite/framework/std/src/gfx/interactivity/extension/base.ts index 00b50d088..e7e80a1a5 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/extension/base.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/extension/base.ts @@ -2,11 +2,10 @@ import { type Container, createIdentifier } from '@blocksuite/global/di'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { Extension } from '@blocksuite/store'; -import type { PointerEventState } from '../../../event/index.js'; import type { GfxController } from '../../controller.js'; import { GfxControllerIdentifier } from '../../identifiers.js'; import type { GfxModel } from '../../model/model.js'; -import type { SupportedEvents } from '../event.js'; +import type { GfxInteractivityContext, SupportedEvents } from '../event.js'; import type { ExtensionElementsCloneContext } from '../types/clone.js'; import type { DragExtensionInitializeContext, @@ -64,10 +63,13 @@ export class InteractivityExtension extends Extension { export class InteractivityEventAPI { private readonly _handlersMap = new Map< SupportedEvents, - ((evt: PointerEventState) => void)[] + ((evt: GfxInteractivityContext) => void)[] >(); - on(eventName: SupportedEvents, handler: (evt: PointerEventState) => void) { + on( + eventName: SupportedEvents, + handler: (evt: GfxInteractivityContext) => void + ) { const handlers = this._handlersMap.get(eventName) ?? []; handlers.push(handler); this._handlersMap.set(eventName, handlers); @@ -81,7 +83,7 @@ export class InteractivityEventAPI { }; } - emit(eventName: SupportedEvents, evt: PointerEventState) { + emit(eventName: SupportedEvents, evt: GfxInteractivityContext) { const handlers = this._handlersMap.get(eventName); if (!handlers) { return; diff --git a/blocksuite/framework/std/src/gfx/interactivity/gfx-view-event-handler.ts b/blocksuite/framework/std/src/gfx/interactivity/gfx-view-event-handler.ts index cd9096819..b6a0e3f19 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/gfx-view-event-handler.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/gfx-view-event-handler.ts @@ -3,14 +3,15 @@ import last from 'lodash-es/last'; import type { PointerEventState } from '../../event'; import type { GfxController } from '../controller.js'; -import type { GfxElementModelView } from '../view/view.js'; +import type { GfxElementModelView, SupportedEvent } from '../view/view.js'; export class GfxViewEventManager { - private _currentStackedElm: GfxElementModelView[] = []; + private _hoveredElementsStack: GfxElementModelView[] = []; + private _draggingElement: GfxElementModelView | null = null; private _callInReverseOrder( callback: (view: GfxElementModelView) => void, - arr = this._currentStackedElm + arr = this._hoveredElementsStack ) { for (let i = arr.length - 1; i >= 0; i--) { const view = arr[i]; @@ -21,26 +22,52 @@ export class GfxViewEventManager { constructor(private readonly gfx: GfxController) {} - click(_evt: PointerEventState): void { - last(this._currentStackedElm)?.dispatch('click', _evt); + dispatch(eventName: SupportedEvent, evt: PointerEventState) { + if (eventName === 'pointermove') { + this._handlePointerMove(evt); + return false; + } else if (eventName.startsWith('drag')) { + return this._handleDrag( + eventName as 'dragstart' | 'dragend' | 'dragmove', + evt + ); + } else { + return last(this._hoveredElementsStack)?.dispatch(eventName, evt); + } } - dblClick(_evt: PointerEventState): void { - last(this._currentStackedElm)?.dispatch('dblclick', _evt); + private _handleDrag( + evtName: 'dragstart' | 'dragend' | 'dragmove', + _evt: PointerEventState + ): boolean { + switch (evtName) { + case 'dragstart': { + if (this._draggingElement) { + this._draggingElement.dispatch('dragend', _evt); + } + this._draggingElement = last(this._hoveredElementsStack) ?? null; + return this._draggingElement?.dispatch('dragstart', _evt) ?? false; + } + case 'dragmove': { + return this._draggingElement?.dispatch('dragmove', _evt) ?? false; + } + case 'dragend': { + const dispatched = + this._draggingElement?.dispatch('dragend', _evt) ?? false; + this._draggingElement = null; + return dispatched; + } + } } - pointerDown(_evt: PointerEventState): void { - last(this._currentStackedElm)?.dispatch('pointerdown', _evt); - } - - pointerMove(_evt: PointerEventState): void { + private _handlePointerMove(_evt: PointerEventState): void { const [x, y] = this.gfx.viewport.toModelCoord(_evt.x, _evt.y); const hoveredElmViews = this.gfx.grid .search(new Bound(x - 5, y - 5, 10, 10), { filter: ['canvas', 'local'], }) .map(model => this.gfx.view.get(model)) as GfxElementModelView[]; - const currentStackedViews = new Set(this._currentStackedElm); + const currentStackedViews = new Set(this._hoveredElementsStack); const visited = new Set(); this._callInReverseOrder(view => { @@ -54,10 +81,6 @@ export class GfxViewEventManager { this._callInReverseOrder( view => !visited.has(view) && view.dispatch('pointerleave', _evt) ); - this._currentStackedElm = hoveredElmViews; - } - - pointerUp(_evt: PointerEventState): void { - last(this._currentStackedElm)?.dispatch('pointerup', _evt); + this._hoveredElementsStack = hoveredElmViews; } } diff --git a/blocksuite/framework/std/src/gfx/interactivity/index.ts b/blocksuite/framework/std/src/gfx/interactivity/index.ts index 7dd2e4639..3cc87a2cf 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/index.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/index.ts @@ -1,3 +1,4 @@ +export type { GfxInteractivityContext } from './event.js'; export { InteractivityExtension } from './extension/base.js'; export { GfxViewEventManager } from './gfx-view-event-handler.js'; export { InteractivityIdentifier, InteractivityManager } from './manager.js'; diff --git a/blocksuite/framework/std/src/gfx/interactivity/manager.ts b/blocksuite/framework/std/src/gfx/interactivity/manager.ts index 25d196e4d..b518edfe2 100644 --- a/blocksuite/framework/std/src/gfx/interactivity/manager.ts +++ b/blocksuite/framework/std/src/gfx/interactivity/manager.ts @@ -5,7 +5,7 @@ import { Bound, Point } from '@blocksuite/global/gfx'; import type { PointerEventState } from '../../event/state/pointer.js'; import { GfxExtension, GfxExtensionIdentifier } from '../extension.js'; import type { GfxModel } from '../model/model.js'; -import type { SupportedEvents } from './event.js'; +import { createInteractionContext, type SupportedEvents } from './event.js'; import { type InteractivityActionAPI, type InteractivityEventAPI, @@ -30,16 +30,6 @@ export const InteractivityIdentifier = GfxExtensionIdentifier( 'interactivity-manager' ) as ServiceIdentifier; -const CAMEL_CASE_MAP: { - [key in ExtensionPointerHandler]: keyof GfxViewEventManager; -} = { - click: 'click', - dblclick: 'dblClick', - pointerdown: 'pointerDown', - pointermove: 'pointerMove', - pointerup: 'pointerUp', -}; - export class InteractivityManager extends GfxExtension { static override key = 'interactivity-manager'; @@ -78,20 +68,26 @@ export class InteractivityManager extends GfxExtension { } /** - * Dispatch the event to canvas elements + * Dispatch event to extensions and gfx view. * @param eventName * @param evt + * @returns */ - dispatch(eventName: ExtensionPointerHandler, evt: PointerEventState) { - const handlerName = CAMEL_CASE_MAP[eventName]; - - this.canvasEventHandler[handlerName](evt); - + dispatchEvent(eventName: ExtensionPointerHandler, evt: PointerEventState) { + const { context, preventDefaultState } = createInteractionContext(evt); const extensions = this.interactExtensions; extensions.forEach(ext => { - (ext.event as InteractivityEventAPI).emit(eventName, evt); + (ext.event as InteractivityEventAPI).emit(eventName, context); }); + + const handledByView = + this.canvasEventHandler.dispatch(eventName, evt) ?? false; + + return { + preventDefaultState, + handledByView, + }; } dispatchOnSelected(evt: PointerEventState) { @@ -126,6 +122,14 @@ export class InteractivityManager extends GfxExtension { return false; } + /** + * Initialize drag operation for elements. + * Handles drag start, move and end events automatically. + * Note: Call this when mouse is already down. + * + * @param options + * @returns + */ initializeDrag(options: DragInitializationOption) { let cancelledByExt = false; diff --git a/blocksuite/framework/std/src/gfx/view/view.ts b/blocksuite/framework/std/src/gfx/view/view.ts index a8752d3bb..4d2d83283 100644 --- a/blocksuite/framework/std/src/gfx/view/view.ts +++ b/blocksuite/framework/std/src/gfx/view/view.ts @@ -26,6 +26,9 @@ export type EventsHandlerMap = { pointerleave: PointerEventState; pointermove: PointerEventState; pointerup: PointerEventState; + dragstart: PointerEventState; + dragmove: PointerEventState; + dragend: PointerEventState; }; export type SupportedEvent = keyof EventsHandlerMap; @@ -97,11 +100,24 @@ export class GfxElementModelView< return this.model.containsBound(bounds); } + /** + * Dispatches an event to the view. + * @param event + * @param evt + * @returns Whether the event view has any handlers for the event. + */ dispatch( event: K, evt: EventsHandlerMap[K] ) { - this._handlers.get(event)?.forEach(callback => callback(evt)); + const handlers = this._handlers.get(event); + + if (handlers?.length) { + handlers.forEach(callback => callback(evt)); + return true; + } + + return false; } getLineIntersections(start: IVec, end: IVec) {