diff --git a/lib/utils/get-role.js b/lib/utils/get-role.js index c9ca3e72..b69431d7 100644 --- a/lib/utils/get-role.js +++ b/lib/utils/get-role.js @@ -3,27 +3,38 @@ const {elementRoles} = require('aria-query') const {getElementType} = require('./get-element-type') const ObjectMap = require('./object-map') -// Clean-up `elementRoles` from `aria-query`. -const elementRolesMap = new ObjectMap() -for (const [key, value] of elementRoles.entries()) { - // - Remove empty `attributes` key - if (!key.attributes || key.attributes?.length === 0) { - delete key.attributes +const elementRolesMap = cleanElementRolesMap() + +/* + Returns an element roles map which uses `aria-query`'s elementRoles as the foundation. + We additionally clean the data so we're able to fetch a role using a key we construct based on the node we're looking at. + In a few scenarios, we stray from the roles returned by `aria-query` and hard code the mapping. +*/ +function cleanElementRolesMap() { + const rolesMap = new ObjectMap() + + for (const [key, value] of elementRoles.entries()) { + // - Remove empty `attributes` key + if (!key.attributes || key.attributes?.length === 0) { + delete key.attributes + } + rolesMap.set(key, value) } - elementRolesMap.set(key, value) -} -// Remove insufficiently-disambiguated `menuitem` entry -elementRolesMap.delete({name: 'menuitem'}) -// Disambiguate `menuitem` and `menu` roles by `type` -elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'command'}]}, ['menuitem']) -elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'radio'}]}, ['menuitemradio']) -elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar']) -elementRolesMap.set({name: 'menu', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar']) + // Remove insufficiently-disambiguated `menuitem` entry + rolesMap.delete({name: 'menuitem'}) + // Disambiguate `menuitem` and `menu` roles by `type` + rolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'command'}]}, ['menuitem']) + rolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'radio'}]}, ['menuitemradio']) + rolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar']) + rolesMap.set({name: 'menu', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar']) -/* These have constraints defined in aria-query's `elementRoles` which depend on knowledge of ancestor roles which we cant accurately determine in a linter context. - However, we benefit more from assuming the role, than assuming it's generic or undefined so we opt to hard code the mapping */ -elementRolesMap.set({name: 'aside'}, ['complementary']) // `aside` still maps to `complementary` in https://www.w3.org/TR/html-aria/#docconformance. -elementRolesMap.set({name: 'li'}, ['listitem']) // `li` can be generic if it's not within a list but we would never want to render `li` outside of a list. + /* These have constraints defined in aria-query's `elementRoles` which depend on knowledge of ancestor roles which we cant accurately determine in a linter context. + However, we benefit more from assuming the role, than assuming it's generic or undefined so we opt to hard code the mapping */ + rolesMap.set({name: 'aside'}, ['complementary']) // `aside` still maps to `complementary` in https://www.w3.org/TR/html-aria/#docconformance. + rolesMap.set({name: 'li'}, ['listitem']) // `li` can be generic if it's not within a list but we would never want to render `li` outside of a list. + + return rolesMap +} /* Determine role of an element, based on its name and attributes.