Tldraw/packages/editor/src/lib/editor/managers/ClickManager.ts

222 wiersze
4.8 KiB
TypeScript

import {
COARSE_DRAG_DISTANCE,
DOUBLE_CLICK_DURATION,
DRAG_DISTANCE,
MULTI_CLICK_DURATION,
} from '../../constants'
import { Vec } from '../../primitives/Vec'
import { uniqueId } from '../../utils/uniqueId'
import type { Editor } from '../Editor'
import { TLClickEventInfo, TLPointerEventInfo } from '../types/event-types'
type TLClickState =
| 'idle'
| 'pendingDouble'
| 'pendingTriple'
| 'pendingQuadruple'
| 'pendingOverflow'
| 'overflow'
const MAX_CLICK_DISTANCE = 40
export class ClickManager {
constructor(public editor: Editor) {}
private _clickId = ''
private _clickTimeout?: any
private _clickScreenPoint?: Vec
private _previousScreenPoint?: Vec
private _getClickTimeout = (state: TLClickState, id = uniqueId()) => {
this._clickId = id
clearTimeout(this._clickTimeout)
this._clickTimeout = setTimeout(
() => {
if (this._clickState === state && this._clickId === id) {
switch (this._clickState) {
case 'pendingTriple': {
this.editor.dispatch({
...this.lastPointerInfo,
type: 'click',
name: 'double_click',
phase: 'settle',
})
break
}
case 'pendingQuadruple': {
this.editor.dispatch({
...this.lastPointerInfo,
type: 'click',
name: 'triple_click',
phase: 'settle',
})
break
}
case 'pendingOverflow': {
this.editor.dispatch({
...this.lastPointerInfo,
type: 'click',
name: 'quadruple_click',
phase: 'settle',
})
break
}
default: {
// noop
}
}
this._clickState = 'idle'
}
},
state === 'idle' || state === 'pendingDouble' ? DOUBLE_CLICK_DURATION : MULTI_CLICK_DURATION
)
}
/**
* The current click state.
*
* @internal
*/
private _clickState?: TLClickState = 'idle'
/**
* The current click state.
*
* @public
*/
// eslint-disable-next-line no-restricted-syntax
get clickState() {
return this._clickState
}
lastPointerInfo = {} as TLPointerEventInfo
handlePointerEvent = (info: TLPointerEventInfo): TLPointerEventInfo | TLClickEventInfo => {
switch (info.name) {
case 'pointer_down': {
if (!this._clickState) return info
this._clickScreenPoint = Vec.From(info.point)
if (
this._previousScreenPoint &&
Vec.Dist2(this._previousScreenPoint, this._clickScreenPoint) > MAX_CLICK_DISTANCE ** 2
) {
this._clickState = 'idle'
}
this._previousScreenPoint = this._clickScreenPoint
this.lastPointerInfo = info
switch (this._clickState) {
case 'pendingDouble': {
this._clickState = 'pendingTriple'
this._clickTimeout = this._getClickTimeout(this._clickState)
return {
...info,
type: 'click',
name: 'double_click',
phase: 'down',
}
}
case 'pendingTriple': {
this._clickState = 'pendingQuadruple'
this._clickTimeout = this._getClickTimeout(this._clickState)
return {
...info,
type: 'click',
name: 'triple_click',
phase: 'down',
}
}
case 'pendingQuadruple': {
this._clickState = 'pendingOverflow'
this._clickTimeout = this._getClickTimeout(this._clickState)
return {
...info,
type: 'click',
name: 'quadruple_click',
phase: 'down',
}
}
case 'idle': {
this._clickState = 'pendingDouble'
break
}
case 'pendingOverflow': {
this._clickState = 'overflow'
break
}
default: {
// overflow
}
}
this._clickTimeout = this._getClickTimeout(this._clickState)
return info
}
case 'pointer_up': {
if (!this._clickState) return info
this._clickScreenPoint = Vec.From(info.point)
switch (this._clickState) {
case 'pendingTriple': {
return {
...this.lastPointerInfo,
type: 'click',
name: 'double_click',
phase: 'up',
}
}
case 'pendingQuadruple': {
return {
...this.lastPointerInfo,
type: 'click',
name: 'triple_click',
phase: 'up',
}
}
case 'pendingOverflow': {
return {
...this.lastPointerInfo,
type: 'click',
name: 'quadruple_click',
phase: 'up',
}
}
default: {
// idle, pendingDouble, overflow
}
}
return info
}
case 'pointer_move': {
if (
this._clickState !== 'idle' &&
this._clickScreenPoint &&
Vec.Dist2(this._clickScreenPoint, this.editor.inputs.currentScreenPoint) >
(this.editor.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE)
) {
this.cancelDoubleClickTimeout()
}
return info
}
}
return info
}
/**
* Cancel the double click timeout.
*
* @internal
*/
cancelDoubleClickTimeout = () => {
this._clickTimeout = clearTimeout(this._clickTimeout)
this._clickState = 'idle'
}
}