Skip to content

Conversation

@z-pangolin
Copy link

@z-pangolin z-pangolin commented Nov 26, 2025

输入层选型:

选择PointerEvent统一输入层,它是W3C推出的新一代指针事件,旨在统一所有 “指针输入设备” 的事件处理,包括触摸(手指),光标(鼠标),触控笔等。所以多端中相同功能只需写一套逻辑,不需要专门针对移动端写一套逻辑造成代码冗余。而且PointerEvent独特的指针特性使得处理起多指操作更为高效便捷。

解决方案:

1.移动端功能的逻辑和pc端功能的逻辑相同的话,就将原本的MouseEvent换成PointerEvent,直接复用B端函数即可以实现移动端适配(绝大部分功能可以正常实现,有些功能可能涉及到浏览器对PointerEvent的隐式捕获,导致指针事件指向的元素与预想不同,这就需要对函数进行改动)
2.当多端功能逻辑不同时(例如移动端长按出现菜单,pc端则是右键;移动端需要双指控制缩放,移动端则是滚轮实现),将事件类型换为PointerEvent只是第一步,具体改动则要根据具体实现逻辑实现。
3.当需要对B端函数进行改动时,要尽可能减少代码改动量,最大限度的复用B端函数

改动后移动端的效果:

1.支持节点的拖拽、双击修改文本,单击节点出现轮廓支持调整节点尺寸和旋转
2.支持边的连接、拖动调整折线中段和端点、贝塞尔曲线可以调整
3.支持画布的拖拽、双指缩放功能
4.支持插件中拖拽小地图来移动画布的功能、框选功能、长按节点/边/画布显示对应菜单

@changeset-bot
Copy link

changeset-bot bot commented Nov 26, 2025

⚠️ No Changeset found

Latest commit: 3299b59

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@DymoneLewis DymoneLewis requested review from DymoneLewis and Copilot and removed request for Copilot November 26, 2025 11:08
Copilot finished reviewing on behalf of DymoneLewis November 26, 2025 11:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements mobile device adaptation by migrating event handling from MouseEvent to PointerEvent API, enabling unified input handling across touch, mouse, and pen devices. The changes introduce touch-specific features including long-press context menus (500ms), two-finger pinch-to-zoom gestures, and proper touch action configurations.

Key changes:

  • Replaced MouseEvent with PointerEvent across core drag interactions, node/edge handlers, and plugin components
  • Added mobile gesture support: long-press context menus and two-finger pinch zoom in CanvasOverlay
  • Configured touchAction: 'none' on interactive elements to prevent default touch behaviors

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/extension/src/tools/label/Label.tsx Updated label drag handlers to use PointerEvent
packages/extension/src/components/selection-select/index.ts Migrated selection box events to PointerEvent with touch action configuration
packages/extension/src/components/mini-map/index.ts Converted mini-map viewport drag to PointerEvent
packages/core/src/view/text/BaseText.tsx Changed text mouse handlers to pointer events
packages/core/src/view/overlay/CanvasOverlay.tsx Added pinch zoom, long-press menu, and pointer event handling for canvas
packages/core/src/view/overlay/BezierAdjustOverlay.tsx Updated Bezier anchor adjustment to PointerEvent
packages/core/src/view/node/BaseNode.tsx Added long-press context menu for touch and migrated to PointerEvent
packages/core/src/view/edge/PolylineEdge.tsx Updated polyline drag start to PointerEvent
packages/core/src/view/edge/BaseEdge.tsx Added long-press menu and pointer event handling with touch styles
packages/core/src/view/edge/AdjustPoint.tsx Converted adjust point handlers to PointerEvent
packages/core/src/view/Rotate.tsx Updated rotation control to PointerEvent
packages/core/src/view/Control.tsx Changed resize control handlers to PointerEvent
packages/core/src/view/Anchor.tsx Migrated anchor handlers to PointerEvent
packages/core/src/util/drag.ts Core drag utility updated with pointer capture, preventDefault, and PointerEvent
packages/core/src/tool/MultipleSelectTool.tsx Updated multiple selection tool to PointerEvent

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// 点拖拽进画布没有触发mousedown事件,没有startTime,用这个值做区分
const isDragging = this.mouseUpDrag === false
const curTime = new Date().getTime()
console.log('6666', e)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug console.log statement should be removed before merging to production.

Suggested change
console.log('6666', e)

