Skip to content

Commit

Permalink
fix(pointer): dispatch mouse events if pointerdown is `defaultPreve…
Browse files Browse the repository at this point in the history
…nted` (#1121)

Co-authored-by: Philipp Fritsche <ph.fritsche@gmail.com>
  • Loading branch information
snowystinger and ph-fritsche authored Jan 21, 2025
1 parent 3e471d1 commit f681f7b
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 51 deletions.
38 changes: 19 additions & 19 deletions src/system/pointer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ export class PointerHost {
this.buttons.down(keyDef)
pointer.down(instance, keyDef)

if (pointer.pointerType !== 'touch' && !pointer.isPrevented) {
this.mouse.down(instance, keyDef, pointer)
if (pointer.pointerType !== 'touch') {
this.mouse.down(instance, keyDef, pointer.isPrevented)
}
}

Expand All @@ -119,9 +119,9 @@ export class PointerHost {
// the order in which they interweave/follow on a user interaction depends on the implementation.
const pointermove = pointer.move(instance, position)
const mousemove =
pointer.pointerType === 'touch' || (pointer.isPrevented && pointer.isDown)
pointer.pointerType === 'touch'
? undefined
: this.mouse.move(instance, position)
: this.mouse.move(instance, position, pointer.isPrevented)

pointermove?.leave()
mousemove?.leave()
Expand All @@ -143,6 +143,8 @@ export class PointerHost {

const pointer = this.pointers.get(this.getPointerName(keyDef))

const isPrevented = pointer.isPrevented

// TODO: deprecate the following implicit setting of position
pointer.position = position
if (pointer.pointerType !== 'touch') {
Expand All @@ -157,23 +159,21 @@ export class PointerHost {
pointer.release(instance)
}

if (!pointer.isPrevented) {
if (pointer.pointerType === 'touch' && !pointer.isMultitouch) {
const mousemove = this.mouse.move(instance, pointer.position)
mousemove?.leave()
mousemove?.enter()
mousemove?.move()
if (pointer.pointerType === 'touch' && !pointer.isMultitouch) {
const mousemove = this.mouse.move(instance, position, isPrevented)
mousemove?.leave()
mousemove?.enter()
mousemove?.move()

this.mouse.down(instance, keyDef, pointer)
}
if (!pointer.isMultitouch) {
const mousemove = this.mouse.move(instance, pointer.position)
mousemove?.leave()
mousemove?.enter()
mousemove?.move()
this.mouse.down(instance, keyDef, isPrevented)
}
if (!pointer.isMultitouch) {
const mousemove = this.mouse.move(instance, position, isPrevented)
mousemove?.leave()
mousemove?.enter()
mousemove?.move()

this.mouse.up(instance, keyDef, pointer)
}
this.mouse.up(instance, keyDef, isPrevented)
}
}

Expand Down
62 changes: 36 additions & 26 deletions src/system/pointer/mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
import {type Instance} from '../../setup'
import {getTreeDiff, isDisabled} from '../../utils'
import {Buttons, getMouseEventButton, MouseButton} from './buttons'
import {type Pointer} from './pointer'
import {isDifferentPointerPosition, pointerKey, PointerPosition} from './shared'

/**
Expand Down Expand Up @@ -66,7 +65,12 @@ export class Mouse {
}
})()

move(instance: Instance, position: PointerPosition) {
move(
instance: Instance,
position: PointerPosition,
/** Whether `preventDefault()` has been called on the `pointerdown` event */
isPrevented: boolean,
) {
const prevPosition = this.position
const prevTarget = this.getTarget(instance)

Expand Down Expand Up @@ -96,14 +100,23 @@ export class Mouse {
}
},
move: () => {
if (isPrevented) {
return
}

instance.dispatchUIEvent(nextTarget, 'mousemove', init)

this.modifySelecting(instance)
},
}
}

down(instance: Instance, keyDef: pointerKey, pointer: Pointer) {
down(
instance: Instance,
keyDef: pointerKey,
/** Whether `preventDefault()` has been called on the `pointerdown` event */
isPrevented: boolean,
) {
const button = this.buttons.down(keyDef)

if (button === undefined) {
Expand All @@ -112,42 +125,49 @@ export class Mouse {

const target = this.getTarget(instance)
this.buttonDownTarget[button] = target
const disabled = isDisabled(target)
const init = this.getEventInit('mousedown', keyDef.button)
if (disabled || instance.dispatchUIEvent(target, 'mousedown', init)) {
const disabled = isDisabled(target)
if (
!isPrevented &&
(disabled || instance.dispatchUIEvent(target, 'mousedown', init))
) {
this.startSelecting(instance, init.detail as number)
focusElement(target)
}
if (!disabled && getMouseEventButton(keyDef.button) === 2) {
instance.dispatchUIEvent(
target,
'contextmenu',
this.getEventInit('contextmenu', keyDef.button, pointer),
this.getEventInit('contextmenu', keyDef.button),
)
}
}

up(instance: Instance, keyDef: pointerKey, pointer: Pointer) {
up(
instance: Instance,
keyDef: pointerKey,
/** Whether `preventDefault()` has been called on the `pointerdown` event */
isPrevented: boolean,
) {
const button = this.buttons.up(keyDef)

if (button === undefined) {
return
}
const target = this.getTarget(instance)
if (!isDisabled(target)) {
instance.dispatchUIEvent(
target,
'mouseup',
this.getEventInit('mouseup', keyDef.button),
)
this.endSelecting()
if (!isPrevented) {
const mouseUpInit = this.getEventInit('mouseup', keyDef.button)
instance.dispatchUIEvent(target, 'mouseup', mouseUpInit)
this.endSelecting()
}

const clickTarget = getTreeDiff(
this.buttonDownTarget[button],
target,
)[2][0] as Element | undefined
if (clickTarget) {
const init = this.getEventInit('click', keyDef.button, pointer)
const init = this.getEventInit('click', keyDef.button)
if (init.detail) {
instance.dispatchUIEvent(
clickTarget,
Expand All @@ -169,21 +189,11 @@ export class Mouse {
this.clickCount.reset()
}

private getEventInit(
type: EventType,
button?: MouseButton,
pointer?: Pointer,
) {
const init: PointerEventInit = {
private getEventInit(type: EventType, button?: MouseButton) {
const init: MouseEventInit = {
...this.position.coords,
}

if (pointer) {
init.pointerId = pointer.pointerId
init.pointerType = pointer.pointerType
init.isPrimary = pointer.isPrimary
}

init.button = getMouseEventButton(button)
init.buttons = this.buttons.getButtons()

Expand Down
1 change: 1 addition & 0 deletions src/system/pointer/pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export class Pointer {

assertPointerEvents(instance, target)

this.isPrevented = false
this.isDown = false
instance.dispatchUIEvent(target, 'pointerup', this.getEventInit())
}
Expand Down
17 changes: 17 additions & 0 deletions tests/convenience/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ describe.each([

expect(getEvents('mouseover')).toHaveLength(1)
expect(getEvents('mousedown')).toHaveLength(clickCount)
expect(getEvents('pointerdown')).toHaveLength(clickCount)
expect(getEvents('click')).toHaveLength(clickCount)
expect(getEvents('dblclick')).toHaveLength(clickCount >= 2 ? 1 : 0)
})

test('preventDefault on pointer down prevents compatibility events', async () => {
const {element, getEvents, user} = setup(`<div></div>`, {
eventHandlers: {pointerdown: e => e.preventDefault()},
})

await user[method](element)

expect(getEvents('mouseover')).toHaveLength(1)
expect(getEvents('mousedown')).toHaveLength(0)
expect(getEvents('mouseup')).toHaveLength(0)
expect(getEvents('pointerdown')).toHaveLength(clickCount)
expect(getEvents('pointerup')).toHaveLength(clickCount)
expect(getEvents('click')).toHaveLength(clickCount)
expect(getEvents('dblclick')).toHaveLength(clickCount >= 2 ? 1 : 0)
})
Expand Down
45 changes: 39 additions & 6 deletions tests/pointer/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,31 @@ test('double click', async () => {
expect(getEvents('click')).toHaveLength(2)

// detail reflects the click count
expect(getEvents('mousedown')[1]).toHaveProperty('detail', 2)
expect(getEvents('dblclick')[0]).toHaveProperty('detail', 2)
})

test('double click with prevent compatibility', async () => {
const {element, getClickEventsSnapshot, getEvents, user} = setup(
`<div></div>`,
{eventHandlers: {pointerdown: e => e.preventDefault()}},
)

await user.pointer({keys: '[MouseLeft][MouseLeft]', target: element})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
click - button=0; buttons=0; detail=1
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
click - button=0; buttons=0; detail=2
dblclick - button=0; buttons=0; detail=2
`)

expect(getEvents('dblclick')).toHaveLength(1)
expect(getEvents('click')).toHaveLength(2)

// detail reflects the click count
expect(getEvents('dblclick')[0]).toHaveProperty('detail', 2)
})

Expand Down Expand Up @@ -138,9 +162,7 @@ test('click per touch device', async () => {
click - button=0; buttons=0; detail=1
`)

// mouse is pointerId=1, every other pointer gets a new id
expect(getEvents('click')).toHaveLength(1)
expect(getEvents('click')[0]).toHaveProperty('pointerId', 2)
})

test('double click per touch device', async () => {
Expand Down Expand Up @@ -176,10 +198,7 @@ test('double click per touch device', async () => {

// mouse is pointerId=1, every other pointer gets a new id
expect(getEvents('click')).toHaveLength(2)
expect(getEvents('click')[0]).toHaveProperty('pointerId', 2)
expect(getEvents('click')[1]).toHaveProperty('pointerId', 3)
expect(getEvents('dblclick')).toHaveLength(1)
expect(getEvents('dblclick')[0]).not.toHaveProperty('pointerId')
})

test('multi touch does not click', async () => {
Expand Down Expand Up @@ -340,3 +359,17 @@ test('click closest common ancestor of pointerdown/pointerup', async () => {
expect(getEvents('mouseup')).toHaveLength(1)
expect(getEvents('click')).toHaveLength(0)
})

test('preventDefault on pointer down prevents compatibility events works with pointer', async () => {
const {element, getClickEventsSnapshot, getEvents, user} = setup('<div />', {
eventHandlers: {pointerdown: e => e.preventDefault()},
})
await user.pointer({keys: '[MouseLeft]', target: element})

expect(getClickEventsSnapshot()).toMatchInlineSnapshot(`
pointerdown - pointerId=1; pointerType=mouse; isPrimary=true
pointerup - pointerId=1; pointerType=mouse; isPrimary=true
click - button=0; buttons=0; detail=1
`)
expect(getEvents('click')).toHaveLength(1)
})

0 comments on commit f681f7b

Please sign in to comment.