Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ With a script tag:

```html
<auto-complete src="/users/search" for="users-popup">
<input type="text">
<input type="text" name="users">
<!--
Optional clear button:
- id must match the id of the input or the name of the input plus "-clear"
- recommended to be *before* UL elements to avoid conflicting with their blur logic
-->
<button id="users-clear">X</button>
<ul id="users-popup"></ul>
<!--
Optional div for screen reader feedback. Note the ID matches the ul, but with -feedback appended.
Expand Down
8 changes: 5 additions & 3 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
<!-- To enable auto-select (select first on Enter), use data-autoselect="true" -->
<auto-complete src="/demo" for="items-popup" aria-labelledby="robots-label" data-autoselect="true">
<input name="robot" type="text" aria-labelledby="robots-label" autofocus>
<!-- if a clear button is passed in, recommended to be *before* UL elements to avoid conflicting with their blur logic -->
<button id="robot-clear">x</button>
Comment thread
inkblotty marked this conversation as resolved.
<ul id="items-popup" aria-labelledby="robots-label"></ul>
<!-- For built-in screen-reader announcements:
- Note the ID is the same as the <ul> with "feedback" appended
Expand All @@ -57,13 +59,13 @@
<form>
<label id="robots-2-label">Robots 2</label>
<auto-complete src="/demo" for="items-2-popup" aria-labelledby="robots-2-label" >
<input name="robot" type="text" aria-labelledby="robots-2-label" autofocus>
<input name="robot-2" type="text" aria-labelledby="robots-2-label" autofocus>
<ul id="items-2-popup" aria-labelledby="robots-2-label"></ul>
<div id="items-2-popup-feedback" class="sr-only"></div>
</auto-complete>
<button type="submit">Save</button>
</form>
<script type="module" src="./dist/bundle.js"></script>
<!-- <script type="module" src="https://unpkg.com/@github/auto-complete-element@latest/dist/bundle.js"></script> -->
<!-- <script type="module" src="./dist/bundle.js"></script> -->
<script type="module" src="https://unpkg.com/@github/auto-complete-element@latest/dist/bundle.js"></script>
Comment thread
inkblotty marked this conversation as resolved.
Outdated
</body>
</html>
27 changes: 27 additions & 0 deletions src/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default class Autocomplete {
feedback: HTMLElement | null
autoselectEnabled: boolean
clientOptions: NodeListOf<HTMLElement> | null
clearButton: HTMLElement | null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can be more strict and use HTMLButtonElement

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, someone can add the ID to another element like an anchor or a span. 🤔 I would like to keep it this way just in case, but I'm open to other ideas.


interactingWithList: boolean

Expand All @@ -30,6 +31,7 @@ export default class Autocomplete {
this.combobox = new Combobox(input, results)
this.feedback = document.getElementById(`${this.results.id}-feedback`)
this.autoselectEnabled = autoselectEnabled
this.clearButton = document.getElementById(`${this.input.id || this.input.getAttribute('name')}-clear`)
Comment thread
inkblotty marked this conversation as resolved.
Outdated

// check to see if there are any default options provided
this.clientOptions = results.querySelectorAll('[role=option]')
Expand All @@ -40,6 +42,20 @@ export default class Autocomplete {
this.feedback.setAttribute('aria-atomic', 'true')
}

// if clearButton is not a button, make it one
if (this.clearButton && this.clearButton.tagName.toLowerCase() !== 'button') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [tagName, ...otherAttributes] = this.clearButton.attributes
const newClearButton = document.createElement('button')
newClearButton.innerHTML = this.clearButton.innerHTML
newClearButton.id = this.clearButton.id
for (const attr of otherAttributes) {
newClearButton.setAttribute(attr.name, attr.value)
}
this.clearButton.parentNode?.replaceChild(newClearButton, this.clearButton)
this.clearButton = newClearButton
}

Comment thread
inkblotty marked this conversation as resolved.
Outdated
this.results.hidden = true
this.input.setAttribute('autocomplete', 'off')
this.input.setAttribute('spellcheck', 'false')
Expand All @@ -52,13 +68,15 @@ export default class Autocomplete {
this.onInputFocus = this.onInputFocus.bind(this)
this.onKeydown = this.onKeydown.bind(this)
this.onCommit = this.onCommit.bind(this)
this.handleClear = this.handleClear.bind(this)

this.input.addEventListener('keydown', this.onKeydown)
this.input.addEventListener('focus', this.onInputFocus)
this.input.addEventListener('blur', this.onInputBlur)
this.input.addEventListener('input', this.onInputChange)
this.results.addEventListener('mousedown', this.onResultsMouseDown)
this.results.addEventListener('combobox-commit', this.onCommit)
this.clearButton?.addEventListener('click', this.handleClear)
Comment thread
inkblotty marked this conversation as resolved.
}

destroy(): void {
Expand All @@ -70,6 +88,15 @@ export default class Autocomplete {
this.results.removeEventListener('combobox-commit', this.onCommit)
}

handleClear(event: Event): void {
event.preventDefault()

this.input.value = ''
this.container.value = ''
this.input.focus()
Comment thread
inkblotty marked this conversation as resolved.
this.updateFeedbackForScreenReaders(`Input cleared. Suggestions hidden.`)
Comment thread
inkblotty marked this conversation as resolved.
Outdated
}

onKeydown(event: KeyboardEvent): void {
// if autoselect is enabled, Enter key will select the first option
if (event.key === 'Enter' && this.container.open && this.autoselectEnabled) {
Expand Down
51 changes: 51 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,57 @@ describe('auto-complete element', function () {
})
})

describe('clear button provided', () => {
it('clears the input value on click and gives focus back to the input', async () => {
document.body.innerHTML = `
<div id="mocha-fixture">
<auto-complete src="/search" for="popup" data-autoselect="true">
<input id="example" type="text">
<button id="example-clear">x</button>
<ul id="popup"></ul>
<div id="popup-feedback"></div>
</auto-complete>
</div>
`

const container = document.querySelector('auto-complete')
const input = container.querySelector('input')
const clearButton = document.getElementById('example-clear')
const feedback = container.querySelector(`#popup-feedback`)

triggerInput(input, 'hub')
await once(container, 'loadend')

assert.equal(input.value, 'hub')
await waitForElementToChange(feedback)

clearButton.click()
assert.equal(input.value, '')
assert.equal(container.value, '')
await waitForElementToChange(feedback)
assert.equal('Input cleared. Suggestions hidden.', feedback.innerHTML)
})

it('replaces a non-button element with a button one', async () => {
document.body.innerHTML = `
<div id="mocha-fixture">
<auto-complete src="/search" for="popup" data-autoselect="true">
<input name="example" type="text">
<span id="example-clear">x</span>
<ul id="popup"></ul>
<div id="popup-feedback"></div>
</auto-complete>
</div>
`
const container = document.querySelector('auto-complete')
const input = container.querySelector('input')
triggerInput(input, 'hub')
await once(container, 'loadend')
const clearButton = document.getElementById('example-clear')
assert.equal(clearButton.tagName, 'BUTTON')
})
})

describe('autoselect enabled', () => {
beforeEach(function () {
document.body.innerHTML = `
Expand Down