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

Cycle focus between the body and page tab sequence #368

Merged
merged 2 commits into from Jun 21, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Expand Up @@ -402,7 +402,11 @@ it('should cycle elements in document tab order', () => {

userEvent.tab()

// cycle goes back to first element
// cycle goes back to the body element
expect(document.body).toHaveFocus()

userEvent.tab()

expect(checkbox).toHaveFocus()
})
```
Expand Down Expand Up @@ -545,6 +549,7 @@ Thanks goes to these people ([emoji key][emojis]):

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors][all-contributors] specification.
Expand Down
29 changes: 27 additions & 2 deletions src/__tests__/tab.js
Expand Up @@ -111,7 +111,11 @@ test('should cycle elements in document tab order', () => {

userEvent.tab()

// cycle goes back to first element
// cycle goes back to the body
expect(document.body).toHaveFocus()

userEvent.tab()

expect(checkbox).toHaveFocus()
})

Expand All @@ -127,14 +131,27 @@ test('should go backwards when shift = true', () => {
'[data-testid="element"]',
)

radio.focus()
expect(document.body).toHaveFocus()

userEvent.tab({shift: true})

expect(number).toHaveFocus()

userEvent.tab({shift: true})

expect(radio).toHaveFocus()

userEvent.tab({shift: true})

expect(checkbox).toHaveFocus()

userEvent.tab({shift: true})

// cycle goes back to the body
expect(document.body).toHaveFocus()

userEvent.tab({shift: true})

expect(number).toHaveFocus()
})

Expand Down Expand Up @@ -164,6 +181,10 @@ test('should respect tabindex, regardless of dom position', () => {

userEvent.tab()

expect(document.body).toHaveFocus()

userEvent.tab()

expect(radio).toHaveFocus()
})

Expand Down Expand Up @@ -193,6 +214,10 @@ test('should respect tab index order, then DOM order', () => {

userEvent.tab()

expect(document.body).toHaveFocus()

userEvent.tab()

expect(checkbox).toHaveFocus()
})

Expand Down
21 changes: 17 additions & 4 deletions src/tab.js
Expand Up @@ -3,6 +3,22 @@ import {getActiveElement, FOCUSABLE_SELECTOR} from './utils'
import {focus} from './focus'
import {blur} from './blur'

function getNextElement(currentIndex, shift, elements, focusTrap) {
if (focusTrap === document && currentIndex === 0 && shift) {
return document.body
} else if (
focusTrap === document &&
currentIndex === elements.length - 1 &&
!shift
) {
return document.body
} else {
const nextIndex = shift ? currentIndex - 1 : currentIndex + 1
const defaultIndex = shift ? elements.length - 1 : 0
return elements[nextIndex] || elements[defaultIndex]
}
}

function tab({shift = false, focusTrap} = {}) {
const previousElement = getActiveElement(focusTrap?.ownerDocument ?? document)

Expand Down Expand Up @@ -57,10 +73,7 @@ function tab({shift = false, focusTrap} = {}) {
el => el === el.ownerDocument.activeElement,
)

const nextIndex = shift ? index - 1 : index + 1
const defaultIndex = shift ? prunedElements.length - 1 : 0

const nextElement = prunedElements[nextIndex] || prunedElements[defaultIndex]
const nextElement = getNextElement(index, shift, prunedElements, focusTrap)

const shiftKeyInit = {
key: 'Shift',
Expand Down