@@ -2,22 +2,33 @@ import {PointerEventsCheckLevel} from '../../options'
2
2
import { Config } from '../../setup'
3
3
import { ApiLevel , getLevelRef } from '..'
4
4
import { getWindow } from '../misc/getWindow'
5
+ import { isElementType } from '../misc/isElementType'
5
6
6
7
export function hasPointerEvents ( element : Element ) : boolean {
8
+ return closestPointerEventsDeclaration ( element ) ?. pointerEvents !== 'none'
9
+ }
10
+
11
+ function closestPointerEventsDeclaration ( element : Element ) :
12
+ | {
13
+ pointerEvents : string
14
+ tree : Element [ ]
15
+ }
16
+ | undefined {
7
17
const window = getWindow ( element )
8
18
9
19
for (
10
- let el : Element | null = element ;
20
+ let el : Element | null = element , tree : Element [ ] = [ ] ;
11
21
el ?. ownerDocument ;
12
22
el = el . parentElement
13
23
) {
24
+ tree . push ( el )
14
25
const pointerEvents = window . getComputedStyle ( el ) . pointerEvents
15
26
if ( pointerEvents && ! [ 'inherit' , 'unset' ] . includes ( pointerEvents ) ) {
16
- return pointerEvents !== 'none'
27
+ return { pointerEvents, tree }
17
28
}
18
29
}
19
30
20
- return true
31
+ return undefined
21
32
}
22
33
23
34
const PointerEventsCheck = Symbol ( 'Last check for pointer-events' )
@@ -52,21 +63,84 @@ export function assertPointerEvents(config: Config, element: Element) {
52
63
return
53
64
}
54
65
55
- const result = hasPointerEvents ( element )
66
+ const declaration = closestPointerEventsDeclaration ( element )
56
67
57
68
element [ PointerEventsCheck ] = {
58
69
[ ApiLevel . Call ] : getLevelRef ( config , ApiLevel . Call ) ,
59
70
[ ApiLevel . Trigger ] : getLevelRef ( config , ApiLevel . Trigger ) ,
60
- result,
71
+ result : declaration ?. pointerEvents !== 'none' ,
61
72
}
62
73
63
- if ( ! result ) {
74
+ if ( declaration ?. pointerEvents === 'none' ) {
64
75
throw new Error (
65
- 'Unable to perform pointer interaction as the element has or inherits pointer-events set to "none".' ,
76
+ [
77
+ `Unable to perform pointer interaction as the element ${
78
+ declaration . tree . length > 1 ? 'inherits' : 'has'
79
+ } \`pointer-events: none\`:`,
80
+ '' ,
81
+ printTree ( declaration . tree ) ,
82
+ ] . join ( '\n' ) ,
66
83
)
67
84
}
68
85
}
69
86
87
+ function printTree ( tree : Element [ ] ) {
88
+ return tree
89
+ . reverse ( )
90
+ . map ( ( el , i ) =>
91
+ [
92
+ '' . padEnd ( i ) ,
93
+ el . tagName ,
94
+ el . id && `#${ el . id } ` ,
95
+ el . hasAttribute ( 'data-testid' ) &&
96
+ `(testId=${ el . getAttribute ( 'data-testid' ) } )` ,
97
+ getLabelDescr ( el ) ,
98
+ tree . length > 1 &&
99
+ i === 0 &&
100
+ ' <-- This element declared `pointer-events: none`' ,
101
+ tree . length > 1 &&
102
+ i === tree . length - 1 &&
103
+ ' <-- Asserted pointer events here' ,
104
+ ]
105
+ . filter ( Boolean )
106
+ . join ( '' ) ,
107
+ )
108
+ . join ( '\n' )
109
+ }
110
+
111
+ function getLabelDescr ( element : Element ) {
112
+ let label : string | undefined | null
113
+ if ( element . hasAttribute ( 'aria-label' ) ) {
114
+ label = element . getAttribute ( 'aria-label' ) as string
115
+ } else if ( element . hasAttribute ( 'aria-labelledby' ) ) {
116
+ label = element . ownerDocument
117
+ . getElementById ( element . getAttribute ( 'aria-labelledby' ) as string )
118
+ ?. textContent ?. trim ( )
119
+ } else if (
120
+ isElementType ( element , [
121
+ 'button' ,
122
+ 'input' ,
123
+ 'meter' ,
124
+ 'output' ,
125
+ 'progress' ,
126
+ 'select' ,
127
+ 'textarea' ,
128
+ ] ) &&
129
+ element . labels ?. length
130
+ ) {
131
+ label = Array . from ( element . labels )
132
+ . map ( el => el . textContent ?. trim ( ) )
133
+ . join ( '|' )
134
+ } else if ( isElementType ( element , 'button' ) ) {
135
+ label = element . textContent ?. trim ( )
136
+ }
137
+ label = label ?. replace ( / \n / g, ' ' )
138
+ if ( Number ( label ?. length ) > 30 ) {
139
+ label = `${ label ?. substring ( 0 , 29 ) } …`
140
+ }
141
+ return label ? `(label=${ label } )` : ''
142
+ }
143
+
70
144
// With the eslint rule and prettier the bitwise operation isn't nice to read
71
145
function hasBitFlag ( conf : number , flag : number ) {
72
146
// eslint-disable-next-line no-bitwise
0 commit comments