Skip to content

Commit

Permalink
fix: consider value attribute before localized input type (#406)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon committed Aug 26, 2020
1 parent 0fc8801 commit 03273b7
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 71 deletions.
18 changes: 18 additions & 0 deletions .changeset/empty-frogs-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"dom-accessibility-api": patch
---

Fix various issues for input types `submit`, `reset` and `image`

Prefer input `value` when `type` is `reset` or `submit`:

```diff
<input type="submit" value="Submit values">
-// accessible name: "Submit"
+// accessible name: "Submit values"
<input type="reset" value="Reset form">
-// accessible name: "Reset"
+// accessible name: "Reset form"
```

For input `type` `image` consider `alt` attribute or fall back to `"Submit query"`.
8 changes: 7 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@
"property": "getComputedStyle"
},
{
// tagName is oftentimes more expensive since it requires a toAsciiUpperCase of the local name.
// `tagName` is oftentimes more expensive since it requires a toAsciiUpperCase of the local name.
// It certainly is in JSDOM: https://github.com/jsdom/jsdom/pull/3008
"property": "tagName",
"message": "Please use `getLocalName` instead because `tagName` is oftentimes more expensive since it requires a toAsciiUpperCase of the local name."
},
{
// `localName` is not available in all supported environments.
// We have a cross-browser helper with `getLocalName`
"property": "localName",
"message": "Please use `getLocalName` which implements .localName for older environments."
}
],
"no-restricted-syntax": [
Expand Down
5 changes: 5 additions & 0 deletions sources/__tests__/accessible-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ test.each([
// https://www.w3.org/TR/svg-aam-1.0/
[`<svg data-test><title><em>greek</em> rho</title></svg>`, "greek rho"],
[`<button title="" data-test>click me</button>`, "click me"],
// https://w3c.github.io/html-aam/#input-type-button-input-type-submit-and-input-type-reset-accessible-name-computation
[`<input data-test value="Submit form" type="submit" />`, "Submit form"],
// https://w3c.github.io/html-aam/#input-type-image
[`<input data-test alt="Select an image" type="image" />`, "Select an image"],
[`<input data-test alt="" type="image" />`, "Submit Query"],
])(`test #%#`, testMarkup);

test("text nodes are not concatenated by space", () => {
Expand Down
160 changes: 90 additions & 70 deletions sources/accessible-name-and-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,36 +372,33 @@ export function computeTextAlternative(
return accumulatedText;
}

function computeAttributeTextAlternative(node: Node): string | null {
function computeElementTextAlternative(node: Node): string | null {
if (!isElement(node)) {
return null;
}

const titleAttribute = node.getAttributeNode("title");
if (
titleAttribute !== null &&
titleAttribute.value.trim() !== "" &&
!consultedNodes.has(titleAttribute)
) {
consultedNodes.add(titleAttribute);
return titleAttribute.value;
}

const altAttribute = node.getAttributeNode("alt");
if (altAttribute !== null && !consultedNodes.has(altAttribute)) {
consultedNodes.add(altAttribute);
return altAttribute.value;
}

if (isHTMLInputElement(node) && node.type === "button") {
consultedNodes.add(node);
return node.getAttribute("value") || "";
/**
*
* @param element
* @param attributeName
* @returns A string non-empty string or `null`
*/
function useAttribute(
element: Element,
attributeName: string
): string | null {
const attribute = element.getAttributeNode(attributeName);
if (
attribute !== null &&
!consultedNodes.has(attribute) &&
attribute.value.trim() !== ""
) {
consultedNodes.add(attribute);
return attribute.value;
}
return null;
}

return null;
}

function computeElementTextAlternative(node: Node): string | null {
// https://w3c.github.io/html-aam/#fieldset-and-legend-elements
if (isHTMLFieldSetElement(node)) {
consultedNodes.add(node);
Expand All @@ -416,11 +413,8 @@ export function computeTextAlternative(
});
}
}
return null;
}

// https://w3c.github.io/html-aam/#table-element
if (isHTMLTableElement(node)) {
} else if (isHTMLTableElement(node)) {
// https://w3c.github.io/html-aam/#table-element
consultedNodes.add(node);
const children = ArrayFrom(node.childNodes);
for (let i = 0; i < children.length; i += 1) {
Expand All @@ -433,11 +427,8 @@ export function computeTextAlternative(
});
}
}
return null;
}

// https://www.w3.org/TR/svg-aam-1.0/
if (isSVGSVGElement(node)) {
} else if (isSVGSVGElement(node)) {
// https://www.w3.org/TR/svg-aam-1.0/
consultedNodes.add(node);
const children = ArrayFrom(node.childNodes);
for (let i = 0; i < children.length; i += 1) {
Expand All @@ -447,45 +438,81 @@ 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
const nameFromAlt = useAttribute(node, "alt");
if (nameFromAlt !== null) {
return nameFromAlt;
}
}

if (
!(
isHTMLInputElement(node) ||
isHTMLSelectElement(node) ||
isHTMLTextAreaElement(node)
)
isHTMLInputElement(node) &&
(node.type === "button" ||
node.type === "submit" ||
node.type === "reset")
) {
return null;
}
const input = node;
// 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
const nameFromValue = useAttribute(node, "value");
if (nameFromValue !== null) {
return nameFromValue;
}

// 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
if (input.type === "submit") {
return "Submit";
// TODO: l10n
if (node.type === "submit") {
return "Submit";
}
// TODO: l10n
if (node.type === "reset") {
return "Reset";
}
}
if (input.type === "reset") {
return "Reset";

if (
isHTMLInputElement(node) ||
isHTMLSelectElement(node) ||
isHTMLTextAreaElement(node)
) {
const input = node;

const labels = getLabels(input);
if (labels !== null && labels.length !== 0) {
consultedNodes.add(input);
return ArrayFrom(labels)
.map((element) => {
return computeTextAlternative(element, {
isEmbeddedInLabel: true,
isReferenced: false,
recursion: true,
});
})
.filter((label) => {
return label.length > 0;
})
.join(" ");
}
}

const labels = getLabels(input);
if (labels === null || labels.length === 0) {
return null;
// https://w3c.github.io/html-aam/#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") {
const nameFromAlt = useAttribute(node, "alt");
if (nameFromAlt !== null) {
return nameFromAlt;
}

const nameFromTitle = useAttribute(node, "title");
if (nameFromTitle !== null) {
return nameFromTitle;
}

// TODO: l10n
return "Submit Query";
}

consultedNodes.add(input);
return ArrayFrom(labels)
.map((element) => {
return computeTextAlternative(element, {
isEmbeddedInLabel: true,
isReferenced: false,
recursion: true,
});
})
.filter((label) => {
return label.length > 0;
})
.join(" ");
return useAttribute(node, "title");
}

function computeTextAlternative(
Expand Down Expand Up @@ -556,13 +583,6 @@ export function computeTextAlternative(
consultedNodes.add(current);
return elementTextAlternative;
}
const attributeTextAlternative = computeAttributeTextAlternative(
current
);
if (attributeTextAlternative !== null) {
consultedNodes.add(current);
return attributeTextAlternative;
}
}
}

Expand Down
1 change: 1 addition & 0 deletions sources/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import getRole from "./getRole";
*/
export function getLocalName(element: Element): string {
return (
// eslint-disable-next-line no-restricted-properties -- actual guard for environments without localName
element.localName ??
// eslint-disable-next-line no-restricted-properties -- required for the fallback
element.tagName.toLowerCase()
Expand Down

0 comments on commit 03273b7

Please sign in to comment.