[firefox] Fix the pointer getting stuck down when you press the control key (#1390)

This PR fixes a bug in firefox where the pointer can get stuck down
while pressing the control key.

It achieves this by taking a previous fix (specifically for
`useShapeEvents`), and it applies the fix at a deeper level (within
`app.dispatch`).

## Before
![2023-05-16 at 15 33 02 - Crimson
Coyote](https://github.com/tldraw/tldraw/assets/15892272/7d4b5bb1-a2e5-400c-9935-fddcc9645e52)

## After
![2023-05-16 at 15 34 03 - Purple
Panda](https://github.com/tldraw/tldraw/assets/15892272/34a598b2-bf6d-4847-8ce9-a3d52c418174)

### Change Type

- [x] `patch` — Bug Fix

### Test Plan

1. Use firefox.
2. On the canvas... pointer down (to start a selection box).
3. Control key down.
4. Pointer up.
5. Make sure that that the selection box is gone.

^ Repeat the above for:
Pointer down on a shape.
Pointer down on a handle.

### Release Notes

- [Firefox] Fixed a bug where the pointer could get stuck down when the
control key is held down.
This commit is contained in:
Lu Wilson 2023-05-19 03:35:24 -07:00 committed by GitHub
parent f59bfe01b1
commit 9c28d8a6bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 27 additions and 29 deletions

View file

@ -156,6 +156,8 @@ export class App extends EventEmitter<TLEventMap> {
set canMoveCamera(canMove: boolean); set canMoveCamera(canMove: boolean);
get canRedo(): boolean; get canRedo(): boolean;
get canUndo(): boolean; get canUndo(): boolean;
// @internal (undocumented)
capturedPointerId: null | number;
centerOnPoint(x: number, y: number, opts?: AnimationOptions): this; centerOnPoint(x: number, y: number, opts?: AnimationOptions): this;
// @internal // @internal
protected _clickManager: ClickManager; protected _clickManager: ClickManager;

View file

@ -3527,6 +3527,9 @@ export class App extends EventEmitter<TLEventMap> {
/** @internal */ /** @internal */
private _selectedIdsAtPointerDown: TLShapeId[] = [] private _selectedIdsAtPointerDown: TLShapeId[] = []
/** @internal */
capturedPointerId: number | null = null
/** /**
* Dispatch an event to the app. * Dispatch an event to the app.
* *
@ -3742,6 +3745,12 @@ export class App extends EventEmitter<TLEventMap> {
case 'pointer_down': { case 'pointer_down': {
this._selectedIdsAtPointerDown = this.selectedIds.slice() this._selectedIdsAtPointerDown = this.selectedIds.slice()
// Firefox bug fix...
// If it's a left-mouse-click, we store the pointer id for later user
if (info.button === 0) {
this.capturedPointerId = info.pointerId
}
// Add the button from the buttons set // Add the button from the buttons set
inputs.buttons.add(info.button) inputs.buttons.add(info.button)
@ -3833,6 +3842,14 @@ export class App extends EventEmitter<TLEventMap> {
return return
} }
// Firefox bug fix...
// If it's the same pointer that we stored earlier...
// ... then it's probably still a left-mouse-click!
if (this.capturedPointerId === info.pointerId) {
this.capturedPointerId = null
info.button = 0
}
if (inputs.isPanning) { if (inputs.isPanning) {
if (info.button === 1) { if (info.button === 1) {
if (!this.inputs.keys.has(' ')) { if (!this.inputs.keys.has(' ')) {

View file

@ -43,7 +43,7 @@ export function useCanvasEvents() {
function onPointerUp(e: React.PointerEvent) { function onPointerUp(e: React.PointerEvent) {
if ((e as any).isKilled) return if ((e as any).isKilled) return
if (e.button !== 0 && e.button !== 1 && e.button !== 5) return if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
lastX = e.clientX lastX = e.clientX
lastY = e.clientY lastY = e.clientY

View file

@ -6,38 +6,18 @@ import { preventDefault, releasePointerCapture, setPointerCapture } from '../uti
import { getPointerInfo } from '../utils/svg' import { getPointerInfo } from '../utils/svg'
import { useApp } from './useApp' import { useApp } from './useApp'
const pointerEventHandler = ( const pointerEventHandler = (app: App, shapeId: TLShapeId, name: TLPointerEventName) => {
app: App,
shapeId: TLShapeId,
name: TLPointerEventName,
capturedPointerIdLookup: Set<string>
) => {
return (e: React.PointerEvent) => { return (e: React.PointerEvent) => {
if (name !== 'pointer_move' && app.pageState.editingId === shapeId) (e as any).isKilled = true if (name !== 'pointer_move' && app.pageState.editingId === shapeId) (e as any).isKilled = true
if ((e as any).isKilled) return if ((e as any).isKilled) return
let pointerInfo = getPointerInfo(e, app.getContainer())
switch (name) { switch (name) {
case 'pointer_down': { case 'pointer_down': {
if (e.button !== 0 && e.button !== 1 && e.button !== 2) return if (e.button !== 0 && e.button !== 1 && e.button !== 2) return
setPointerCapture(e.currentTarget, e) setPointerCapture(e.currentTarget, e)
if (e.button === 0) {
capturedPointerIdLookup.add(`pointer_down:${e.pointerId}:0`)
}
break break
} }
case 'pointer_up': { case 'pointer_up': {
const key = `pointer_down:${e.pointerId}:0`
// Due to an issue with how firefox handles click events, see <https://linear.app/tldraw/issue/TLD-1056/firefox-pointer-events-do-not-fire-when-control-clicking>
if (capturedPointerIdLookup.has(key)) {
// Because we've tracked the pointer event as a pointer_down with button 0, we can assume this is the invalid right click FF issue.
pointerInfo = {
...pointerInfo,
button: 0,
}
capturedPointerIdLookup.delete(key)
}
releasePointerCapture(e.currentTarget, e) releasePointerCapture(e.currentTarget, e)
break break
} }
@ -55,7 +35,7 @@ const pointerEventHandler = (
target: 'shape', target: 'shape',
shape, shape,
name, name,
...pointerInfo, ...getPointerInfo(e, app.getContainer()),
}) })
} }
} }
@ -64,7 +44,6 @@ export function useShapeEvents(id: TLShapeId) {
const app = useApp() const app = useApp()
return React.useMemo(() => { return React.useMemo(() => {
const capturedPointerIdLookup = new Set<string>()
function onTouchStart(e: React.TouchEvent) { function onTouchStart(e: React.TouchEvent) {
;(e as any).isKilled = true ;(e as any).isKilled = true
preventDefault(e) preventDefault(e)
@ -75,7 +54,7 @@ export function useShapeEvents(id: TLShapeId) {
preventDefault(e) preventDefault(e)
} }
const handlePointerMove = pointerEventHandler(app, id, 'pointer_move', capturedPointerIdLookup) const handlePointerMove = pointerEventHandler(app, id, 'pointer_move')
// Track the last screen point // Track the last screen point
let lastX: number, lastY: number let lastX: number, lastY: number
@ -90,10 +69,10 @@ export function useShapeEvents(id: TLShapeId) {
} }
return { return {
onPointerDown: pointerEventHandler(app, id, 'pointer_down', capturedPointerIdLookup), onPointerDown: pointerEventHandler(app, id, 'pointer_down'),
onPointerUp: pointerEventHandler(app, id, 'pointer_up', capturedPointerIdLookup), onPointerUp: pointerEventHandler(app, id, 'pointer_up'),
onPointerEnter: pointerEventHandler(app, id, 'pointer_enter', capturedPointerIdLookup), onPointerEnter: pointerEventHandler(app, id, 'pointer_enter'),
onPointerLeave: pointerEventHandler(app, id, 'pointer_leave', capturedPointerIdLookup), onPointerLeave: pointerEventHandler(app, id, 'pointer_leave'),
onPointerMove, onPointerMove,
onTouchStart, onTouchStart,
onTouchEnd, onTouchEnd,