Skip to content

Instantly share code, notes, and snippets.

@jeremysmithco
Created October 24, 2024 13:44
Show Gist options
  • Save jeremysmithco/80b2919bf09b5a4c6544bd74b4b7c3c7 to your computer and use it in GitHub Desktop.
Save jeremysmithco/80b2919bf09b5a4c6544bd74b4b7c3c7 to your computer and use it in GitHub Desktop.
Stimulus autosuggest with @github/combobox-nav
<%= 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 %>
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();
}
}
@jeremysmithco
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment