diff --git a/src/system/pointer/index.ts b/src/system/pointer/index.ts index ba7158a2..ddbf190d 100644 --- a/src/system/pointer/index.ts +++ b/src/system/pointer/index.ts @@ -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 @@ -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 - private nextId = 2 - - new(pointerName: string, keyDef: pointerKey) { + private registry: Record = {} + 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] } @@ -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 @@ -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') { this.mouse.down(instance, keyDef, pointer.isPrevented) @@ -152,7 +155,7 @@ export class PointerHost { } if (device.countPressed === 0) { - pointer.up(instance, keyDef) + pointer.up(instance, keyDef.button) } if (pointer.pointerType === 'touch') { diff --git a/src/system/pointer/pointer.ts b/src/system/pointer/pointer.ts index 8897d80c..feed1c37 100644 --- a/src/system/pointer/pointer.ts +++ b/src/system/pointer/pointer.ts @@ -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 @@ -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 @@ -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() @@ -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) @@ -84,7 +88,7 @@ export class Pointer { } } - down(instance: Instance, _keyDef: pointerKey) { + down(instance: Instance, button: MouseButton = 0) { if (this.isDown) { return } @@ -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 } @@ -110,7 +114,7 @@ export class Pointer { this.isPrevented = false this.isDown = false - instance.dispatchUIEvent(target, 'pointerup', this.getEventInit()) + instance.dispatchUIEvent(target, 'pointerup', this.getEventInit(button)) } release(instance: Instance) { @@ -133,12 +137,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(), } } } diff --git a/tests/_helpers/listeners.ts b/tests/_helpers/listeners.ts index a579b2f8..d8f6b136 100644 --- a/tests/_helpers/listeners.ts +++ b/tests/_helpers/listeners.ts @@ -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')} diff --git a/tests/pointer/click.ts b/tests/pointer/click.ts index fd337a86..44f0350a 100644 --- a/tests/pointer/click.ts +++ b/tests/pointer/click.ts @@ -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 `) @@ -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 `) @@ -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 @@ -89,14 +89,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 `) @@ -117,22 +117,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 `) @@ -148,12 +148,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 @@ -172,24 +172,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 diff --git a/tests/pointer/drag.ts b/tests/pointer/drag.ts index 232cdd50..818dcbe8 100644 --- a/tests/pointer/drag.ts +++ b/tests/pointer/drag.ts @@ -10,11 +10,11 @@ test('drag sequence', 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 - pointermove - pointerId=1; pointerType=mouse; isPrimary=true + pointermove - pointerId=1; pointerType=mouse; isPrimary=true; button=-1; buttons=1 mousemove - button=0; buttons=1; detail=0 - 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 `) @@ -31,11 +31,11 @@ test('drag sequence w/ differing client coordinates', 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 - pointermove - pointerId=1; pointerType=mouse; isPrimary=true + pointermove - pointerId=1; pointerType=mouse; isPrimary=true; button=-1; buttons=1 mousemove - button=0; buttons=1; detail=0 - 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 `) @@ -51,13 +51,13 @@ test('drag touch', async () => { ]) expect(getClickEventsSnapshot()).toMatchInlineSnapshot(` - pointerover - pointerId=2; pointerType=touch; isPrimary=true - pointerenter - pointerId=2; pointerType=touch; isPrimary=true - pointerdown - pointerId=2; pointerType=touch; isPrimary=true - pointermove - 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 + pointermove - pointerId=2; pointerType=touch; isPrimary=true; button=-1; 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