| title | status | created_at | updated_at | pr |
|---|---|---|---|---|
Declarative Binding for Non-Standard Event Names |
DRAFTED |
2024-02-25 |
2024-02-25 |
(leave this empty until the PR is created) |
LWC support for declarative event binding has always been limited to event names using lower-case alphabetic characters, underscores, and numeric characters. Event names containing upper-case characters (e.g., camelCase, CAPScase, PascalCase, etc) have never been supported in LWC due to the case-insensitive nature of HTML. In addition, event names containing hyphens were never supported due to the reluctance to avoid introducing ambiguity into the LWC convention of using hyphens in attribute names to map to upper-case letters in property names.
This has worked well in practice with developers sticking to LWC conventions and using
addEventListener() when required as a workaround, but with the recent introduction of
lwc:external which provides a paved path for integrating third party web components, there is
greater value in providing a declarative way to listen for non-standard event names.
This RFC proposes the introduction of a new template directive lwc:on, which allows us to support
declarative event binding for third party web components, while sidestepping all the aforementioned
limitations and ambiguities.
customElements.define('third-party', class extends HTMLElement{
connectedCallback() {
this.dispatchEvent(new CustomEvent('Foo-Bar-BAZ'));
}
});These are examples of some of the options that are currently on the table:
<!-- Event as part of directive -->
<third-party lwc:external lwc:on:Foo-Bar-BAZ={handleConnected}></third-party>
<!-- Event as part of attribute name (special treatment for lwc:external) -->
<third-party lwc:external onFoo-Bar-BAZ={handleConnected}></third-party>
<!-- Event and listener as part of attribute value -->
<third-party lwc:external lwc:on="Foo-Bar-BAZ:handleConnected"></third-party>This is an example of a less declarative approach but should also be useful for things like creating components from metadata:
// foo.js
class Foo extends LightningElement {
handlers = {
'kebab-case': (event) => console.log("'kebab-case' handled"),
camelCase: (event) => console.log("'camelCase' handled"),
CAPScase: (event) => console.log("'CAPScase' handled"),
PascalCase: (event) => console.log("'PascalCase' handled"),
};
}<!-- foo.html -->
<x-bar lwc:external lwc:on={handlers}></x-bar>The primary motivation for this feature is to enable component authors to listen for non-standard
event names in a declarative manner without having to resort to calls to addEventListener(). This
should be especially useful when interfacing with non-LWC components that do not follow LWC
conventions, which should become more common with the recent introduction of native web component
support through the use of the lwc:external directive.
The usage of this feature will be restricted to custom elements using lwc:external.
The design will be based on the agreed upon API shape, which is still under discussion.
A drawback of this feature is that component authors of LWC components may be encouraged to
implement non-standard event names that requires the use of lwc:on for declarative consumption.
This is not desirable because it makes static analysis more difficult. Conversely, consumers might
use this directive when they could just as easily have used a standard on* attribute in their
template. These scenarios could be avoided, however, by restricting the usage of lwc:on to custom
elements that use the lwc:external directive.
An alternative design that was considered was to build this feature into the existing lwc:spread
directive which currently only supports the setting of property values. In fact, lwc:spread can be
used to listen for standard events like click or focus by assigning event listeners to the
onclick and onfocus properties. However, adding support for non-standard event names would add
complexity to lwc:spread. Introducing a new directive also makes it easier to restrict the usage
of lwc:on to lwc:external components.
Encoding both the event name and event handler name as a string using some delimiter would allow us to work around the case-insensitive nature of HTML attributes. This was simply unpopular during an initial informal survey.
This feature should only be used for interoperability with third party web components. The main
benefit would be an improvement in static analysis when used over listening to events using
addEventListener() and we could document it as such.
We can teach this by adding to the documentation we already have for third party web components.
Would this be sufficiently declarative if the event name does not appear in the template but the
user does not have to invoke addEventListener()?