Skip to content

Commit

Permalink
feat(dom.focusDisabled,dom.isVisibleForScreenreader): support the ine…
Browse files Browse the repository at this point in the history
…rt attribute (#3857)

* feat(dom.focus-disabled,dom.is-visible-for-screenreader): support the inert attribute

* typos

* integration tests
  • Loading branch information
straker committed Jan 19, 2023
1 parent 3be2bad commit 273c971
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 4 deletions.
7 changes: 5 additions & 2 deletions lib/commons/dom/focus-disabled.js
@@ -1,6 +1,8 @@
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';
import { getNodeFromTree } from '../../core/utils';
import isHiddenForEveryone from './is-hidden-for-everyone';
import isInert from './is-inert';

// Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled
const allowedDisabledNodeNames = [
'button',
Expand All @@ -27,8 +29,9 @@ function focusDisabled(el) {
const vNode = el instanceof AbstractVirtualNode ? el : getNodeFromTree(el);

if (
isDisabledAttrAllowed(vNode.props.nodeName) &&
vNode.hasAttr('disabled')
(isDisabledAttrAllowed(vNode.props.nodeName) &&
vNode.hasAttr('disabled')) ||
isInert(vNode)
) {
return true;
}
Expand Down
1 change: 1 addition & 0 deletions lib/commons/dom/index.js
Expand Up @@ -31,6 +31,7 @@ export { default as isHiddenForEveryone } from './is-hidden-for-everyone';
export { default as isHTML5 } from './is-html5';
export { default as isInTabOrder } from './is-in-tab-order';
export { default as isInTextBlock } from './is-in-text-block';
export { default as isInert } from './is-inert';
export { default as isModalOpen } from './is-modal-open';
export { default as isMultiline } from './is-multiline';
export { default as isNativelyFocusable } from './is-natively-focusable';
Expand Down
37 changes: 37 additions & 0 deletions lib/commons/dom/is-inert.js
@@ -0,0 +1,37 @@
import memoize from '../../core/utils/memoize';

/**
* Determines if an element is inside an inert subtree.
* @param {VirtualNode} vNode
* @param {Boolean} [options.skipAncestors] If the ancestor tree should not be used
* @return {Boolean} The element's inert state
*/
export default function isInert(vNode, { skipAncestors } = {}) {
if (skipAncestors) {
return isInertSelf(vNode);
}

return isInertAncestors(vNode);
}

/**
* Check the element for inert
*/
const isInertSelf = memoize(function isInertSelfMemoized(vNode) {
return vNode.hasAttr('inert');
});

/**
* Check the element and ancestors for inert
*/
const isInertAncestors = memoize(function isInertAncestorsMemoized(vNode) {
if (isInertSelf(vNode)) {
return true;
}

if (!vNode.parent) {
return false;
}

return isInertAncestors(vNode.parent);
});
3 changes: 2 additions & 1 deletion lib/commons/dom/is-visible-for-screenreader.js
Expand Up @@ -3,6 +3,7 @@ import { getNodeFromTree } from '../../core/utils';
import memoize from '../../core/utils/memoize';
import isHiddenForEveryone from './is-hidden-for-everyone';
import { ariaHidden, areaHidden } from './visibility-methods';
import isInert from './is-inert';

/**
* Determine if an element is visible to a screen reader
Expand All @@ -21,7 +22,7 @@ export default function isVisibleToScreenReaders(vNode) {
*/
const isVisibleToScreenReadersVirtual = memoize(
function isVisibleToScreenReadersMemoized(vNode, isAncestor) {
if (ariaHidden(vNode)) {
if (ariaHidden(vNode) || isInert(vNode, { skipAncestors: true })) {
return false;
}

Expand Down
23 changes: 23 additions & 0 deletions test/commons/dom/focus-disabled.js
Expand Up @@ -51,6 +51,29 @@ describe('dom.focus-disabled', () => {
assert.isTrue(focusDisabled(vNode));
});

it('returns true for element with inert', () => {
const vNode = queryFixture('<button id="target" inert></button>');

assert.isTrue(focusDisabled(vNode));
});

it('returns true for ancestor with inert', () => {
const vNode = queryFixture(
'<div inert><div><button id="target"></button></div></div>'
);

assert.isTrue(focusDisabled(vNode));
});

it('returns true for ancestor with inert outside shadow tree', () => {
const vNode = queryShadowFixture(
'<div inert><div id="shadow"></div></div>',
'<input id="target"/>'
);

assert.isTrue(focusDisabled(vNode));
});

describe('SerialVirtualNode', () => {
it('returns false if element is hidden for everyone', () => {
const vNode = new axe.SerialVirtualNode({
Expand Down
40 changes: 40 additions & 0 deletions test/commons/dom/is-inert.js
@@ -0,0 +1,40 @@
describe('dom.isInert', () => {
const isInert = axe.commons.dom.isInert;
const { queryFixture } = axe.testUtils;

it('returns true for element with "inert=false`', () => {
const vNode = queryFixture('<div id="target" inert="false"></div>');

assert.isTrue(isInert(vNode));
});

it('returns true for element with "inert`', () => {
const vNode = queryFixture('<div id="target" inert></div>');

assert.isTrue(isInert(vNode));
});

it('returns false for element without inert', () => {
const vNode = queryFixture('<div id="target"></div>');

assert.isFalse(isInert(vNode));
});

it('returns true for ancestor with inert', () => {
const vNode = queryFixture(
'<div inert><div><div id="target"></div></div></div>'
);

assert.isTrue(isInert(vNode));
});

describe('options.skipAncestors', () => {
it('returns false for ancestor with inert', () => {
const vNode = queryFixture(
'<div inert><div><div id="target"></div></div></div>'
);

assert.isFalse(isInert(vNode, { skipAncestors: true }));
});
});
});
32 changes: 32 additions & 0 deletions test/commons/dom/is-visible-for-screenreader.js
Expand Up @@ -62,6 +62,13 @@ describe('dom.isVisibleToScreenReaders', function () {
assert.isFalse(isVisibleToScreenReaders(vNode));
});

it('should return false if `inert` is set', function () {
var vNode = queryFixture(
'<div id="target" inert>Hidden from screen readers</div>'
);
assert.isFalse(isVisibleToScreenReaders(vNode));
});

it('should return false if `display: none` is set', function () {
var vNode = queryFixture(
'<div id="target" style="display: none">Hidden from screen readers</div>'
Expand Down Expand Up @@ -230,5 +237,30 @@ describe('dom.isVisibleToScreenReaders', function () {
vNode.parent = parentVNode;
assert.isFalse(isVisibleToScreenReaders(vNode));
});

it('should return false if `inert` is set', function () {
var vNode = new axe.SerialVirtualNode({
nodeName: 'div',
attributes: {
inert: true
}
});
assert.isFalse(isVisibleToScreenReaders(vNode));
});

it('should return false if `inert` is set on parent', function () {
var vNode = new axe.SerialVirtualNode({
nodeName: 'div'
});
var parentVNode = new axe.SerialVirtualNode({
nodeName: 'div',
attributes: {
inert: true
}
});
parentVNode.children = [vNode];
vNode.parent = parentVNode;
assert.isFalse(isVisibleToScreenReaders(vNode));
});
});
});
Expand Up @@ -27,6 +27,12 @@
</label>
</div>

<div id="pass7" aria-hidden="true">
<div inert>
<button>hello</button>
</div>
</div>

<!-- ///////////////// -->
<!-- Fail -->
<!-- ///////////////// -->
Expand Down
Expand Up @@ -16,7 +16,8 @@
["#pass3"],
["#pass4"],
["#pass5"],
["#pass6"]
["#pass6"],
["#pass7"]
],
"incomplete": [["#incomplete1"], ["#incomplete2"]]
}
Expand Up @@ -46,3 +46,8 @@
height="0"
id="inapplicable-3"
></iframe>
<iframe
src="/integration/rules/frame-focusable-content/frames/focusable.html"
inert
id="inapplicable-4"
></iframe>

0 comments on commit 273c971

Please sign in to comment.