Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider focusables with tabindex="-1" to be unreachable #43

Merged
merged 1 commit into from
Jun 5, 2024
Merged
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ navigate _to_.
LRUD spatial defines focusable elements as those which match any of the
following CSS selectors:

- `[tabindex]`
- `[tabindex]` (for tabindex >= 0)
- `a`
- `button`
- `input`
Expand All @@ -41,6 +41,8 @@ following CSS selectors:

Any potential candidate with the `lrud-ignore` class, or inside any parent with the `lrud-ignore` class, will not be considered focusable and will be skipped over. By default LRUD will not ignore candidates that have `opacity: 0` or have a parent with `opacity: 0`, so this class can be used for that.

Focusables with a `tabindex="-1"` attribute will be skipped over, however any focusable inside any parent with `tabindex="-1"` will still be considered focusable.

### Focusable Overlap

By default, LRUD will measure to all candidates that are in the direction to move. It will also include candidates that overlap the current focus by up to 30%, allowing for e.g. a 'right' movement to include something that is above the current focus, but has half of it's size expanding to the right.
Expand Down
3 changes: 2 additions & 1 deletion lib/lrud.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ const getFocusables = (scope) => {
if (scope.className.indexOf(ignoredClass) > -1) ignoredElements.push(scope);

return toArray(scope.querySelectorAll(focusableSelector))
.filter(node => !ignoredElements.some(ignored => ignored == node || ignored.contains(node)));
.filter(node => !ignoredElements.some(ignored => ignored == node || ignored.contains(node)))
.filter(node => parseInt(node.getAttribute('tabIndex') || 0, 10) > -1);
};

/**
Expand Down
31 changes: 31 additions & 0 deletions test/layouts/unreachable.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<h1>Test file with tabindex of -1 (unreachable) and >= 0</h1>

<a id="item-1" class="item" href="">
<h2>1</h2>
</a>

<a id="item-2" class="item" tabindex="0" href="">
<h2>2</h2>
</a>

<a id="item-3" class="item" tabindex="-1" href="">
<h2>3</h2>
</a>

<a id="item-4" class="item" tabindex="1" href="">
<h2>4</h2>
</a>

<section tabindex="-1">
<a id="item-5" class="item" tabindex="-1" href="">
<h2>5</h2>
</a>

<a id="item-6" class="item" href="">
<h2>6</h2>
</a>
</section>

<a id="item-7" class="item" href="">
<h2>7</h2>
</a>
36 changes: 35 additions & 1 deletion test/lrud.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ describe('LRUD spatial', () => {
* width, height, and coordinates set to 0. These should not be considered focusable candidates.
*
* Elements with the `lrud-ignore` class, or inside a parent with the `lrud-ignore` class, should
* be not be considered focusable candidates
* not be considered focusable candidates
*
*/
it('should ignore hidden items as possible candidates and move past them', async () => {
Expand All @@ -291,6 +291,40 @@ describe('LRUD spatial', () => {
});
});

describe('Unreachable foucsable elements', () => {
/*
* Elements with a tabindex of -1 should not be considered focusable candidates.
* Elements inside a parent with tabindex -1 should be considered focusable candidates.
*
*/
it('should ignore tabindex -1 items as possible candidates and move past them', async () => {
await page.goto(`${testPath}/unreachable.html`);
await page.waitForFunction('document.activeElement');
await page.keyboard.press('ArrowRight');
await page.keyboard.press('ArrowRight');

const result = await page.evaluate(() => document.activeElement.id);

expect(result).toEqual('item-4');
});

it('should not ignore visible items inside tabindex -1 containers', async () => {
await page.goto(`${testPath}/unreachable.html`);
await page.waitForFunction('document.activeElement');
await page.keyboard.press('ArrowDown');

const result = await page.evaluate(() => document.activeElement.id);

expect(result).toEqual('item-6');

await page.keyboard.press('ArrowDown');

const nextResult = await page.evaluate(() => document.activeElement.id);

expect(nextResult).toEqual('item-7');
});
});

describe('Page with 11 candidates, with varying sizes', () => {
it('should focus on candidate 6 when right, down is pressed', async () => {
await page.goto(`${testPath}/0c-v-11f-size.html`);
Expand Down
Loading