1
- import { elementRoles } from 'aria-query'
1
+ import {
2
+ ARIARoleDefinitionKey ,
3
+ ARIARoleRelationConcept ,
4
+ elementRoles ,
5
+ } from 'aria-query'
2
6
import {
3
7
computeAccessibleDescription ,
4
8
computeAccessibleName ,
5
9
} from 'dom-accessibility-api'
6
10
import { prettyDOM } from './pretty-dom'
7
11
import { getConfig } from './config'
8
12
13
+ type ElementRoles = typeof elementRoles
14
+
9
15
const elementRoleList = buildElementRoleList ( elementRoles )
10
16
11
17
/**
12
18
* @param {Element } element -
13
19
* @returns {boolean } - `true` if `element` and its subtree are inaccessible
14
20
*/
15
- function isSubtreeInaccessible ( element ) {
16
- if ( element . hidden === true ) {
21
+ type IsSubtreeInaccessible = typeof isSubtreeInaccessible
22
+ function isSubtreeInaccessible ( element : Element ) {
23
+ if ( element . getAttribute ( 'hidden' ) !== null ) {
17
24
return true
18
25
}
19
26
20
27
if ( element . getAttribute ( 'aria-hidden' ) === 'true' ) {
21
28
return true
22
29
}
23
30
24
- const window = element . ownerDocument . defaultView
31
+ const window = element . ownerDocument . defaultView !
25
32
if ( window . getComputedStyle ( element ) . display === 'none' ) {
26
33
return true
27
34
}
@@ -43,11 +50,12 @@ function isSubtreeInaccessible(element) {
43
50
* can be used to return cached results from previous isSubtreeInaccessible calls
44
51
* @returns {boolean } true if excluded, otherwise false
45
52
*/
46
- function isInaccessible ( element , options = { } ) {
53
+ type IsInaccessibleOptions = { isSubtreeInaccessible ?: IsSubtreeInaccessible }
54
+ function isInaccessible ( element : Element , options : IsInaccessibleOptions = { } ) {
47
55
const {
48
56
isSubtreeInaccessible : isSubtreeInaccessibleImpl = isSubtreeInaccessible ,
49
57
} = options
50
- const window = element . ownerDocument . defaultView
58
+ const window = element . ownerDocument . defaultView !
51
59
// since visibility is inherited we can exit early
52
60
if ( window . getComputedStyle ( element ) . visibility === 'hidden' ) {
53
61
return true
@@ -59,13 +67,13 @@ function isInaccessible(element, options = {}) {
59
67
return true
60
68
}
61
69
62
- currentElement = currentElement . parentElement
70
+ currentElement = currentElement . parentElement !
63
71
}
64
72
65
73
return false
66
74
}
67
75
68
- function getImplicitAriaRoles ( currentNode ) {
76
+ function getImplicitAriaRoles ( currentNode : Element ) {
69
77
// eslint bug here:
70
78
// eslint-disable-next-line no-unused-vars
71
79
for ( const { match, roles} of elementRoleList ) {
@@ -76,12 +84,16 @@ function getImplicitAriaRoles(currentNode) {
76
84
77
85
return [ ]
78
86
}
79
-
80
- function buildElementRoleList ( elementRolesMap ) {
81
- function makeElementSelector ( { name, attributes} ) {
82
- return `${ name } ${ attributes
87
+ interface RoleList {
88
+ match : any
89
+ roles : ARIARoleDefinitionKey [ ]
90
+ specificity : number
91
+ }
92
+ function buildElementRoleList ( elementRolesMap : ElementRoles ) {
93
+ function makeElementSelector ( { name, attributes} : ARIARoleRelationConcept ) {
94
+ return `${ name } ${ attributes !
83
95
. map ( ( { name : attributeName , value, constraints = [ ] } ) => {
84
- const shouldNotExist = constraints . indexOf ( 'undefined' ) !== - 1
96
+ const shouldNotExist = constraints . indexOf ( 'undefined' as any ) !== - 1
85
97
if ( shouldNotExist ) {
86
98
return `:not([${ attributeName } ])`
87
99
} else if ( value ) {
@@ -93,18 +105,18 @@ function buildElementRoleList(elementRolesMap) {
93
105
. join ( '' ) } `
94
106
}
95
107
96
- function getSelectorSpecificity ( { attributes = [ ] } ) {
108
+ function getSelectorSpecificity ( { attributes = [ ] } : ARIARoleRelationConcept ) {
97
109
return attributes . length
98
110
}
99
111
100
112
function bySelectorSpecificity (
101
- { specificity : leftSpecificity } ,
102
- { specificity : rightSpecificity } ,
113
+ { specificity : leftSpecificity } : RoleList ,
114
+ { specificity : rightSpecificity } : RoleList ,
103
115
) {
104
116
return rightSpecificity - leftSpecificity
105
117
}
106
118
107
- function match ( element ) {
119
+ function match ( element : ARIARoleRelationConcept ) {
108
120
let { attributes = [ ] } = element
109
121
110
122
// https://github.com/testing-library/dom-testing-library/issues/814
@@ -125,16 +137,16 @@ function buildElementRoleList(elementRolesMap) {
125
137
126
138
const selector = makeElementSelector ( { ...element , attributes} )
127
139
128
- return node => {
129
- if ( typeTextIndex >= 0 && node . type !== 'text' ) {
140
+ return ( node : Element ) => {
141
+ if ( typeTextIndex >= 0 && ( node as HTMLInputElement ) . type !== 'text' ) {
130
142
return false
131
143
}
132
144
133
145
return node . matches ( selector )
134
146
}
135
147
}
136
148
137
- let result = [ ]
149
+ let result : RoleList [ ] = [ ]
138
150
139
151
// eslint bug here:
140
152
// eslint-disable-next-line no-unused-vars
@@ -152,13 +164,13 @@ function buildElementRoleList(elementRolesMap) {
152
164
return result . sort ( bySelectorSpecificity )
153
165
}
154
166
155
- function getRoles ( container , { hidden = false } = { } ) {
156
- function flattenDOM ( node ) {
167
+ function getRoles ( container : Element , { hidden = false } = { } ) {
168
+ function flattenDOM ( node : Element ) : Element [ ] {
157
169
return [
158
170
node ,
159
171
...Array . from ( node . children ) . reduce (
160
172
( acc , child ) => [ ...acc , ...flattenDOM ( child ) ] ,
161
- [ ] ,
173
+ [ ] as Element [ ] ,
162
174
) ,
163
175
]
164
176
}
@@ -171,7 +183,7 @@ function getRoles(container, {hidden = false} = {}) {
171
183
let roles = [ ]
172
184
// TODO: This violates html-aria which does not allow any role on every element
173
185
if ( node . hasAttribute ( 'role' ) ) {
174
- roles = node . getAttribute ( 'role' ) . split ( ' ' ) . slice ( 0 , 1 )
186
+ roles = node . getAttribute ( 'role' ) ! . split ( ' ' ) . slice ( 0 , 1 )
175
187
} else {
176
188
roles = getImplicitAriaRoles ( node )
177
189
}
@@ -181,12 +193,19 @@ function getRoles(container, {hidden = false} = {}) {
181
193
Array . isArray ( rolesAcc [ role ] )
182
194
? { ...rolesAcc , [ role ] : [ ...rolesAcc [ role ] , node ] }
183
195
: { ...rolesAcc , [ role ] : [ node ] } ,
184
- acc ,
196
+ acc as Record < string , Element [ ] > ,
185
197
)
186
- } , { } )
198
+ } , { } as Record < string , Element [ ] > )
187
199
}
188
200
189
- function prettyRoles ( dom , { hidden, includeDescription} ) {
201
+ interface PrettyRolesOptions {
202
+ hidden ?: boolean
203
+ includeDescription ?: boolean
204
+ }
205
+ function prettyRoles (
206
+ dom : Element ,
207
+ { hidden, includeDescription} : PrettyRolesOptions ,
208
+ ) {
190
209
const roles = getRoles ( dom , { hidden} )
191
210
// We prefer to skip generic role, we don't recommend it
192
211
return Object . entries ( roles )
@@ -222,14 +241,19 @@ function prettyRoles(dom, {hidden, includeDescription}) {
222
241
. join ( '\n' )
223
242
}
224
243
225
- const logRoles = ( dom , { hidden = false } = { } ) =>
244
+ interface LogRolesOptions {
245
+ hidden ?: boolean
246
+ }
247
+ const logRoles = ( dom : Element , { hidden = false } : LogRolesOptions = { } ) =>
226
248
console . log ( prettyRoles ( dom , { hidden} ) )
227
249
228
250
/**
229
251
* @param {Element } element -
230
252
* @returns {boolean | undefined } - false/true if (not)selected, undefined if not selectable
231
253
*/
232
- function computeAriaSelected ( element ) {
254
+ function computeAriaSelected (
255
+ element : Element & Partial < { selected : boolean } > ,
256
+ ) : boolean | undefined {
233
257
// implicit value from html-aam mappings: https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
234
258
// https://www.w3.org/TR/html-aam-1.0/#details-id-97
235
259
if ( element . tagName === 'OPTION' ) {
@@ -244,7 +268,7 @@ function computeAriaSelected(element) {
244
268
* @param {Element } element -
245
269
* @returns {boolean } -
246
270
*/
247
- function computeAriaBusy ( element ) {
271
+ function computeAriaBusy ( element : Element ) : boolean {
248
272
// https://www.w3.org/TR/wai-aria-1.1/#aria-busy
249
273
return element . getAttribute ( 'aria-busy' ) === 'true'
250
274
}
@@ -253,7 +277,9 @@ function computeAriaBusy(element) {
253
277
* @param {Element } element -
254
278
* @returns {boolean | undefined } - false/true if (not)checked, undefined if not checked-able
255
279
*/
256
- function computeAriaChecked ( element ) {
280
+ function computeAriaChecked (
281
+ element : Element & Partial < { checked : boolean ; indeterminate : boolean } > ,
282
+ ) : boolean | undefined {
257
283
// implicit value from html-aam mappings: https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
258
284
// https://www.w3.org/TR/html-aam-1.0/#details-id-56
259
285
// https://www.w3.org/TR/html-aam-1.0/#details-id-67
@@ -272,7 +298,7 @@ function computeAriaChecked(element) {
272
298
* @param {Element } element -
273
299
* @returns {boolean | undefined } - false/true if (not)pressed, undefined if not press-able
274
300
*/
275
- function computeAriaPressed ( element ) {
301
+ function computeAriaPressed ( element : Element ) : boolean | undefined {
276
302
// https://www.w3.org/TR/wai-aria-1.1/#aria-pressed
277
303
return checkBooleanAttribute ( element , 'aria-pressed' )
278
304
}
@@ -281,7 +307,7 @@ function computeAriaPressed(element) {
281
307
* @param {Element } element -
282
308
* @returns {boolean | string | null } -
283
309
*/
284
- function computeAriaCurrent ( element ) {
310
+ function computeAriaCurrent ( element : Element ) : boolean | string | null {
285
311
// https://www.w3.org/TR/wai-aria-1.1/#aria-current
286
312
return (
287
313
checkBooleanAttribute ( element , 'aria-current' ) ??
@@ -294,12 +320,15 @@ function computeAriaCurrent(element) {
294
320
* @param {Element } element -
295
321
* @returns {boolean | undefined } - false/true if (not)expanded, undefined if not expand-able
296
322
*/
297
- function computeAriaExpanded ( element ) {
323
+ function computeAriaExpanded ( element : Element ) : boolean | undefined {
298
324
// https://www.w3.org/TR/wai-aria-1.1/#aria-expanded
299
325
return checkBooleanAttribute ( element , 'aria-expanded' )
300
326
}
301
327
302
- function checkBooleanAttribute ( element , attribute ) {
328
+ function checkBooleanAttribute (
329
+ element : Element ,
330
+ attribute : string ,
331
+ ) : boolean | undefined {
303
332
const attributeValue = element . getAttribute ( attribute )
304
333
if ( attributeValue === 'true' ) {
305
334
return true
@@ -314,7 +343,7 @@ function checkBooleanAttribute(element, attribute) {
314
343
* @param {Element } element -
315
344
* @returns {number | undefined } - number if implicit heading or aria-level present, otherwise undefined
316
345
*/
317
- function computeHeadingLevel ( element ) {
346
+ function computeHeadingLevel ( element : Element ) : number | undefined {
318
347
// https://w3c.github.io/html-aam/#el-h1-h6
319
348
// https://w3c.github.io/html-aam/#el-h1-h6
320
349
const implicitHeadingLevels = {
@@ -331,14 +360,16 @@ function computeHeadingLevel(element) {
331
360
element . getAttribute ( 'aria-level' ) &&
332
361
Number ( element . getAttribute ( 'aria-level' ) )
333
362
334
- return ariaLevelAttribute || implicitHeadingLevels [ element . tagName ]
363
+ const tagName = element . tagName as keyof typeof implicitHeadingLevels
364
+
365
+ return ariaLevelAttribute || implicitHeadingLevels [ tagName ]
335
366
}
336
367
337
368
/**
338
369
* @param {Element } element -
339
370
* @returns {number | undefined } -
340
371
*/
341
- function computeAriaValueNow ( element ) {
372
+ function computeAriaValueNow ( element : Element ) : number | undefined {
342
373
const valueNow = element . getAttribute ( 'aria-valuenow' )
343
374
return valueNow === null ? undefined : + valueNow
344
375
}
@@ -347,7 +378,7 @@ function computeAriaValueNow(element) {
347
378
* @param {Element } element -
348
379
* @returns {number | undefined } -
349
380
*/
350
- function computeAriaValueMax ( element ) {
381
+ function computeAriaValueMax ( element : Element ) : number | undefined {
351
382
const valueMax = element . getAttribute ( 'aria-valuemax' )
352
383
return valueMax === null ? undefined : + valueMax
353
384
}
@@ -356,7 +387,7 @@ function computeAriaValueMax(element) {
356
387
* @param {Element } element -
357
388
* @returns {number | undefined } -
358
389
*/
359
- function computeAriaValueMin ( element ) {
390
+ function computeAriaValueMin ( element : Element ) : number | undefined {
360
391
const valueMin = element . getAttribute ( 'aria-valuemin' )
361
392
return valueMin === null ? undefined : + valueMin
362
393
}
@@ -365,7 +396,7 @@ function computeAriaValueMin(element) {
365
396
* @param {Element } element -
366
397
* @returns {string | undefined } -
367
398
*/
368
- function computeAriaValueText ( element ) {
399
+ function computeAriaValueText ( element : Element ) : string | undefined {
369
400
const valueText = element . getAttribute ( 'aria-valuetext' )
370
401
return valueText === null ? undefined : valueText
371
402
}
0 commit comments