зеркало из https://github.com/github/combobox-nav.git
Add new option
This commit is contained in:
Родитель
f7aeecf5ec
Коммит
c28985b6c7
20
src/index.ts
20
src/index.ts
|
@ -1,9 +1,12 @@
|
|||
export type ComboboxSettings = {
|
||||
tabInsertsSuggestions?: boolean
|
||||
defaultFirstOption?: boolean
|
||||
firstOptionSelectionMode?: FirstOptionSelectionMode
|
||||
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions
|
||||
}
|
||||
|
||||
// Indicates the default behaviour for the first option when the list is shown.
|
||||
export type FirstOptionSelectionMode = 'none' | 'selected' | 'focused'
|
||||
|
||||
export default class Combobox {
|
||||
isComposing: boolean
|
||||
list: HTMLElement
|
||||
|
@ -13,18 +16,18 @@ export default class Combobox {
|
|||
inputHandler: (event: Event) => void
|
||||
ctrlBindings: boolean
|
||||
tabInsertsSuggestions: boolean
|
||||
defaultFirstOption: boolean
|
||||
firstOptionSelectionMode: FirstOptionSelectionMode
|
||||
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions
|
||||
|
||||
constructor(
|
||||
input: HTMLTextAreaElement | HTMLInputElement,
|
||||
list: HTMLElement,
|
||||
{tabInsertsSuggestions, defaultFirstOption, scrollIntoViewOptions}: ComboboxSettings = {},
|
||||
{tabInsertsSuggestions, firstOptionSelectionMode, scrollIntoViewOptions}: ComboboxSettings = {},
|
||||
) {
|
||||
this.input = input
|
||||
this.list = list
|
||||
this.tabInsertsSuggestions = tabInsertsSuggestions ?? true
|
||||
this.defaultFirstOption = defaultFirstOption ?? false
|
||||
this.firstOptionSelectionMode = firstOptionSelectionMode ?? 'none'
|
||||
this.scrollIntoViewOptions = scrollIntoViewOptions ?? {block: 'nearest', inline: 'nearest'}
|
||||
|
||||
this.isComposing = false
|
||||
|
@ -64,6 +67,7 @@ export default class Combobox {
|
|||
;(this.input as HTMLElement).addEventListener('keydown', this.keyboardEventHandler)
|
||||
this.list.addEventListener('click', commitWithElement)
|
||||
this.indicateDefaultOption()
|
||||
this.focusDefaultOptionIfNeeded()
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
|
@ -77,13 +81,19 @@ export default class Combobox {
|
|||
}
|
||||
|
||||
indicateDefaultOption(): void {
|
||||
if (this.defaultFirstOption) {
|
||||
if (this.firstOptionSelectionMode === 'selected') {
|
||||
Array.from(this.list.querySelectorAll<HTMLElement>('[role="option"]:not([aria-disabled="true"])'))
|
||||
.filter(visible)[0]
|
||||
?.setAttribute('data-combobox-option-default', 'true')
|
||||
}
|
||||
}
|
||||
|
||||
focusDefaultOptionIfNeeded(): void {
|
||||
if (this.firstOptionSelectionMode === 'focused') {
|
||||
this.navigate(1)
|
||||
}
|
||||
}
|
||||
|
||||
navigate(indexDiff: -1 | 1 = 1): void {
|
||||
const focusEl = Array.from(this.list.querySelectorAll<HTMLElement>('[aria-selected="true"]')).filter(visible)[0]
|
||||
const els = Array.from(this.list.querySelectorAll<HTMLElement>('[role="option"]')).filter(visible)
|
||||
|
|
62
test/test.js
62
test/test.js
|
@ -263,7 +263,7 @@ describe('combobox-nav', function () {
|
|||
input = document.querySelector('input')
|
||||
list = document.querySelector('ul')
|
||||
options = document.querySelectorAll('[role=option]')
|
||||
combobox = new Combobox(input, list, {defaultFirstOption: true})
|
||||
combobox = new Combobox(input, list, {firstOptionSelectionMode: 'selected'})
|
||||
combobox.start()
|
||||
})
|
||||
|
||||
|
@ -276,6 +276,7 @@ describe('combobox-nav', function () {
|
|||
it('indicates first option when started', () => {
|
||||
assert.equal(document.querySelector('[data-combobox-option-default]'), options[0])
|
||||
assert.equal(document.querySelectorAll('[data-combobox-option-default]').length, 1)
|
||||
assert.equal(list.children[0].getAttribute('aria-selected'), null)
|
||||
})
|
||||
|
||||
it('indicates first option when restarted', () => {
|
||||
|
@ -311,4 +312,63 @@ describe('combobox-nav', function () {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with defaulting to focusing the first option', function () {
|
||||
let input
|
||||
let list
|
||||
let combobox
|
||||
beforeEach(function () {
|
||||
document.body.innerHTML = `
|
||||
<input type="text">
|
||||
<ul role="listbox" id="list-id">
|
||||
<li id="baymax" role="option">Baymax</li>
|
||||
<li><del>BB-8</del></li>
|
||||
<li id="hubot" role="option">Hubot</li>
|
||||
<li id="r2-d2" role="option">R2-D2</li>
|
||||
<li id="johnny-5" hidden role="option">Johnny 5</li>
|
||||
<li id="wall-e" role="option" aria-disabled="true">Wall-E</li>
|
||||
<li><a href="#link" role="option" id="link">Link</a></li>
|
||||
</ul>
|
||||
`
|
||||
input = document.querySelector('input')
|
||||
list = document.querySelector('ul')
|
||||
combobox = new Combobox(input, list, {firstOptionSelectionMode: 'focused'})
|
||||
combobox.start()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
combobox.destroy()
|
||||
combobox = null
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
it('focuses first option when started', () => {
|
||||
// Does not set the default attribute
|
||||
assert.equal(document.querySelectorAll('[data-combobox-option-default]').length, 0)
|
||||
// Item is correctly selected
|
||||
assert.equal(list.children[0].getAttribute('aria-selected'), 'true')
|
||||
})
|
||||
|
||||
it('indicates first option when restarted', () => {
|
||||
combobox.stop()
|
||||
combobox.start()
|
||||
assert.equal(list.children[0].getAttribute('aria-selected'), 'true')
|
||||
})
|
||||
|
||||
it('applies default option on Enter', () => {
|
||||
let commits = 0
|
||||
document.addEventListener('combobox-commit', () => commits++)
|
||||
|
||||
assert.equal(commits, 0)
|
||||
press(input, 'Enter')
|
||||
assert.equal(commits, 1)
|
||||
})
|
||||
|
||||
it('does not error when no options are visible', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
document.getElementById('list-id').style.display = 'none'
|
||||
combobox.clearSelection()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче