11import {
2+ type AncestorList ,
23 getRole as getImplicitRole ,
34 type TagName ,
45 type VirtualElement ,
@@ -7,14 +8,16 @@ import { getLocalName } from "../getLocalName";
78import { isElement } from "../isElement" ;
89import { roles } from "aria-query" ;
910
10- export const presentationRoles = [ "presentation" , "none" ] ;
11+ export const presentationRoles = new Set ( [ "presentation" , "none" ] ) ;
1112
12- const allowedNonAbstractRoles = roles
13- . entries ( )
14- . filter ( ( [ , { abstract } ] ) => ! abstract )
15- . map ( ( [ key ] ) => key ) as string [ ] ;
13+ const allowedNonAbstractRoles = new Set (
14+ roles
15+ . entries ( )
16+ . filter ( ( [ , { abstract } ] ) => ! abstract )
17+ . map ( ( [ key ] ) => key ) as string [ ]
18+ ) ;
1619
17- const rolesRequiringName = [ "form" , "region" ] ;
20+ const rolesRequiringName = new Set ( [ "form" , "region" ] ) ;
1821
1922export const globalStatesAndProperties = [
2023 "aria-atomic" ,
@@ -90,7 +93,7 @@ function getExplicitRole({
9093 *
9194 * REF: https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors_roles
9295 */
93- . filter ( ( role ) => allowedNonAbstractRoles . includes ( role ) )
96+ . filter ( ( role ) => allowedNonAbstractRoles . has ( role ) )
9497 /**
9598 * Certain landmark roles require names from authors. In situations where
9699 * an author has not specified names for these landmarks, it is
@@ -105,7 +108,7 @@ function getExplicitRole({
105108 *
106109 * REF: https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors_roles
107110 */
108- . filter ( ( role ) => ! ! accessibleName || ! rolesRequiringName . includes ( role ) ) ;
111+ . filter ( ( role ) => ! ! accessibleName || ! rolesRequiringName . has ( role ) ) ;
109112
110113 /**
111114 * If an allowed child element has an explicit non-presentational role, user
@@ -152,7 +155,7 @@ function getExplicitRole({
152155 * REF: https://www.w3.org/TR/wai-aria-1.2/#conflict_resolution_presentation_none
153156 */
154157 . filter ( ( role ) => {
155- if ( ! presentationRoles . includes ( role ) ) {
158+ if ( ! presentationRoles . has ( role ) ) {
156159 return true ;
157160 }
158161
@@ -182,6 +185,57 @@ function virtualizeElement(element: HTMLElement): VirtualElement {
182185 return { tagName, attributes } ;
183186}
184187
188+ const rolesDependentOnHierarchy = new Set ( [
189+ "footer" ,
190+ "header" ,
191+ "li" ,
192+ "td" ,
193+ "th" ,
194+ "tr" ,
195+ ] ) ;
196+ const ignoredAncestors = new Set ( [ "body" , "document" ] ) ;
197+
198+ // TODO: Thought needed if the `getAncestors()` can limit the number of parents
199+ // it enumerates? Presumably as ancestors only matter for a limited number of
200+ // roles, there might be a ceiling to the amount of nesting that is even valid,
201+ // and therefore put an upper bound on how far to backtrack without having to
202+ // stop at the document level for every single element.
203+ //
204+ // Another thought is that we special case each element so the backtracking can
205+ // exit early if an ancestor with a relevant role has already been found.
206+ //
207+ // Alternatively see if providing an element that is part of a DOM can be
208+ // traversed by the `html-aria` library itself so these concerns are
209+ // centralised.
210+ function getAncestors ( node : HTMLElement ) : AncestorList | undefined {
211+ if ( ! rolesDependentOnHierarchy . has ( getLocalName ( node ) ) ) {
212+ return undefined ;
213+ }
214+
215+ const ancestors : AncestorList = [ ] ;
216+
217+ let target : HTMLElement | null = node ;
218+ let targetLocalName : string ;
219+
220+ while ( true ) {
221+ target = target . parentElement ;
222+
223+ if ( ! target ) {
224+ break ;
225+ }
226+
227+ targetLocalName = getLocalName ( target ) ;
228+
229+ if ( ignoredAncestors . has ( targetLocalName ) ) {
230+ break ;
231+ }
232+
233+ ancestors . push ( { tagName : targetLocalName as TagName } ) ;
234+ }
235+
236+ return ancestors ;
237+ }
238+
185239export function getRole ( {
186240 accessibleName,
187241 allowedAccessibilityRoles,
@@ -222,21 +276,9 @@ export function getRole({
222276
223277 const baseImplicitRole = isBodyElement
224278 ? "document"
225- : // TODO: explore whether can supply ancestors without encountering
226- // performance issues.
227- //
228- // `getImplicitRole(virtualizeElement(target, { ancestors: getAncestors(node) }));`
229- //
230- // Thought needed if the `getAncestors()` can limit the number of parents
231- // it enumerates. Presumably as ancestors only matter for a limited
232- // number of roles, there might be a ceiling to the amount of nesting
233- // that is even valid, and therefore put an upper bound on how far to
234- // backtrack without having to stop at the document level for every
235- // single element.
236- //
237- // Alternatively see if providing an element that is part of a DOM can be
238- // traversed by the `html-aria` library itself.
239- getImplicitRole ( virtualizeElement ( target ) ) ?? "" ;
279+ : getImplicitRole ( virtualizeElement ( target ) , {
280+ ancestors : getAncestors ( node ) ,
281+ } ) ?? "" ;
240282
241283 const implicitRole = mapAliasedRoles ( baseImplicitRole ) ;
242284
0 commit comments