The combo box composite widget is composed of two elements: an input
field and a popup results container.
This commit is contained in:
David Graham 2018-05-16 18:02:58 -06:00
Родитель 864920e72d
Коммит 0c96ef6424
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: AA9405DCE2E0D208
5 изменённых файлов: 27 добавлений и 28 удалений

Просмотреть файл

@ -15,14 +15,21 @@ import 'auto-complete-element'
```
```html
<auto-complete src="/users/search">
<auto-complete src="/users/search" aria-owns="users-popup">
<input slot="field" type="text" data-autocomplete-autofocus>
<div slot="popup">
<ul slot="results"></ul>
</div>
<ul slot="popup" id="users-popup"></ul>
</auto-complete>
```
The server response should include the items that matched the search query.
```html
<li role="option" data-autocomplete-value="@hubot">Hubot</li>
<li role="option" data-autocomplete-value="@bender">Bender</li>
<li role="option" data-autocomplete-value="@bb-8">BB-8</li>
<li role="option" data-autocomplete-value="@r2d2" aria-disabled="true">R2-D2 (powered down)</li>
```
## Browser support
- Chrome

Просмотреть файл

@ -25,10 +25,10 @@
send() {
this.status = 200
this.responseText = `
<li role="option" data-autocomplete-value="one">Hubot</li>
<li role="option" data-autocomplete-value="two">Bender</li>
<li role="option" data-autocomplete-value="three">BB-8</li>
<li role="option" data-autocomplete-value="four" aria-disabled="true">R2-D2 (powered down)</li>
<li role="option" data-autocomplete-value="@hubot">Hubot</li>
<li role="option" data-autocomplete-value="@bender">Bender</li>
<li role="option" data-autocomplete-value="@bb-8">BB-8</li>
<li role="option" data-autocomplete-value="@r2d2" aria-disabled="true">R2-D2 (powered down)</li>
`
setTimeout(this.onload.bind(this), 0)
}
@ -41,10 +41,7 @@
<label id="robots-label">Robots</label>
<auto-complete src="/demo" aria-owns="items-popup" aria-labelledby="robots-label">
<input type="text" slot="field" aria-labelledby="robots-label" autofocus>
<div slot="popup" id="items-popup" aria-labelledby="robots-label">
<ul slot="results">
</ul>
</div>
<ul slot="popup" id="items-popup" aria-labelledby="robots-label"></ul>
</auto-complete>
</body>
</html>

Просмотреть файл

@ -12,9 +12,8 @@ export default class AutocompleteElement extends HTMLElement {
connectedCallback() {
const input = this.querySelector('input[slot="field"]')
const results = this.querySelector('[slot="popup"]')
const list = this.querySelector('[slot="results"]')
if (!(input instanceof HTMLInputElement) || !results || !list) return
state.set(this, new Autocomplete(this, input, results, list))
if (!(input instanceof HTMLInputElement) || !results) return
state.set(this, new Autocomplete(this, input, results))
this.setAttribute('role', 'combobox')
this.setAttribute('aria-haspopup', 'listbox')

Просмотреть файл

@ -8,7 +8,6 @@ export default class Autocomplete {
container: AutocompleteElement
input: HTMLInputElement
results: HTMLElement
list: HTMLElement
onInputChange: Function
onResultsClick: Function
@ -19,11 +18,10 @@ export default class Autocomplete {
mouseDown: boolean
constructor(container: AutocompleteElement, input: HTMLInputElement, results: HTMLElement, list: HTMLElement) {
constructor(container: AutocompleteElement, input: HTMLInputElement, results: HTMLElement) {
this.container = container
this.input = input
this.results = results
this.list = list
this.results.hidden = true
this.input.setAttribute('autocomplete', 'off')
@ -56,8 +54,8 @@ export default class Autocomplete {
}
sibling(next: boolean): Element {
const options = Array.from(this.list.querySelectorAll('[role="option"]'))
const selected = this.list.querySelector('[aria-selected="true"]')
const options = Array.from(this.results.querySelectorAll('[role="option"]'))
const selected = this.results.querySelector('[aria-selected="true"]')
const index = options.indexOf(selected)
const sibling = next ? options[index + 1] : options[index - 1]
const def = next ? options[0] : options[options.length - 1]
@ -65,7 +63,7 @@ export default class Autocomplete {
}
select(target: Element) {
for (const el of this.list.querySelectorAll('[aria-selected="true"]')) {
for (const el of this.results.querySelectorAll('[aria-selected="true"]')) {
el.removeAttribute('aria-selected')
}
target.setAttribute('aria-selected', 'true')
@ -99,7 +97,7 @@ export default class Autocomplete {
break
case 'Enter':
{
const selected = this.list.querySelector('[aria-selected="true"]')
const selected = this.results.querySelector('[aria-selected="true"]')
if (selected) {
this.commit(selected)
event.preventDefault()
@ -159,7 +157,7 @@ export default class Autocomplete {
this.container.dispatchEvent(new CustomEvent('loadstart'))
fragment(this.input, url.toString())
.then(html => {
this.list.innerHTML = html
this.results.innerHTML = html
const hasResults = !!this.results.querySelector('[data-autocomplete-value]')
this.container.open = hasResults
this.container.dispatchEvent(new CustomEvent('load'))

Просмотреть файл

@ -17,9 +17,7 @@ describe('auto-complete element', function() {
container.innerHTML = `
<auto-complete src="/search">
<input slot="field" type="text">
<div slot="popup">
<ul slot="results"></ul>
</div>
<ul slot="popup"></ul>
</auto-complete>`
document.body.append(container)
})
@ -30,11 +28,11 @@ describe('auto-complete element', function() {
it('requests html fragment', async function() {
const input = document.querySelector('input')
const results = document.querySelector('[slot="results"]')
const popup = document.querySelector('[slot="popup"]')
input.value = 'hub'
input.dispatchEvent(new InputEvent('input'))
await sleep(500)
assert.equal('hubot', results.textContent)
assert.equal('hubot', popup.textContent)
})
})
})