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.   const mappedByTag = tagToRoleMappings[element.tagName];
    
  64.   if (mappedByTag !== undefined) {
    
  65.     return mappedByTag;
    
  66.   }
    
  67. 
    
  68.   switch (element.tagName) {
    
  69.     case 'A':
    
  70.     case 'AREA':
    
  71.     case 'LINK':
    
  72.       if (element.hasAttribute('href')) {
    
  73.         return 'link';
    
  74.       }
    
  75.       break;
    
  76.     case 'IMG':
    
  77.       if ((element.getAttribute('alt') || '').length > 0) {
    
  78.         return 'img';
    
  79.       }
    
  80.       break;
    
  81.     case 'INPUT': {
    
  82.       const type = (element: any).type;
    
  83.       switch (type) {
    
  84.         case 'button':
    
  85.         case 'image':
    
  86.         case 'reset':
    
  87.         case 'submit':
    
  88.           return 'button';
    
  89.         case 'checkbox':
    
  90.         case 'radio':
    
  91.           return type;
    
  92.         case 'range':
    
  93.           return 'slider';
    
  94.         case 'email':
    
  95.         case 'tel':
    
  96.         case 'text':
    
  97.         case 'url':
    
  98.           if (element.hasAttribute('list')) {
    
  99.             return 'combobox';
    
  100.           }
    
  101.           return 'textbox';
    
  102.         case 'search':
    
  103.           if (element.hasAttribute('list')) {
    
  104.             return 'combobox';
    
  105.           }
    
  106.           return 'searchbox';
    
  107.         default:
    
  108.           return null;
    
  109.       }
    
  110.     }
    
  111. 
    
  112.     case 'SELECT':
    
  113.       if (element.hasAttribute('multiple') || (element: any).size > 1) {
    
  114.         return 'listbox';
    
  115.       }
    
  116.       return 'combobox';
    
  117.   }
    
  118. 
    
  119.   return null;
    
  120. }
    
  121. 
    
  122. function getExplicitRoles(element: Element): Array<string> | null {
    
  123.   const role = element.getAttribute('role');
    
  124.   if (role) {
    
  125.     return role.trim().split(' ');
    
  126.   }
    
  127. 
    
  128.   return null;
    
  129. }
    
  130. 
    
  131. // https://w3c.github.io/html-aria/#document-conformance-requirements-for-use-of-aria-attributes-in-html
    
  132. export function hasRole(element: Element, role: string): boolean {
    
  133.   const explicitRoles = getExplicitRoles(element);
    
  134.   if (explicitRoles !== null && explicitRoles.indexOf(role) >= 0) {
    
  135.     return true;
    
  136.   }
    
  137. 
    
  138.   return role === getImplicitRole(element);
    
  139. }