behavior CustomSelect(trigger, label, popover, listbox, input, filter) -- Mark as selected the option that matches input.value init set selected to (<[role='option']/> in listbox).find(\ option -> option's @data-value is input.value) if selected trigger select:markSelected(option: selected) end end on click from trigger trigger select:toggle(focusTrigger: true) end on select:toggle(focusTrigger) if popover's @aria-hidden is 'true' trigger select:open else trigger select:close(focusTrigger: focusTrigger) end end on select:open set popover's @aria-hidden to 'false' set trigger's @aria-expanded to 'true' if filter set filter's @aria-expanded to 'true' end set selected to the first of <[role='option'][aria-selected='true']/> in listbox if no selected set selected to the first of <[role='option']:not([aria-hidden='true'])/> in listbox end if selected trigger select:activate(option: selected) selected.scrollIntoView({ block: 'nearest' }) end if filter settle then call filter.focus() end end on select:close(focusTrigger) if popover's @aria-hidden is 'true' exit end if filter set filter.value to '' end remove @aria-hidden from <[role='option']/> in listbox set popover's @aria-hidden to 'true' set trigger's @aria-expanded to 'false' if filter set filter's @aria-expanded to 'false' end trigger select:clearActive if focusTrigger call trigger.focus() end end on select:clear if filter set filter.value to '' end remove @aria-hidden from <[role='option']/> in listbox remove @aria-selected from <[role='option']/> in listbox set popover's @aria-hidden to 'true' set trigger's @aria-expanded to 'false' if filter set filter's @aria-expanded to 'false' end trigger select:clearActive put '' into label.innerHTML set input.value to '' end on select:activate(option) trigger select:clearActive if option set my._activeOption to option add .active to option end end on select:markSelected(option) if no option exit end put option.innerHTML into label.innerHTML set selected to the first of <[role='option'][aria-selected='true']/> in listbox if selected remove @aria-selected from selected end set option@aria-selected to 'true' end on click(target) from listbox set option to the closest <[role='option']/> to target if option and option's @aria-hidden is not 'true' trigger select:markSelected(option: option) set input.value to option.dataset.value trigger select:close(focusTrigger: true) end end on mousemove(target) from listbox set option to the closest <[role='option']/> to target if option and option's @aria-hidden is not 'true' and option is not my._activeOption trigger select:activate(option: option) end end on mouseleave from listbox set selected to the first of <[role='option'][aria-selected='true']:not([aria-hidden='true'])/> in listbox trigger select:activate(option: selected) end on click from elsewhere trigger select:close(focusTrigger: false) end on input from filter set searchTerm to filter.value.trim().toLowerCase() trigger select:clearActive for option in <[role='option']/> in listbox set optionText to option.textContent.trim().toLowerCase() if searchTerm is empty or optionText contains searchTerm remove @aria-hidden from option else set option's @aria-hidden to 'true' end end end on select:clearActive if my._activeOption remove .active from my._activeOption end set my._activeOption to null end -- Keyboard navigation on keydown(key) from trigger or keydown(key) from filter if key is 'ArrowDown' halt the event's default if popover's @aria-hidden is 'true' trigger select:open set option to the first of <[role='option']:not([aria-hidden='true'])/> in listbox else if no my._activeOption set option to the first of <[role='option']:not([aria-hidden='true'])/> in listbox else set option to the next <[role='option']:not([aria-hidden='true'])/> from my._activeOption within listbox end end if option trigger select:activate(option: option) option.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) end exit end if key is 'ArrowUp' halt the event's default if popover's @aria-hidden is 'true' trigger select:open set option to the last of <[role='option']:not([aria-hidden='true'])/> in listbox else if no my._activeOption set option to the last of <[role='option']:not([aria-hidden='true'])/> in listbox else set option to the previous <[role='option']:not([aria-hidden='true'])/> from my._activeOption within listbox end end if option trigger select:activate(option: option) option.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) end exit end if key is 'Enter' if popover's @aria-hidden is 'false' halt the event's default if my._activeOption trigger select:markSelected(option: my._activeOption) set input.value to my._activeOption.dataset.value trigger select:close(focusTrigger: true) end end exit end if key is 'Escape' if popover's @aria-hidden is 'false' halt the event's default trigger select:close(focusTrigger: true) end end end end