feat(minimap): support touch operation (#381)

* fix(core): touch end clear hover

* feat(minimap): support touch operation

* fix(demo): touch create comment should not trigger drag

* feat(demo): comment node support touch resize
This commit is contained in:
Louis Young 2025-06-16 19:42:38 +08:00 committed by GitHub
parent 83ae052705
commit fd423d9cb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 80 additions and 39 deletions

View File

@ -1,6 +1,6 @@
import { CSSProperties, type FC } from 'react';
import { useNodeRender, usePlayground } from '@flowgram.ai/free-layout-editor';
import { MouseTouchEvent, useNodeRender, usePlayground } from '@flowgram.ai/free-layout-editor';
import type { CommentEditorModel } from '../model';
@ -26,23 +26,27 @@ export const ResizeArea: FC<IResizeArea> = (props) => {
const { selectNode } = useNodeRender();
const handleMouseDown = (mouseDownEvent: React.MouseEvent) => {
mouseDownEvent.preventDefault();
mouseDownEvent.stopPropagation();
const handleResizeStart = (
startResizeEvent: React.MouseEvent | React.TouchEvent | MouseEvent
) => {
MouseTouchEvent.preventDefault(startResizeEvent);
startResizeEvent.stopPropagation();
if (!onResize) {
return;
}
const { resizing, resizeEnd } = onResize();
model.setFocus(false);
selectNode(mouseDownEvent);
selectNode(startResizeEvent as React.MouseEvent);
playground.node.focus(); // 防止节点无法被删除
const startX = mouseDownEvent.clientX;
const startY = mouseDownEvent.clientY;
const { clientX: startX, clientY: startY } = MouseTouchEvent.getEventCoord(
startResizeEvent as MouseEvent
);
const handleMouseMove = (mouseMoveEvent: MouseEvent) => {
const deltaX = mouseMoveEvent.clientX - startX;
const deltaY = mouseMoveEvent.clientY - startY;
const handleResizing = (mouseMoveEvent: MouseEvent | TouchEvent) => {
const { clientX: moveX, clientY: moveY } = MouseTouchEvent.getEventCoord(mouseMoveEvent);
const deltaX = moveX - startX;
const deltaY = moveY - startY;
const delta = getDelta?.({ x: deltaX, y: deltaY });
if (!delta || !resizing) {
return;
@ -50,16 +54,22 @@ export const ResizeArea: FC<IResizeArea> = (props) => {
resizing(delta);
};
const handleMouseUp = () => {
const handleResizeEnd = () => {
resizeEnd();
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('click', handleMouseUp);
document.removeEventListener('mousemove', handleResizing);
document.removeEventListener('mouseup', handleResizeEnd);
document.removeEventListener('click', handleResizeEnd);
document.removeEventListener('touchmove', handleResizing);
document.removeEventListener('touchend', handleResizeEnd);
document.removeEventListener('touchcancel', handleResizeEnd);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('click', handleMouseUp);
document.addEventListener('mousemove', handleResizing);
document.addEventListener('mouseup', handleResizeEnd);
document.addEventListener('click', handleResizeEnd);
document.addEventListener('touchmove', handleResizing, { passive: false });
document.addEventListener('touchend', handleResizeEnd);
document.addEventListener('touchcancel', handleResizeEnd);
};
return (
@ -67,7 +77,8 @@ export const ResizeArea: FC<IResizeArea> = (props) => {
className="workflow-comment-resize-area"
style={style}
data-flow-editor-selectable="false"
onMouseDown={handleMouseDown}
onMouseDown={handleResizeStart}
onTouchStart={handleResizeStart}
/>
);
};

View File

@ -36,14 +36,17 @@ export const Comment = () => {
async (mouseEvent: React.MouseEvent<HTMLButtonElement>) => {
setTooltipVisible(false);
const canvasPosition = calcNodePosition(mouseEvent);
// 创建节点
// create comment node - 创建节点
const node = document.createWorkflowNodeByType(WorkflowNodeType.Comment, canvasPosition);
// 等待节点渲染
// wait comment node render - 等待节点渲染
await delay(16);
// 选中节点
// select comment node - 选中节点
selectService.selectNode(node);
// 开始拖拽
dragService.startDragSelectedNodes(mouseEvent);
// maybe touch event - 可能是触摸事件
if (mouseEvent.detail !== 0) {
// start drag -开始拖拽
dragService.startDragSelectedNodes(mouseEvent);
}
},
[selectService, calcNodePosition, document, dragService]
);

View File

@ -145,6 +145,12 @@ export class PlaygroundLayer extends Layer<PlaygroundLayerOptions> {
// 这里必须监听 NORMAL_LAYER该图层最先触发
PipelineLayerPriority.NORMAL_LAYER
),
this.listenPlaygroundEvent('touchend', (e: TouchEvent) => {
this.options.hoverService?.clearHovered();
}),
this.listenPlaygroundEvent('touchcancel', (e: TouchEvent) => {
this.options.hoverService?.clearHovered();
}),
this.listenPlaygroundEvent(
'mousedown',
(e: MouseEvent) => {

View File

@ -84,6 +84,12 @@ export const MinimapRender: React.FC<MinimapProps> = (props) => {
onMouseLeave={() => {
service.setActivate(false);
}}
onTouchStartCapture={() => {
service.setActivate(true);
}}
onTouchEndCapture={() => {
service.setActivate(false);
}}
></div>
</div>
);

View File

@ -4,7 +4,7 @@ import { Disposable, DisposableCollection, IPoint, Rectangle } from '@flowgram.a
import { FlowNodeEntity, FlowNodeTransformData } from '@flowgram.ai/document';
import { FlowNodeBaseType } from '@flowgram.ai/document';
import { FlowDocument } from '@flowgram.ai/document';
import { EntityManager, PlaygroundConfigEntity } from '@flowgram.ai/core';
import { EntityManager, MouseTouchEvent, PlaygroundConfigEntity } from '@flowgram.ai/core';
import type { MinimapRenderContext, MinimapServiceOptions, MinimapCanvasStyle } from './type';
import { MinimapDraw } from './draw';
@ -312,26 +312,29 @@ export class FlowMinimapService {
private addEventListeners(): void {
this.canvas.addEventListener('wheel', this.handleWheel);
this.canvas.addEventListener('mousedown', this.handleStartDrag);
this.canvas.addEventListener('touchstart', this.handleStartDrag, { passive: false });
this.canvas.addEventListener('mousemove', this.handleCursor);
}
private removeEventListeners(): void {
this.canvas.removeEventListener('wheel', this.handleWheel);
this.canvas.removeEventListener('mousedown', this.handleStartDrag);
this.canvas.removeEventListener('touchstart', this.handleStartDrag);
this.canvas.removeEventListener('mousemove', this.handleCursor);
}
private handleWheel = (event: WheelEvent): void => {};
private handleStartDrag = (event: MouseEvent): void => {
event.preventDefault();
private handleStartDrag = (event: MouseEvent | TouchEvent): void => {
MouseTouchEvent.preventDefault(event);
event.stopPropagation();
const renderContext = this.createRenderContext();
const { viewRect, scale, offset } = renderContext;
const canvasRect = this.canvas.getBoundingClientRect();
const { clientX, clientY } = MouseTouchEvent.getEventCoord(event);
const mousePoint: IPoint = {
x: event.clientX - canvasRect.left,
y: event.clientY - canvasRect.top,
x: clientX - canvasRect.left,
y: clientY - canvasRect.top,
};
const viewRectOnCanvas = this.rectOnCanvas({
@ -344,20 +347,26 @@ export class FlowMinimapService {
}
this.isDragging = true;
this.dragStart = mousePoint;
// click
document.addEventListener('mousemove', this.handleDragging);
document.addEventListener('mouseup', this.handleEndDrag);
// touch
document.addEventListener('touchmove', this.handleDragging, { passive: false });
document.addEventListener('touchend', this.handleEndDrag);
document.addEventListener('touchcancel', this.handleEndDrag);
};
private handleDragging = (event: MouseEvent): void => {
private handleDragging = (event: MouseEvent | TouchEvent): void => {
if (!this.isDragging || !this.dragStart) return;
event.preventDefault();
MouseTouchEvent.preventDefault(event);
event.stopPropagation();
const renderContext = this.createRenderContext();
const { scale } = renderContext;
const canvasRect = this.canvas.getBoundingClientRect();
const mouseX = event.clientX - canvasRect.left;
const mouseY = event.clientY - canvasRect.top;
const { clientX, clientY } = MouseTouchEvent.getEventCoord(event);
const mouseX = clientX - canvasRect.left;
const mouseY = clientY - canvasRect.top;
const deltaX = (mouseX - this.dragStart.x) / scale;
const deltaY = (mouseY - this.dragStart.y) / scale;
@ -368,11 +377,16 @@ export class FlowMinimapService {
this.render();
};
private handleEndDrag = (event: MouseEvent): void => {
event.preventDefault();
private handleEndDrag = (event: MouseEvent | TouchEvent): void => {
MouseTouchEvent.preventDefault(event);
event.stopPropagation();
// click
document.removeEventListener('mousemove', this.handleDragging);
document.removeEventListener('mouseup', this.handleEndDrag);
// touch
document.removeEventListener('touchmove', this.handleDragging);
document.removeEventListener('touchend', this.handleEndDrag);
document.removeEventListener('touchcancel', this.handleEndDrag);
this.isDragging = false;
this.dragStart = undefined;
this.setActivate(this.isMouseInCanvas(event));
@ -404,13 +418,14 @@ export class FlowMinimapService {
}
};
private isMouseInCanvas(event: MouseEvent): boolean {
private isMouseInCanvas(event: MouseEvent | TouchEvent): boolean {
const canvasRect = this.canvas.getBoundingClientRect();
const { clientX, clientY } = MouseTouchEvent.getEventCoord(event);
return (
event.clientX >= canvasRect.left &&
event.clientX <= canvasRect.right &&
event.clientY >= canvasRect.top &&
event.clientY <= canvasRect.bottom
clientX >= canvasRect.left &&
clientX <= canvasRect.right &&
clientY >= canvasRect.top &&
clientY <= canvasRect.bottom
);
}