dom-accessibility-api
Advanced tools
Comparing version
# [0.1.0](https://github.com/eps1lon/dom-accessibility-api/compare/v0.0.1...v0.1.0) (2019-11-05) | ||
## 0.2.0 | ||
### Minor Changes | ||
- eb86842: Add option to mock window.getComputedStyle | ||
This option has two use cases in mind: | ||
1. fake the style and assume everything is visible. | ||
This increases performance (window.getComputedStyle) is expensive) by not distinguishing between various levels of visual impairments. If one can't see the name with a screen reader then neither will a sighted user | ||
2. Wrap a cache provider around `window.getComputedStyle`. We don't implement any because the returned `CSSStyleDeclaration` is only live in a browser. `jsdom` does not implement live declarations. | ||
### Bug Fixes | ||
* Fix test name_heading-combobox ([#16](https://github.com/eps1lon/dom-accessibility-api/issues/16)) ([e969395](https://github.com/eps1lon/dom-accessibility-api/commit/e969395d8da637862993aeee0b86f379342d56f2)) | ||
- Fix test name_heading-combobox ([#16](https://github.com/eps1lon/dom-accessibility-api/issues/16)) ([e969395](https://github.com/eps1lon/dom-accessibility-api/commit/e969395d8da637862993aeee0b86f379342d56f2)) | ||
### Features | ||
* **name:** Consider prohibited naming ([#19](https://github.com/eps1lon/dom-accessibility-api/issues/19)) ([6692d6b](https://github.com/eps1lon/dom-accessibility-api/commit/6692d6bd86030da9b340b0895f623394b21e2656)) | ||
* Consider all cases of "name from content" ([#13](https://github.com/eps1lon/dom-accessibility-api/issues/13)) ([835cb76](https://github.com/eps1lon/dom-accessibility-api/commit/835cb76e7c1dd577af1fa891ad849385e58fcd56)) | ||
* Consider content from before and after pseudo elements ([#5](https://github.com/eps1lon/dom-accessibility-api/issues/5)) ([0987426](https://github.com/eps1lon/dom-accessibility-api/commit/0987426734cc7b980a8edf39435820a24ea2a162)) | ||
* Fork elementToRole from aria-query ([#7](https://github.com/eps1lon/dom-accessibility-api/issues/7)) ([fe4fab5](https://github.com/eps1lon/dom-accessibility-api/commit/fe4fab57786324705c4ac4434de8aabd3e7bbc09)) | ||
- **name:** Consider prohibited naming ([#19](https://github.com/eps1lon/dom-accessibility-api/issues/19)) ([6692d6b](https://github.com/eps1lon/dom-accessibility-api/commit/6692d6bd86030da9b340b0895f623394b21e2656)) | ||
- Consider all cases of "name from content" ([#13](https://github.com/eps1lon/dom-accessibility-api/issues/13)) ([835cb76](https://github.com/eps1lon/dom-accessibility-api/commit/835cb76e7c1dd577af1fa891ad849385e58fcd56)) | ||
- Consider content from before and after pseudo elements ([#5](https://github.com/eps1lon/dom-accessibility-api/issues/5)) ([0987426](https://github.com/eps1lon/dom-accessibility-api/commit/0987426734cc7b980a8edf39435820a24ea2a162)) | ||
- Fork elementToRole from aria-query ([#7](https://github.com/eps1lon/dom-accessibility-api/issues/7)) ([fe4fab5](https://github.com/eps1lon/dom-accessibility-api/commit/fe4fab57786324705c4ac4434de8aabd3e7bbc09)) |
@@ -5,9 +5,15 @@ /** | ||
/** | ||
* interface for an options-bag where `window.getComputedStyle` can be mocked | ||
*/ | ||
interface GetComputedStyleOptions { | ||
getComputedStyle?: typeof window.getComputedStyle; | ||
} | ||
/** | ||
* implements https://w3c.github.io/accname/#mapping_additional_nd_te | ||
* @param root | ||
* @param context | ||
* @param [options] | ||
* @parma [options.getComputedStyle] - mock window.getComputedStyle. Needs `content`, `display` and `visibility` | ||
*/ | ||
export declare function computeAccessibleName(root: Element, context?: { | ||
isReferenced?: boolean; | ||
}): string; | ||
export declare function computeAccessibleName(root: Element, options?: GetComputedStyleOptions): string; | ||
export {}; | ||
//# sourceMappingURL=accessible-name.d.ts.map |
@@ -10,3 +10,19 @@ "use strict"; | ||
const getRole_1 = __importDefault(require("./getRole")); | ||
const util_1 = require("./util"); | ||
/** | ||
* Small utility that handles all the JS quirks with `this` which is important | ||
* if no mock is provided. | ||
* @param element | ||
* @param options - These are not optional to prevent accidentally calling it without options in `computeAccessibleName` | ||
*/ | ||
function createGetComputedStyle(element, options) { | ||
const window = util_1.safeWindow(element); | ||
const { | ||
// This might be overengineered. I don't know what happens if I call | ||
// window.getComputedStyle(elementFromAnotherWindow) or if I don't bind it | ||
// the type declarations don't require a `this` | ||
getComputedStyle = window.getComputedStyle.bind(window) } = options; | ||
return getComputedStyle; | ||
} | ||
/** | ||
* | ||
@@ -20,41 +36,27 @@ * @param {string} string - | ||
/** | ||
* TODO | ||
* https://w3c.github.io/aria/#namefromprohibited | ||
*/ | ||
function prohibitsNaming(node) { | ||
return false; | ||
return hasAnyConcreteRoles(node, [ | ||
"caption", | ||
"code", | ||
"deletion", | ||
"emphasis", | ||
"generic", | ||
"insertion", | ||
"paragraph", | ||
"presentation", | ||
"strong", | ||
"subscript", | ||
"superscript" | ||
]); | ||
} | ||
function isElement(node) { | ||
return ( | ||
// @ts-ignore | ||
node !== null && node instanceof node.ownerDocument.defaultView.Element); | ||
} | ||
function isHTMLInputElement(node) { | ||
return (isElement(node) && | ||
// @ts-ignore | ||
node instanceof node.ownerDocument.defaultView.HTMLInputElement); | ||
} | ||
function isHTMLSelectElement(node) { | ||
return (isElement(node) && | ||
// @ts-ignore | ||
node instanceof node.ownerDocument.defaultView.HTMLSelectElement); | ||
} | ||
function isHTMLTextAreaElement(node) { | ||
return (isElement(node) && | ||
// @ts-ignore | ||
node instanceof node.ownerDocument.defaultView.HTMLTextAreaElement); | ||
} | ||
function safeWindow(node) { | ||
const { defaultView } = node.ownerDocument === null ? node : node.ownerDocument; | ||
if (defaultView === null) { | ||
throw new TypeError("no window available"); | ||
} | ||
return defaultView; | ||
} | ||
/** | ||
* | ||
* @param {Node} node - | ||
* @param node - | ||
* @param options - These are not optional to prevent accidentally calling it without options in `computeAccessibleName` | ||
* @returns {boolean} - | ||
*/ | ||
function isHidden(node) { | ||
if (!isElement(node)) { | ||
function isHidden(node, options) { | ||
if (!util_1.isElement(node)) { | ||
return false; | ||
@@ -66,3 +68,3 @@ } | ||
} | ||
const style = safeWindow(node).getComputedStyle(node); | ||
const style = createGetComputedStyle(node, options)(node); | ||
return (style.getPropertyValue("display") === "none" || | ||
@@ -78,5 +80,9 @@ style.getPropertyValue("visibility") === "hidden"); | ||
function idRefs(node, attributeName) { | ||
if (isElement(node) && node.hasAttribute(attributeName)) { | ||
if (util_1.isElement(node) && node.hasAttribute(attributeName)) { | ||
// safe due to hasAttribute check | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const ids = node.getAttribute(attributeName).split(" "); | ||
return ids | ||
// safe since it can't be null for an Element | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
.map(id => node.ownerDocument.getElementById(id)) | ||
@@ -106,3 +112,3 @@ .filter((element) => element !== null | ||
function hasAbstractRole(node, role) { | ||
if (!isElement(node)) { | ||
if (!util_1.isElement(node)) { | ||
return false; | ||
@@ -124,3 +130,3 @@ } | ||
function hasAnyConcreteRoles(node, roles) { | ||
if (isElement(node)) { | ||
if (util_1.isElement(node)) { | ||
return roles.indexOf(getRole_1.default(node)) !== -1; | ||
@@ -143,3 +149,3 @@ } | ||
function querySelectedOptions(listbox) { | ||
if (isHTMLSelectElement(listbox)) { | ||
if (util_1.isHTMLSelectElement(listbox)) { | ||
// IE11 polyfill | ||
@@ -199,3 +205,3 @@ return (listbox.selectedOptions || querySelectorAllSubtree(listbox, "[selected]")); | ||
function getValueOfTextbox(element) { | ||
if (isHTMLInputElement(element) || isHTMLTextAreaElement(element)) { | ||
if (util_1.isHTMLInputElement(element) || util_1.isHTMLTextAreaElement(element)) { | ||
return element.value; | ||
@@ -216,14 +222,8 @@ } | ||
* @param root | ||
* @param context | ||
* @param [options] | ||
* @parma [options.getComputedStyle] - mock window.getComputedStyle. Needs `content`, `display` and `visibility` | ||
*/ | ||
function computeAccessibleName(root, context = {}) { | ||
/** | ||
* @type {Set<Node>} | ||
*/ | ||
function computeAccessibleName(root, options = {}) { | ||
const consultedNodes = new Set(); | ||
/** | ||
* @type {FlatString} | ||
*/ | ||
let totalAccumulatedText = ""; | ||
if (prohibitsNaming(root) && !context.isReferenced) { | ||
if (prohibitsNaming(root)) { | ||
return ""; | ||
@@ -234,4 +234,4 @@ } | ||
let accumulatedText = ""; | ||
if (isElement(node)) { | ||
const pseudoBefore = safeWindow(node).getComputedStyle(node, "::before"); | ||
if (util_1.isElement(node)) { | ||
const pseudoBefore = createGetComputedStyle(node, options)(node, "::before"); | ||
const beforeContent = getTextualContent(pseudoBefore); | ||
@@ -247,11 +247,9 @@ accumulatedText = `${beforeContent} ${accumulatedText}`; | ||
// TODO: Unclear why display affects delimiter | ||
const display = isElement(node) && | ||
safeWindow(node) | ||
.getComputedStyle(node) | ||
.getPropertyValue("display"); | ||
const display = util_1.isElement(node) && | ||
createGetComputedStyle(node, options)(node).getPropertyValue("display"); | ||
const separator = display !== "inline" ? " " : ""; | ||
accumulatedText += `${separator}${result}`; | ||
} | ||
if (isElement(node)) { | ||
const pseudoAfter = safeWindow(node).getComputedStyle(node, ":after"); | ||
if (util_1.isElement(node)) { | ||
const pseudoAfter = createGetComputedStyle(node, options)(node, ":after"); | ||
const afterContent = getTextualContent(pseudoAfter); | ||
@@ -266,3 +264,3 @@ accumulatedText = `${accumulatedText} ${afterContent}`; | ||
function computeAttributeTextAlternative(node) { | ||
if (!isElement(node)) { | ||
if (!util_1.isElement(node)) { | ||
return null; | ||
@@ -280,3 +278,3 @@ } | ||
} | ||
if (isHTMLInputElement(node) && node.type === "button") { | ||
if (util_1.isHTMLInputElement(node) && node.type === "button") { | ||
consultedNodes.add(node); | ||
@@ -288,3 +286,3 @@ return node.getAttribute("value") || ""; | ||
function computeElementTextAlternative(node) { | ||
if (!isHTMLInputElement(node)) { | ||
if (!util_1.isHTMLInputElement(node)) { | ||
return null; | ||
@@ -324,2 +322,3 @@ } | ||
// special casing, cheating to make tests pass | ||
// https://github.com/w3c/accname/issues/67 | ||
if (hasAnyConcreteRoles(current, ["menu"])) { | ||
@@ -330,3 +329,3 @@ consultedNodes.add(current); | ||
// 2A | ||
if (isHidden(current) && !context.isReferenced) { | ||
if (isHidden(current, options) && !context.isReferenced) { | ||
consultedNodes.add(current); | ||
@@ -350,5 +349,7 @@ return ""; | ||
// 2C | ||
// Changed from the spec in anticipation of https://github.com/w3c/accname/issues/64 | ||
// spec says we should only consider skipping if we have a non-empty label | ||
const skipToStep2E = context.recursion && isControl(current); | ||
if (!skipToStep2E) { | ||
const ariaLabel = ((isElement(current) && current.getAttribute("aria-label")) || | ||
const ariaLabel = ((util_1.isElement(current) && current.getAttribute("aria-label")) || | ||
"").trim(); | ||
@@ -360,3 +361,3 @@ if (ariaLabel !== "") { | ||
// 2D | ||
if (!hasAnyConcreteRoles(current, ["none", "presentation"])) { | ||
if (!isMarkedPresentational(current)) { | ||
const elementTextAlternative = computeElementTextAlternative(current); | ||
@@ -381,3 +382,3 @@ if (elementTextAlternative !== null) { | ||
// defined per test `name_heading_combobox` | ||
return isHTMLInputElement(current) ? current.value : ""; | ||
return util_1.isHTMLInputElement(current) ? current.value : ""; | ||
} | ||
@@ -397,5 +398,9 @@ return Array.from(selectedOptions) | ||
if (current.hasAttribute("aria-valuetext")) { | ||
// safe due to hasAttribute guard | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
return current.getAttribute("aria-valuetext"); | ||
} | ||
if (current.hasAttribute("aria-valuenow")) { | ||
// safe due to hasAttribute guard | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
return current.getAttribute("aria-valuenow"); | ||
@@ -411,5 +416,5 @@ } | ||
} | ||
// 2F | ||
// 2F: https://w3c.github.io/accname/#step2F | ||
if (allowsNameFromContent(current) || | ||
(isElement(current) && context.isReferenced) || | ||
(util_1.isElement(current) && context.isReferenced) || | ||
isNativeHostLanguageTextAlternativeElement(current) || | ||
@@ -416,0 +421,0 @@ isDescendantOfNativeHostLanguageTextAlternativeElement(current)) { |
@@ -73,2 +73,3 @@ "use strict"; | ||
} | ||
break; | ||
case "IMG": | ||
@@ -78,3 +79,4 @@ if ((element.getAttribute("alt") || "").length > 0) { | ||
} | ||
case "INPUT": | ||
break; | ||
case "INPUT": { | ||
const { type } = element; | ||
@@ -108,2 +110,3 @@ switch (type) { | ||
} | ||
} | ||
case "SELECT": | ||
@@ -120,2 +123,4 @@ if (element.hasAttribute("multiple") || | ||
if (element.hasAttribute("role")) { | ||
// safe due to hasAttribute check | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const [explicitRole] = element | ||
@@ -122,0 +127,0 @@ .getAttribute("role") |
{ | ||
"name": "dom-accessibility-api", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/eps1lon/dom-accessibility-api.git" | ||
}, | ||
"files": [ | ||
@@ -14,2 +18,4 @@ "dist/" | ||
"lint": "eslint --report-unused-disable-directives \"sources/**/*.ts\"", | ||
"release:prepare": "yarn changeset version", | ||
"release": "yarn changeset publish", | ||
"test": "jest", | ||
@@ -27,32 +33,28 @@ "test:ci": "jest --ci --config jest.ci.config.js --runInBand", | ||
"devDependencies": { | ||
"@babel/core": "^7.6.2", | ||
"@babel/preset-env": "^7.6.2", | ||
"@babel/preset-typescript": "^7.6.0", | ||
"@semantic-release/changelog": "^3.0.5", | ||
"@semantic-release/commit-analyzer": "^6.3.2", | ||
"@semantic-release/git": "^7.0.18", | ||
"@semantic-release/npm": "^5.3.4", | ||
"@semantic-release/release-notes-generator": "^7.3.2", | ||
"@testing-library/dom": "^6.6.0", | ||
"@types/jest": "^24.0.18", | ||
"@typescript-eslint/eslint-plugin": "^2.5.0", | ||
"@babel/core": "^7.7.4", | ||
"@babel/preset-env": "^7.7.4", | ||
"@babel/preset-typescript": "^7.7.4", | ||
"@changesets/cli": "^2.4.0", | ||
"@testing-library/dom": "^6.10.1", | ||
"@types/jest": "^24.0.23", | ||
"@typescript-eslint/parser": "^2.9.0", | ||
"@typescript-eslint/eslint-plugin": "^2.9.0", | ||
"concurrently": "^5.0.0", | ||
"cypress": "^3.4.1", | ||
"eslint": "^6.6.0", | ||
"cypress": "^3.6.1", | ||
"eslint": "^6.7.1", | ||
"jest": "^24.9.0", | ||
"jest-diff": "^24.9.0", | ||
"jest-environment-jsdom-thirteen": "^1.0.1", | ||
"jest-junit": "^8.0.0", | ||
"jest-junit": "^9.0.0", | ||
"js-yaml": "^3.13.1", | ||
"jsdom": "^15.1.1", | ||
"jsdom": "^15.2.1", | ||
"minimatch": "^3.0.4", | ||
"mocha": "^6.2.1", | ||
"mocha": "^6.2.2", | ||
"mocha-sugar-free": "^1.4.0", | ||
"prettier": "^1.18.2", | ||
"prettier": "^1.19.1", | ||
"q": "^1.5.1", | ||
"request": "^2.34", | ||
"request-promise-native": "^1.0.7", | ||
"semantic-release": "^15.13.30", | ||
"request-promise-native": "^1.0.8", | ||
"serve": "^11.2.0", | ||
"typescript": "^3.6.3" | ||
"typescript": "^3.7.2" | ||
}, | ||
@@ -62,9 +64,10 @@ "prettier": { | ||
}, | ||
"dependencies": { | ||
"@typescript-eslint/parser": "^2.5.0" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/eps1lon/dom-accessibility-api.git" | ||
"keywords": [ | ||
"accessibility", | ||
"ARIA", | ||
"accname" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
51018
10.84%0
-100%26
-13.33%19
26.67%629
9.58%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed