diff --git a/README.md b/README.md index dde1a4f..c6f6ca3 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,12 @@ These settings are available: - `tabInsertsSuggestions: boolean = true` - Control whether the highlighted suggestion is inserted when Tab is pressed (Enter 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 Enter, 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 diff --git a/src/index.ts b/src/index.ts index 61a94c8..6537f7c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export type ComboboxSettings = { tabInsertsSuggestions?: boolean defaultFirstOption?: boolean + scrollIntoViewOptions?: boolean | ScrollIntoViewOptions } export default class Combobox { @@ -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 @@ -108,7 +111,7 @@ export default class Combobox { this.input.setAttribute('aria-activedescendant', target.id) target.setAttribute('aria-selected', 'true') fireSelectEvent(target) - scrollTo(this.list, target) + target.scrollIntoView(this.scrollIntoViewOptions) } else { el.removeAttribute('aria-selected') } @@ -209,17 +212,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 -}