diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index c3a7827ab..14c0d41c8 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -1824,6 +1824,8 @@ export abstract class StateNode implements Partial { // (undocumented) onKeyUp?: TLEventHandlers['onKeyUp']; // (undocumented) + onLongPress?: TLEventHandlers['onLongPress']; + // (undocumented) onMiddleClick?: TLEventHandlers['onMiddleClick']; // (undocumented) onPointerDown?: TLEventHandlers['onPointerDown']; @@ -2144,6 +2146,8 @@ export interface TLEventHandlers { // (undocumented) onKeyUp: TLKeyboardEvent; // (undocumented) + onLongPress: TLPointerEvent; + // (undocumented) onMiddleClick: TLPointerEvent; // (undocumented) onPointerDown: TLPointerEvent; @@ -2418,7 +2422,7 @@ export type TLPointerEventInfo = TLBaseEventInfo & { } & TLPointerEventTarget; // @public (undocumented) -export type TLPointerEventName = 'middle_click' | 'pointer_down' | 'pointer_move' | 'pointer_up' | 'right_click'; +export type TLPointerEventName = 'long_press' | 'middle_click' | 'pointer_down' | 'pointer_move' | 'pointer_up' | 'right_click'; // @public (undocumented) export type TLPointerEventTarget = { diff --git a/packages/editor/api/api.json b/packages/editor/api/api.json index 135223a10..b7ebe74b8 100644 --- a/packages/editor/api/api.json +++ b/packages/editor/api/api.json @@ -34940,6 +34940,41 @@ "isProtected": false, "isAbstract": false }, + { + "kind": "Property", + "canonicalReference": "@tldraw/editor!StateNode#onLongPress:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onLongPress?: " + }, + { + "kind": "Reference", + "text": "TLEventHandlers", + "canonicalReference": "@tldraw/editor!TLEventHandlers:interface" + }, + { + "kind": "Content", + "text": "['onLongPress']" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": true, + "releaseTag": "Public", + "name": "onLongPress", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 3 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + }, { "kind": "Property", "canonicalReference": "@tldraw/editor!StateNode#onMiddleClick:member", @@ -38355,6 +38390,34 @@ "endIndex": 2 } }, + { + "kind": "PropertySignature", + "canonicalReference": "@tldraw/editor!TLEventHandlers#onLongPress:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "onLongPress: " + }, + { + "kind": "Reference", + "text": "TLPointerEvent", + "canonicalReference": "@tldraw/editor!TLPointerEvent:type" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "onLongPress", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, { "kind": "PropertySignature", "canonicalReference": "@tldraw/editor!TLEventHandlers#onMiddleClick:member", @@ -41004,7 +41067,7 @@ }, { "kind": "Content", - "text": "'middle_click' | 'pointer_down' | 'pointer_move' | 'pointer_up' | 'right_click'" + "text": "'long_press' | 'middle_click' | 'pointer_down' | 'pointer_move' | 'pointer_up' | 'right_click'" }, { "kind": "Content", diff --git a/packages/editor/src/lib/constants.ts b/packages/editor/src/lib/constants.ts index fbdb79814..43dcbfb10 100644 --- a/packages/editor/src/lib/constants.ts +++ b/packages/editor/src/lib/constants.ts @@ -104,3 +104,6 @@ export const COARSE_HANDLE_RADIUS = 20 /** @internal */ export const HANDLE_RADIUS = 12 + +/** @internal */ +export const LONG_PRESS_DURATION = 500 diff --git a/packages/editor/src/lib/editor/Editor.ts b/packages/editor/src/lib/editor/Editor.ts index d44f2c75e..c05a93365 100644 --- a/packages/editor/src/lib/editor/Editor.ts +++ b/packages/editor/src/lib/editor/Editor.ts @@ -79,6 +79,7 @@ import { FOLLOW_CHASE_ZOOM_UNSNAP, HIT_TEST_MARGIN, INTERNAL_POINTER_IDS, + LONG_PRESS_DURATION, MAX_PAGES, MAX_SHAPES_PER_PAGE, MAX_ZOOM, @@ -8348,6 +8349,9 @@ export class Editor extends EventEmitter { /** @internal */ private _selectedShapeIdsAtPointerDown: TLShapeId[] = [] + /** @internal */ + private _longPressTimeout = -1 as any + /** @internal */ capturedPointerId: number | null = null @@ -8384,8 +8388,8 @@ export class Editor extends EventEmitter { } if (elapsed > 0) { this.root.handleEvent({ type: 'misc', name: 'tick', elapsed }) - this.scribbles.tick(elapsed) } + this.scribbles.tick(elapsed) }) } @@ -8450,6 +8454,7 @@ export class Editor extends EventEmitter { switch (type) { case 'pinch': { if (!this.getInstanceState().canMoveCamera) return + clearTimeout(this._longPressTimeout) this._updateInputsFromEvent(info) switch (info.name) { @@ -8574,6 +8579,7 @@ export class Editor extends EventEmitter { (this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) / this.getZoomLevel() ) { + clearTimeout(this._longPressTimeout) inputs.isDragging = true } } @@ -8591,6 +8597,10 @@ export class Editor extends EventEmitter { case 'pointer_down': { this.clearOpenMenus() + this._longPressTimeout = setTimeout(() => { + this.dispatch({ ...info, name: 'long_press' }) + }, LONG_PRESS_DURATION) + this._selectedShapeIdsAtPointerDown = this.getSelectedShapeIds() // Firefox bug fix... @@ -8659,6 +8669,7 @@ export class Editor extends EventEmitter { (this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) / this.getZoomLevel() ) { + clearTimeout(this._longPressTimeout) inputs.isDragging = true } break @@ -8801,6 +8812,8 @@ export class Editor extends EventEmitter { break } case 'pointer_up': { + clearTimeout(this._longPressTimeout) + const otherEvent = this._clickManager.transformPointerUpEvent(info) if (info.name !== otherEvent.name) { this.root.handleEvent(info) diff --git a/packages/editor/src/lib/editor/tools/StateNode.ts b/packages/editor/src/lib/editor/tools/StateNode.ts index 5c5378b63..f170fe90e 100644 --- a/packages/editor/src/lib/editor/tools/StateNode.ts +++ b/packages/editor/src/lib/editor/tools/StateNode.ts @@ -198,6 +198,7 @@ export abstract class StateNode implements Partial { onWheel?: TLEventHandlers['onWheel'] onPointerDown?: TLEventHandlers['onPointerDown'] onPointerMove?: TLEventHandlers['onPointerMove'] + onLongPress?: TLEventHandlers['onLongPress'] onPointerUp?: TLEventHandlers['onPointerUp'] onDoubleClick?: TLEventHandlers['onDoubleClick'] onTripleClick?: TLEventHandlers['onTripleClick'] diff --git a/packages/editor/src/lib/editor/types/event-types.ts b/packages/editor/src/lib/editor/types/event-types.ts index 5fa41deb8..89ab5725e 100644 --- a/packages/editor/src/lib/editor/types/event-types.ts +++ b/packages/editor/src/lib/editor/types/event-types.ts @@ -16,6 +16,7 @@ export type TLPointerEventTarget = export type TLPointerEventName = | 'pointer_down' | 'pointer_move' + | 'long_press' | 'pointer_up' | 'right_click' | 'middle_click' @@ -152,6 +153,7 @@ export type TLExitEventHandler = (info: any, to: string) => void export interface TLEventHandlers { onPointerDown: TLPointerEvent onPointerMove: TLPointerEvent + onLongPress: TLPointerEvent onRightClick: TLPointerEvent onDoubleClick: TLClickEvent onTripleClick: TLClickEvent @@ -176,6 +178,7 @@ export const EVENT_NAME_MAP: Record< wheel: 'onWheel', pointer_down: 'onPointerDown', pointer_move: 'onPointerMove', + long_press: 'onLongPress', pointer_up: 'onPointerUp', right_click: 'onRightClick', middle_click: 'onMiddleClick', diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingCropHandle.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingCropHandle.ts index c17db4fc2..aefbbdc7d 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingCropHandle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingCropHandle.ts @@ -37,16 +37,23 @@ export class PointingCropHandle extends StateNode { } override onPointerMove: TLEventHandlers['onPointerMove'] = () => { - const isDragging = this.editor.inputs.isDragging - - if (isDragging) { - this.parent.transition('cropping', { - ...this.info, - onInteractionEnd: this.info.onInteractionEnd, - }) + if (this.editor.inputs.isDragging) { + this.startCropping() } } + override onLongPress: TLEventHandlers['onLongPress'] = () => { + this.startCropping() + } + + private startCropping() { + if (this.editor.getInstanceState().isReadonly) return + this.parent.transition('cropping', { + ...this.info, + onInteractionEnd: this.info.onInteractionEnd, + }) + } + override onPointerUp: TLEventHandlers['onPointerUp'] = () => { if (this.info.onInteractionEnd) { this.editor.setCurrentTool(this.info.onInteractionEnd, this.info) diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingHandle.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingHandle.ts index c199dc9e8..2d8220841 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingHandle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingHandle.ts @@ -37,10 +37,19 @@ export class PointingHandle extends StateNode { override onPointerMove: TLEventHandlers['onPointerMove'] = () => { if (this.editor.inputs.isDragging) { - this.parent.transition('dragging_handle', this.info) + this.startDraggingHandle() } } + override onLongPress: TLEventHandlers['onLongPress'] = () => { + this.startDraggingHandle() + } + + private startDraggingHandle() { + if (this.editor.getInstanceState().isReadonly) return + this.parent.transition('dragging_handle', this.info) + } + override onCancel: TLEventHandlers['onCancel'] = () => { this.cancel() } diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingResizeHandle.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingResizeHandle.ts index 477306814..e353d50ea 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingResizeHandle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingResizeHandle.ts @@ -48,13 +48,20 @@ export class PointingResizeHandle extends StateNode { } override onPointerMove: TLEventHandlers['onPointerMove'] = () => { - const isDragging = this.editor.inputs.isDragging - - if (isDragging) { - this.parent.transition('resizing', this.info) + if (this.editor.inputs.isDragging) { + this.startResizing() } } + override onLongPress: TLEventHandlers['onLongPress'] = () => { + this.startResizing() + } + + private startResizing() { + if (this.editor.getInstanceState().isReadonly) return + this.parent.transition('resizing', this.info) + } + override onPointerUp: TLEventHandlers['onPointerUp'] = () => { this.complete() } diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingRotateHandle.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingRotateHandle.ts index 989d1473d..4053a7764 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingRotateHandle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingRotateHandle.ts @@ -33,14 +33,21 @@ export class PointingRotateHandle extends StateNode { ) } - override onPointerMove = () => { - const { isDragging } = this.editor.inputs - - if (isDragging) { - this.parent.transition('rotating', this.info) + override onPointerMove: TLEventHandlers['onPointerMove'] = () => { + if (this.editor.inputs.isDragging) { + this.startRotating() } } + override onLongPress: TLEventHandlers['onLongPress'] = () => { + this.startRotating() + } + + private startRotating() { + if (this.editor.getInstanceState().isReadonly) return + this.parent.transition('rotating', this.info) + } + override onPointerUp = () => { this.complete() } diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingSelection.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingSelection.ts index 14ce18f57..8ac57a1e1 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingSelection.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingSelection.ts @@ -25,11 +25,19 @@ export class PointingSelection extends StateNode { override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { - if (this.editor.getInstanceState().isReadonly) return - this.parent.transition('translating', info) + this.startTranslating(info) } } + override onLongPress: TLEventHandlers['onLongPress'] = (info) => { + this.startTranslating(info) + } + + private startTranslating(info: TLPointerEventInfo) { + if (this.editor.getInstanceState().isReadonly) return + this.parent.transition('translating', info) + } + override onDoubleClick?: TLClickEvent | undefined = (info) => { const hoveredShape = this.editor.getHoveredShape() const hitShape = diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts index 4dad8d5e4..7a08ee826 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts @@ -195,11 +195,19 @@ export class PointingShape extends StateNode { override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => { if (this.editor.inputs.isDragging) { - if (this.editor.getInstanceState().isReadonly) return - this.parent.transition('translating', info) + this.startTranslating(info) } } + override onLongPress: TLEventHandlers['onLongPress'] = (info) => { + this.startTranslating(info) + } + + private startTranslating(info: TLPointerEventInfo) { + if (this.editor.getInstanceState().isReadonly) return + this.parent.transition('translating', info) + } + override onCancel: TLEventHandlers['onCancel'] = () => { this.cancel() } diff --git a/packages/tldraw/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts b/packages/tldraw/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts index 4d869e018..d94a953c5 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts @@ -42,9 +42,7 @@ export class ScribbleBrushing extends StateNode { this.updateScribbleSelection(true) - requestAnimationFrame(() => { - this.editor.updateInstanceState({ brush: null }) - }) + this.editor.updateInstanceState({ brush: null }) } override onExit = () => {