Copilot uses AI. Check for mistakes.
e.preventDefault()
const target = e.target as any
if (target && typeof target.setPointerCapture === 'function') {
target.setPointerCapture((e as PointerEvent).pointerId)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant type casting. The parameter e is already typed as PointerEvent, so (e as PointerEvent).pointerId can be simplified to e.pointerId.

Suggested change
target.setPointerCapture((e as PointerEvent).pointerId)
target.setPointerCapture(e.pointerId)

Copilot uses AI. Check for mistakes.
if (this.isStopPropagation) e.stopPropagation()
const target = e.target as any
if (target && typeof target.releasePointerCapture === 'function') {
target.releasePointerCapture((e as PointerEvent).pointerId)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant type casting. The parameter e is already typed as PointerEvent, so (e as PointerEvent).pointerId can be simplified to e.pointerId.

Suggested change
target.releasePointerCapture((e as PointerEvent).pointerId)
target.releasePointerCapture(e.pointerId)

Copilot uses AI. Check for mistakes.
eventCenter.emit(EventType.BLANK_MOUSEDOWN, { e: ev })
}
// 为了处理画布移动的时候,编辑和菜单仍然存在的问题。
this.clickHandler(ev)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch: clickHandler expects MouseEvent but is being called with PointerEvent. The method signature should be updated to accept MouseEvent | PointerEvent to maintain consistency with the new pointer event handling.

Copilot uses AI. Check for mistakes.
onMouseDown={this.handleMouseDown}
onPointerDown={this.handleMouseDown}
onContextMenu={this.handleContextMenu}
onWheel={this.handleWheelEvent}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mark 确认一下这里的onContextMenu和onWheel要不要处理?


DOC.removeEventListener('mousemove', this.handleMouseMove, false)
DOC.removeEventListener('mouseup', this.handleMouseUp, false)
DOC.removeEventListener('pointermove', this.handleMouseMove as any, false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any改成指定类型

nodeConfig: OnDragNodeConfig | null = null
lf: LogicFlow
fakeNode: BaseNodeModel | null = null
docPointerMove?: (e: PointerEvent) => void
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check docPointerMove和docPointerUp的必要性

e.clientX,
e.clientY,
) as HTMLElement | null
const inside = topEl === overlay || (topEl && overlay?.contains(topEl))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inside这个变量名需要改一下,现在看不是很语义化

}
this.docPointerUp = (e: PointerEvent) => {
if (!this.nodeConfig) return
const overlay = this.lf.graphModel.rootEl.querySelector(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的inside获取逻辑和上面PointerMove的逻辑是一样的,可以抽成一个通用的方法,减少重复代码

onMouseUp: this.onDrop,
}
}
// eventMap() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check这里的eventMap还有没有在使用的地方 + 去掉会有什么影响

} = graphModel
model.isDragging = true
const { clientX, clientY } = event!
const { clientX, clientY } = event as PointerEvent
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里写as有点太临时了,是不是可以直接改IDragParams里的参数类型

} = this.props
this.pointers.set(ev.pointerId, { x: ev.clientX, y: ev.clientY })
if (this.longPressTimer) {
clearTimeout(this.longPressTimer)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果这个timeout是只有touch才触发的话,这个clearTimeout(this.longPressTimer)应该放下面if (ev.pointerType === 'touch') 里,否则mouse和pen也会走这个逻辑

this.pinchStartDistance = Math.hypot(dx, dy)
this.pinchStartScale = transformModel.SCALE_X
// 双指操作下取消画布拖拽,避免与捏合缩放冲突
this.stepDrag.cancelDrag()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔cancelDrag是不是应该放前面?先取消拖拽 再记录位置
需要check一下会不会出现画布先动一点点然后再固定缩放的情况

name="canvas-overlay"
onWheel={this.zoomHandler}
onMouseDown={this.mouseDownHandler}
onPointerDown={this.mouseDownHandler}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上的问题,check方法名要不要统一

if (shapeItem.callback) {
shapeItem.callback(this.lf, this.domContainer)
}
// try {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check注释能不能删

this.__container = container
this.__currentData = null // 当前展示的菜单所属元素的model数据

lf.updateEditConfig({ stopZoomGraph: false })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

多余改动?


startDrag(nodeConfig: OnDragNodeConfig) {
const { editConfigModel } = this.lf.graphModel
if (!editConfigModel?.isSilentMode) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果这个if里逻辑太多的话,就改成反向判断吧
即if(editConfigModel?.isSilentMode) { ...do something; return; }

window.document.removeEventListener('mouseup', this.stopDrag)
if (this.docPointerMove) {
window.document.removeEventListener('pointermove', this.docPointerMove)
this.docPointerMove = undefined
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里为什么要置undefined?

this.nodeConfig = nodeConfig
window.document.addEventListener('mouseup', this.stopDrag)
// 指针移动:根据命中结果判断是否在画布覆盖层上,驱动假节点创建/移动或清理
this.docPointerMove = (e: PointerEvent) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是不是可以直接提到外面做单独的方法?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants