Skip to content
Open
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
87 changes: 50 additions & 37 deletions lib/commons/dom/is-in-text-block.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
import getComposedParent from './get-composed-parent';
import sanitize from '../text/sanitize';
import { getNodeFromTree } from '../../core/utils';
import { getNodeFromTree, nodeLookup } from '../../core/utils';
import getRoleType from '../aria/get-role-type';

function walkDomNode(node, functor) {
if (functor(node.actualNode) !== false) {
node.children.forEach(child => walkDomNode(child, functor));
}
}
const blockLike = ['block', 'list-item', 'table', 'flex', 'grid'];

const blockLike = [
'block',
'list-item',
'table',
'flex',
'grid',
'inline-block'
];

function isBlock(elm) {
const display = window.getComputedStyle(elm).getPropertyValue('display');
return blockLike.includes(display) || display.substr(0, 6) === 'table-';
}

function getBlockParent(node) {
// Find the closest parent
let parentBlock = getComposedParent(node);
while (parentBlock && !isBlock(parentBlock)) {
parentBlock = getComposedParent(parentBlock);
}

return getNodeFromTree(parentBlock);
}
const inlineBlockLike = ['inline-block', 'inline-flex', 'inline-grid'];

/**
* Determines if an element is within a text block
Expand All @@ -41,23 +15,32 @@ function getBlockParent(node) {
* @param {Element} node [description]
* @param {Object} options Optional
* @property {Bool} noLengthCompare
* @property {Bool} includeInlineBlock
* @return {Boolean} [description]
*/
function isInTextBlock(node, options) {
if (isBlock(node)) {
// Ignore if the link is a block
function isInTextBlock(
node,
{ noLengthCompare, includeInlineBlock = false } = {}
) {
const { vNode, domNode } = nodeLookup(node);
if (isBlock(domNode) || (!includeInlineBlock && isInlineBlockLike(vNode))) {
// Ignore if the element is a block
return false;
}

// Find all the text part of the parent block not in a link, and all the text in a link
const virtualParent = getBlockParent(node);
const virtualParent = getBlockParent(domNode);
let parentText = '';
let widgetText = '';
let inBrBlock = 0;

// We want to ignore hidden text, and if br / hr is used, only use the section of the parent
// that has the link we're looking at
walkDomNode(virtualParent, currNode => {
if (currNode === virtualParent.actualNode) {
return true; // Skip the element itself
}

// We're already passed it, skip everything else
if (inBrBlock === 2) {
return false;
Expand All @@ -73,11 +56,12 @@ function isInTextBlock(node, options) {
}

const nodeName = (currNode.nodeName || '').toUpperCase();
if (currNode === node) {
if (currNode === domNode) {
inBrBlock = 1;
}
const nodeIsBlock = isBlock(currNode);
// BR and HR elements break the line
if (['BR', 'HR'].includes(nodeName)) {
if (nodeIsBlock || ['BR', 'HR'].includes(nodeName)) {
if (inBrBlock === 0) {
parentText = '';
widgetText = '';
Expand All @@ -86,7 +70,9 @@ function isInTextBlock(node, options) {
}

// Don't walk nodes with content not displayed on screen.
} else if (
}
if (
nodeIsBlock ||
currNode.style.display === 'none' ||
currNode.style.overflow === 'hidden' ||
!['', null, 'none'].includes(currNode.style.float) ||
Expand All @@ -103,7 +89,7 @@ function isInTextBlock(node, options) {
});

parentText = sanitize(parentText);
if (options?.noLengthCompare) {
if (noLengthCompare) {
return parentText.length !== 0;
}

Expand All @@ -112,3 +98,30 @@ function isInTextBlock(node, options) {
}

export default isInTextBlock;

function isBlock(node) {
const { vNode } = nodeLookup(node);
const display = vNode.getComputedStylePropertyValue('display');
return blockLike.includes(display) || display.substr(0, 6) === 'table-';
}

function isInlineBlockLike(vNode) {
const display = vNode.getComputedStylePropertyValue('display');
return inlineBlockLike.includes(display);
}

function walkDomNode(node, functor) {
if (functor(node.actualNode) !== false) {
node.children.forEach(child => walkDomNode(child, functor));
}
}

function getBlockParent(node) {
// Find the closest parent
let parentBlock = getComposedParent(node);
while (parentBlock && !isBlock(parentBlock)) {
parentBlock = getComposedParent(parentBlock);
}

return getNodeFromTree(parentBlock);
}
3 changes: 2 additions & 1 deletion lib/rules/widget-not-inline-matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const matchesFns = [
(node, vNode) => isFocusable(vNode),
// Skip nested widgets with tabindex=-1
(node, vNode) => isInTabOrder(vNode) || !hasWidgetAncestorInTabOrder(vNode),
node => !isInTextBlock(node, { noLengthCompare: true })
node =>
!isInTextBlock(node, { noLengthCompare: true, includeInlineBlock: true })
];

function isWidgetType(vNode) {
Expand Down
Loading
Loading