Skip to content

Commit

Permalink
update the rules to use accessible name calculation #1974
Browse files Browse the repository at this point in the history
  • Loading branch information
shunguoy committed Oct 22, 2024
1 parent 823a48f commit 3f8f276
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export const input_label_exists: Rule = {
passed = CommonUtil.hasInnerContentHidden(ruleContext);
}
*/
const pair = AccNameUtil.computeAccessibleName(ruleContext);console.log("node="+ruleContext.nodeName+", pair="+JSON.stringify(pair));
const pair = AccNameUtil.computeAccessibleName(ruleContext);
passed = pair && pair.name && pair.name.trim().length > 0;

if (passed) {
Expand Down
26 changes: 19 additions & 7 deletions accessibility-checker-engine/src/v4/rules/input_label_visible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { CommonUtil } from "../util/CommonUtil";
import { FragmentUtil } from "../../v2/checker/accessibility/util/fragment";
import { VisUtil } from "../util/VisUtil";
import { DOMUtil } from "../../v2/dom/DOMUtil";
import { AccNameUtil } from "../util/AccNameUtil";

export const input_label_visible: Rule = {
id: "input_label_visible",
Expand Down Expand Up @@ -94,10 +95,11 @@ export const input_label_visible: Rule = {
}
}
}


const pair = AccNameUtil.computeAccessibleName(ruleContext);
// check visible label for input or button
if (nodeName === 'input' || nodeName === 'button') {

/**
if (CommonUtil.hasImplicitLabel(ruleContext))
return RulePass("pass");
Expand All @@ -116,6 +118,9 @@ export const input_label_visible: Rule = {
if (type === 'image' && CommonUtil.attributeNonEmpty(ruleContext, "alt"))
return RulePass("pass");
}
*/
if (pair && pair.name && pair.name.trim().length > 0 && (pair.nameFrom === 'label' || pair.nameFrom === 'internal' || pair.nameFrom === 'alt'))
return RulePass("pass");
}

// custom widget submission is not in scope for this success criteria (IBMa/equal-access#204) if it is not associated with data entry
Expand All @@ -128,13 +133,17 @@ export const input_label_visible: Rule = {

// check if any visible text from the control.
// note that (1) the text doesn’t need to be associated with the control to form a relationship
// and (2) the text doesn't need to follow accessible name requirement (e.g. nameFrom)
if (!CommonUtil.isInnerTextEmpty(ruleContext))
// (2) the text doesn't need to follow accessible name requirement (e.g. nameFrom)
// and (3) an alternative tooltip exists that can be made visible through mouseover
/**if (!CommonUtil.isInnerTextEmpty(ruleContext))
return RulePass("pass");

// check if an alternative tooltip exists that can be made visible through mouseover
if (CommonUtil.attributeNonEmpty(ruleContext, "title"))
return RulePass("pass");
*/
if (pair && pair.name && pair.name.trim().length > 0 && (pair.nameFrom === 'text' || pair.nameFrom === 'title'))
return RulePass("pass");

// check if any descendant with an alternative tooltip that can be made visible through mouseover
// only consider img and svg, and other text content of the descendant is covered in the isInnerText above
Expand All @@ -158,12 +167,15 @@ export const input_label_visible: Rule = {
}
}

if (nodeName === "optgroup" && CommonUtil.attributeNonEmpty(ruleContext, "label"))
/**if (nodeName === "optgroup" && CommonUtil.attributeNonEmpty(ruleContext, "label"))
return RulePass("pass");
if (nodeName == "option" && (CommonUtil.attributeNonEmpty(ruleContext, "label") || ruleContext.innerHTML.trim().length > 0))
return RulePass("pass");

*/
if ((nodeName === "optgroup" || nodeName == "option") && (pair && pair.name && pair.name.trim().length > 0 && (pair.nameFrom === 'label' || pair.nameFrom === 'content')))
return RulePass("pass");

// Determine if this is referenced by a combobox. If so, the label belongs to the combobox
let id = ruleContext.getAttribute("id");
if (id && id.trim().length > 0) {
Expand Down
62 changes: 13 additions & 49 deletions accessibility-checker-engine/src/v4/util/AccNameUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class AccNameUtil {
// get aria label even for the role where the name is prohibited or is 'presentation' or 'none'
let accName = AriaUtil.getAriaLabel(elem);
if (accName && accName.trim() !== "") {
CacheUtil.setCache(elem, "ELEMENT_ACCESSBLE_NAME", {"name":accName, "nameFrom": "ariaLabel"});
CacheUtil.setCache(elem, "ELEMENT_ACCESSBLE_NAME", {"name":CommonUtil.truncateText(accName), "nameFrom": "ariaLabel"});
return {"name":CommonUtil.truncateText(accName), "nameFrom": "ariaLabel"};
}

Expand Down Expand Up @@ -82,7 +82,7 @@ export class AccNameUtil {
let placeholder = elem.getAttribute("placeholder");
if (placeholder && placeholder.trim().length > 0) {
placeholder = CommonUtil.truncateText(placeholder);
CacheUtil.setCache(elem, "ELEMENT_ACCESSBLE_NAME", placeholder);
CacheUtil.setCache(elem, "ELEMENT_ACCESSBLE_NAME", {"name":placeholder, "nameFrom": "placeholder"});
return {"name":placeholder, "nameFrom": "placeholder"};
}
}
Expand Down Expand Up @@ -265,10 +265,20 @@ export class AccNameUtil {
}
});
if (text.trim() !== '')
return {"name":text.trim(), "nameFrom": "iamges"};
return {"name":text.trim(), "nameFrom": "alt"};
}
}

