-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(select a11y): make PageUp keypress respect disabled options
- Loading branch information
Showing
8 changed files
with
215 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
...rc/single-select-a11y/use-handle-key-press/use-highlight-first-option-on-previous-page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { useCallback } from 'react' | ||
import { isOptionHidden } from '../is-option-hidden.js' | ||
|
||
export function useHighlightFirstOptionOnPreviousPage({ | ||
options, | ||
focussedOptionIndex, | ||
setFocussedOptionIndex, | ||
listBoxRef, | ||
}) { | ||
return useCallback(() => { | ||
const listBoxParent = listBoxRef.current.parentNode | ||
const optionElements = Array.from(listBoxRef.current.childNodes) | ||
const visibleOptionsAmount = optionElements.filter( | ||
(optionElement) => !isOptionHidden(optionElement, listBoxParent) | ||
).length | ||
|
||
const nextTopOptionIndex = Math.max( | ||
0, | ||
focussedOptionIndex - visibleOptionsAmount | ||
) | ||
|
||
// If there's no next option and we already have the last option in the list highlighted | ||
if (!options[nextTopOptionIndex]) { | ||
return | ||
} | ||
|
||
if (!options[nextTopOptionIndex].disabled) { | ||
const nextTopOption = optionElements[nextTopOptionIndex] | ||
const scrollPosition = nextTopOption.offsetTop | ||
listBoxParent.scrollTop = scrollPosition | ||
setFocussedOptionIndex(nextTopOptionIndex) | ||
return | ||
} | ||
|
||
const lowerEnabledOptionIndex = options | ||
.slice(0, nextTopOptionIndex) | ||
.findLastIndex((option) => !option.disabled) | ||
|
||
if (lowerEnabledOptionIndex !== -1) { | ||
const lowerEnabledOption = optionElements[lowerEnabledOptionIndex] | ||
const lowerScrollPosition = lowerEnabledOption.offsetTop | ||
listBoxParent.scrollTop = lowerScrollPosition | ||
setFocussedOptionIndex(lowerEnabledOptionIndex) | ||
return | ||
} | ||
|
||
const inbetweenEnabledOptionIndex = | ||
nextTopOptionIndex + | ||
options | ||
.slice(nextTopOptionIndex, focussedOptionIndex) | ||
.findIndex((option) => !option.disabled) | ||
|
||
if (inbetweenEnabledOptionIndex === -1) { | ||
// We're already on the first enabled option | ||
return | ||
} | ||
|
||
const inbetweenTopOption = optionElements[inbetweenEnabledOptionIndex] | ||
const scrollPosition = inbetweenTopOption.offsetTop | ||
listBoxParent.scrollTop = scrollPosition | ||
setFocussedOptionIndex(inbetweenEnabledOptionIndex) | ||
}, [options, focussedOptionIndex, setFocussedOptionIndex, listBoxRef]) | ||
} |
38 changes: 38 additions & 0 deletions
38
.../select/src/single-select-a11y/use-handle-key-press/use-highlight-first-visible-option.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { useCallback } from 'react' | ||
|
||
export function useHighlightFirstVisibleOption({ | ||
options, | ||
focussedOptionIndex, | ||
setFocussedOptionIndex, | ||
}) { | ||
return useCallback( | ||
(lowestVisibleIndex) => { | ||
if (!options[lowestVisibleIndex].disabled) { | ||
setFocussedOptionIndex(lowestVisibleIndex) | ||
return | ||
} | ||
|
||
const previousEnabledOptionIndex = options | ||
.slice(0, lowestVisibleIndex) | ||
.findLastIndex((option) => !option.disabled) | ||
|
||
if (previousEnabledOptionIndex !== -1) { | ||
setFocussedOptionIndex(previousEnabledOptionIndex) | ||
return | ||
} | ||
|
||
const nextEnabledOptionIndex = | ||
lowestVisibleIndex + | ||
options | ||
.slice(lowestVisibleIndex, focussedOptionIndex) | ||
.findIndex((option) => !option.disabled) | ||
|
||
if (nextEnabledOptionIndex !== -1) { | ||
return | ||
} | ||
|
||
setFocussedOptionIndex(nextEnabledOptionIndex) | ||
}, | ||
[options, focussedOptionIndex, setFocussedOptionIndex] | ||
) | ||
} |
50 changes: 50 additions & 0 deletions
50
components/select/src/single-select-a11y/use-handle-key-press/use-page-down.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { useCallback } from 'react' | ||
import { isOptionHidden } from '../is-option-hidden.js' | ||
import { useHighlightLastOptionOnNextPage } from './use-highlight-last-option-on-next-page.js' | ||
import { useHighlightLastVisibleOption } from './use-highlight-last-visible-option.js' | ||
|
||
export function usePageDown({ | ||
options, | ||
focussedOptionIndex, | ||
setFocussedOptionIndex, | ||
listBoxRef, | ||
}) { | ||
const highlightLastVisibleOption = useHighlightLastVisibleOption({ | ||
options, | ||
focussedOptionIndex, | ||
setFocussedOptionIndex, | ||
}) | ||
|
||
const highlightLastOptionOnNextPage = useHighlightLastOptionOnNextPage({ | ||
options, | ||
focussedOptionIndex, | ||
setFocussedOptionIndex, | ||
listBoxRef, | ||
}) | ||
|
||
return useCallback(() => { | ||
const listBoxParent = listBoxRef.current.parentNode | ||
const options = Array.from(listBoxRef.current.childNodes) | ||
const highestVisibleIndex = options.findLastIndex( | ||
(option) => !isOptionHidden(option, listBoxParent) | ||
) | ||
|
||
if (highestVisibleIndex > focussedOptionIndex) { | ||
highlightLastVisibleOption(highestVisibleIndex) | ||
return | ||
} | ||
|
||
if (highestVisibleIndex > -1) { | ||
highlightLastOptionOnNextPage(listBoxParent) | ||
return | ||
} | ||
|
||
// No visible option (e.g. when menu is empty) | ||
return | ||
}, [ | ||
focussedOptionIndex, | ||
listBoxRef, | ||
highlightLastVisibleOption, | ||
highlightLastOptionOnNextPage, | ||
]) | ||
} |
117 changes: 0 additions & 117 deletions
117
components/select/src/single-select-a11y/use-handle-key-press/use-page-up-down.js
This file was deleted.
Oops, something went wrong.
50 changes: 50 additions & 0 deletions
50
components/select/src/single-select-a11y/use-handle-key-press/use-page-up.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { useCallback } from 'react' | ||
import { isOptionHidden } from '../is-option-hidden.js' | ||
import { useHighlightFirstOptionOnPreviousPage } from './use-highlight-first-option-on-previous-page.js' | ||
import { useHighlightFirstVisibleOption } from './use-highlight-first-visible-option.js' | ||
|
||
export function usePageUp({ | ||
options, | ||
listBoxRef, | ||
focussedOptionIndex, | ||
setFocussedOptionIndex, | ||
}) { | ||
const highlightFirstVisibleOption = useHighlightFirstVisibleOption({ | ||
options, | ||
focussedOptionIndex, | ||
setFocussedOptionIndex, | ||
}) | ||
|
||
const highlightFirstOptionOnPreviousPage = | ||
useHighlightFirstOptionOnPreviousPage({ | ||
options, | ||
focussedOptionIndex, | ||
setFocussedOptionIndex, | ||
listBoxRef, | ||
}) | ||
|
||
return useCallback(() => { | ||
const listBoxParent = listBoxRef.current.parentNode | ||
const optionElements = Array.from(listBoxRef.current.childNodes) | ||
const lowestVisibleIndex = optionElements.findIndex( | ||
(optionElement) => !isOptionHidden(optionElement, listBoxParent) | ||
) | ||
|
||
// No visible option (e.g. when menu is empty) | ||
if (lowestVisibleIndex === -1) { | ||
return | ||
} | ||
|
||
if (lowestVisibleIndex < focussedOptionIndex) { | ||
highlightFirstVisibleOption(lowestVisibleIndex) | ||
return | ||
} | ||
|
||
highlightFirstOptionOnPreviousPage() | ||
}, [ | ||
focussedOptionIndex, | ||
listBoxRef, | ||
highlightFirstOptionOnPreviousPage, | ||
highlightFirstVisibleOption, | ||
]) | ||
} |