main
js 140 lines 3.14 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 // Below code forked from dom-accessibility-api
11
12 const tagToRoleMappings = {
13 ARTICLE: 'article',
14 ASIDE: 'complementary',
15 BODY: 'document',
16 BUTTON: 'button',
17 DATALIST: 'listbox',
18 DD: 'definition',
19 DETAILS: 'group',
20 DIALOG: 'dialog',
21 DT: 'term',
22 FIELDSET: 'group',
23 FIGURE: 'figure',
24 // WARNING: Only with an accessible name
25 FORM: 'form',
26 FOOTER: 'contentinfo',
27 H1: 'heading',
28 H2: 'heading',
29 H3: 'heading',
30 H4: 'heading',
31 H5: 'heading',
32 H6: 'heading',
33 HEADER: 'banner',
34 HR: 'separator',
35 LEGEND: 'legend',
36 LI: 'listitem',
37 MATH: 'math',
38 MAIN: 'main',
39 MENU: 'list',
40 NAV: 'navigation',
41 OL: 'list',
42 OPTGROUP: 'group',
43 // WARNING: Only in certain context
44 OPTION: 'option',
45 OUTPUT: 'status',
46 PROGRESS: 'progressbar',
47 // WARNING: Only with an accessible name
48 SECTION: 'region',
49 SUMMARY: 'button',
50 TABLE: 'table',
51 TBODY: 'rowgroup',
52 TEXTAREA: 'textbox',
53 TFOOT: 'rowgroup',
54 // WARNING: Only in certain context
55 TD: 'cell',
56 TH: 'columnheader',
57 THEAD: 'rowgroup',
58 TR: 'row',
59 UL: 'list',
60 };
61
62 function getImplicitRole(element: Element): string | null {
63 // $FlowFixMe[invalid-computed-prop]
64 const mappedByTag = tagToRoleMappings[element.tagName];
65 if (mappedByTag !== undefined) {
66 return mappedByTag;
67 }
68
69 switch (element.tagName) {
70 case 'A':
71 case 'AREA':
72 case 'LINK':
73 if (element.hasAttribute('href')) {
74 return 'link';
75 }
76 break;
77 case 'IMG':
78 if ((element.getAttribute('alt') || '').length > 0) {
79 return 'img';
80 }
81 break;
82 case 'INPUT': {
83 const type = (element as any).type;
84 switch (type) {
85 case 'button':
86 case 'image':
87 case 'reset':
88 case 'submit':
89 return 'button';
90 case 'checkbox':
91 case 'radio':
92 return type;
93 case 'range':
94 return 'slider';
95 case 'email':
96 case 'tel':
97 case 'text':
98 case 'url':
99 if (element.hasAttribute('list')) {
100 return 'combobox';
101 }
102 return 'textbox';
103 case 'search':
104 if (element.hasAttribute('list')) {
105 return 'combobox';
106 }
107 return 'searchbox';
108 default:
109 return null;
110 }
111 }
112
113 case 'SELECT':
114 if (element.hasAttribute('multiple') || (element as any).size > 1) {
115 return 'listbox';
116 }
117 return 'combobox';
118 }
119
120 return null;
121 }
122
123 function getExplicitRoles(element: Element): Array<string> | null {
124 const role = element.getAttribute('role');
125 if (role) {
126 return role.trim().split(' ');
127 }
128
129 return null;
130 }
131
132 // https://w3c.github.io/html-aria/#document-conformance-requirements-for-use-of-aria-attributes-in-html
133 export function hasRole(element: Element, role: string): boolean {
134 const explicitRoles = getExplicitRoles(element);
135 if (explicitRoles !== null && explicitRoles.indexOf(role) >= 0) {
136 return true;
137 }
138
139 return role === getImplicitRole(element);
140 }