// optgroup
// label participate in accessible name calculation: https://www.w3.org/TR/html-aam-1.0/#att-label
// The label attribute must be specified. Its value gives the name of the group
// the value is disabled in the interface
if (nodeName === "optgroup" || nodeName === "option" || nodeName === "track") {
const label = elem.getAttribute("label");
if (label && label.trim().length > 0)
return {"name":CommonUtil.truncateText(label), "nameFrom": "label"};
}

// svg
if (nodeName === "svg") {
const pair = AccNameUtil.computeAccessibleNameForSVGElement(elem);
Expand Down Expand Up @@ -341,52 +351,6 @@ export class AccNameUtil {
}
}

// calculate accessible name for custom elements marked with aria
/**public static computeAccessibleNameFromAttribute(elem: Element) : any | null {
const nodeName = elem.nodeName.toLowerCase();
const role = AriaUtil.getResolvedRole(elem);
// textbox etc. return its text value
if (role === "textbox") {
const name = elem.getAttribute("value");
if (name && name.trim().length > 0)
return {"name":CommonUtil.truncateText(name), "nameFrom": "value"};
}
// for combobox or listbox roles, return the text alternative of the chosen option.
if (role === "combobox" || role === "listbox") {
const selectedId = elem.getAttribute("aria-activedescendant") || elem.getAttribute("aria-selected") || elem.getAttribute("aria-checked");
if (selectedId) {
let selectedOption = elem.ownerDocument.getElementById(selectedId);
if (selectedOption && !DOMUtil.sameNode(elem, selectedOption)) {
const pair = AccNameUtil.computeAccessibleName(selectedOption);
if (pair && pair.name)
return {"name": pair.name, "nameFrom": "option"};
}
}
}
// for range role type, including "progressbar", "scrollbar", "slider", "spinbutton" roles
if (["progressbar", "scrollbar", "slider", "spinbutton", "meter"].includes(role)) {
// If the aria-valuetext property is present, return its value
let value = elem.getAttribute("aria-valuetext");
if (value && value.trim().length > 0)
return {"name":value, "nameFrom": "aria-valuetext"};
// Otherwise, if the aria-valuenow property is present, return its value,
value = elem.getAttribute("aria-valuenow");
if (value && value.trim().length > 0)
return {"name":CommonUtil.truncateText(value), "nameFrom": "aria-valuenow"};
// finally use native value attribute
value = elem.getAttribute("value");
if (value && value.trim().length > 0)
return {"name":CommonUtil.truncateText(value), "nameFrom": "value"};
}
// no accessible name exists
return null;
}*/

// calculate accessible name for custom elements marked with aria
public static computeAccessibleNameFromContent(elem: Element) : any | null {
const nodeName = elem.nodeName.toLowerCase();
Expand Down
46 changes: 23 additions & 23 deletions accessibility-checker-engine/src/v4/util/CommonUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,27 +851,34 @@ export class CommonUtil {
public static getFormFieldLabel(elem) : string | null {
// get the label from the attribute "for" of the label element
// Get only the non-hidden labels for element
let value = "";
let label = null;
let labelElem = CommonUtil.getLabelForElementHidden(elem, true);

/** if it's not label with for attribute, then find implicit label
* cases for explict label:
* <label for='my'></label><input id='my'/>
* <label for='my'><input id='my'/></label>
* cases for implicit label:
* <label><input /></label>
*/
if (!labelElem) {
if (labelElem) {
// value directly from element text
label = labelElem.innerText; // ignore hidden text
} else {
/** if it's not label with for attribute, then find implicit label
* cases for explict label:
* <label for='my'></label><input id='my'/>
* <label for='my'><input id='my'/></label>
* cases for implicit label:
* <label><input /></label>
*/
labelElem = CommonUtil.getAncestor(elem, "label");
if (!labelElem || labelElem.tagName.toLowerCase() !== "label" || !CommonUtil.isFirstFormElement(labelElem, elem))
return null;
if (labelElem && labelElem.tagName.toLowerCase() === "label" && CommonUtil.isFirstFormElement(labelElem, elem)) {
let parentClone = labelElem.cloneNode(true);
// exclude all the text from the first form element since they might also
// have inner content that is part of innerText
parentClone = CommonUtil.removeAllFormElementsFromLabel(parentClone);
label = CommonUtil.getInnerText(parentClone);
} else
return null;
}

let value = "";
// value directly from element text
let label = labelElem.innerText; // ignore hidden text

if (label && label.trim() !== "")
value += label.trim();

// value from child element attribute
label = CommonUtil.getLabelTextFromAttribute(labelElem, true);
if (label && label.trim() !== "")
Expand Down Expand Up @@ -906,13 +913,6 @@ export class CommonUtil {
break;
}
}
//let labeledElem = null;
/**if (labelElem.hasAttribute("for")) {
const id = labelElem.getAttribute("for").trim();
labeledElem = document.getElementById(id);
if (!labeledElem || DOMUtil.sameNode(labeledElem, labelElem))
labeledElem = null;
}*/

let nw = new DOMWalker(labelElem);
let text = '';
Expand Down

0 comments on commit 3f8f276

Please sign in to comment.