- Add Test Coverage to Playwright Tests if Possible
- Some of the notes in this "Filterable/Searchable
combobox" section below really need to beNotesorDevelopment Notes. Please update them as soon as possible to reduce the clutter here without losing any of the important information on design decisions. Feel free to open aDesign Decisionsfile if you feel it's necessary as well. - When do we want to consider a
searchableCombobox -- if ever? Should it be its own component? Or should theComboboxFieldsimply be configurable?- Inform developers that
ComboboxOption.filteredOutIS NOT to be used whenever one pleases. Instead, it allows developers to override how theComboboxFieldmarksoptions as filtered out. This property should only be toggled/read while theComboboxFieldis actively resetting its filter or performing its filtering logic (whether with its own implementation or with a developer's overriding implementation). - Document the difference between
requiredandunclearable/clearableclearly. - Document why we always attach
handleSearchas a listener, and right at the beginning. (Wanting to make sure we can callevent.stopImmediatePropagationin time.) EDIT: I think this is irrelevant now. - Document that developers who don't want to show a "No Options" message to their end users can simply hide the entire
listboxwith CSS if alloptions are[data-filtered-out](i.e.,[role="listbox"]:not(:has([role="option"]:not([data-filtered-out])))). - If deemed necessary, document why we went with an attribute like
filter/searchinstead of using a standardized attribute (likearia-autocomplete) directly. (We don't know if we'll support the other variant ofaria-autocompleteyet. It's too weird/complicated/confusing for consumers and maybe even maintainers to have a mount-only attribute that triggers another attribute which triggers another attribute (filter-->aria-autocomplete-->contenteditable). If we want to support other versions ofaria-autocompletein the future, we can just update the "universally owning attribute" (e.g.,filter-->filter-type).filteris arguably part of the API, where as the other 2 attributes are implementation details. (We could technically just useElementInternals.MyAriaAttribute.) We don't want to mess up the JS-free<select>instance by encouraging users to use standardized props likeautocompleteeither.) - Document why we will use
<input>/<div>instead of modifying text directly inside<combobox-field>. (Basically, Shadow DOM support forRanges andSelections is sketchy across browsers, sadly. So it's safer to just use an<input>that's in a Shadow DOM. If our suggestion gets accepted, then we'll have an advantage with this change in the future anyway... Actually, we went back to using acontenteditable<combobox-field>because we concluded that both the filterable and non-filterable versions of our component should have a consistent UX to avoid confusion. So we don't need an easy way to move the cursor around. HOWEVER, we later realized thatcontenteditable="plaintext-only"elements can still include newline characters! They're basically<textarea>s! That's HUGE no-no for us. And it's far more complicated to try to combat that than it is to use a regular<input>which doesn't support newline characters. So now we're back to using<input>/<div>. We don't like the idea of switching element types as thefilterattribute is updated, but we don't have a choice. We played with having aninput[readonly]so that we didn't have to switch elements, but it seems to risk confusing Screen Readers, and it sounds like some browsers apply default styles. Unfortunately... if we go this route... we'll have to figure out transfering attributes appropriately onmount... there might be a way that isn't complicated?)- Note: Before you go crazy and think about trying to use
beforeinputso that we can still stick to modifying the text content of the<combobox-field>during "Filter Mode", note that taking this approach will aggressively move the input cursor to the beginning of the text field (though only during value updates). This is a bad UX on top of a weird maintenance decision. Just use the correctHTMLElement(<input>) for the job of searching and stop debating this topic. EDIT: We debated the topic again. lol. We actually found thatbeforeinputis needed to remove a lot of confusion regardless of whether we're using<combobox-field contenteditable="true">or<input>. There are reasons for that which we'll explain later. Unfortunately, it seems<input>might have better a11y support/clarity than acontenteditableelement, so we'll probably switch to<div>/<input>in the future. But using an editable<combobox-field>is a far better Maintainer Experience and Developer Experience. We'll see if there's any way we can stick to justcontenteditable, but probably won't work out. EDIT x2: We forgot that, as pointed out in our 2nd note, we want our tool to be compatible with theFormValidityObserver, so we might need to keepcontenteditableafter all... Maybe we can update theFormValidityObserverto work even if we use an inner<input>? Or maybe there's a way to get Screen Readers to viewcontenteditableelements more accurately... (NOTE: It seems that thefilterablecomboboxexperience usingcontenteditableis significantly more normal when usingVoiceOverwithSafari. Things get a little weird withChrome, and they get very weird withFirefox. In fact, some things are weird in Firefox even just for regular form controls/labels when it comes to VoiceOver. In any case, it might not be the end of the world if we usecontenteditableafter all... But there will likely be mixed support for Screen Readers. We should probably test tools besidesVoiceOver, though hopefully users can still get the jist of what's happening when interacting with ourcontenteditableelement.)
- Note: Before you go crazy and think about trying to use
- Document why put
ComboboxField.formControlin Shadow DOM instead of Light DOM. (Putting in Shadow DOM saves users from having their wrapping<form>elements from picking up "noise" by seeing an irrelevant<input>element when theComboboxFieldisfilterable. Unfortunately, this puts us at a disadvantage because we can't style based on[role="combobox]anymore. So we'll have to useElementInternals.statesinstead... NOT ideal AT ALL... But we're kinda forced into this functionality. We can still do our best to indicate ARIA attributes in ourroles andstates. Hmmmm... How does that affect stuff like styling for:focus,:hover, and the like, though? ... Maybe we should transfer all CSS styles to the inner element? EDIT: Actually, if we want to avoid the "Form Noise" problem, we can just setform=""on the related internal form controls as needed and leave our stuff in the Light DOM. This might be more convenient since it could let us reuse our existing styles... in fact, this approach will be NECESSARY if for ANY reason we need to apply the primary styles on a parent element that hosts the Shadow DOM element. For example, if we put theborderstyles on<select-enhance>to account for potential Icons that could appear within the component -- like a Caret Icon -- then we would have to update the border styles based on the:hover/:focusstatus of theComboboxField.formControl... but you can't access Shadow DOMparts with something likecombobox-field:has(combobox-field::part(form-control):state(my-state))! So that locks us into a specific approach, basically... unless perhaps:focus-withinworks (unlikely)... actually,:focus-withinworks so maybe we're good? ... Eh... Actually, we still wouldn't be able to style based on things like[aria-invalid="true"]... We could try to keep track of when the field is invalid from within our own code and perhaps applyaria-invalidto our internal form control accordingly and then expose something like:states(aria-invalid)...but the problem with that approach is that it doesn't handle use cases where the user wants to supplyaria-invalidto the form control directly and then apply styles accordingly... Yes, they can technically accessComboboxField.formControlto apply thearia-invalidrole -- yes, it belongs on the item withrole="combobox", not necessarily on the<combobox-field>element -- but that gets hacky and inconvenient pretty quickly, don't you think? Or maybe people listening for events on theComboboxFieldwould already have to do that foraria-invalidanyway? Man... this is hard... Actually, never mind. Scrap ALL OF IT. We want our component to be compatible with theFormValidityObserver, and that basically requires the<combobox-field>to be the element that contains the ARIA attributes and everything else. So we're going to have to go back tocontenteditableand do some fancy logic to preventnewlinesfrom appearing. Additionally, maybe we can usebeforeinputto preventinputfrom being captured or bubbled? 🤔) - Document that users need to leverage the
white-space(orwhite-space-collapse) CSS Property if they want users to see when they've entered multiple whitespaces into the filter. (Note that this is a very important CSS style to apply. If thewhite-spaceCSS property collapses all whitespace, the spaces will still exist in the user's physical search value, thus disrupting the displayed filteredoptions. This would certainly result in an unintuitive UX.) - Document why we may support custom filering logic for
filterablecomboboxes but not not regular ones. (Fortypeaheadfunctionality, any logic besidesstartsWithwould result not only in an experience that's inconsistent with<select>, but also an experience that's incredibly confusing for Users — perceivably. It would be hard to know where the activeoptionwould jump next since you can jump to anyoptionthat vaguely matches the current search string. This is different from when you're infiltermode: The user can always see and easily modify any part of their filter at any time. And within a few keystrokes, it becomes obvious what the rules are for filtering.) - Document internally why we default to
startsWithinstead ofincludesin filter mode. - Document the order in which the
ComboboxWeb Component parts must be registered/defined.
- Inform developers that
- Do we want to add/support a Caret Icon for the Combobox component?
- Add CSS for
select-enhancer > select(for when JS is disabled/unavailable). - Make a note about using different kinds of Combobox "Adapters"/"Wrappers".
- Make a note that the
valueof<combobox-option>(and therefore a<select>'s<option>) MUST be unique (for accessibility reasons related toaria-activedescendantand HTML's disallowing of duplicateids). This shouldn't realistically cause problems for anyone. I don't know if duplicate values have a valid use case anyway. Duplicate values will produce unpredictable behavior. - Add documentation in general about how this component works, what expectations are, and what feature parity is with native
<select>- DEFINITELY don't forget to document how delegated event listeners for
inputwill get a little wild. (Regular delegated event listeners should be fine. But for captured event listeners, people will have to check things like!event.isTrustedor something similar which would indicate that the event actually originated from<combobox-field>instead of<input>.)
- DEFINITELY don't forget to document how delegated event listeners for
- Make YouTube video(s) documenting how to augment the TypeScript element/JSX types in the popular JS Frameworks. This will be helpful for us and for any others who try to accomplish what we did. And it'll save everyone a lot of time.
- Should our tests assume that our Combobox Component is in a
<form>by default since the custom element itself is a Form Control? - We should definitely add a test proving that our component works in Shadow DOMs at some future point.
- Open a GitHub Issue that addresses the various issues with
ElementInternals.validityandElementInternals.setValidity(). See our Notes for more details. - Consider adding a GitHub CI Action to lint our code.
- Unless we're mistaken, Playwright currently has a bug. For some reason, the tests related to
tabbing are failing for Playwright'sWebKitbrowser. However, tabbing works fine manually in Safari. Since it works manually, we can investigate this more later or open a Playwright bug. (It's probably a Playwright bug since we didn't actually change any of our code surrounding focusing/tabbing -- unless this is an Operating System issue.) NOTE: For right now, the tests still seem to be passing on CI. So this really seems to be a Playwright issue and/or an OS issue (or some other similar, inconspicuous issue).
- Don't the native
<option>elements allow safe attribute/property changes even if they aren't connected to the DOM? Should we allow something similar? (Mainly thinking of supporting changing theComboboxOption.selectedproperty in isolation.) For example, maybe we could support this by returning early based on!this.combobox? Need to investigate... and evaluate if this is even worthwhile... - Far-off thought: Should we give developers an easy way to set this component up themselves? We'd also want to make sure that frameworks can create our Web Component just fine, without running into any problems. (There would only be a concern if people wanted to provide
<combobox-option>and<combobox-field>directly instead of using<select-enhancer>in conjunction with<select>-- at least, that's our assumption). Note: This ties into our idea about "Adapters", and it might be sufficient to delegate this problem to UserLand with the "Adapters" concept.
Double check our existing code to make sure it is [sufficiently] coherent and not absolute garbage before you go adding new features (like the formResetCallback support).