Skip to content

Commit

Permalink
Use Element.scrollIntoView
Browse files Browse the repository at this point in the history
Related to [@github/auto-complete-element#91][]

The current `scrollTo` helper method implementation can be unreliable
(or have no effect) at times.

This commit replaces it with a call to [Element.scrollIntoView][]. To
control that behavior, this commit also introduces a
`scrollIntoViewOptions:` key to the package's constructor call.

[@github/auto-complete-element#91]: github/auto-complete-element#91
[Element.scrollIntoView]: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
[ScrollIntoViewOptions]: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#sect1
  • Loading branch information
seanpdoyle committed Sep 5, 2023
1 parent a3d2178 commit b0dec8e
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 16 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ These settings are available:
- `tabInsertsSuggestions: boolean = true` - Control whether the highlighted suggestion is inserted when <kbd>Tab</kbd> is pressed (<kbd>Enter</kbd> will always insert a suggestion regardless of this setting). When `true`, tab-navigation will be hijacked when open (which can have negative impacts on accessibility) but the combobox will more closely imitate a native IDE experience.
- `defaultFirstOption: boolean = false` - If no options are selected and the user presses <kbd>Enter</kbd>, should the first item be inserted? If enabled, the default option can be selected and styled with `[data-combobox-option-default]` . This should be styled differently from the `aria-selected` option.
> **Warning** Screen readers will not announce that the first item is the default. This should be announced explicitly with the use of `aria-live` status text.
- `scrollIntoViewOptions?: boolean | ScrollIntoViewOptions = undefined` - When
controlling the element marked `[aria-selected="true"]` with keyboard navigation, the selected element will be scrolled into the viewport by a call to [Element.scrollIntoView][]. Configure this value to control the scrolling behavior (either with a `boolean` or a [ScrollIntoViewOptions][] object.

[Element.scrollIntoView]: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
[ScrollIntoViewOptions]: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#sect1


## Development

Expand Down
21 changes: 5 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type ComboboxSettings = {
tabInsertsSuggestions?: boolean
defaultFirstOption?: boolean
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions
}

export default class Combobox {
Expand All @@ -13,16 +14,18 @@ export default class Combobox {
ctrlBindings: boolean
tabInsertsSuggestions: boolean
defaultFirstOption: boolean
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions

constructor(
input: HTMLTextAreaElement | HTMLInputElement,
list: HTMLElement,
{tabInsertsSuggestions, defaultFirstOption}: ComboboxSettings = {}
{tabInsertsSuggestions, defaultFirstOption, scrollIntoViewOptions}: ComboboxSettings = {}
) {
this.input = input
this.list = list
this.tabInsertsSuggestions = tabInsertsSuggestions ?? true
this.defaultFirstOption = defaultFirstOption ?? false
this.scrollIntoViewOptions = scrollIntoViewOptions

this.isComposing = false

Expand Down Expand Up @@ -107,7 +110,7 @@ export default class Combobox {
if (target === el) {
this.input.setAttribute('aria-activedescendant', target.id)
target.setAttribute('aria-selected', 'true')
scrollTo(this.list, target)
target.scrollIntoView(this.scrollIntoViewOptions)
} else {
el.removeAttribute('aria-selected')
}
Expand Down Expand Up @@ -204,17 +207,3 @@ function trackComposition(event: Event, combobox: Combobox): void {

combobox.clearSelection()
}

function scrollTo(container: HTMLElement, target: HTMLElement) {
if (!inViewport(container, target)) {
container.scrollTop = target.offsetTop
}
}

function inViewport(container: HTMLElement, element: HTMLElement): boolean {
const scrollTop = container.scrollTop
const containerBottom = scrollTop + container.clientHeight
const top = element.offsetTop
const bottom = top + element.clientHeight
return top >= scrollTop && bottom <= containerBottom
}

0 comments on commit b0dec8e

Please sign in to comment.