<custom-listbox>
<custom-option>One</custom-option>
<custom-option>Two</custom-option>
<custom-option>Three</custom-option>
</custom-listbox>
class CustomOption extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals();
this._internals.role = "option";
this._internals.focusability = "default"; // or "unfocusable"? or `false`?
this._selected = false;
this._internals.ariaSelected = "false";
// Add event listeners here
}
connectedCallback() {
this._listbox = this.parentElement;
}
onClick(event) {
this.setSelected(true);
this.focus();
}
setSelected(selected) {
if (this._selected == selected)
return;
this._selected = selected;
this._internals.ariaSelected = String(selected);
this._internals.focusability = "auto"; // or "focusable"? Or `true`?
this._listbox.setSelectedOption(this);
}
}
class CustomListbox extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals();
this._internals.focusability = "auto";
this._selectedOption = null;
// Add event listeners here
}
setSelectedOption(optionElement) {
if (this._selectedOption === optionElement)
return;
if (this._selectedOption !== null) {
// Make previously selected option unfocusable
this._selectedOption.setSelected(false);
}
this._selectedOption = optionElement;
if (this._selectedOption === null) {
this._internals.focusability = "auto"; // or "focusable"? or `true`?
return;
}
this._internals.focusability = "default"; // or "unfocusable"? or `false`?
// Ensure calling this method directly sets up the right state
this._selectedOption.setSelected(true);
}
// keyboard event handling goes here - keyboard events bubble up from options
// up arrow => setSelectedOption(previousOption) or no-op if at start
// down arrow => setSelectedOption(nextOption) or no-op if at end
}