diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index 8eb1e5474..f740a401d 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -1830,6 +1830,8 @@ export abstract class StateNode implements Partial { // (undocumented) onKeyUp?: TLEventHandlers['onKeyUp']; // (undocumented) + onLongPress?: TLEventHandlers['onLongPress']; + // (undocumented) onMiddleClick?: TLEventHandlers['onMiddleClick']; // (undocumented) onPointerDown?: TLEventHandlers['onPointerDown']; @@ -2150,6 +2152,8 @@ export interface TLEventHandlers { // (undocumented) onKeyUp: TLKeyboardEvent; // (undocumented) + onLongPress: TLPointerEvent; + // (undocumented) onMiddleClick: TLPointerEvent; // (undocumented) onPointerDown: TLPointerEvent; @@ -2424,7 +2428,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 f67abc756..7438c7e6b 100644 --- a/packages/editor/api/api.json +++ b/packages/editor/api/api.json @@ -35012,6 +35012,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", @@ -38427,6 +38462,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", @@ -41076,7 +41139,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 a786e4de8..caa0fff82 100644 --- a/packages/editor/src/lib/constants.ts +++ b/packages/editor/src/lib/constants.ts @@ -107,3 +107,6 @@ export const HANDLE_RADIUS = 12 /** @public */ export const SIDES = ['top', 'right', 'bottom', 'left'] as const + +/** @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 57859a173..d8cabe174 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, @@ -8354,6 +8355,9 @@ export class Editor extends EventEmitter { /** @internal */ private _selectedShapeIdsAtPointerDown: TLShapeId[] = [] + /** @internal */ + private _longPressTimeout = -1 as any + /** @internal */ capturedPointerId: number | null = null @@ -8390,8 +8394,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) }) } @@ -8456,6 +8460,7 @@ export class Editor extends EventEmitter { switch (type) { case 'pinch': { if (!this.getInstanceState().canMoveCamera) return + clearTimeout(this._longPressTimeout) this._updateInputsFromEvent(info) switch (info.name) { @@ -8580,6 +8585,7 @@ export class Editor extends EventEmitter { (this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) / this.getZoomLevel() ) { + clearTimeout(this._longPressTimeout) inputs.isDragging = true } } @@ -8597,6 +8603,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... @@ -8665,6 +8675,7 @@ export class Editor extends EventEmitter { (this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) / this.getZoomLevel() ) { + clearTimeout(this._longPressTimeout) inputs.isDragging = true } break @@ -8807,6 +8818,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 4e182837a..a13ff3968 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingHandle.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingHandle.ts @@ -64,6 +64,8 @@ export class PointingHandle extends StateNode { override onPointerMove: TLEventHandlers['onPointerMove'] = () => { const { editor } = this if (editor.inputs.isDragging) { + if (this.editor.getInstanceState().isReadonly) return + const { shape, handle } = this.info if (editor.isShapeOfType(shape, 'note')) { @@ -94,10 +96,19 @@ export class PointingHandle extends StateNode { } } - 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 0d9d9609d..8225a81b5 100644 --- a/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts +++ b/packages/tldraw/src/lib/tools/SelectTool/childStates/PointingShape.ts @@ -212,11 +212,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 = () => {