@guidepup/virtual-screen-reader
Advanced tools
Comparing version 0.6.1 to 0.7.0
@@ -6,2 +6,3 @@ export interface AccessibilityNode { | ||
accessibleValue: string; | ||
allowedAccessibilityChildRoles: string[][]; | ||
childrenPresentational: boolean; | ||
@@ -8,0 +9,0 @@ node: Node; |
@@ -21,4 +21,6 @@ "use strict"; | ||
// to include. | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
accessibleName === (node.textContent || node.value)?.trim()); | ||
accessibleName === | ||
(node.textContent || | ||
`${node.value}` || | ||
"")?.trim()); | ||
} | ||
@@ -44,2 +46,3 @@ function flattenTree(tree) { | ||
accessibleValue: treeNode.accessibleValue, | ||
allowedAccessibilityChildRoles: treeNode.allowedAccessibilityChildRoles, | ||
childrenPresentational: treeNode.childrenPresentational, | ||
@@ -58,3 +61,4 @@ node: treeNode.node, | ||
} | ||
const { accessibleAttributeLabels, accessibleDescription, accessibleName, accessibleValue, childrenPresentational, role, spokenRole, } = (0, getNodeAccessibilityData_1.getNodeAccessibilityData)({ | ||
const { accessibleAttributeLabels, accessibleDescription, accessibleName, accessibleValue, allowedAccessibilityChildRoles, childrenPresentational, role, spokenRole, } = (0, getNodeAccessibilityData_1.getNodeAccessibilityData)({ | ||
allowedAccessibilityRoles: tree.allowedAccessibilityChildRoles, | ||
node: childNode, | ||
@@ -68,2 +72,3 @@ inheritedImplicitPresentational: tree.childrenPresentational, | ||
accessibleValue, | ||
allowedAccessibilityChildRoles, | ||
children: [], | ||
@@ -82,3 +87,4 @@ childrenPresentational, | ||
} | ||
const { accessibleAttributeLabels, accessibleDescription, accessibleName, accessibleValue, childrenPresentational, role, spokenRole, } = (0, getNodeAccessibilityData_1.getNodeAccessibilityData)({ | ||
const { accessibleAttributeLabels, accessibleDescription, accessibleName, accessibleValue, allowedAccessibilityChildRoles, childrenPresentational, role, spokenRole, } = (0, getNodeAccessibilityData_1.getNodeAccessibilityData)({ | ||
allowedAccessibilityRoles: [], | ||
node, | ||
@@ -92,2 +98,3 @@ inheritedImplicitPresentational: false, | ||
accessibleValue, | ||
allowedAccessibilityChildRoles, | ||
children: [], | ||
@@ -94,0 +101,0 @@ childrenPresentational, |
@@ -5,3 +5,3 @@ "use strict"; | ||
const aria_query_1 = require("aria-query"); | ||
const getRole_1 = require("../../getRole"); | ||
const getRole_1 = require("../getRole"); | ||
const ignoreAttributesWithAccessibleValue = ["aria-placeholder"]; | ||
@@ -8,0 +8,0 @@ const getAttributesByRole = ({ accessibleValue, role, }) => { |
export declare const getLabelFromAriaAttribute: ({ attributeName, node, }: { | ||
attributeName: string; | ||
node: HTMLElement; | ||
}) => any; | ||
}) => { | ||
label: string; | ||
value: string; | ||
}; |
@@ -7,7 +7,10 @@ "use strict"; | ||
const attributeValue = node.getAttribute(attributeName); | ||
return (0, mapAttributeNameAndValueToLabel_1.mapAttributeNameAndValueToLabel)({ | ||
attributeName, | ||
attributeValue, | ||
}); | ||
return { | ||
label: (0, mapAttributeNameAndValueToLabel_1.mapAttributeNameAndValueToLabel)({ | ||
attributeName, | ||
attributeValue, | ||
}), | ||
value: attributeValue, | ||
}; | ||
}; | ||
exports.getLabelFromAriaAttribute = getLabelFromAriaAttribute; |
export declare const getLabelFromHtmlEquivalentAttribute: ({ attributeName, node, }: { | ||
attributeName: string; | ||
node: HTMLElement; | ||
}) => any; | ||
}) => { | ||
label: string; | ||
value: string; | ||
}; |
@@ -24,3 +24,3 @@ "use strict"; | ||
if (!htmlAttribute?.length) { | ||
return null; | ||
return { label: "", value: "" }; | ||
} | ||
@@ -35,7 +35,7 @@ for (const { name, negative = false } of htmlAttribute) { | ||
if (label) { | ||
return label; | ||
return { label, value: attributeValue }; | ||
} | ||
} | ||
return null; | ||
return { label: "", value: "" }; | ||
}; | ||
exports.getLabelFromHtmlEquivalentAttribute = getLabelFromHtmlEquivalentAttribute; |
export declare const getLabelFromImplicitHtmlElementValue: ({ attributeName, node, }: { | ||
attributeName: string; | ||
node: HTMLElement; | ||
}) => any; | ||
}) => { | ||
label: string; | ||
value: any; | ||
}; |
@@ -18,7 +18,10 @@ "use strict"; | ||
const implicitValue = mapLocalNameToImplicitValue[attributeName]?.[localName]; | ||
return (0, mapAttributeNameAndValueToLabel_1.mapAttributeNameAndValueToLabel)({ | ||
attributeName, | ||
attributeValue: implicitValue, | ||
}); | ||
return { | ||
label: (0, mapAttributeNameAndValueToLabel_1.mapAttributeNameAndValueToLabel)({ | ||
attributeName, | ||
attributeValue: implicitValue, | ||
}), | ||
value: implicitValue, | ||
}; | ||
}; | ||
exports.getLabelFromImplicitHtmlElementValue = getLabelFromImplicitHtmlElementValue; |
@@ -10,10 +10,11 @@ "use strict"; | ||
const mapAttributeNameAndValueToLabel_1 = require("./mapAttributeNameAndValueToLabel"); | ||
const postProcessLabels_1 = require("./postProcessLabels"); | ||
const getAccessibleAttributeLabels = ({ accessibleValue, node, role, }) => { | ||
const labels = []; | ||
if (!(0, isElement_1.isElement)(node)) { | ||
return labels; | ||
return []; | ||
} | ||
const labels = {}; | ||
const attributes = (0, getAttributesByRole_1.getAttributesByRole)({ accessibleValue, role }); | ||
attributes.forEach(([attributeName, implicitAttributeValue]) => { | ||
const labelFromHtmlEquivalentAttribute = (0, getLabelFromHtmlEquivalentAttribute_1.getLabelFromHtmlEquivalentAttribute)({ | ||
const { label: labelFromHtmlEquivalentAttribute, value: valueFromHtmlEquivalentAttribute, } = (0, getLabelFromHtmlEquivalentAttribute_1.getLabelFromHtmlEquivalentAttribute)({ | ||
attributeName, | ||
@@ -23,6 +24,9 @@ node, | ||
if (labelFromHtmlEquivalentAttribute) { | ||
labels.push(labelFromHtmlEquivalentAttribute); | ||
labels[attributeName] = { | ||
label: labelFromHtmlEquivalentAttribute, | ||
value: valueFromHtmlEquivalentAttribute, | ||
}; | ||
return; | ||
} | ||
const labelFromAriaAttribute = (0, getLabelFromAriaAttribute_1.getLabelFromAriaAttribute)({ | ||
const { label: labelFromAriaAttribute, value: valueFromAriaAttribute } = (0, getLabelFromAriaAttribute_1.getLabelFromAriaAttribute)({ | ||
attributeName, | ||
@@ -32,6 +36,9 @@ node, | ||
if (labelFromAriaAttribute) { | ||
labels.push(labelFromAriaAttribute); | ||
labels[attributeName] = { | ||
label: labelFromAriaAttribute, | ||
value: valueFromAriaAttribute, | ||
}; | ||
return; | ||
} | ||
const labelFromImplicitHtmlElementValue = (0, getLabelFromImplicitHtmlElementValue_1.getLabelFromImplicitHtmlElementValue)({ | ||
const { label: labelFromImplicitHtmlElementValue, value: valueFromImplicitHtmlElementValue, } = (0, getLabelFromImplicitHtmlElementValue_1.getLabelFromImplicitHtmlElementValue)({ | ||
attributeName, | ||
@@ -41,3 +48,6 @@ node, | ||
if (labelFromImplicitHtmlElementValue) { | ||
labels.push(labelFromImplicitHtmlElementValue); | ||
labels[attributeName] = { | ||
label: labelFromImplicitHtmlElementValue, | ||
value: valueFromImplicitHtmlElementValue, | ||
}; | ||
return; | ||
@@ -50,8 +60,11 @@ } | ||
if (labelFromImplicitAriaAttributeValue) { | ||
labels.push(labelFromImplicitAriaAttributeValue); | ||
labels[attributeName] = { | ||
label: labelFromImplicitAriaAttributeValue, | ||
value: implicitAttributeValue, | ||
}; | ||
return; | ||
} | ||
}); | ||
return labels; | ||
return (0, postProcessLabels_1.postProcessLabels)({ labels, role }); | ||
}; | ||
exports.getAccessibleAttributeLabels = getAccessibleAttributeLabels; |
@@ -5,2 +5,2 @@ export declare const mapAttributeNameAndValueToLabel: ({ attributeName, attributeValue, negative, }: { | ||
negative?: boolean; | ||
}) => any; | ||
}) => string; |
@@ -59,2 +59,8 @@ "use strict"; | ||
"aria-haspopup": token({ | ||
/** | ||
* Assistive technologies SHOULD NOT expose the aria-haspopup property if | ||
* it has a value of false. | ||
* | ||
* REF: // https://w3c.github.io/aria/#aria-haspopup | ||
*/ | ||
false: null, | ||
@@ -108,6 +114,4 @@ true: "has popup menu", | ||
"aria-valuemin": number("min value"), | ||
// TODO: don't announce if have an aria-valuetext | ||
// TODO: map to percentage as per https://w3c.github.io/aria/#aria-valuenow for certain roles | ||
"aria-valuenow": number("current value"), | ||
"aria-valuetext": string("current value"), // TODO: don't announce if have a value? | ||
"aria-valuetext": string("current value"), | ||
}; | ||
@@ -114,0 +118,0 @@ function state(stateValue) { |
@@ -0,1 +1,2 @@ | ||
export type HTMLElementWithValue = HTMLButtonElement | HTMLDataElement | HTMLInputElement | HTMLLIElement | HTMLMeterElement | HTMLOptionElement | HTMLProgressElement | HTMLParamElement; | ||
export declare function getAccessibleValue(node: Node): string; |
@@ -5,6 +5,19 @@ "use strict"; | ||
const isElement_1 = require("../isElement"); | ||
const ignoredInputTypes = ["checkbox", "radio"]; | ||
const allowedLocalNames = [ | ||
"button", | ||
"data", | ||
"input", | ||
// "li", | ||
"meter", | ||
"option", | ||
"progress", | ||
"param", | ||
]; | ||
function getSelectValue(node) { | ||
const selectedOptions = [...node.options].filter((option) => option.selected); | ||
const selectedOptions = [...node.options].filter((optionElement) => optionElement.selected); | ||
if (node.multiple) { | ||
return [...selectedOptions].map((opt) => opt.value).join("; "); | ||
return [...selectedOptions] | ||
.map((optionElement) => getValue(optionElement)) | ||
.join("; "); | ||
} | ||
@@ -14,10 +27,20 @@ if (selectedOptions.length === 0) { | ||
} | ||
return selectedOptions[0].value; | ||
return getValue(selectedOptions[0]); | ||
} | ||
function getInputValue(node) { | ||
if (["checkbox", "radio"].includes(node.type)) { | ||
if (ignoredInputTypes.includes(node.type)) { | ||
return ""; | ||
} | ||
return node.value; | ||
return getValue(node); | ||
} | ||
function getValue(node) { | ||
if (!allowedLocalNames.includes(node.localName)) { | ||
return ""; | ||
} | ||
if (node.getAttribute("aria-valuetext") || | ||
node.getAttribute("aria-valuenow")) { | ||
return ""; | ||
} | ||
return typeof node.value === "number" ? `${node.value}` : node.value; | ||
} | ||
function getAccessibleValue(node) { | ||
@@ -35,4 +58,4 @@ if (!(0, isElement_1.isElement)(node)) { | ||
} | ||
return ""; | ||
return getValue(node); | ||
} | ||
exports.getAccessibleValue = getAccessibleValue; |
@@ -1,2 +0,3 @@ | ||
export declare function getNodeAccessibilityData({ inheritedImplicitPresentational, node, }: { | ||
export declare function getNodeAccessibilityData({ allowedAccessibilityRoles, inheritedImplicitPresentational, node, }: { | ||
allowedAccessibilityRoles: string[][]; | ||
inheritedImplicitPresentational: boolean; | ||
@@ -9,5 +10,6 @@ node: Node; | ||
accessibleValue: string; | ||
allowedAccessibilityChildRoles: string[][]; | ||
childrenPresentational: boolean; | ||
role: string; | ||
spokenRole: string; | ||
spokenRole: any; | ||
}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getNodeAccessibilityData = void 0; | ||
const getRole_1 = require("../getRole"); | ||
const aria_query_1 = require("aria-query"); | ||
const getRole_1 = require("./getRole"); | ||
const getAccessibleAttributeLabels_1 = require("./getAccessibleAttributeLabels"); | ||
@@ -9,8 +10,37 @@ const getAccessibleDescription_1 = require("./getAccessibleDescription"); | ||
const getAccessibleValue_1 = require("./getAccessibleValue"); | ||
function getNodeAccessibilityData({ inheritedImplicitPresentational, node, }) { | ||
const isElement_1 = require("../isElement"); | ||
const childrenPresentationalRoles = aria_query_1.roles | ||
.entries() | ||
.filter(([, { childrenPresentational }]) => childrenPresentational) | ||
.map(([key]) => key); | ||
const getSpokenRole = ({ isGeneric, isPresentational, node, role }) => { | ||
if (isPresentational || isGeneric) { | ||
return ""; | ||
} | ||
if ((0, isElement_1.isElement)(node)) { | ||
/** | ||
* Assistive technologies SHOULD use the value of aria-roledescription when | ||
* presenting the role of an element, but SHOULD NOT change other | ||
* functionality based on the role of an element that has a value for | ||
* aria-roledescription. For example, an assistive technology that provides | ||
* functions for navigating to the next region or button SHOULD allow those | ||
* functions to navigate to regions and buttons that have an | ||
* aria-roledescription. | ||
* | ||
* REF: https://w3c.github.io/aria/#aria-roledescription | ||
*/ | ||
const roledescription = node.getAttribute("aria-roledescription"); | ||
if (roledescription) { | ||
return roledescription; | ||
} | ||
} | ||
return role; | ||
}; | ||
function getNodeAccessibilityData({ allowedAccessibilityRoles, inheritedImplicitPresentational, node, }) { | ||
const accessibleDescription = (0, getAccessibleDescription_1.getAccessibleDescription)(node); | ||
const accessibleName = (0, getAccessibleName_1.getAccessibleName)(node); | ||
const accessibleValue = (0, getAccessibleValue_1.getAccessibleValue)(node); | ||
const role = (0, getRole_1.getRole)({ | ||
const { explicitRole, implicitRole, role } = (0, getRole_1.getRole)({ | ||
accessibleName, | ||
allowedAccessibilityRoles, | ||
inheritedImplicitPresentational, | ||
@@ -24,8 +54,40 @@ node, | ||
}); | ||
const amendedAccessibleDescription = accessibleDescription === accessibleName ? "" : accessibleDescription; | ||
const isExplicitPresentational = getRole_1.presentationRoles.includes(explicitRole); | ||
const isPresentational = getRole_1.presentationRoles.includes(role); | ||
const isGeneric = role === "generic"; | ||
const childrenPresentational = inheritedImplicitPresentational || | ||
getRole_1.childrenPresentationalRoles.includes(role); | ||
const spokenRole = isPresentational || isGeneric ? "" : role; | ||
const amendedAccessibleDescription = accessibleDescription === accessibleName ? "" : accessibleDescription; | ||
const spokenRole = getSpokenRole({ isGeneric, isPresentational, node, role }); | ||
const { requiredOwnedElements: allowedAccessibilityChildRoles } = aria_query_1.roles.get(role) ?? { requiredOwnedElements: [] }; | ||
const { requiredOwnedElements: implicitAllowedAccessibilityChildRoles } = aria_query_1.roles.get(implicitRole) ?? { requiredOwnedElements: [] }; | ||
/** | ||
* Any descendants of elements that have the characteristic "Children | ||
* Presentational: True" unless the descendant is not allowed to be | ||
* presentational because it meets one of the conditions for exception | ||
* described in Presentational Roles Conflict Resolution. However, the text | ||
* content of any excluded descendants is included. | ||
* | ||
* REF: https://w3c.github.io/aria/#tree_exclusion | ||
*/ | ||
const isChildrenPresentationalRole = childrenPresentationalRoles.includes(role); | ||
/** | ||
* When an explicit or inherited role of presentation is applied to an | ||
* element with the implicit semantic of a WAI-ARIA role that has Allowed | ||
* Accessibility Child Roles, in addition to the element with the explicit | ||
* role of presentation, the user agent MUST apply an inherited role of | ||
* presentation to any owned elements that do not have an explicit role | ||
* defined. Also, when an explicit or inherited role of presentation is | ||
* applied to a host language element which has specifically allowed children | ||
* as defined by the host language specification, in addition to the element | ||
* with the explicit role of presentation, the user agent MUST apply an | ||
* inherited role of presentation to any specifically allowed children that | ||
* do not have an explicit role defined. | ||
* | ||
* REF: https://w3c.github.io/aria/#presentational-role-inheritance | ||
*/ | ||
const isExplicitOrInheritedPresentation = isExplicitPresentational || inheritedImplicitPresentational; | ||
const isElementWithImplicitAllowedAccessibilityChildRoles = !!implicitAllowedAccessibilityChildRoles.length; | ||
const childrenInheritPresentationExceptAllowedRoles = isExplicitOrInheritedPresentation && | ||
isElementWithImplicitAllowedAccessibilityChildRoles; | ||
const childrenPresentational = isChildrenPresentationalRole || | ||
childrenInheritPresentationExceptAllowedRoles; | ||
return { | ||
@@ -36,2 +98,3 @@ accessibleAttributeLabels, | ||
accessibleValue, | ||
allowedAccessibilityChildRoles, | ||
childrenPresentational, | ||
@@ -38,0 +101,0 @@ role, |
@@ -10,2 +10,11 @@ import { CommandOptions, ScreenReader } from "@guidepup/guidepup"; | ||
} | ||
/** | ||
* TODO: When an assistive technology reading cursor moves from one article to | ||
* another, assistive technologies SHOULD set user agent focus on the article | ||
* that contains the reading cursor. If the reading cursor lands on a focusable | ||
* element inside the article, the assistive technology MAY set focus on that | ||
* element in lieu of setting focus on the containing article. | ||
* | ||
* REF: https://w3c.github.io/aria/#feed | ||
*/ | ||
export declare class Virtual implements ScreenReader { | ||
@@ -12,0 +21,0 @@ #private; |
@@ -50,2 +50,8 @@ "use strict"; | ||
* | ||
* When live regions are marked as polite, assistive technologies SHOULD | ||
* announce updates at the next graceful opportunity, such as at the end of | ||
* speaking the current sentence or when the user pauses typing. When live | ||
* regions are marked as assertive, assistive technologies SHOULD notify the | ||
* user immediately. | ||
* | ||
* REF: | ||
@@ -56,2 +62,3 @@ * | ||
* - https://w3c.github.io/aria/#attrs_liveregions | ||
* - https://w3c.github.io/aria/#aria-live | ||
*/ | ||
@@ -63,2 +70,11 @@ /** | ||
*/ | ||
/** | ||
* TODO: When a modal element is displayed, assistive technologies SHOULD | ||
* navigate to the element unless focus has explicitly been set elsewhere. Some | ||
* assistive technologies limit navigation to the modal element's contents. If | ||
* focus moves to an element outside the modal element, assistive technologies | ||
* SHOULD NOT limit navigation to the modal element. | ||
* | ||
* REF: https://w3c.github.io/aria/#aria-modal | ||
*/ | ||
const observeDOM = (function () { | ||
@@ -91,2 +107,11 @@ const MutationObserver = window.MutationObserver; | ||
} | ||
/** | ||
* TODO: When an assistive technology reading cursor moves from one article to | ||
* another, assistive technologies SHOULD set user agent focus on the article | ||
* that contains the reading cursor. If the reading cursor lands on a focusable | ||
* element inside the article, the assistive technology MAY set focus on that | ||
* element in lieu of setting focus on the containing article. | ||
* | ||
* REF: https://w3c.github.io/aria/#feed | ||
*/ | ||
class Virtual { | ||
@@ -350,4 +375,75 @@ #activeNode = null; | ||
await tick(); | ||
// TODO: decide what this means as there is no established "common" command | ||
// set for different screen readers. | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* elements with role banner. | ||
* | ||
* REF: https://w3c.github.io/aria/#banner | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* elements with role complementary. | ||
* | ||
* REF: https://w3c.github.io/aria/#complementary | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* elements with role contentinfo. | ||
* | ||
* REF: https://w3c.github.io/aria/#contentinfo | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* figures. | ||
* | ||
* REF: https://w3c.github.io/aria/#figure | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* elements with role form. | ||
* | ||
* REF: https://w3c.github.io/aria/#form | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* landmark regions. | ||
* | ||
* REF: https://w3c.github.io/aria/#landmark | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* elements with role main. | ||
* | ||
* REF: https://w3c.github.io/aria/#main | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* elements with role navigation. | ||
* | ||
* REF: https://w3c.github.io/aria/#navigation | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* elements with role region. | ||
* | ||
* REF: https://w3c.github.io/aria/#region | ||
*/ | ||
/** | ||
* TODO: Assistive technologies SHOULD enable users to quickly navigate to | ||
* elements with role search. | ||
* | ||
* REF: https://w3c.github.io/aria/#search | ||
*/ | ||
/** | ||
* TODO: However, when aria-flowto is provided with multiple ID | ||
* references, assistive technologies SHOULD present the referenced | ||
* elements as path choices. | ||
* | ||
* In the case of one or more ID references, user agents or assistive | ||
* technologies SHOULD give the user the option of navigating to any of the | ||
* targeted elements. The name of the path can be determined by the name of | ||
* the target element of the aria-flowto attribute. Accessibility APIs can | ||
* provide named path relationships. | ||
* | ||
* REF: https://w3c.github.io/aria/#aria-flowto | ||
*/ | ||
(0, notImplemented_1.notImplemented)(); | ||
@@ -354,0 +450,0 @@ } |
{ | ||
"name": "@guidepup/virtual-screen-reader", | ||
"version": "0.6.1", | ||
"version": "0.7.0", | ||
"description": "Virtual screen reader driver for unit test automation.", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
68348
45
1633