Skip to content

Commit d5d947c

Browse files
committed
feat: migrate role helpers to ts
1 parent eadf748 commit d5d947c

File tree

1 file changed

+72
-41
lines changed

1 file changed

+72
-41
lines changed

Diff for: src/role-helpers.js renamed to src/role-helpers.ts

+72-41
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
1-
import {elementRoles} from 'aria-query'
1+
import {
2+
ARIARoleDefinitionKey,
3+
ARIARoleRelationConcept,
4+
elementRoles,
5+
} from 'aria-query'
26
import {
37
computeAccessibleDescription,
48
computeAccessibleName,
59
} from 'dom-accessibility-api'
610
import {prettyDOM} from './pretty-dom'
711
import {getConfig} from './config'
812

13+
type ElementRoles = typeof elementRoles
14+
915
const elementRoleList = buildElementRoleList(elementRoles)
1016

1117
/**
1218
* @param {Element} element -
1319
* @returns {boolean} - `true` if `element` and its subtree are inaccessible
1420
*/
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) {
1724
return true
1825
}
1926

2027
if (element.getAttribute('aria-hidden') === 'true') {
2128
return true
2229
}
2330

24-
const window = element.ownerDocument.defaultView
31+
const window = element.ownerDocument.defaultView!
2532
if (window.getComputedStyle(element).display === 'none') {
2633
return true
2734
}
@@ -43,11 +50,12 @@ function isSubtreeInaccessible(element) {
4350
* can be used to return cached results from previous isSubtreeInaccessible calls
4451
* @returns {boolean} true if excluded, otherwise false
4552
*/
46-
function isInaccessible(element, options = {}) {
53+
type IsInaccessibleOptions = {isSubtreeInaccessible?: IsSubtreeInaccessible}
54+
function isInaccessible(element: Element, options: IsInaccessibleOptions = {}) {
4755
const {
4856
isSubtreeInaccessible: isSubtreeInaccessibleImpl = isSubtreeInaccessible,
4957
} = options
50-
const window = element.ownerDocument.defaultView
58+
const window = element.ownerDocument.defaultView!
5159
// since visibility is inherited we can exit early
5260
if (window.getComputedStyle(element).visibility === 'hidden') {
5361
return true
@@ -59,13 +67,13 @@ function isInaccessible(element, options = {}) {
5967
return true
6068
}
6169

62-
currentElement = currentElement.parentElement
70+
currentElement = currentElement.parentElement!
6371
}
6472

6573
return false
6674
}
6775

68-
function getImplicitAriaRoles(currentNode) {
76+
function getImplicitAriaRoles(currentNode: Element) {
6977
// eslint bug here:
7078
// eslint-disable-next-line no-unused-vars
7179
for (const {match, roles} of elementRoleList) {
@@ -76,12 +84,16 @@ function getImplicitAriaRoles(currentNode) {
7684

7785
return []
7886
}
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!
8395
.map(({name: attributeName, value, constraints = []}) => {
84-
const shouldNotExist = constraints.indexOf('undefined') !== -1
96+
const shouldNotExist = constraints.indexOf('undefined' as any) !== -1
8597
if (shouldNotExist) {
8698
return `:not([${attributeName}])`
8799
} else if (value) {
@@ -93,18 +105,18 @@ function buildElementRoleList(elementRolesMap) {
93105
.join('')}`
94106
}
95107

96-
function getSelectorSpecificity({attributes = []}) {
108+
function getSelectorSpecificity({attributes = []}: ARIARoleRelationConcept) {
97109
return attributes.length
98110
}
99111

100112
function bySelectorSpecificity(
101-
{specificity: leftSpecificity},
102-
{specificity: rightSpecificity},
113+
{specificity: leftSpecificity}: RoleList,
114+
{specificity: rightSpecificity}: RoleList,
103115
) {
104116
return rightSpecificity - leftSpecificity
105117
}
106118

107-
function match(element) {
119+
function match(element: ARIARoleRelationConcept) {
108120
let {attributes = []} = element
109121

110122
// https://github.com/testing-library/dom-testing-library/issues/814
@@ -125,16 +137,16 @@ function buildElementRoleList(elementRolesMap) {
125137

126138
const selector = makeElementSelector({...element, attributes})
127139

128-
return node => {
129-
if (typeTextIndex >= 0 && node.type !== 'text') {
140+
return (node: Element) => {
141+
if (typeTextIndex >= 0 && (node as HTMLInputElement).type !== 'text') {
130142
return false
131143
}
132144

133145
return node.matches(selector)
134146
}
135147
}
136148

137-
let result = []
149+
let result: RoleList[] = []
138150

139151
// eslint bug here:
140152
// eslint-disable-next-line no-unused-vars
@@ -152,13 +164,13 @@ function buildElementRoleList(elementRolesMap) {
152164
return result.sort(bySelectorSpecificity)
153165
}
154166

155-
function getRoles(container, {hidden = false} = {}) {
156-
function flattenDOM(node) {
167+
function getRoles(container: Element, {hidden = false} = {}) {
168+
function flattenDOM(node: Element): Element[] {
157169
return [
158170
node,
159171
...Array.from(node.children).reduce(
160172
(acc, child) => [...acc, ...flattenDOM(child)],
161-
[],
173+
[] as Element[],
162174
),
163175
]
164176
}
@@ -171,7 +183,7 @@ function getRoles(container, {hidden = false} = {}) {
171183
let roles = []
172184
// TODO: This violates html-aria which does not allow any role on every element
173185
if (node.hasAttribute('role')) {
174-
roles = node.getAttribute('role').split(' ').slice(0, 1)
186+
roles = node.getAttribute('role')!.split(' ').slice(0, 1)
175187
} else {
176188
roles = getImplicitAriaRoles(node)
177189
}
@@ -181,12 +193,19 @@ function getRoles(container, {hidden = false} = {}) {
181193
Array.isArray(rolesAcc[role])
182194
? {...rolesAcc, [role]: [...rolesAcc[role], node]}
183195
: {...rolesAcc, [role]: [node]},
184-
acc,
196+
acc as Record<string, Element[]>,
185197
)
186-
}, {})
198+
}, {} as Record<string, Element[]>)
187199
}
188200

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+
) {
190209
const roles = getRoles(dom, {hidden})
191210
// We prefer to skip generic role, we don't recommend it
192211
return Object.entries(roles)
@@ -222,14 +241,19 @@ function prettyRoles(dom, {hidden, includeDescription}) {
222241
.join('\n')
223242
}
224243

225-
const logRoles = (dom, {hidden = false} = {}) =>
244+
interface LogRolesOptions {
245+
hidden?: boolean
246+
}
247+
const logRoles = (dom: Element, {hidden = false}: LogRolesOptions = {}) =>
226248
console.log(prettyRoles(dom, {hidden}))
227249

228250
/**
229251
* @param {Element} element -
230252
* @returns {boolean | undefined} - false/true if (not)selected, undefined if not selectable
231253
*/
232-
function computeAriaSelected(element) {
254+
function computeAriaSelected(
255+
element: Element & Partial<{selected: boolean}>,
256+
): boolean | undefined {
233257
// implicit value from html-aam mappings: https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
234258
// https://www.w3.org/TR/html-aam-1.0/#details-id-97
235259
if (element.tagName === 'OPTION') {
@@ -244,7 +268,7 @@ function computeAriaSelected(element) {
244268
* @param {Element} element -
245269
* @returns {boolean} -
246270
*/
247-
function computeAriaBusy(element) {
271+
function computeAriaBusy(element: Element): boolean {
248272
// https://www.w3.org/TR/wai-aria-1.1/#aria-busy
249273
return element.getAttribute('aria-busy') === 'true'
250274
}
@@ -253,7 +277,9 @@ function computeAriaBusy(element) {
253277
* @param {Element} element -
254278
* @returns {boolean | undefined} - false/true if (not)checked, undefined if not checked-able
255279
*/
256-
function computeAriaChecked(element) {
280+
function computeAriaChecked(
281+
element: Element & Partial<{checked: boolean; indeterminate: boolean}>,
282+
): boolean | undefined {
257283
// implicit value from html-aam mappings: https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
258284
// https://www.w3.org/TR/html-aam-1.0/#details-id-56
259285
// https://www.w3.org/TR/html-aam-1.0/#details-id-67
@@ -272,7 +298,7 @@ function computeAriaChecked(element) {
272298
* @param {Element} element -
273299
* @returns {boolean | undefined} - false/true if (not)pressed, undefined if not press-able
274300
*/
275-
function computeAriaPressed(element) {
301+
function computeAriaPressed(element: Element): boolean | undefined {
276302
// https://www.w3.org/TR/wai-aria-1.1/#aria-pressed
277303
return checkBooleanAttribute(element, 'aria-pressed')
278304
}
@@ -281,7 +307,7 @@ function computeAriaPressed(element) {
281307
* @param {Element} element -
282308
* @returns {boolean | string | null} -
283309
*/
284-
function computeAriaCurrent(element) {
310+
function computeAriaCurrent(element: Element): boolean | string | null {
285311
// https://www.w3.org/TR/wai-aria-1.1/#aria-current
286312
return (
287313
checkBooleanAttribute(element, 'aria-current') ??
@@ -294,12 +320,15 @@ function computeAriaCurrent(element) {
294320
* @param {Element} element -
295321
* @returns {boolean | undefined} - false/true if (not)expanded, undefined if not expand-able
296322
*/
297-
function computeAriaExpanded(element) {
323+
function computeAriaExpanded(element: Element): boolean | undefined {
298324
// https://www.w3.org/TR/wai-aria-1.1/#aria-expanded
299325
return checkBooleanAttribute(element, 'aria-expanded')
300326
}
301327

302-
function checkBooleanAttribute(element, attribute) {
328+
function checkBooleanAttribute(
329+
element: Element,
330+
attribute: string,
331+
): boolean | undefined {
303332
const attributeValue = element.getAttribute(attribute)
304333
if (attributeValue === 'true') {
305334
return true
@@ -314,7 +343,7 @@ function checkBooleanAttribute(element, attribute) {
314343
* @param {Element} element -
315344
* @returns {number | undefined} - number if implicit heading or aria-level present, otherwise undefined
316345
*/
317-
function computeHeadingLevel(element) {
346+
function computeHeadingLevel(element: Element): number | undefined {
318347
// https://w3c.github.io/html-aam/#el-h1-h6
319348
// https://w3c.github.io/html-aam/#el-h1-h6
320349
const implicitHeadingLevels = {
@@ -331,14 +360,16 @@ function computeHeadingLevel(element) {
331360
element.getAttribute('aria-level') &&
332361
Number(element.getAttribute('aria-level'))
333362

334-
return ariaLevelAttribute || implicitHeadingLevels[element.tagName]
363+
const tagName = element.tagName as keyof typeof implicitHeadingLevels
364+
365+
return ariaLevelAttribute || implicitHeadingLevels[tagName]
335366
}
336367

337368
/**
338369
* @param {Element} element -
339370
* @returns {number | undefined} -
340371
*/
341-
function computeAriaValueNow(element) {
372+
function computeAriaValueNow(element: Element): number | undefined {
342373
const valueNow = element.getAttribute('aria-valuenow')
343374
return valueNow === null ? undefined : +valueNow
344375
}
@@ -347,7 +378,7 @@ function computeAriaValueNow(element) {
347378
* @param {Element} element -
348379
* @returns {number | undefined} -
349380
*/
350-
function computeAriaValueMax(element) {
381+
function computeAriaValueMax(element: Element): number | undefined {
351382
const valueMax = element.getAttribute('aria-valuemax')
352383
return valueMax === null ? undefined : +valueMax
353384
}
@@ -356,7 +387,7 @@ function computeAriaValueMax(element) {
356387
* @param {Element} element -
357388
* @returns {number | undefined} -
358389
*/
359-
function computeAriaValueMin(element) {
390+
function computeAriaValueMin(element: Element): number | undefined {
360391
const valueMin = element.getAttribute('aria-valuemin')
361392
return valueMin === null ? undefined : +valueMin
362393
}
@@ -365,7 +396,7 @@ function computeAriaValueMin(element) {
365396
* @param {Element} element -
366397
* @returns {string | undefined} -
367398
*/
368-
function computeAriaValueText(element) {
399+
function computeAriaValueText(element: Element): string | undefined {
369400
const valueText = element.getAttribute('aria-valuetext')
370401
return valueText === null ? undefined : valueText
371402
}

0 commit comments

Comments
 (0)