Skip to content

Commit

Permalink
improvement: a11y: roving tabindex: End / Home
Browse files Browse the repository at this point in the history
  • Loading branch information
WofWca committed Jan 6, 2025
1 parent aca110f commit 60f4937
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## Added
- accessibility: arrow-key navigation for gallery #4376
- accessibility: arrow-key navigation: handle "End" and "Home" keys to go to last / first item #4438

## Changed
- dev: upgrade react to v18 and react pinch pan zoom to v3
Expand Down
73 changes: 44 additions & 29 deletions packages/frontend/src/contexts/RovingTabindex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export function RovingTabindexProvider({
return
}

const indexChange: -1 | 1 | undefined =
const indexChange: -1 | 1 | 'first' | 'last' | undefined =
indexChangeTable[direction][event.code]
if (indexChange == undefined) {
return
Expand All @@ -192,34 +192,42 @@ export function RovingTabindexProvider({
classNameOfTargetElements
)

let oldActiveElementInd: number | undefined
for (let i = 0; i < eligibleElements.length; i++) {
const eligibleEl = eligibleElements[i]
if (eligibleEl === activeElement) {
oldActiveElementInd = i
break
let newActiveElement: Element
if (indexChange === 'first') {
newActiveElement = eligibleElements[0]
} else if (indexChange === 'last') {
newActiveElement = eligibleElements[eligibleElements.length - 1]
} else {
let oldActiveElementInd: number | undefined
for (let i = 0; i < eligibleElements.length; i++) {
const eligibleEl = eligibleElements[i]
if (eligibleEl === activeElement) {
oldActiveElementInd = i
break
}
}
if (oldActiveElementInd == undefined) {
log.warn(
'Could not find the currently active element in DOM',
activeElement
)
return
}
}
if (oldActiveElementInd == undefined) {
log.warn(
'Could not find the currently active element in DOM',
activeElement
)
return
}

const newActiveElement =
eligibleElements[oldActiveElementInd + indexChange]
// `newActiveElement` could be `undefined` if the active element is either
// the last or the first.
if (newActiveElement != undefined) {
const newActiveElement_ = newActiveElement as HTMLElement
setActiveElement(newActiveElement_)
// It is fine to `.focus()` here without wating for a render
// or `useEffect`, because elements with `tabindex="-1"`
// are still programmatically focusable.
newActiveElement_.focus()
newActiveElement = eligibleElements[oldActiveElementInd + indexChange]
// `newActiveElement` could be `undefined` if the active element
// is either the last or the first.
if (newActiveElement == undefined) {
return
}
}

const newActiveElement_ = newActiveElement as HTMLElement
setActiveElement(newActiveElement_)
// It is fine to `.focus()` here without wating for a render
// or `useEffect`, because elements with `tabindex="-1"`
// are still programmatically focusable.
newActiveElement_.focus()
},
[activeElement, classNameOfTargetElements, direction, wrapperElementRef]
)
Expand All @@ -238,19 +246,26 @@ export function RovingTabindexProvider({
)
}

type IndexChange = -1 | 1 | 'first' | 'last'
const indexChangeTable = {
vertical: {
Home: 'first',
End: 'last',
ArrowUp: -1,
ArrowDown: 1,
} as { [key: string]: -1 | 1 },
} as { [key: string]: IndexChange },
horizontal: {
Home: 'first',
End: 'last',
ArrowLeft: -1,
ArrowRight: 1,
} as { [key: string]: -1 | 1 },
} as { [key: string]: IndexChange },
both: {
Home: 'first',
End: 'last',
ArrowUp: -1,
ArrowLeft: -1,
ArrowDown: 1,
ArrowRight: 1,
} as { [key: string]: -1 | 1 },
} as { [key: string]: IndexChange },
} as const

0 comments on commit 60f4937

Please sign in to comment.