Description
What problem are you trying to solve?
Currently, there is no way for a Custom Form Control composed of primitive form controls to prevent "the outside world" from receiving undesirable/noisy events from said primitives. Consider the following example:
Use Case: Comboboxes (where only valid values are accepted)
Many combobox
components in the wild effectively act as stylable <select>
elements whose restricted values can be conveniently searched via a textbox. In such cases, the combobox
's value is only permitted to be empty or a valid option
value -- just as in the <select>
's case.
From an event dispatching standpoint, the <select>
element only dispatches input
/change
events when a user interaction updates the value. Similarly, many of these combobox
es desire to only dispatch an input
/change
event when a new valid value is selected. And this is where things get tricky...
Typically, searchable combobox
es are implemented with a native <input>
(or some other contenteditable
element). But these emit input
events on every keystroke -- creating undesirable noise for delegated event listeners which are trying to watch for updates to the Combobox Value only. Particularly, the delegated event listeners will see input
events whenever the textbox is typed into, and whenever a valid option
is selected (in which case the Combobox
will dispatch its own custom input
event). But ideally, these events should only be detected/observed in the latter case to avoid noise/confusion.
There is currently no way to prevent this undesirable behavior. Even if someone tried to use a Shadow DOM, both open
and closed
Shadow DOMs will propagate input
events to the Light DOM because input
is composed
. But closed
Shadow DOMs will yield a slightly worse UX because they make it impossible to know whether the input
came from the Combobox
itself or the inner <input>
. (I guess technically, someone could check event.isTrusted
, but that requires knowledge of implementation details.)
(Sidenote for Clarity: the Combobox value should not be updated until the user selects a valid option
. If the user bails out while searching, the textfield should revert its text content back to the last valid value of the Combobox, and the Combobox should not emit an input
/change
event. This prevents bad data from being sent to the server.)
Depending on how a <checkbox-group>
component with minimum-selected
items would be implemented, it might run into similar issues.
What solutions exist today?
There technically aren't any real/genuine solutions to this problem today. Probably the best thing that developers can do is tell users how to filter out the events that they don't want in their delegated event listeners. However, this will almost certainly entail exposing implementation details, which is not optimal. For example, a developer could say, "Check for event.isTrusted === false
or event instanceof CustomEvent
to distinguish between the correct input
/change
events."
Perhaps another option is for the developer to create very clever listeners for keydown
(composed textboxes), click
(composed checkboxes), and the like which could call event.preventDefault()
as needed. But it's possible that this approach could get out of hand quickly. And it might not cover all use cases.
How would you solve it?
I've noticed that a number of new ShadowDOM-related attributes/options have appeared in the spec to resolve problems like these. Perhaps a new one could be created for this scenario? Something like:
this.attachShadow({ mode: "closed", concealsEvents: true });
or
<template shadowrootmode="closed" shadowrootconcealsevents>
Another alternative is perhaps to enable ElementInternals
to specify that it conceals its own events through a togglable property.
const internals = this.attachInternals();
internals.concealsEvents = true;
Could also be a static property, perhaps, like formAssociated
and observedAttributes
.
class ComboboxField extends HTMLElement {
static get concealedEvents() {
return ["input", "change"];
}
// ...
}
The main idea is that in some way, the Web Component would be able to dictate that it conceals its events (or a specific set of events) within the class declaration.
Anything else?
After writing up this issue, I'm starting to conclude that the issue may surround Custom Form Controls and the events which they want to emit more than it does the Shadow DOM. In simple terms, the issue is that a Custom Form Control may wish to take full responsibility over a specific set of events, because the events are irrelevant/noisy if they bubble up from any children (e.g., input
/change
in the combobox scenario). To that end, I'd probably prefer the static concealedEvents
approach, with an ability to specify some kind of "all"
option as needed.
Whether it's related to this problem or not, I feel like I can still make this comparison: Developers can't listen to click
events on <option>
elements in a <select>
element -- nor do they need to. All that they really need to listen for is input
/change
from the <select>
element itself. A sophisticated Custom Form Control leveraging "subcomponents"/"primitive controls" may require similar behavior. On that note... Perhaps the greater issue is needing the ability to conceal all events from specific sub-elements (as with the <select>
/<option>
combination)?