Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: PointerEvent button and buttons set to incorrect values; fixes #1083 #1219

Merged
merged 2 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions src/system/pointer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class PointerHost {
this.system = system
this.buttons = new Buttons()
this.mouse = new Mouse()
this.pointers.new('mouse', 'mouse', this.buttons)
}
private readonly mouse
private readonly buttons
Expand All @@ -28,35 +29,32 @@ export class PointerHost {
})()

private readonly pointers = new (class {
private registry = {
mouse: new Pointer({
pointerId: 1,
pointerType: 'mouse',
isPrimary: true,
}),
} as Record<string, Pointer>
private nextId = 2

new(pointerName: string, keyDef: pointerKey) {
private registry: Record<string, Pointer> = {}
private nextId = 1

new(pointerName: string, pointerType: string, buttons: Buttons) {
const isPrimary =
keyDef.pointerType !== 'touch' ||
pointerType !== 'touch' ||
!Object.values(this.registry).some(
p => p.pointerType === 'touch' && !p.isCancelled,
)

if (!isPrimary) {
Object.values(this.registry).forEach(p => {
if (p.pointerType === keyDef.pointerType && !p.isCancelled) {
if (p.pointerType === pointerType && !p.isCancelled) {
p.isMultitouch = true
}
})
}

this.registry[pointerName] = new Pointer({
pointerId: this.nextId++,
pointerType: keyDef.pointerType,
isPrimary,
})
this.registry[pointerName] = new Pointer(
{
pointerId: this.nextId++,
pointerType,
isPrimary,
},
buttons,
)

return this.registry[pointerName]
}
Expand Down Expand Up @@ -84,10 +82,14 @@ export class PointerHost {
keyDef: pointerKey,
position: PointerPosition,
) {
this.devices.get(keyDef.pointerType).addPressed(keyDef)

this.buttons.down(keyDef)

const pointerName = this.getPointerName(keyDef)
const pointer =
keyDef.pointerType === 'touch'
? this.pointers.new(pointerName, keyDef).init(instance, position)
? this.pointers.new(pointerName, keyDef.pointerType, this.buttons)
: this.pointers.get(pointerName)

// TODO: deprecate the following implicit setting of position
Expand All @@ -96,10 +98,11 @@ export class PointerHost {
this.mouse.position = position
}

this.devices.get(keyDef.pointerType).addPressed(keyDef)
if (pointer.pointerType === 'touch') {
pointer.init(instance)
}

this.buttons.down(keyDef)
pointer.down(instance, keyDef)
pointer.down(instance, keyDef.button)

if (pointer.pointerType !== 'touch' && !pointer.isPrevented) {
this.mouse.down(instance, keyDef, pointer)
Expand Down Expand Up @@ -150,7 +153,7 @@ export class PointerHost {
}

if (device.countPressed === 0) {
pointer.up(instance, keyDef)
pointer.up(instance, keyDef.button)
}

if (pointer.pointerType === 'touch') {
Expand Down
36 changes: 25 additions & 11 deletions src/system/pointer/pointer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {type Instance} from '../../setup'
import {assertPointerEvents, getTreeDiff, hasPointerEvents} from '../../utils'
import {isDifferentPointerPosition, pointerKey, PointerPosition} from './shared'
import {isDifferentPointerPosition, PointerPosition} from './shared'
import {Buttons, getMouseEventButton, MouseButton} from './buttons'

type PointerInit = {
pointerId: number
Expand All @@ -9,15 +10,20 @@ type PointerInit = {
}

export class Pointer {
constructor({pointerId, pointerType, isPrimary}: PointerInit) {
constructor(
{pointerId, pointerType, isPrimary}: PointerInit,
buttons: Buttons,
) {
this.pointerId = pointerId
this.pointerType = pointerType
this.isPrimary = isPrimary
this.isMultitouch = !isPrimary
this.buttons = buttons
}
readonly pointerId: number
readonly pointerType: string
readonly isPrimary: boolean
readonly buttons: Buttons

isMultitouch: boolean = false
isCancelled: boolean = false
Expand All @@ -26,9 +32,7 @@ export class Pointer {

position: PointerPosition = {}

init(instance: Instance, position: PointerPosition) {
this.position = position

init(instance: Instance) {
const target = this.getTarget(instance)
const [, enter] = getTreeDiff(null, target)
const init = this.getEventInit()
Expand All @@ -53,7 +57,7 @@ export class Pointer {

const nextTarget = this.getTarget(instance)

const init = this.getEventInit()
const init = this.getEventInit(-1)

const [leave, enter] = getTreeDiff(prevTarget, nextTarget)

Expand Down Expand Up @@ -84,7 +88,7 @@ export class Pointer {
}
}

down(instance: Instance, _keyDef: pointerKey) {
down(instance: Instance, button: MouseButton = 0) {
if (this.isDown) {
return
}
Expand All @@ -96,11 +100,11 @@ export class Pointer {
this.isPrevented = !instance.dispatchUIEvent(
target,
'pointerdown',
this.getEventInit(),
this.getEventInit(button),
)
}

up(instance: Instance, _keyDef: pointerKey) {
up(instance: Instance, button: MouseButton = 0) {
if (!this.isDown) {
return
}
Expand All @@ -109,7 +113,7 @@ export class Pointer {
assertPointerEvents(instance, target)

this.isDown = false
instance.dispatchUIEvent(target, 'pointerup', this.getEventInit())
instance.dispatchUIEvent(target, 'pointerup', this.getEventInit(button))
}

release(instance: Instance) {
Expand All @@ -132,12 +136,22 @@ export class Pointer {
return this.position.target ?? instance.config.document.body
}

private getEventInit(): PointerEventInit {
private getEventInit(
/**
* The `button` that caused the event.
*
* This should be `-1` if the event is not caused by a button or touch/pen contact,
* e.g. a moving pointer.
*/
button?: MouseButton,
): PointerEventInit {
return {
...this.position.coords,
pointerId: this.pointerId,
pointerType: this.pointerType,
isPrimary: this.isPrimary,
button: getMouseEventButton(button),
buttons: this.buttons.getButtons(),
}
}
}
2 changes: 1 addition & 1 deletion tests/_helpers/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function addListeners(
isMouseEvent(e)
? `${e.type} - button=${e.button}; buttons=${e.buttons}; detail=${e.detail}`
: isPointerEvent(e)
? `${e.type} - pointerId=${e.pointerId}; pointerType=${e.pointerType}; isPrimary=${e.isPrimary}`
? `${e.type} - pointerId=${e.pointerId}; pointerType=${e.pointerType}; isPrimary=${e.isPrimary}; button=${e.button}; buttons=${e.buttons}`
: e.type,
)
return {snapshot: lines.join('\n')}
Expand Down
70 changes: 35 additions & 35 deletions tests/pointer/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ test('click element', async () => {
await user.pointer({keys: '[MouseLeft]', target: element})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=1
mousedown - button=0; buttons=1; detail=1
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=0
mouseup - button=0; buttons=0; detail=1
click - button=0; buttons=0; detail=1
`)
Expand All @@ -19,7 +19,7 @@ test('secondary button triggers contextmenu', async () => {
await user.pointer({keys: '[MouseRight>]', target: element})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=2; buttons=2
mousedown - button=2; buttons=2; detail=1
contextmenu - button=2; buttons=2; detail=0
`)
Expand All @@ -33,14 +33,14 @@ test('double click', async () => {
await user.pointer({keys: '[MouseLeft][MouseLeft]', target: element})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=1
mousedown - button=0; buttons=1; detail=1
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=0
mouseup - button=0; buttons=0; detail=1
click - button=0; buttons=0; detail=1
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=1
mousedown - button=0; buttons=1; detail=2
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=0
mouseup - button=0; buttons=0; detail=2
click - button=0; buttons=0; detail=2
dblclick - button=0; buttons=0; detail=2
Expand All @@ -65,14 +65,14 @@ test('two clicks', async () => {
await user.pointer({keys: '[MouseLeft]'})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=1
mousedown - button=0; buttons=1; detail=1
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=0
mouseup - button=0; buttons=0; detail=1
click - button=0; buttons=0; detail=1
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=1
mousedown - button=0; buttons=1; detail=1
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=0
mouseup - button=0; buttons=0; detail=1
click - button=0; buttons=0; detail=1
`)
Expand All @@ -93,22 +93,22 @@ test('other keys reset click counter', async () => {
})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=1
mousedown - button=0; buttons=1; detail=1
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=0
mouseup - button=0; buttons=0; detail=1
click - button=0; buttons=0; detail=1
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=1
mousedown - button=0; buttons=1; detail=2
mousedown - button=2; buttons=3; detail=1
contextmenu - button=2; buttons=3; detail=0
mouseup - button=2; buttons=1; detail=1
auxclick - button=2; buttons=1; detail=1
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=0
mouseup - button=0; buttons=0; detail=0
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=1
mousedown - button=0; buttons=1; detail=1
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true; button=0; buttons=0
mouseup - button=0; buttons=0; detail=1
click - button=0; buttons=0; detail=1
`)
Expand All @@ -124,12 +124,12 @@ test('click per touch device', async () => {
await user.pointer({keys: '[TouchA]', target: element})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerover - pointerId=2; pointerType=touch; isPrimary=true
pointerenter - pointerId=2; pointerType=touch; isPrimary=true
pointerdown - pointerId=2; pointerType=touch; isPrimary=true
pointerup - pointerId=2; pointerType=touch; isPrimary=true
pointerout - pointerId=2; pointerType=touch; isPrimary=true
pointerleave - pointerId=2; pointerType=touch; isPrimary=true
pointerover - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerenter - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerdown - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerup - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=0
pointerout - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=0
pointerleave - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=0
mouseover - button=0; buttons=0; detail=0
mouseenter - button=0; buttons=0; detail=0
mousemove - button=0; buttons=0; detail=0
Expand All @@ -150,24 +150,24 @@ test('double click per touch device', async () => {
await user.pointer({keys: '[TouchA][TouchA]', target: element})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerover - pointerId=2; pointerType=touch; isPrimary=true
pointerenter - pointerId=2; pointerType=touch; isPrimary=true
pointerdown - pointerId=2; pointerType=touch; isPrimary=true
pointerup - pointerId=2; pointerType=touch; isPrimary=true
pointerout - pointerId=2; pointerType=touch; isPrimary=true
pointerleave - pointerId=2; pointerType=touch; isPrimary=true
pointerover - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerenter - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerdown - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerup - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=0
pointerout - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=0
pointerleave - pointerId=2; pointerType=touch; isPrimary=true; button=0; buttons=0
mouseover - button=0; buttons=0; detail=0
mouseenter - button=0; buttons=0; detail=0
mousemove - button=0; buttons=0; detail=0
mousedown - button=0; buttons=1; detail=1
mouseup - button=0; buttons=0; detail=1
click - button=0; buttons=0; detail=1
pointerover - pointerId=3; pointerType=touch; isPrimary=true
pointerenter - pointerId=3; pointerType=touch; isPrimary=true
pointerdown - pointerId=3; pointerType=touch; isPrimary=true
pointerup - pointerId=3; pointerType=touch; isPrimary=true
pointerout - pointerId=3; pointerType=touch; isPrimary=true
pointerleave - pointerId=3; pointerType=touch; isPrimary=true
pointerover - pointerId=3; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerenter - pointerId=3; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerdown - pointerId=3; pointerType=touch; isPrimary=true; button=0; buttons=1
pointerup - pointerId=3; pointerType=touch; isPrimary=true; button=0; buttons=0
pointerout - pointerId=3; pointerType=touch; isPrimary=true; button=0; buttons=0
pointerleave - pointerId=3; pointerType=touch; isPrimary=true; button=0; buttons=0
mousedown - button=0; buttons=1; detail=2
mouseup - button=0; buttons=0; detail=2
click - button=0; buttons=0; detail=2
Expand Down
Loading
Loading