UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

248 lines (216 loc) 6.93 kB
import type { ModifierKey } from '../common' import { Dom, disposable, isModifierKeyMatch } from '../common' import { Base } from './base' type EventType = | 'leftMouseDown' | 'rightMouseDown' | 'mouseWheel' | 'mouseWheelDown' export interface PanningOptions { enabled?: boolean modifiers?: string | ModifierKey[] | null eventTypes?: EventType[] } export class PanningManager extends Base { private panning: boolean private clientX: number private clientY: number private mousewheelHandle: Dom.MouseWheelHandle private isSpaceKeyPressed: boolean protected get widgetOptions() { return this.options.panning } get pannable() { return this.widgetOptions && this.widgetOptions.enabled === true } protected init() { this.onRightMouseDown = this.onRightMouseDown.bind(this) this.onKeyDown = this.onKeyDown.bind(this) this.onKeyUp = this.onKeyUp.bind(this) this.startListening() this.updateClassName() } protected startListening() { this.graph.on('blank:mousedown', this.onMouseDown, this) this.graph.on('node:unhandled:mousedown', this.onMouseDown, this) this.graph.on('edge:unhandled:mousedown', this.onMouseDown, this) Dom.Event.on(this.graph.container, 'mousedown', this.onRightMouseDown) Dom.Event.on(document.body, { keydown: this.onKeyDown, keyup: this.onKeyUp, }) this.mousewheelHandle = new Dom.MouseWheelHandle( this.graph.container, this.onMouseWheel.bind(this), this.allowMouseWheel.bind(this), ) this.mousewheelHandle.enable() } protected stopListening() { this.graph.off('blank:mousedown', this.onMouseDown, this) this.graph.off('node:unhandled:mousedown', this.onMouseDown, this) this.graph.off('edge:unhandled:mousedown', this.onMouseDown, this) Dom.Event.off(this.graph.container, 'mousedown', this.onRightMouseDown) Dom.Event.off(document.body, { keydown: this.onKeyDown, keyup: this.onKeyUp, }) if (this.mousewheelHandle) { this.mousewheelHandle.disable() } } allowPanning(e: Dom.EventObject, strict?: boolean) { ;(e as any).spaceKey = this.isSpaceKeyPressed return ( this.pannable && isModifierKeyMatch(e, this.widgetOptions.modifiers, strict) ) } protected startPanning(evt: Dom.MouseDownEvent) { const e = this.view.normalizeEvent(evt) this.clientX = e.clientX this.clientY = e.clientY this.panning = true this.updateClassName(evt) Dom.Event.on(document.body, { 'mousemove.panning touchmove.panning': this.pan.bind(this), 'mouseup.panning touchend.panning': this.stopPanning.bind(this), 'mouseleave.panning': this.stopPanning.bind(this), }) Dom.Event.on(window as any, 'mouseup.panning', this.stopPanning.bind(this)) } protected pan(evt: Dom.MouseMoveEvent) { const e = this.view.normalizeEvent(evt) const dx = e.clientX - this.clientX const dy = e.clientY - this.clientY this.clientX = e.clientX this.clientY = e.clientY this.graph.translateBy(dx, dy) } // eslint-disable-next-line protected stopPanning(e: Dom.MouseUpEvent) { this.panning = false this.updateClassName(e) Dom.Event.off(document.body, '.panning') Dom.Event.off(window as any, '.panning') } protected updateClassName(e?: Dom.EventObject) { const eventTypes = this.widgetOptions.eventTypes if (eventTypes?.length === 1 && eventTypes.includes('mouseWheel')) { return } const container = this.view.container const panning = this.view.prefixClassName('graph-panning') const pannable = this.view.prefixClassName('graph-pannable') const selection = this.graph.getPlugin<any>('selection') const allowRubberband = selection && selection.allowRubberband(e, true) const allowRightMouseRubberband = eventTypes?.includes('leftMouseDown') && !allowRubberband if ( this.allowPanning(e ?? ({} as Dom.EventObject), true) || (this.allowPanning(e ?? ({} as Dom.EventObject)) && allowRightMouseRubberband) ) { if (this.panning) { Dom.addClass(container, panning) Dom.removeClass(container, pannable) } else { Dom.removeClass(container, panning) Dom.addClass(container, pannable) } } else if (!this.panning) { Dom.removeClass(container, panning) Dom.removeClass(container, pannable) } } protected onMouseDown({ e }: { e: Dom.MouseDownEvent }) { if (!this.allowBlankMouseDown(e)) { return } const selection = this.graph.getPlugin<any>('selection') const allowRubberband = selection && selection.allowRubberband(e, true) if ( this.allowPanning(e, true) || (this.allowPanning(e) && !allowRubberband) ) { this.startPanning(e) } } protected onRightMouseDown(e: Dom.MouseDownEvent) { const eventTypes = this.widgetOptions.eventTypes if (!(eventTypes?.includes('rightMouseDown') && e.button === 2)) { return } if (this.allowPanning(e, true)) { this.startPanning(e) } } protected onMouseWheel(e: WheelEvent, deltaX: number, deltaY: number) { this.graph.translateBy(-deltaX, -deltaY) } protected onKeyDown(e: Dom.KeyDownEvent) { if (e.which === 32) { this.isSpaceKeyPressed = true } this.updateClassName(e) } protected onKeyUp(e: Dom.KeyUpEvent) { if (e.which === 32) { this.isSpaceKeyPressed = false } this.updateClassName(e) } protected allowBlankMouseDown(e: Dom.MouseDownEvent) { const eventTypes = this.widgetOptions.eventTypes const isTouchEvent = (typeof e.type === 'string' && e.type.startsWith('touch')) || e.pointerType === 'touch' if (isTouchEvent) return eventTypes?.includes('leftMouseDown') return ( (eventTypes?.includes('leftMouseDown') && e.button === 0) || (eventTypes?.includes('mouseWheelDown') && e.button === 1) ) } protected allowMouseWheel(e: WheelEvent) { return ( this.pannable && !e.ctrlKey && this.widgetOptions.eventTypes?.includes('mouseWheel') ) } autoPanning(x: number, y: number) { const buffer = 10 const graphArea = this.graph.getGraphArea() let dx = 0 let dy = 0 if (x <= graphArea.left + buffer) { dx = -buffer } if (y <= graphArea.top + buffer) { dy = -buffer } if (x >= graphArea.right - buffer) { dx = buffer } if (y >= graphArea.bottom - buffer) { dy = buffer } if (dx !== 0 || dy !== 0) { this.graph.translateBy(-dx, -dy) } } enablePanning() { if (!this.pannable) { this.widgetOptions.enabled = true this.updateClassName() } } disablePanning() { if (this.pannable) { this.widgetOptions.enabled = false this.updateClassName() } } @disposable() dispose() { this.stopListening() } }