Created
October 24, 2024 13:44
-
-
Save jeremysmithco/80b2919bf09b5a4c6544bd74b4b7c3c7 to your computer and use it in GitHub Desktop.
Stimulus autosuggest with @github/combobox-nav
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= tag.div data: { controller: "autosuggest", action: "autosuggest:click:outside->autosuggest#hide" }, class: "relative" do %> | |
<%= f.text_field :subject, autocomplete: "off", data: { autosuggest_target: "input", action: "input->autosuggest#search focus->autosuggest#search keydown.up->autosuggest#arrowSearch keydown.down->autosuggest#arrowSearch keydown.esc->autosuggest#hide" } %> | |
<%= tag.ul role: "listbox", hidden: true, data: { autosuggest_target: "list", action: "combobox-commit->autosuggest#commit" }, class: "z-40 absolute w-full max-h-60 overflow-auto mt-1 rounded border border-gray-300 shadow-lg p-1 bg-white" do %> | |
<% @subjects.each do |subject| %> | |
<%= tag.li subject.name, role: "option", id: dom_id(subject, :listbox), data: { autosuggest_target: "item" }, class: "w-full text-left cursor-pointer p-2 hover:bg-gray-100 aria-selected:bg-gray-100 aria-selected:font-semibold" %> | |
<% end %> | |
<% end %> | |
<% end %> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from '@hotwired/stimulus'; | |
import Combobox from '@github/combobox-nav'; | |
import { useClickOutside } from 'stimulus-use'; | |
export default class extends Controller { | |
static targets = ['input', 'list', 'item']; | |
connect() { | |
useClickOutside(this); | |
this.combobox = new Combobox(this.inputTarget, this.listTarget); | |
this.arrowMapping = new Map([["ArrowDown", 1], ["ArrowUp", -1]]); | |
} | |
disconnect() { | |
this.combobox.destroy(); | |
} | |
search() { | |
const query = this.inputTarget.value; | |
this.itemTargets.forEach(item => item.hidden = !this.isMatch(item, query)); | |
const filteredCount = this.itemTargets.filter(item => !item.hidden).length; | |
if (filteredCount > 0) { | |
this.show(); | |
} else { | |
this.hide(); | |
} | |
} | |
isMatch(item, query) { | |
return item.textContent.toLowerCase().includes(query.toLowerCase()); | |
} | |
// Only triggers search on closed list, doesn't handle traversing open list | |
arrowSearch(event) { | |
if (!this.arrowMapping.has(event.key)) return; | |
if (!this.listTarget.hidden) return; | |
this.search(); | |
if (this.listTarget.hidden) return; | |
this.combobox.navigate(this.arrowMapping.get(event.key)); | |
} | |
show() { | |
this.listTarget.hidden = false; | |
this.combobox.start(); | |
} | |
hide() { | |
this.listTarget.hidden = true; | |
this.combobox.clearSelection(); | |
this.combobox.stop(); | |
} | |
commit(event) { | |
this.inputTarget.value = event.target.textContent; | |
this.hide(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Using:
This implementation was based largely off the combobox-nav example.