From 0a7dd5b29ab21a294c9fc1c0be3611227ee0921b Mon Sep 17 00:00:00 2001 From: Gaurav Kochar Date: Mon, 13 Jan 2025 20:22:59 +0530 Subject: [PATCH 1/8] RFC: `lwc:on` First draft Changes to be committed: new file: text/0000-dynamic-events.md --- text/0000-dynamic-events.md | 136 ++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 text/0000-dynamic-events.md diff --git a/text/0000-dynamic-events.md b/text/0000-dynamic-events.md new file mode 100644 index 0000000..6f20bda --- /dev/null +++ b/text/0000-dynamic-events.md @@ -0,0 +1,136 @@ +--- +title: `lwc:on` directive +status: DRAFTED +created_at: 2025-01-13 +updated_at: 2025-01-13 +pr: (leave this empty until the PR is created) +--- + +# `lwc:on` Directive + +## Summary + +This proposal adds a declarative mechanism to add a collection of event listeners to elements in an LWC template using a new directive `lwc:on`. + +## Basic example + +```js +// x/myComponent.js +export default class MyComponent extends LightningElement { + + childEventHandlers = { + foo: function (event) {console.log('foo');} , + bar: function (event) {console.log('bar');} + }; + +} +``` + +```html + + +``` + +For each property in `childEventHandlers`, the new `lwc:on` directive adds an event listener to `x-child`. The event listener listens for the event specified by the property's key and uses the property's value, bound to the instance of `MyComponent`, as the event handler. + +## Motivation + +LWC's support for declarative event listeners is limited to scenarios where event name are known while authoring the owner component template. This is sufficient for most of the cases, however when using the `lwc:component` directive, it is common to require event listeners based on the current value of the constructor passed to `lwc:is`. In such cases, it is not possible to know the event name while authoring the owner component template. + +As an alternative, it is possible to add event listeners imperatively. However it requires the owner component to have a reference of the element on which it needs to add event listener. The owner component can have a reference of a element only after the element has been connected and hence typically only after the connectedCallback of corresponding component has terminated , making it impossible for the owner component to handle events dispatched by the connectedCallback of the corresponding component. + + +## Detailed design + +A new directive `lwc:on` will be introduced that can be used to declaratively add a collection of event listeners whose name or event handlers may not be known while authoring the component. + +### Structure + +The `lwc:on` directive would accept an object. For each property of the object, it would add a event listener which would listen for the event specified by the property's key and handle it using the property's value bound to the owner component. + +### Caching + +#### For static components, i.e. components declared in template directly using its selector +Since it is uncommon for event listeners to change after the start of Owner component's intial render, We can cache them to improve performance. Note that this is same as how `onevent` on template works currently. For consumers, the implication of this would be that any changes made to the object passed to `lwc:on` after the first `connectedCallback` of owner component would cause no effect. + +#### For dynamic components, i.e. components created using `lwc:component` directive +Since it is uncommon for event listeners to change if the constructor passed to `lwc:is` doesn't change, We can skip patching of event listeners if the element doesn't change. For consumers, the implication of this would be that after the first `connectedCallback` of owner component, any changes made to the object passed to `lwc:on` would cause no effect until the constructor passed to `lwc:is` itself is changed. + + +### Overriding + +```js +// x/myComponent.js +export default class MyComponent extends LightningElement { + + childEventHandlers = { + foo: function (event) {console.log('lwc:on');} , + }; + + fooHandler(){ + console.log('onfoo'); + } + +} +``` + +```html + + +``` + +`lwc:on` will always be applied last. That means it will take precedence over whatever event listeners are declared in the template directly. In the above example, `x-child`, only one listener for event `foo` would be added and `childEventHandlers.foo` would be used for event handler . +Additionally, there can be only one `lwc:on` directive on a element + +### Event names + +The keys of object passed to `lwc:on` should conform to the requirement set by DOM Event Specification. There would be no other constraint on the object's keys. + +## Drawbacks + +Why should we *not* do this? Please consider: + +- implementation cost, both in term of code size and complexity +- whether the proposed feature can be implemented in user space +- the impact on teaching people Lightning Web Components +- integration of this feature with other existing and planned features +- cost of migrating existing Lightning Web Components applications (is it a breaking change?) + +There are tradeoffs to choosing any path. Attempt to identify them here. + + +To add : Static Analyzability. + +## Alternatives + +What other designs have been considered? What is the impact of not doing this? + +To add : lwc:spread and reflecting on* properties as event listeners + +## Adoption strategy + +If we implement this proposal, how will existing Lightning Web Components developers adopt it? Is +this a breaking change? Can we write a codemod? Should we coordinate with +other projects or libraries? + +# How we teach this + +What names and terminology work best for these concepts and why? How is this +idea best presented? As a continuation of existing Lightning Web Components patterns? + +Would the acceptance of this proposal mean the Lightning Web Components documentation must be +re-organized or altered? Does it change how Lightning Web Components is taught to new developers +at any level? + +How should this feature be taught to existing Lightning Web Components developers? + +# Unresolved questions + +Optional, but suggested for first drafts. What parts of the design are still +TBD? + +Need Input on all parts of design. \ No newline at end of file From a629fa0cabe2900e0ee39f118f7bdd2eeb112114 Mon Sep 17 00:00:00 2001 From: Gaurav Kochar Date: Mon, 13 Jan 2025 22:40:58 +0530 Subject: [PATCH 2/8] remove declarative word as it is questionable Changes to be committed: modified: text/0000-dynamic-events.md --- text/0000-dynamic-events.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-dynamic-events.md b/text/0000-dynamic-events.md index 6f20bda..337914c 100644 --- a/text/0000-dynamic-events.md +++ b/text/0000-dynamic-events.md @@ -10,7 +10,7 @@ pr: (leave this empty until the PR is created) ## Summary -This proposal adds a declarative mechanism to add a collection of event listeners to elements in an LWC template using a new directive `lwc:on`. +This proposal adds a mechanism to add a collection of event listeners to elements in an LWC template using a new directive `lwc:on`. ## Basic example @@ -44,7 +44,7 @@ As an alternative, it is possible to add event listeners imperatively. However i ## Detailed design -A new directive `lwc:on` will be introduced that can be used to declaratively add a collection of event listeners whose name or event handlers may not be known while authoring the component. +A new directive `lwc:on` will be introduced that can be used to add a collection of event listeners whose name may not be known while authoring the component. ### Structure From 45f3aa03a3c70ee0c0aea2769cac199482227fc4 Mon Sep 17 00:00:00 2001 From: Gaurav Kochar Date: Tue, 14 Jan 2025 01:23:28 +0530 Subject: [PATCH 3/8] Adds alternative lwc:spread --- text/0000-dynamic-events.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/text/0000-dynamic-events.md b/text/0000-dynamic-events.md index 337914c..31a1b64 100644 --- a/text/0000-dynamic-events.md +++ b/text/0000-dynamic-events.md @@ -109,7 +109,17 @@ To add : Static Analyzability. What other designs have been considered? What is the impact of not doing this? -To add : lwc:spread and reflecting on* properties as event listeners +To add : reflecting on* properties as event listeners + +### lwc:spread + +An alternative design would be for `lwc:spread` to behave as a directive that appears to just spread its properties on the template. + +It is possible to have a component that executes logic in its `connectedCallback` based on the the value of a attribute in its corresponding element. An owner component that consumes this component using `lwc:component` would run into issues similar to [# Motivation](#motivation). Though this is anti-pattern, the author of owner component may not have any control over this component, and hence the author of owner component would notice a feature gap. This design would also solve this issue. + +Currently `lwc:spread` can be used to listen for standard events like `click` or `focus` by assigning event handlers to the `onclick` and `onfocus` properties. This works because the element on which these properties are applied inherits from HTMLElement. This implies that the event handlers would be bound to these elements itself and not the owner component as `onevent` directly on template does. Without `lwc:spread`, there is not a completely equivalent way of doing this. So modifying this behaviour would break components with no straightforward workaround. However it must also be noted that this difference is also a frequent source of confusion. + +Implementing of this design would cause significant degradation of performance, since we would need to parse the object passed to `lwc:spread` in each render. ## Adoption strategy From fa131b6e9a6a5aae0653a53ed4935d668a257d41 Mon Sep 17 00:00:00 2001 From: gaurav-rk9 Date: Tue, 14 Jan 2025 12:42:34 +0530 Subject: [PATCH 4/8] Apply suggestions from code review PR link and grammer Co-authored-by: Nolan Lawson --- text/0000-dynamic-events.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/text/0000-dynamic-events.md b/text/0000-dynamic-events.md index 31a1b64..09057dc 100644 --- a/text/0000-dynamic-events.md +++ b/text/0000-dynamic-events.md @@ -3,7 +3,8 @@ title: `lwc:on` directive status: DRAFTED created_at: 2025-01-13 updated_at: 2025-01-13 -pr: (leave this empty until the PR is created) +pr: https://github.com/salesforce/lwc-rfcs/pull/92 + --- # `lwc:on` Directive @@ -53,7 +54,8 @@ The `lwc:on` directive would accept an object. For each property of the object, ### Caching #### For static components, i.e. components declared in template directly using its selector -Since it is uncommon for event listeners to change after the start of Owner component's intial render, We can cache them to improve performance. Note that this is same as how `onevent` on template works currently. For consumers, the implication of this would be that any changes made to the object passed to `lwc:on` after the first `connectedCallback` of owner component would cause no effect. +Since it is uncommon for event listeners to change after the start of the owner component's intial render, We can cache them to improve performance. Note that this is same as how `onevent` in template HTML works currently. For consumers, the implication of this would be that any changes made to the object passed to `lwc:on` after the first `connectedCallback` of owner component would cause no effect. + #### For dynamic components, i.e. components created using `lwc:component` directive Since it is uncommon for event listeners to change if the constructor passed to `lwc:is` doesn't change, We can skip patching of event listeners if the element doesn't change. For consumers, the implication of this would be that after the first `connectedCallback` of owner component, any changes made to the object passed to `lwc:on` would cause no effect until the constructor passed to `lwc:is` itself is changed. @@ -84,7 +86,8 @@ export default class MyComponent extends LightningElement { ``` `lwc:on` will always be applied last. That means it will take precedence over whatever event listeners are declared in the template directly. In the above example, `x-child`, only one listener for event `foo` would be added and `childEventHandlers.foo` would be used for event handler . -Additionally, there can be only one `lwc:on` directive on a element +Additionally, there can be only one `lwc:on` directive on an element + ### Event names From 86c132570980cf6e849b856220e0025b9f56b9cd Mon Sep 17 00:00:00 2001 From: Gaurav Kochar Date: Fri, 17 Jan 2025 00:06:45 +0530 Subject: [PATCH 5/8] Improved alternative lwc:spread extension Changes to be committed: modified: text/0000-dynamic-events.md --- text/0000-dynamic-events.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/text/0000-dynamic-events.md b/text/0000-dynamic-events.md index 09057dc..d87f8a7 100644 --- a/text/0000-dynamic-events.md +++ b/text/0000-dynamic-events.md @@ -2,7 +2,7 @@ title: `lwc:on` directive status: DRAFTED created_at: 2025-01-13 -updated_at: 2025-01-13 +updated_at: 2025-01-17 pr: https://github.com/salesforce/lwc-rfcs/pull/92 --- @@ -114,15 +114,17 @@ What other designs have been considered? What is the impact of not doing this? To add : reflecting on* properties as event listeners -### lwc:spread +### lwc:spread - Just spreading it in template -An alternative design would be for `lwc:spread` to behave as a directive that appears to just spread its properties on the template. +An alternative design would be for `lwc:spread` to behave as a directive that appears to just spread the object's properties on the template. -It is possible to have a component that executes logic in its `connectedCallback` based on the the value of a attribute in its corresponding element. An owner component that consumes this component using `lwc:component` would run into issues similar to [# Motivation](#motivation). Though this is anti-pattern, the author of owner component may not have any control over this component, and hence the author of owner component would notice a feature gap. This design would also solve this issue. +Currently, a component can execute logic in its `connectedCallback` based on the value of an attribute in its corresponding element. If an owner component uses lwc:component to consume this component, it may encounter issues similar to those described in the [# Motivation](#motivation). Although this is considered an anti-pattern, the author of the owner component might not have control over the consumed component, leading to a noticeable feature gap. The proposed design would address this issue + +At present, `lwc:spread` can be used to listen for standard events like `click` or `focus` by assigning event handlers to the `onclick` or `onfocus` properties. This works because these properties are applied to the rendered element, meaning the event handlers are bound to the elements themselves, not to the owner component as `onevent` on the template does. This behavior might be unexpected for consumers. The distinction between the rendered element and the `LightningElement` component is an implementation detail. For consumers, it is simply an Lwc, and this behavior cannot be explained as the application of properties to Lwc. The proposed design would be a breaking change. However, any component that relies on the current behavior can be rewritten to accommodate the new design. -Currently `lwc:spread` can be used to listen for standard events like `click` or `focus` by assigning event handlers to the `onclick` and `onfocus` properties. This works because the element on which these properties are applied inherits from HTMLElement. This implies that the event handlers would be bound to these elements itself and not the owner component as `onevent` directly on template does. Without `lwc:spread`, there is not a completely equivalent way of doing this. So modifying this behaviour would break components with no straightforward workaround. However it must also be noted that this difference is also a frequent source of confusion. +Although properties and event listeners use the same HTML attribute syntax, consumers understand their differences. They recognize whether a variable in an HTML template is a property, attribute, or event listener and reason about them separately. Combining them into a single directive does not enhance the developer experience. In fact, a combined directive might lead consumers to treat them as a unified concept, causing confusion. Additionally, the usage patterns of properties and event listeners are different, and a combined directive would complicate consumer code. -Implementing of this design would cause significant degradation of performance, since we would need to parse the object passed to `lwc:spread` in each render. +Currently, the segregation of properties, attributes, and event listeners is handled solely by the template compiler. This design would require implementing a runtime segregator, causing additional runtime overhead. Any changes in segregation rules would necessitate updates in both the compiler and the runtime engine, leading to increased maintenance effort. ## Adoption strategy From 0396d6a1e330969f58a74c45e83572415ed9d8f2 Mon Sep 17 00:00:00 2001 From: Gaurav Kochar Date: Sun, 26 Jan 2025 09:59:35 +0530 Subject: [PATCH 6/8] adds alternative event handler properties, minor edits in other sections --- text/0000-dynamic-events.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/text/0000-dynamic-events.md b/text/0000-dynamic-events.md index d87f8a7..918c78b 100644 --- a/text/0000-dynamic-events.md +++ b/text/0000-dynamic-events.md @@ -91,7 +91,10 @@ Additionally, there can be only one `lwc:on` directive on an element ### Event names -The keys of object passed to `lwc:on` should conform to the requirement set by DOM Event Specification. There would be no other constraint on the object's keys. +The keys of object passed to `lwc:on` should conform to the requirement set by DOM Event Specification. There would be no other constraint on the object's keys. + + +to add: own enumerable properties whose keys are not symbols ## Drawbacks @@ -105,20 +108,15 @@ Why should we *not* do this? Please consider: There are tradeoffs to choosing any path. Attempt to identify them here. - -To add : Static Analyzability. +One downside of this proposal is that it would prevent us from being able to statically analyze the names of event listeners added by this directive. ## Alternatives -What other designs have been considered? What is the impact of not doing this? - -To add : reflecting on* properties as event listeners - ### lwc:spread - Just spreading it in template An alternative design would be for `lwc:spread` to behave as a directive that appears to just spread the object's properties on the template. -Currently, a component can execute logic in its `connectedCallback` based on the value of an attribute in its corresponding element. If an owner component uses lwc:component to consume this component, it may encounter issues similar to those described in the [# Motivation](#motivation). Although this is considered an anti-pattern, the author of the owner component might not have control over the consumed component, leading to a noticeable feature gap. The proposed design would address this issue +Currently, a component can execute logic in its `connectedCallback` based on the value of an attribute in its corresponding element. If an owner component uses `lwc:component` to consume this component, it may encounter issues similar to those described in the [# Motivation](#motivation). Although this is considered an anti-pattern, the author of the owner component might not have control over the consumed component, leading to a noticeable feature gap. The proposed design would address this issue At present, `lwc:spread` can be used to listen for standard events like `click` or `focus` by assigning event handlers to the `onclick` or `onfocus` properties. This works because these properties are applied to the rendered element, meaning the event handlers are bound to the elements themselves, not to the owner component as `onevent` on the template does. This behavior might be unexpected for consumers. The distinction between the rendered element and the `LightningElement` component is an implementation detail. For consumers, it is simply an Lwc, and this behavior cannot be explained as the application of properties to Lwc. The proposed design would be a breaking change. However, any component that relies on the current behavior can be rewritten to accommodate the new design. @@ -126,6 +124,13 @@ Although properties and event listeners use the same HTML attribute syntax, cons Currently, the segregation of properties, attributes, and event listeners is handled solely by the template compiler. This design would require implementing a runtime segregator, causing additional runtime overhead. Any changes in segregation rules would necessitate updates in both the compiler and the runtime engine, leading to increased maintenance effort. +### Event Handler Properties + +An alternative design considered is for the framework to create new properties named `on{eventName}` on the components. The setter for these properties would add an event listener to the rendered element. This listener would listen for events with the name `eventName` and handle them using the input value bound to the component. In this design, the template-compiler output shall not distinguish between properties and event listeners, they shall all be treated as properties + +This approach presents significant implementation challenges. Since we can't know all the required event listeners during component construction, we would need to create properties dynamically when they are first encountered. The framework can handle `on*` properties defined in the HTML template easily because it manages the assignment. However it would require a proxy based mechanism for the framework to create these properties when first encountered elsewhere. + + ## Adoption strategy If we implement this proposal, how will existing Lightning Web Components developers adopt it? Is From 8c89f537ef1c7138a1e6f94aa2bee5c419d94679 Mon Sep 17 00:00:00 2001 From: Gaurav Kochar Date: Fri, 31 Jan 2025 16:12:07 +0530 Subject: [PATCH 7/8] adds prior arts and improves clarity of other sections --- text/0000-dynamic-events.md | 81 +++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/text/0000-dynamic-events.md b/text/0000-dynamic-events.md index 918c78b..93cf336 100644 --- a/text/0000-dynamic-events.md +++ b/text/0000-dynamic-events.md @@ -2,7 +2,7 @@ title: `lwc:on` directive status: DRAFTED created_at: 2025-01-13 -updated_at: 2025-01-17 +updated_at: 2025-01-31 pr: https://github.com/salesforce/lwc-rfcs/pull/92 --- @@ -11,7 +11,7 @@ pr: https://github.com/salesforce/lwc-rfcs/pull/92 ## Summary -This proposal adds a mechanism to add a collection of event listeners to elements in an LWC template using a new directive `lwc:on`. +This proposal introduces a mechanism to dynamically add a collection of event listeners to elements in an LWC template using a new directive, `lwc:on`. ## Basic example @@ -19,9 +19,11 @@ This proposal adds a mechanism to add a collection of event listeners to element // x/myComponent.js export default class MyComponent extends LightningElement { + _someField = 'some value'; + childEventHandlers = { foo: function (event) {console.log('foo');} , - bar: function (event) {console.log('bar');} + bar: function (event) {console.log(this._someField);} }; } @@ -34,35 +36,56 @@ export default class MyComponent extends LightningElement { ``` -For each property in `childEventHandlers`, the new `lwc:on` directive adds an event listener to `x-child`. The event listener listens for the event specified by the property's key and uses the property's value, bound to the instance of `MyComponent`, as the event handler. +The `lwc:on` directive uses the properties of the `childEventHandlers` object to set up event listeners on ``. Each property key in the object specifies an event type to listen for, and when that event occurs, the corresponding property's value will be invoked with `this` set to the `MyComponent` instance. +In this example, when `` receives a 'foo' event, it logs 'foo' to the console. When it receives a 'bar' event, it logs 'some value' to the console because `this` is the `MyComponent` instance. + ## Motivation -LWC's support for declarative event listeners is limited to scenarios where event name are known while authoring the owner component template. This is sufficient for most of the cases, however when using the `lwc:component` directive, it is common to require event listeners based on the current value of the constructor passed to `lwc:is`. In such cases, it is not possible to know the event name while authoring the owner component template. +LWC's current support for declarative event listeners is limited to scenarios where event types are known at the time of authoring the owner component's HTML template. There is no way to declaratively add event listeners for event types that are determined dynamically. + +A common use case arises when using the `lwc:component` directive, where event listeners may depend on the current value of the constructor passed to `lwc:is`. In such cases, the event types cannot be known in advance when authoring the owner component's HTML template. -As an alternative, it is possible to add event listeners imperatively. However it requires the owner component to have a reference of the element on which it needs to add event listener. The owner component can have a reference of a element only after the element has been connected and hence typically only after the connectedCallback of corresponding component has terminated , making it impossible for the owner component to handle events dispatched by the connectedCallback of the corresponding component. +As an alternative, it is possible to add event listeners imperatively. However, this requires the owner component to have a reference to the element on which the listener needs to be added. This reference is only available after the element has been connected, and hence, it is typically only available after the `connectedCallback` of the corresponding component has terminated. This makes it impossible for the owner component to handle events dispatched by the `connectedCallback` of the corresponding component. +## Prior Art + +- React ([JSX Spread Operator](https://stackblitz.com/edit/vitejs-vite-wvbk6vet?file=src%2FApp.jsx)) +- Vue ([`v-on`](https://vuejs.org/api/built-in-directives.html#v-on)) +- Svelte ([Spread Events](https://svelte.dev/docs/svelte/basic-markup#Events:~:text=you%20can%20spread%20them%3A%20%3Cbutton%20%7B...thisSpreadContainsEventAttributes%7D%3Eclick%20me%3C/button%3E)) +- Lit ([Discussion for support](https://github.com/lit/lit/issues/923)) +- Solid ([JSX Spread Operator](https://stackblitz.com/edit/solidjs-templates-k1nr5puc?file=src%2FApp.jsx)) ## Detailed design -A new directive `lwc:on` will be introduced that can be used to add a collection of event listeners whose name may not be known while authoring the component. +The `lwc:on` directive will enable the addition of a collection of event listeners whose names may not be known at the time of HTML template authoring. + +In this document, `element` shall refer to the rendered `Element` and `component` shall refer to the instance of `LightningElememt`. +In this document, the value of an object's property shall refer to the result of calling the object's `[[Get]]` internal method with the property's key as the argument. -### Structure +### Directive behaviour -The `lwc:on` directive would accept an object. For each property of the object, it would add a event listener which would listen for the event specified by the property's key and handle it using the property's value bound to the owner component. +The `lwc:on` directive would accept an object. For each property of the object, it would add a event listener to the `element` that listens for the event type specified by property's key. The property's value with `this` set to the owner `component` would be used for handling the event. + +### Considered Properties + +Only own enumerable string-keyed properties of object passed to `lwc:on` will be considered. Other properties, i.e. inherited properties, non-enumerable properties and symbol-keyed properties shall be ignored. ### Caching #### For static components, i.e. components declared in template directly using its selector -Since it is uncommon for event listeners to change after the start of the owner component's intial render, We can cache them to improve performance. Note that this is same as how `onevent` in template HTML works currently. For consumers, the implication of this would be that any changes made to the object passed to `lwc:on` after the first `connectedCallback` of owner component would cause no effect. +Since it is uncommon for event listeners to change after the start of the owner component's intial render, We can cache them to improve performance. Note that this is same as how `on{eventType}` in template HTML works currently. For consumers, the implication of this would be that any changes made to the object passed to `lwc:on` after the first `connectedCallback` of owner component would cause no effect. #### For dynamic components, i.e. components created using `lwc:component` directive -Since it is uncommon for event listeners to change if the constructor passed to `lwc:is` doesn't change, We can skip patching of event listeners if the element doesn't change. For consumers, the implication of this would be that after the first `connectedCallback` of owner component, any changes made to the object passed to `lwc:on` would cause no effect until the constructor passed to `lwc:is` itself is changed. +Since it is uncommon for event listeners to change if the constructor passed to `lwc:is` doesn't change, We can skip patching of event listeners if the `element` doesn't change. For consumers, the implication of this would be that after the first `connectedCallback` of owner component, any changes made to the object passed to `lwc:on` would cause no effect until the constructor passed to `lwc:is` itself is changed. ### Overriding +`lwc:on` will always be applied last. That means it will take precedence over whatever event listeners are declared in the template directly. In the below example, `x-child`, only one listener for event type `foo` would be added and `childEventHandlers.foo` would be used for event handler. +Additionally, there can be only one `lwc:on` directive on an element + ```js // x/myComponent.js export default class MyComponent extends LightningElement { @@ -81,55 +104,35 @@ export default class MyComponent extends LightningElement { ```html ``` -`lwc:on` will always be applied last. That means it will take precedence over whatever event listeners are declared in the template directly. In the above example, `x-child`, only one listener for event `foo` would be added and `childEventHandlers.foo` would be used for event handler . -Additionally, there can be only one `lwc:on` directive on an element - - -### Event names - -The keys of object passed to `lwc:on` should conform to the requirement set by DOM Event Specification. There would be no other constraint on the object's keys. - - -to add: own enumerable properties whose keys are not symbols - ## Drawbacks -Why should we *not* do this? Please consider: - -- implementation cost, both in term of code size and complexity -- whether the proposed feature can be implemented in user space -- the impact on teaching people Lightning Web Components -- integration of this feature with other existing and planned features -- cost of migrating existing Lightning Web Components applications (is it a breaking change?) - -There are tradeoffs to choosing any path. Attempt to identify them here. - One downside of this proposal is that it would prevent us from being able to statically analyze the names of event listeners added by this directive. ## Alternatives ### lwc:spread - Just spreading it in template -An alternative design would be for `lwc:spread` to behave as a directive that appears to just spread the object's properties on the template. +An alternative design would be for `lwc:spread` to behave as a directive that appears to just spread the object's properties onto the template. -Currently, a component can execute logic in its `connectedCallback` based on the value of an attribute in its corresponding element. If an owner component uses `lwc:component` to consume this component, it may encounter issues similar to those described in the [# Motivation](#motivation). Although this is considered an anti-pattern, the author of the owner component might not have control over the consumed component, leading to a noticeable feature gap. The proposed design would address this issue +Currently, a component can execute logic in its `connectedCallback` based on the value of an attribute in its corresponding `element`. If an owner component uses `lwc:component` to consume this component, it may encounter issues similar to those described in the [# Motivation](#motivation). Although this is considered an anti-pattern, the author of the owner component might not have control over the consumed component, leading to a noticeable feature gap. The proposed design would address this issue. -At present, `lwc:spread` can be used to listen for standard events like `click` or `focus` by assigning event handlers to the `onclick` or `onfocus` properties. This works because these properties are applied to the rendered element, meaning the event handlers are bound to the elements themselves, not to the owner component as `onevent` on the template does. This behavior might be unexpected for consumers. The distinction between the rendered element and the `LightningElement` component is an implementation detail. For consumers, it is simply an Lwc, and this behavior cannot be explained as the application of properties to Lwc. The proposed design would be a breaking change. However, any component that relies on the current behavior can be rewritten to accommodate the new design. +Currently, `lwc:spread` can be used to listen for standard events like `click` or `focus` by assigning event handlers to the `onclick` or `onfocus` properties. This works because these properties are applied to `element`, meaning the event handlers are bound to the `elements` themselves, not to the owner `component` as `onclick` or `onfocus` on the template does. This behavior might be unexpected for consumers. The distinction between `element` and `component` is an implementation detail. For consumers, it is simply an Lwc, and this behavior cannot be explained as the application of properties to Lwc. The proposed design breaks this behaviour. However, any component that relies on the current behavior can be rewritten to accommodate the new design. -Although properties and event listeners use the same HTML attribute syntax, consumers understand their differences. They recognize whether a variable in an HTML template is a property, attribute, or event listener and reason about them separately. Combining them into a single directive does not enhance the developer experience. In fact, a combined directive might lead consumers to treat them as a unified concept, causing confusion. Additionally, the usage patterns of properties and event listeners are different, and a combined directive would complicate consumer code. +Although properties and event listeners use the same HTML attribute syntax, the distinction between them is a part of public API. Consumers are expected to understand their differences and reason about them separately. Combining them into a single directive does not seem to significantly enhance the developer experience. In fact, a combined directive might lead consumers to treat them as a unified concept, causing confusion. Additionally, the usage patterns of properties and event listeners are different, and a combined directive would complicate consumer code. -Currently, the segregation of properties, attributes, and event listeners is handled solely by the template compiler. This design would require implementing a runtime segregator, causing additional runtime overhead. Any changes in segregation rules would necessitate updates in both the compiler and the runtime engine, leading to increased maintenance effort. +Currently, the segregation of properties, attributes, and event listeners is handled solely by the template compiler. This design would require implementing a runtime segregator, causing additional runtime overhead. Any changes in segregation rules would necessitate updates in both the compiler and the runtime engine, leading to increased maintenance effort. ### Event Handler Properties -An alternative design considered is for the framework to create new properties named `on{eventName}` on the components. The setter for these properties would add an event listener to the rendered element. This listener would listen for events with the name `eventName` and handle them using the input value bound to the component. In this design, the template-compiler output shall not distinguish between properties and event listeners, they shall all be treated as properties +Another alternative design considered is for the framework to create new properties named `on{eventType}` on the `component`. The setter for these properties would add an event listener to the corresponding `element`. This listener would listen for events with the type `eventType` and handle them using the input value bound to the `component`. In this design, the template-compiler output shall not distinguish between properties and event listeners, they shall all be treated as properties. This would enable us to use `lwc:spread` for dynamic addition of event listeners, This approach presents significant implementation challenges. Since we can't know all the required event listeners during component construction, we would need to create properties dynamically when they are first encountered. The framework can handle `on*` properties defined in the HTML template easily because it manages the assignment. However it would require a proxy based mechanism for the framework to create these properties when first encountered elsewhere. +Similar to previous alternative, this design would be a breaking change and it doesn't seem to improve the developer experience significantly. ## Adoption strategy From e359d31159f9c314280c668760fd4496a31e29a9 Mon Sep 17 00:00:00 2001 From: Gaurav Kochar Date: Sun, 2 Feb 2025 23:59:21 +0530 Subject: [PATCH 8/8] corrections --- text/0000-dynamic-events.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-dynamic-events.md b/text/0000-dynamic-events.md index 93cf336..8afc54e 100644 --- a/text/0000-dynamic-events.md +++ b/text/0000-dynamic-events.md @@ -2,7 +2,7 @@ title: `lwc:on` directive status: DRAFTED created_at: 2025-01-13 -updated_at: 2025-01-31 +updated_at: 2025-02-02 pr: https://github.com/salesforce/lwc-rfcs/pull/92 --- @@ -61,7 +61,7 @@ As an alternative, it is possible to add event listeners imperatively. However, The `lwc:on` directive will enable the addition of a collection of event listeners whose names may not be known at the time of HTML template authoring. In this document, `element` shall refer to the rendered `Element` and `component` shall refer to the instance of `LightningElememt`. -In this document, the value of an object's property shall refer to the result of calling the object's `[[Get]]` internal method with the property's key as the argument. +In this document, the value of an object's property shall refer to the result of calling the [Get](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-get-o-p) operation with the object and the property's key as arguments. ### Directive behaviour @@ -82,8 +82,8 @@ Since it is uncommon for event listeners to change if the constructor passed to ### Overriding - -`lwc:on` will always be applied last. That means it will take precedence over whatever event listeners are declared in the template directly. In the below example, `x-child`, only one listener for event type `foo` would be added and `childEventHandlers.foo` would be used for event handler. + +If `lwc:on` specifies a listener for the same event type as an event listener declared directly in the template, only the listener from `lwc:on` will be applied, and the other listener will be ignored. In the below example, for `x-child`, only one listener for event type `foo` would be added and `childEventHandlers.foo` would be used for event handler. Additionally, there can be only one `lwc:on` directive on an element ```js