Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refresh mappings to latest editor's draft #937

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tricky-socks-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"dom-accessibility-api": minor
---

feat: refresh mappings to latest editor's draft
20 changes: 18 additions & 2 deletions sources/__tests__/accessible-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ afterEach(cleanup);
describe("to upstream", () => {
// name from content
test.each([
[
"button",
`<div data-test role="button"><em>latin</em> a</div>`,
"latin a",
],
[
"cell",
`<div data-test role="cell"><em>greek</em> alpha</div>`,
Expand All @@ -78,6 +83,11 @@ describe("to upstream", () => {
`<div data-test role="columnheader"><em>greek</em> gamma</div>`,
"greek gamma",
],
[
"comment",
`<div data-test role="button"><em>latin</em> b</div>`,
"latin b",
],
[
"gridcell",
`<div data-test role="gridcell"><em>greek</em> delta</div>`,
Expand All @@ -88,6 +98,12 @@ describe("to upstream", () => {
`<fieldset><legend data-test><em>greek</em> zeta</legend></fieldset>`,
"greek zeta",
],
[
"heading",
`<div data-test role="heading"><em>latin</em> c</div>`,
"latin c",
],
["link", `<div data-test role="link"><em>latin</em> d</div>`, "latin d"],
[
"menuitem",
`<li data-test role="menuitem"><em>greek</em> eta</li>`,
Expand Down Expand Up @@ -283,14 +299,14 @@ describe("slots", () => {
test.each([
[
`
<div data-test aria-labelledby="label">I reference my name</div>
<abbr data-test aria-labelledby="label">I reference my name</abbr>
<div id="label" role="presentation">I'm prohibited a name</div>
`,
"I'm prohibited a name",
],
[
`
<div data-test aria-labelledby="label">I reference my name</div>
<abbr data-test aria-labelledby="label">I reference my name</div>
<div id="label" role="none">I'm prohibited a name</div>
`,
"I'm prohibited a name",
Expand Down
103 changes: 71 additions & 32 deletions sources/__tests__/getRole.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ it("uses the first role", () => {
expect(getRole(element)).toBe("textbox");
});

it("ignores empty roles", () => {
it("ignores empty roles when have implicit role", () => {
const element = document.createElement("div");
element.setAttribute("role", " ");

expect(getRole(element)).toBe("generic");
});

it("ignores empty roles when have no implicit role", () => {
const element = document.createElement("abbr");
element.setAttribute("role", " ");

expect(getRole(element)).toBeNull();
});

Expand All @@ -43,34 +50,47 @@ function createElementFactory(tagName, attributes) {
// prettier-ignore
const cases = [
["a element with a href", "link", createElementFactory("a", { href: "any" })],
["a element without a href", null, createElementFactory("a", {})],
["a element without a href", "generic", createElementFactory("a", {})],
["abbr", null, createElementFactory("abbr", {})],
["address", null, createElementFactory("address", {})],
["address", "group", createElementFactory("address", {})],
["area with a href", "link", createElementFactory("area", { href: "any" })],
["area without a href", null, createElementFactory("area", {})],
["area without a href", "generic", createElementFactory("area", {})],
["article", "article", createElementFactory("article", {})],
// WARNING: Only in certain context
["aside", "complementary", createElementFactory("aside", {})],
["audio", null, createElementFactory("audio", {})],
["b", "generic", createElementFactory("b", {})],
["base", null, createElementFactory("base", {})],
["blockquote", null, createElementFactory("blockquote", {})],
["body", null, createElementFactory("body", {})],
["bdi", "generic", createElementFactory("bdi", {})],
["bdo", "generic", createElementFactory("bdo", {})],
["blockquote", "blockquote", createElementFactory("blockquote", {})],
["body", "generic", createElementFactory("body", {})],
["br", null, createElementFactory("br", {})],
["button", "button", createElementFactory("button", {})],
["canvas", null, createElementFactory("canvas", {})],
["caption", null, createElementFactory("caption", {})],
["caption", "caption", createElementFactory("caption", {})],
["cite", null, createElementFactory("cite", {})],
["code", "code", createElementFactory("code", {})],
["col", null, createElementFactory("col", {})],
["colgroup", null, createElementFactory("colgroup", {})],
["data", "generic", createElementFactory("data", {})],
["datalist", "listbox", createElementFactory("datalist", {})],
["dd", "definition", createElementFactory("dd", {})],
["del", null, createElementFactory("del", {})],
// WARNING: html-aria and html-aam conflict on this role assignment
jlp-craigmorten marked this conversation as resolved.
Show resolved Hide resolved
// REF: https://github.com/w3c/html-aam/pull/376
["dd", null, createElementFactory("dd", {})],
["del", "deletion", createElementFactory("del", {})],
["details", "group", createElementFactory("details", {})],
["dfn", "term", createElementFactory("dfn", {})],
["dialog", "dialog", createElementFactory("dialog", {})],
["div", null, createElementFactory("div", {})],
["div", "generic", createElementFactory("div", {})],
["dl", null, createElementFactory("dl", {})],
["dt", "term", createElementFactory("dt", {})],
["em", null, createElementFactory("em", {})],
// WARNING: html-aria and html-aam conflict on this role assignment
// REF: https://github.com/w3c/html-aam/pull/376
["dt", null, createElementFactory("dt", {})],
["em", "emphasis", createElementFactory("em", {})],
["embed", null, createElementFactory("embed", {})],
["figcaption", null, createElementFactory("figcaption", {})],
["fieldset", "group", createElementFactory("fieldset", {})],
["figcaption", null, createElementFactory("figcaption", {})],
["figure", "figure", createElementFactory("figure", {})],
// WARNING: Only in certain context
["footer", "contentinfo", createElementFactory("footer", {})],
Expand All @@ -84,9 +104,12 @@ const cases = [
["h6", "heading", createElementFactory("h6", {})],
// WARNING: Only in certain context
["header", "banner", createElementFactory("header", {})],
["hgroup", null, createElementFactory("hgroup", {})],
// WARNING: html-aria and html-aam conflict on this role assignment
// REF: https://github.com/w3c/html-aria/issues/451
["hgroup", "group", createElementFactory("hgroup", {})],
["hr", "separator", createElementFactory("hr", {})],
["html", "document", createElementFactory("html", {})],
["i", "generic", createElementFactory("i", {})],
["iframe", null, createElementFactory("iframe", {})],
["img with alt=\"some text\"", "img", createElementFactory("img", {alt: "text"})],
["img with missing alt", "img", createElementFactory("img", {})],
Expand Down Expand Up @@ -117,18 +140,20 @@ const cases = [
["input type=time", null, createElementFactory("input", {type: "time"})],
["input type=url", "textbox", createElementFactory("input", {type: "url"})],
["input type=week", null, createElementFactory("input", {type: "week"})],
["ins", null, createElementFactory("ins", {})],
["ins", "insertion", createElementFactory("ins", {})],
["kbd", null, createElementFactory("kbd", {})],
["label", null, createElementFactory("label", {})],
["legend", 'legend', createElementFactory("legend", {})],
["legend", null, createElementFactory("legend", {})],
// WARNING: Only in certain context
["li", "listitem", createElementFactory("li", {})],
["link element with a href", "link", createElementFactory("link", {href: "some"})],
["link", null, createElementFactory("link", {})],
["main", "main", createElementFactory("main", {})],
["map", null, createElementFactory("map", {})],
["mark", null, createElementFactory("mark", {})],
["math", "math", createElementFactory("math", {})],
["menu", "list", createElementFactory("menu", {})],
["meta", null, createElementFactory("meta", {})],
["meter", null, createElementFactory("meter", {})],
["meter", "meter", createElementFactory("meter", {})],
["nav", "navigation", createElementFactory("nav", {})],
["noscript", null, createElementFactory("noscript", {})],
["object", null, createElementFactory("object", {})],
Expand All @@ -137,49 +162,63 @@ const cases = [
// Warning: Only in certain context
["option", "option", createElementFactory("option", {})],
["output", "status", createElementFactory("output", {})],
["p", null, createElementFactory("p", {})],
["p", "paragraph", createElementFactory("p", {})],
["param", null, createElementFactory("param", {})],
["picture", null, createElementFactory("picture", {})],
["pre", null, createElementFactory("pre", {})],
["pre", "generic", createElementFactory("pre", {})],
["progress", "progressbar", createElementFactory("progress", {})],
["q", "generic", createElementFactory("q", {})],
["rp", null, createElementFactory("rp", {})],
["rt", null, createElementFactory("rt", {})],
["ruby", null, createElementFactory("ruby", {})],
// WARNING: html-aria and html-aam conflict on this role assignment
// REF: https://github.com/w3c/html-aria/issues/466
["s", "deletion", createElementFactory("s", {})],
["samp", "generic", createElementFactory("samp", {})],
["script", null, createElementFactory("script", {})],
["search", "search", createElementFactory("search", {})],
// WARNING: Only with a name
["section", "region", createElementFactory("section", {})],
["select, no multiple, no size", "combobox", createElementFactory("select", {})],
["select, no multiple, no size greater 1", "combobox", createElementFactory("select", {size: 1})],
["select, size greater 1", "listbox", createElementFactory("select", {size: 2})],
["select, multiple", "listbox", createElementFactory("select", {multiple: true})],
["slot", null, createElementFactory("slot", {})],
["small", "generic", createElementFactory("small", {})],
["source", null, createElementFactory("source", {})],
["span", null, createElementFactory("span", {})],
["strong", null, createElementFactory("strong", {})],
["span", "generic", createElementFactory("span", {})],
["strong", "strong", createElementFactory("strong", {})],
["style", null, createElementFactory("style", {})],
["svg", null, createElementFactory("svg", {})],
["sub", null, createElementFactory("sub", {})],
["sub", "subscript", createElementFactory("sub", {})],
["summary", "button", createElementFactory("summary", {})],
["sup", null, createElementFactory("sup", {})],
["sup", "superscript", createElementFactory("sup", {})],
["svg", "graphics-document", createElementFactory("svg", {})],
["table", "table", createElementFactory("table", {})],
["tbody", "rowgroup", createElementFactory("tbody", {})],
// WARNING: Only in certain contexts
["td", "cell", createElementFactory("td", {})],
["template", null, createElementFactory("template", {})],
["textarea", "textbox", createElementFactory("textarea", {})],
["tfoot", "rowgroup", createElementFactory("tfoot", {})],
// WARNING: Only in certain context
["th", "columnheader", createElementFactory("th", {})],
["thead", "rowgroup", createElementFactory("thead", {})],
["time", null, createElementFactory("time", {})],
["time", "time", createElementFactory("time", {})],
["title", null, createElementFactory("title", {})],
// WARNING: Only in certain contexts
["td", "cell", createElementFactory("td", {})],
["th", "columnheader", createElementFactory("th", {})],
["tr", "row", createElementFactory("tr", {})],
["track", null, createElementFactory("track", {})],
["u", "generic", createElementFactory("u", {})],
["ul", "list", createElementFactory("ul", {})],
["var", null, createElementFactory("var", {})],
["video", null, createElementFactory("video", {})],
["wbr", null, createElementFactory("wbr", {})],
// https://rawgit.com/w3c/aria/stable/#conflict_resolution_presentation_none
["presentational <img /> with accessible name", "img", createElementFactory("img", {alt: "", 'aria-label': "foo"})],
["presentational <h1 /> global aria attributes", "heading", createElementFactory("h1", {'aria-describedby': "comment-1", role: "presentation"})],
["presentational <h1 /> global aria attributes", "heading", createElementFactory("h1", {'aria-describedby': "comment-1", role: "none"})],
// <div /> isn't mapped to `"generic"` yet so implicit semantics are `No role`
["presentational <div /> with prohibited aria attributes", null, createElementFactory("div", {'aria-label': "hello", role: "presentation"})],
["presentational <div /> with prohibited aria attributes", null, createElementFactory("div", {'aria-label': "hello", role: "none"})],
// <abbr /> isn't mapped to `"generic"` yet so implicit semantics are `No role`
["presentational <abbr /> with prohibited aria attributes", null, createElementFactory("abbr", {'aria-label': "hello", role: "presentation"})],
["presentational <abbr /> with prohibited aria attributes", null, createElementFactory("abbr", {'aria-label': "hello", role: "none"})],
];

it.each(cases)("%s has the role %s", (name, role, elementFactory) => {
Expand Down
61 changes: 31 additions & 30 deletions sources/accessible-name-and-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isHTMLTextAreaElement,
safeWindow,
isHTMLFieldSetElement,
isHTMLLabelElement,
isHTMLLegendElement,
isHTMLOptGroupElement,
isHTMLTableElement,
Expand Down Expand Up @@ -91,7 +92,7 @@ function isHidden(

/**
* @param {Node} node -
* @returns {boolean} - As defined in step 2E of https://w3c.github.io/accname/#mapping_additional_nd_te
* @returns {boolean} - As defined in step 2C of https://w3c.github.io/accname/#mapping_additional_nd_te
*/
function isControl(node: Node): boolean {
return (
Expand Down Expand Up @@ -157,44 +158,44 @@ function isMarkedPresentational(node: Node): node is Element {
/**
* Elements specifically listed in html-aam
*
* We don't need this for `label` or `legend` elements.
* Their implicit roles already allow "naming from content".
*
* sources:
*
* - https://w3c.github.io/html-aam/#table-element
* - https://www.w3.org/TR/html-aam-1.0/#table-element-accessible-name-computation
* - https://www.w3.org/TR/html-aam-1.0/#fieldset-element-accessible-name-computation
*/
function isNativeHostLanguageTextAlternativeElement(
node: Node,
): node is Element {
return isHTMLTableCaptionElement(node);
return (
isHTMLTableCaptionElement(node) ||
isHTMLLegendElement(node) ||
isHTMLLabelElement(node)
);
}

/**
* https://w3c.github.io/aria/#namefromcontent
* https://rawgit.com/w3c/aria/stable/#namefromcontent
*/
function allowsNameFromContent(node: Node): boolean {
return hasAnyConcreteRoles(node, [
"button",
"button", // name required
"cell",
"checkbox",
"columnheader",
"checkbox", // name required
"columnheader", // name required
"gridcell",
"heading",
"label",
"legend",
"link",
"menuitem",
"menuitemcheckbox",
"menuitemradio",
"option",
"radio",
"heading", // name required
"link", // name required
"menuitem", // name required
"menuitemcheckbox", // name required
"menuitemradio", // name required
"option", // name required
"radio", // name required
"row",
"rowheader",
"switch",
"tab",
"rowheader", // name required
"switch", // name required
"tab", // name required
"tooltip",
"treeitem",
"treeitem", // name required
]);
}

Expand Down Expand Up @@ -429,7 +430,7 @@ export function computeTextAlternative(
return null;
}

// https://w3c.github.io/html-aam/#fieldset-and-legend-elements
// https://www.w3.org/TR/html-aam-1.0/#fieldset-element-accessible-name-computation
if (isHTMLFieldSetElement(node)) {
consultedNodes.add(node);
const children = ArrayFrom(node.childNodes);
Expand All @@ -444,7 +445,7 @@ export function computeTextAlternative(
}
}
} else if (isHTMLTableElement(node)) {
// https://w3c.github.io/html-aam/#table-element
// https://www.w3.org/TR/html-aam-1.0/#table-element-accessible-name-computation
consultedNodes.add(node);
const children = ArrayFrom(node.childNodes);
for (let i = 0; i < children.length; i += 1) {
Expand All @@ -469,8 +470,8 @@ export function computeTextAlternative(
}
return null;
} else if (getLocalName(node) === "img" || getLocalName(node) === "area") {
// https://w3c.github.io/html-aam/#area-element
// https://w3c.github.io/html-aam/#img-element
// https://www.w3.org/TR/html-aam-1.0/#area-element-accessible-name-computation
// https://www.w3.org/TR/html-aam-1.0/#img-element-accessible-name-computation
const nameFromAlt = useAttribute(node, "alt");
if (nameFromAlt !== null) {
return nameFromAlt;
Expand All @@ -488,7 +489,7 @@ export function computeTextAlternative(
node.type === "submit" ||
node.type === "reset")
) {
// https://w3c.github.io/html-aam/#input-type-text-input-type-password-input-type-search-input-type-tel-input-type-email-input-type-url-and-textarea-element-accessible-description-computation
// https://www.w3.org/TR/html-aam-1.0/#input-type-text-input-type-password-input-type-number-input-type-search-input-type-tel-input-type-email-input-type-url-and-textarea-element-accessible-name-computation
const nameFromValue = useAttribute(node, "value");
if (nameFromValue !== null) {
return nameFromValue;
Expand Down Expand Up @@ -521,7 +522,7 @@ export function computeTextAlternative(
.join(" ");
}

// https://w3c.github.io/html-aam/#input-type-image-accessible-name-computation
// https://www.w3.org/TR/html-aam-1.0/#input-type-image-accessible-name-computation
// TODO: wpt test consider label elements but html-aam does not mention them
// We follow existing implementations over spec
if (isHTMLInputElement(node) && node.type === "image") {
Expand All @@ -540,7 +541,7 @@ export function computeTextAlternative(
}

if (hasAnyConcreteRoles(node, ["button"])) {
// https://www.w3.org/TR/html-aam-1.0/#button-element
// https://www.w3.org/TR/html-aam-1.0/#button-element-accessible-name-computation
const nameFromSubTree = computeMiscTextAlternative(node, {
isEmbeddedInLabel: false,
isReferenced: false,
Expand Down
Loading
Loading