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

fix: consider value attribute before localized input type #406

Merged
merged 4 commits into from
Aug 26, 2020
Merged
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
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