Reputation: 4275
I have a custom lit web component that contains an <input>
element inside its Shadow DOM. I want to react to the change
event fired by the input outside the custom element, but the change
event has by default composed: false
, so the event doesn't pass through the Shadow DOM boundary. I could catch the event inside my component implementation, but the composed
property is read-only, so I can't update it and dispatch the same event object. I could create a new object with new Event('change', {'composed': true})
, but then it doesn't have the properties like target
of the original event. What's a good approach? Should I manually copy the original event's properties to the new event object?
Upvotes: 4
Views: 1419
Reputation: 853
It's not possible to dispatch a single Event
instance more than once, so even if you could modify the composed
property of the original event you couldn't redispatch it anyway.
So you need to create a new event to dispatch from your custom element, but the details of how exactly you want to create the event and what it should contain will probably depend on your use case. I'd suggest to try to keep it as simple as possible and make an event that includes the information you need and then dispatch it.
There's probably a reason why the native change event is not composed
, but you can simulate propagating it out of your custom element by creating an event with the name change
and dispatching it from your custom element. You probably don't even need to use composed in most cases as just dispatching it from your custom element (this
) makes it available in the parent scope (one level up from your shadow root) which is where the event probably should be handled in most cases.
The fact that you have an <input>
in your shadow root should probably be treated as an implementation detail (at least in some cases) and not exposed outside unnecessarily, but when you do need to expose it directly, you can make it available e.g. as a property on your custom element (which could then be accessed from your custom event) or you can include a reference to it in the event object (e.g. in detail
property of CustomEvent or a property of a custom event class).
For example here's how Vaadin components like <vaadint-text-field>
propagate the change
event:
const changeEvent = new CustomEvent('change', {
detail: {
sourceEvent: e
},
bubbles: e.bubbles,
cancelable: e.cancelable
});
this.dispatchEvent(changeEvent);
Here the original event is explicitly exposed as event.detail.sourceEvent
so you could for example get the input value from your custom change
event like event.detail.sourceEvent.target.value
.
If you expose the input element via a property (e.g. myInput
) you would not need to use CustomEvent
and detail
as then you could do something like event.target.myInput.value
, or if the original change
event actually causes a value
property on your custom element to change, you could just read that instead.
// Dispatch event (in your custom element)
const changeEvent = new Event('change', {
bubbles: e.bubbles,
cancelable: e.cancelable
});
this.dispatchEvent(changeEvent);
// Read the input value in event handler (assuming your custom element
// has declared `myInput` as a reference to the `<input>`)
event.target.myInput.value
// Alternatively access via shadowRoot (not very nice)
event.target.shadowRoot.querySelector('input').value
You could also include custom properties or methods in your event if you create a custom event class like this:
// Declare event class once somewhere
class MyChangeEvent extends Event {
constructor(sourceEvent) {
super('change', {
bubbles: sourceEvent.bubbles,
cancelable: sourceEvent.cancelable,
});
this.sourceEvent = sourceEvent;
}
}
// Trigger a custom event
this.dispatchEvent(new MyChangeEvent(e));
// Access custom event property in an event handler
event.sourceEvent
Instead of using the event name change
you could of course use your own custom event name like my-change-event
but it should be ok to use change
when you want your custom element to behave like a native <input>
which might allow your component to be used as a drop in replacement for a native <input>
in some situations (with limitations probably) or if you just want to mimic the native change
event which is already familiar to developers. In most cases it probably doesn't matter if the change
event you dispatch is created as an Event
, CustomEvent
or other instance extending from Event
.
Upvotes: 4