Skip to content

Commit 6e4058b

Browse files
authored
fix(pointer): do not throw for pointer-events: none on previous target (#991)
1 parent 9816d38 commit 6e4058b

File tree

6 files changed

+73
-27
lines changed

6 files changed

+73
-27
lines changed

src/convenience/hover.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import {Instance} from '../setup'
1+
import {Config, Instance} from '../setup'
2+
import {assertPointerEvents} from '../utils'
23

34
export async function hover(this: Instance, element: Element) {
45
return this.pointer({target: element})
56
}
67

78
export async function unhover(this: Instance, element: Element) {
9+
assertPointerEvents(
10+
this[Config],
11+
this[Config].pointerState.position.mouse.target as Element,
12+
)
813
return this.pointer({target: element.ownerDocument.body})
914
}

src/pointer/pointerMove.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
assertPointerEvents,
88
setLevelRef,
99
ApiLevel,
10+
hasPointerEvents,
1011
} from '../utils'
1112
import {firePointerEvent} from './firePointerEvents'
1213
import {resolveSelectionTarget} from './resolveSelectionTarget'
@@ -37,13 +38,13 @@ export async function pointerMove(
3738

3839
if (prevTarget && prevTarget !== target) {
3940
setLevelRef(config, ApiLevel.Trigger)
40-
assertPointerEvents(config, prevTarget)
41+
if (hasPointerEvents(config, prevTarget)) {
42+
// Here we could probably calculate a few coords to a fake boundary(?)
43+
fireMove(prevTarget, prevCoords)
4144

42-
// Here we could probably calculate a few coords to a fake boundary(?)
43-
fireMove(prevTarget, prevCoords)
44-
45-
if (!isDescendantOrSelf(target, prevTarget)) {
46-
fireLeave(prevTarget, prevCoords)
45+
if (!isDescendantOrSelf(target, prevTarget)) {
46+
fireLeave(prevTarget, prevCoords)
47+
}
4748
}
4849
}
4950

src/utility/selectOptions.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ async function selectOptionsBase(
8080
const withPointerEvents =
8181
this[Config].pointerEventsCheck === 0
8282
? true
83-
: hasPointerEvents(option)
83+
: hasPointerEvents(this[Config], option)
8484

8585
// events fired for multiple select are weird. Can't use hover...
8686
if (withPointerEvents) {
@@ -111,7 +111,9 @@ async function selectOptionsBase(
111111
}
112112
} else if (selectedOptions.length === 1) {
113113
const withPointerEvents =
114-
this[Config].pointerEventsCheck === 0 ? true : hasPointerEvents(select)
114+
this[Config].pointerEventsCheck === 0
115+
? true
116+
: hasPointerEvents(this[Config], select)
115117
// the click to open the select options
116118
if (withPointerEvents) {
117119
await this.click(select)

src/utils/pointer/cssPointerEvents.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {ApiLevel, getLevelRef} from '..'
44
import {getWindow} from '../misc/getWindow'
55
import {isElementType} from '../misc/isElementType'
66

7-
export function hasPointerEvents(element: Element): boolean {
8-
return closestPointerEventsDeclaration(element)?.pointerEvents !== 'none'
7+
export function hasPointerEvents(config: Config, element: Element): boolean {
8+
return checkPointerEvents(config, element)?.pointerEvents !== 'none'
99
}
1010

1111
function closestPointerEventsDeclaration(element: Element):
@@ -37,12 +37,12 @@ declare global {
3737
[PointerEventsCheck]?: {
3838
[k in ApiLevel]?: object
3939
} & {
40-
result: boolean
40+
result: ReturnType<typeof closestPointerEventsDeclaration>
4141
}
4242
}
4343
}
4444

45-
export function assertPointerEvents(config: Config, element: Element) {
45+
function checkPointerEvents(config: Config, element: Element) {
4646
const lastCheck = element[PointerEventsCheck]
4747

4848
const needsCheck =
@@ -60,17 +60,23 @@ export function assertPointerEvents(config: Config, element: Element) {
6060
lastCheck[ApiLevel.Trigger] !== getLevelRef(config, ApiLevel.Trigger)))
6161

6262
if (!needsCheck) {
63-
return
63+
return lastCheck?.result
6464
}
6565

6666
const declaration = closestPointerEventsDeclaration(element)
6767

6868
element[PointerEventsCheck] = {
6969
[ApiLevel.Call]: getLevelRef(config, ApiLevel.Call),
7070
[ApiLevel.Trigger]: getLevelRef(config, ApiLevel.Trigger),
71-
result: declaration?.pointerEvents !== 'none',
71+
result: declaration,
7272
}
7373

74+
return declaration
75+
}
76+
77+
export function assertPointerEvents(config: Config, element: Element) {
78+
const declaration = checkPointerEvents(config, element)
79+
7480
if (declaration?.pointerEvents === 'none') {
7581
throw new Error(
7682
[

tests/pointer/index.ts

+40-8
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,30 @@ test('only pointer events on disabled elements', async () => {
122122
})
123123

124124
describe('check for pointer-events', () => {
125-
const getComputedStyle = jest
126-
.spyOn(window, 'getComputedStyle')
127-
.mockImplementation(
128-
() =>
129-
({
130-
pointerEvents: 'foo',
131-
} as CSSStyleDeclaration),
132-
)
125+
let getComputedStyle: jest.SpyInstance<
126+
ReturnType<Window['getComputedStyle']>,
127+
Parameters<Window['getComputedStyle']>
128+
>
129+
beforeAll(() => {
130+
getComputedStyle = jest
131+
.spyOn(window, 'getComputedStyle')
132+
.mockImplementation(
133+
() =>
134+
({
135+
pointerEvents: 'foo',
136+
} as CSSStyleDeclaration),
137+
)
138+
})
133139
beforeEach(() => {
134140
getComputedStyle.mockClear()
135141
document.body.parentElement?.replaceChild(
136142
document.createElement('body'),
137143
document.body,
138144
)
139145
})
146+
afterAll(() => {
147+
jest.restoreAllMocks()
148+
})
140149

141150
test('skip check', async () => {
142151
const {element, user} = setup(`<input>`, {
@@ -216,3 +225,26 @@ describe('check for pointer-events', () => {
216225
expect(getComputedStyle).toHaveBeenNthCalledWith(6, document.body) // enter
217226
})
218227
})
228+
229+
test('reject if target has `pointer-events: none`', async () => {
230+
const {element, user} = setup(`<input style="pointer-events: none"/>`)
231+
232+
await expect(user.pointer({target: element})).rejects.toThrowError(
233+
'pointer-events',
234+
)
235+
await expect(
236+
user.pointer({target: element, keys: '[MouseLeft]'}),
237+
).rejects.toThrowError('pointer-events')
238+
})
239+
240+
test('omit pointer events on previous target if it has `pointer-events: none`', async () => {
241+
const {element, user} = setup(`<input/>`)
242+
const onPointerLeave = jest.fn()
243+
element.addEventListener('pointerleave', onPointerLeave)
244+
245+
await user.pointer({target: element})
246+
element.style.pointerEvents = 'none'
247+
await user.pointer({target: document.body})
248+
249+
expect(onPointerLeave).not.toBeCalled()
250+
})

tests/utils/pointer/cssPointerEvents.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ test('get pointer-events from element or ancestor', async () => {
1111
</div>
1212
`)
1313

14-
expect(hasPointerEvents(element)).toBe(false)
15-
expect(hasPointerEvents(element.children[0])).toBe(true)
16-
expect(hasPointerEvents(element.children[1])).toBe(false)
17-
expect(hasPointerEvents(element.children[2])).toBe(false)
14+
expect(hasPointerEvents(createConfig(), element)).toBe(false)
15+
expect(hasPointerEvents(createConfig(), element.children[0])).toBe(true)
16+
expect(hasPointerEvents(createConfig(), element.children[1])).toBe(false)
17+
expect(hasPointerEvents(createConfig(), element.children[2])).toBe(false)
1818
})
1919

2020
test('report element that declared pointer-events', async () => {

0 commit comments

Comments
 (0)