diff --git a/lib/standards/html-elms.js b/lib/standards/html-elms.js new file mode 100644 index 0000000000..f9cda2b0ff --- /dev/null +++ b/lib/standards/html-elms.js @@ -0,0 +1,919 @@ +// Source: https://www.w3.org/TR/html-aria/#allowed-aria-roles-states-and-properties +// Source: https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings +// Source https://html.spec.whatwg.org/multipage/dom.html#content-models +// Source https://dom.spec.whatwg.org/#dom-element-attachshadow +const htmlElms = { + a: { + // Note: variants work by matching the node against the + // `matches` attribute. if the variant matches AND has the + // desired property (contentTypes, etc.) then we use it, + // otherwise we move on to the next matching variant + variant: { + href: { + matches: '[href]', + contentTypes: ['interactive', 'phrasing', 'flow'], + allowedRoles: [ + 'button', + 'checkbox', + 'menuitem', + 'menuitemcheckbox', + 'menuitemradio', + 'option', + 'radio', + 'switch', + 'tab', + 'treeitem', + 'doc-backlink', + 'doc-biblioref', + 'doc-glossref', + 'doc-noteref' + ] + }, + // Note: the default variant is a special variant and is + // used as the last match if none of the other variants + // match or have the desired attribute + default: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + } + } + }, + abbr: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + addres: { + contentTypes: ['flow'], + allowedRoles: true + }, + area: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false + }, + article: { + contentTypes: ['sectioning', 'flow'], + allowedRoles: [ + 'feed', + 'presentation', + 'none', + 'document', + 'application', + 'main', + 'region' + ], + shadowRoot: true + }, + aside: { + contentTypes: ['sectioning', 'flow'], + allowedRoles: [ + 'feed', + 'note', + 'presentation', + 'none', + 'region', + 'search', + 'doc-dedication', + 'doc-example', + 'doc-footnote', + 'doc-pullquote', + 'doc-tip' + ] + }, + audio: { + variant: { + controls: { + matches: '[controls]', + contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'] + }, + default: { + contentTypes: ['embedded', 'phrasing', 'flow'] + } + }, + // Note: if the property applies regardless of variants it is + // placed at the top level instead of the default variant + allowedRoles: ['application'] + }, + b: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false + }, + base: { + allowedRoles: false, + noAriaAttrs: true + }, + bdi: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + bdo: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + blockquote: { + contentTypes: ['flow'], + allowedRoles: true, + shadowRoot: true + }, + body: { + allowedRoles: false, + shadowRoot: true + }, + br: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: ['presentation', 'none'], + namingMethods: ['titleText', 'singleSpace'] + }, + button: { + contentTypes: ['interactive', 'phrasing', 'flow'], + allowedRoles: [ + 'checkbox', + 'link', + 'menuitem', + 'menuitemcheckbox', + 'menuitemradio', + 'option', + 'radio', + 'switch', + 'tab' + ], + // 5.4 button Element + namingMethods: 'subtreeText' + }, + canvas: { + allowedRoles: true, + contentTypes: ['embedded', 'phrasing', 'flow'] + }, + caption: { + allowedRoles: false + }, + cite: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + code: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + col: { + allowedRoles: false, + noAriaAttrs: true + }, + colgroup: { + allowedRoles: false, + noAriaAttrs: true + }, + data: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + datalist: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + implicitAttrs: { + // Note: even though the value of aria-multiselectable is based + // on the attributes, we don't currently need to know the + // precise value. however, this allows us to make the attribute + // future proof in case we ever do need to know it + 'aria-multiselectable': 'false' + } + }, + dd: { + allowedRoles: false + }, + del: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + dfn: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + details: { + contentTypes: ['interactive', 'flow'], + allowedRoles: false + }, + dialog: { + contentTypes: ['flow'], + allowedRoles: ['alertdialog'] + }, + div: { + contentTypes: ['flow'], + allowedRoles: true, + shadowRoot: true + }, + dl: { + contentTypes: ['flow'], + allowedRoles: ['group', 'list', 'presentation', 'none'] + }, + dt: { + allowedRoles: ['listitem'] + }, + em: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + embed: { + contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'], + allowedRoles: ['application', 'document', 'img', 'presentation', 'none'] + }, + fieldset: { + contentTypes: ['flow'], + allowedRoles: ['none', 'presentation', 'radiogroup'], + // 5.5 fieldset and legend Elements + namingMethods: 'fieldsetLegendText' + }, + figcaption: { + allowedRoles: ['group', 'none', 'presentation'] + }, + figure: { + contentTypes: ['flow'], + // Note: technically you're allowed no role when a figcaption + // descendant, but we can't match that so we'll go with any role + allowedRoles: true, + // 5.9 figure and figcaption Elements + namingMethods: ['figureText', 'titleText'] + }, + footer: { + contentTypes: ['flow'], + allowedRoles: ['group', 'none', 'presentation', 'doc-footnote'], + shadowRoot: true + }, + form: { + contentTypes: ['flow'], + allowedRoles: ['search', 'none', 'presentation'] + }, + h1: { + contentTypes: ['heading', 'flow'], + allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], + shadowRoot: true, + implicitAttrs: { + 'aria-level': '1' + } + }, + h2: { + contentTypes: ['heading', 'flow'], + allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], + shadowRoot: true, + implicitAttrs: { + 'aria-level': '2' + } + }, + h3: { + contentTypes: ['heading', 'flow'], + allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], + shadowRoot: true, + implicitAttrs: { + 'aria-level': '3' + } + }, + h4: { + contentTypes: ['heading', 'flow'], + allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], + shadowRoot: true, + implicitAttrs: { + 'aria-level': '4' + } + }, + h5: { + contentTypes: ['heading', 'flow'], + allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], + shadowRoot: true, + implicitAttrs: { + 'aria-level': '5' + } + }, + h6: { + contentTypes: ['heading', 'flow'], + allowedRoles: ['none', 'presentation', 'tab', 'doc-subtitle'], + shadowRoot: true, + implicitAttrs: { + 'aria-level': '6' + } + }, + head: { + allowedRoles: false, + noAriaAttrs: true + }, + header: { + contentTypes: ['flow'], + allowedRoles: ['group', 'none', 'presentation', 'doc-footnote'], + shadowRoot: true + }, + hgroup: { + contentTypes: ['heading', 'flow'], + allowedRoles: true + }, + hr: { + contentTypes: ['flow'], + allowedRoles: ['none', 'presentation', 'doc-pagebreak'], + namingMethods: ['titleText', 'singleSpace'] + }, + html: { + allowedRoles: false, + noAriaAttrs: true + }, + i: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + iframe: { + contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'], + allowedRoles: ['application', 'document', 'img', 'none', 'presentation'] + }, + img: { + variant: { + nonEmptyAlt: { + matches: { + attributes: { + alt: '/.+/' + } + }, + allowedRoles: [ + 'button', + 'checkbox', + 'link', + 'menuitem', + 'menuitemcheckbox', + 'menuitemradio', + 'option', + 'progressbar', + 'scrollbar', + 'separator', + 'slider', + 'switch', + 'tab', + 'treeitem', + 'doc-cover' + ] + }, + emptyAlt: { + matches: '[alt=""]', + allowedRoles: ['presentation', 'none'] + }, + noAlt: { + matches: { + attributes: { + alt: null + } + }, + // Note: if img has accessible name then uses alt="some text" + // but we can't match that so we'll go with false + allowedRoles: false + }, + usemap: { + matches: '[usemap]', + contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'] + }, + default: { + contentTypes: ['embedded', 'phrasing', 'flow'] + } + }, + // 5.10 img Element + namingMethods: 'altText' + }, + input: { + variant: { + button: { + matches: { + properties: { + type: 'button' + } + }, + allowedRoles: [ + 'link', + 'menuitem', + 'menuitemcheckbox', + 'menuitemradio', + 'option', + 'radio', + 'switch', + 'tab' + ] + }, + // 5.2 input type="button", input type="submit" and input type="reset" + buttonType: { + matches: { + properties: { + type: ['button', 'submit', 'reset'] + } + }, + namingMethods: ['valueText', 'titleText', 'buttonDefaultText'] + }, + checkboxPressed: { + matches: { + properties: { + type: 'checkbox' + }, + attributes: { + 'aria-pressed': '/.*/' + } + }, + allowedRoles: ['button', 'menuitemcheckbox', 'option', 'switch'], + implicitAttrs: { + 'aria-checked': 'false' + } + }, + checkbox: { + matches: { + properties: { + type: 'checkbox' + }, + attributes: { + 'aria-pressed': null + } + }, + allowedRoles: ['menuitemcheckbox', 'option', 'switch'], + implicitAttrs: { + 'aria-checked': 'false' + } + }, + noRoles: { + matches: { + properties: { + type: [ + 'color', + 'date', + 'datetime-local', + 'email', + 'file', + 'month', + 'number', + 'password', + 'range', + 'reset', + 'search', + 'submit', + 'tel', + 'time', + 'url', + 'week' + ] + } + }, + allowedRoles: false + }, + hidden: { + matches: { + properties: { + type: 'hidden' + } + }, + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + noAriaAttrs: true + }, + image: { + matches: { + properties: { + type: 'image' + } + }, + allowedRoles: [ + 'link', + 'menuitem', + 'menuitemcheckbox', + 'menuitemradio', + 'radio', + 'switch' + ], + // 5.3 input type="image" + namingMethods: [ + 'altText', + 'valueText', + 'labelText', + 'titleText', + 'buttonDefaultText' + ] + }, + radio: { + matches: { + properties: { + type: 'radio' + } + }, + allowedRoles: ['menuitemradio'], + implicitAttrs: { + 'aria-checked': 'false' + } + }, + textWithList: { + matches: { + properties: { + type: 'text' + }, + attributes: { + list: '/.*/' + } + }, + allowedRoles: false + }, + default: { + contentTypes: ['interactive', 'phrasing', 'flow'], + allowedRoles: ['combobox', 'searchbox', 'spinbutton'], + // 5.1 input type="text", input type="password", input type="search", input type="tel", input type="url" and textarea Element + // 5.7 Other Form Elements + namingMethods: 'labelText' + } + } + }, + ins: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + kdb: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + label: { + contentTypes: ['interactive', 'phrasing', 'flow'], + allowedRoles: false + }, + legend: { + allowedRoles: false + }, + li: { + allowedRoles: [ + 'menuitem', + 'menuitemcheckbox', + 'menuitemradio', + 'option', + 'none', + 'presentation', + 'radio', + 'separator', + 'tab', + 'treeitem', + 'doc-biblioentry', + 'doc-endnote' + ], + implicitAttrs: { + 'aria-setsize': '1', + 'aria-posinset': '1' + } + }, + link: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + noAriaAttrs: true + }, + main: { + contentTypes: ['flow'], + allowedRoles: false, + shadowRoot: true + }, + map: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + noAriaAttrs: true + }, + math: { + contentTypes: ['embedded', 'phrasing', 'flow'], + allowedRoles: false + }, + mark: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + menu: { + contentTypes: ['flow'], + allowedRoles: [ + 'directory', + 'group', + 'listbox', + 'menu', + 'menubar', + 'none', + 'presentation', + 'radiogroup', + 'tablist', + 'toolbar', + 'tree' + ] + }, + meta: { + variant: { + itemprop: { + matches: '[itemprop]', + contentTypes: ['phrasing', 'flow'] + } + }, + allowedRoles: false, + noAriaAttrs: true + }, + meter: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false + }, + nav: { + contentTypes: ['sectioning', 'flow'], + allowedRoles: ['doc-index', 'doc-pagelist', 'doc-toc'], + shadowRoot: true + }, + noscript: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + noAriaAttrs: true + }, + object: { + variant: { + usemap: { + matches: '[usemap]', + contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'] + }, + default: { + contentTypes: ['embedded', 'phrasing', 'flow'] + } + }, + allowedRoles: ['application', 'document', 'img'] + }, + ol: { + contentTypes: ['flow'], + allowedRoles: [ + 'directory', + 'group', + 'listbox', + 'menu', + 'menubar', + 'none', + 'presentation', + 'radiogroup', + 'tablist', + 'toolbar', + 'tree' + ] + }, + optgroup: { + allowedRoles: false + }, + option: { + allowedRoles: false, + implicitAttrs: { + 'aria-selected': 'false' + } + }, + output: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true, + // 5.6 output Element + namingMethods: 'subtreeText' + }, + p: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true, + shadowRoot: true + }, + param: { + allowedRoles: false, + noAriaAttrs: true + }, + picture: { + contentTypes: ['embedded', 'phrasing', 'flow'], + allowedRoles: false, + noAriaAttrs: true + }, + pre: { + contentTypes: ['flow'], + allowedRoles: true + }, + progress: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true, + implicitAttrs: { + 'aria-valuemax': '100', + 'aria-valuemin': '0', + 'aria-valuenow': '0' + } + }, + q: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + rp: { + allowedRoles: true + }, + rt: { + allowedRoles: true + }, + ruby: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + s: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + samp: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + script: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + noAriaAttrs: true + }, + section: { + contentTypes: ['sectioning', 'flow'], + allowedRoles: [ + 'alert', + 'alertdialog', + 'application', + 'banner', + 'complementary', + 'contentinfo', + 'dialog', + 'document', + 'feed', + 'log', + 'main', + 'marquee', + 'navigation', + 'none', + 'note', + 'presentation', + 'search', + 'status', + 'tabpanel', + 'doc-abstract', + 'doc-acknowledgments', + 'doc-afterword', + 'doc-appendix', + 'doc-bibliography', + 'doc-chapter', + 'doc-colophon', + 'doc-conclusion', + 'doc-credit', + 'doc-credits', + 'doc-dedication', + 'doc-endnotes', + 'doc-epigraph', + 'doc-epilogue', + 'doc-errata', + 'doc-example', + 'doc-foreword', + 'doc-glossary', + 'doc-index', + 'doc-introduction', + 'doc-notice', + 'doc-pagelist', + 'doc-part', + 'doc-preface', + 'doc-prologue', + 'doc-pullquote', + 'doc-qna', + 'doc-toc' + ], + shadowRoot: true + }, + select: { + variant: { + combobox: { + matches: { + attributes: { + multiple: null, + size: [null, '1'] + } + }, + allowedRoles: ['menu'] + }, + default: { + allowedRoles: false + } + }, + contentTypes: ['interactive', 'phrasing', 'flow'] + }, + slot: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + noAriaAttrs: true + }, + small: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + source: { + allowedRoles: false, + noAriaAttrs: true + }, + span: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true, + shadowRoot: true + }, + strong: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + style: { + allowedRoles: false, + noAriaAttrs: true + }, + svg: { + contentTypes: ['embedded', 'phrasing', 'flow'], + allowedRoles: ['application', 'document', 'img'] + }, + sub: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + summary: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + // 5.8 summary Element + namingMethods: 'subtreeText' + }, + sup: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + table: { + contentTypes: ['flow'], + allowedRoles: true, + // 5.11 table Element + namingMethods: ['tableCaptionText', 'tableSummaryText'] + }, + tbody: { + allowedRoles: true + }, + template: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: false, + noAriaAttrs: true + }, + textarea: { + contentTypes: ['interactive', 'phrasing', 'flow'], + allowedRoles: false, + implicitAttrs: { + 'aria-multiline': 'true' + }, + namingMethods: 'labelText' + }, + tfoot: { + allowedRoles: true + }, + thead: { + allowedRoles: true + }, + time: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + title: { + allowedRoles: false, + noAriaAttrs: true + }, + td: { + allowedRoles: true + }, + th: { + allowedRoles: true + }, + tr: { + allowedRoles: true + }, + track: { + allowedRoles: false, + noAriaAttrs: true + }, + u: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + ul: { + contentTypes: ['flow'], + allowedRoles: [ + 'directory', + 'group', + 'listbox', + 'menu', + 'menubar', + 'none', + 'presentation', + 'radiogroup', + 'tablist', + 'toolbar', + 'tree' + ] + }, + var: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + }, + video: { + variant: { + controls: { + matches: '[controls]', + contentTypes: ['interactive', 'embedded', 'phrasing', 'flow'] + }, + default: { + contentTypes: ['embedded', 'phrasing', 'flow'] + } + }, + allowedRoles: ['application'] + }, + wbr: { + contentTypes: ['phrasing', 'flow'], + allowedRoles: true + } +}; + +export default htmlElms;