webcomponents / custom-elements-everywhere

Custom Element + Framework Interoperability Tests.
https://custom-elements-everywhere.com
Other
1.19k stars 103 forks source link

Include supporting events in the capture and bubble phases #909

Open rajsite opened 4 years ago

rajsite commented 4 years ago

I think it would be useful to test that libraries supporting custom elements include the ability to listen for events from either the capture or the bubble phase.

We recently ran into an issue where the Angular Framework can only listen to bubble events while Angular Elements can only emit capture phase events. In this case the Angular Framework was not able to easily consume arbitrary custom elements (created by Angular Framework).

justinfagnani commented 3 years ago

It doesn't look like Angular Elements emit "capture phase" events (there really isn't such a thing), they're just emitting non-bubbling events. Many template systems, if they don't use event delegation, will be able to listen to these events just fine because the listener is put directly on the event target.

So I'm not sure if there should be a test for capturing listeners specifically, but it might make sense to have a test to see if event delegation interferes with non-bubbling events. So fire a non-bubbling event on a nested element with an event binding and see if it's handled:

let handled = false;
const handleTest = (e: Event) => {
  handled = true;
};
render(html`<div><div id="target" @test=${handleTest}></div></div>`, container);
const el = container.querySelector('#target');
el.dispatchEvent(new Event('test'));
assert(handled);
rajsite commented 3 years ago

It doesn't look like Angular Elements emit "capture phase" events (there really isn't such a thing), they're just emitting non-bubbling events.

Fair enough, I should have spoke more precisely, but sounds like the idea came across.

it might make sense to have a test to see if event delegation interferes with non-bubbling events

Makes sense to me, but it appears the example given does not exercise event delegation. Shouldn't it register the event listener on the parent or am I missing the intention of the example test?

ie:

let handled = false;
const handleTest = (e: Event) => {
  handled = true;
};
render(html`<div @test=${handleTest}><div id="target"></div></div>`, container);
const el = container.querySelector('#target');
el.dispatchEvent(new Event('test'));
assert(handled);
justinfagnani commented 3 years ago

The intent of the test is to make sure that the template language can listen to non-bubbling events like that. If a system used event delegation at some component root, then the handler wouldn't be called.

You change would be a good negative test. The handler should not be called in that case, since the event doesn't bubble. Calling the handler would be a bug in the event targeting logic of the delegation system.

rajsite commented 3 years ago

I think I better understand what you are describing.

it might make sense to have a test to see if event delegation interferes with non-bubbling events

If a system used event delegation at some component root, then the handler wouldn't be called.

Your test was written to validate that the framework does not interfere in some way with listeners placed directly on the event target (by the system relying on event delegation and for example stopping immediate propagation, etc.).

The scenario I ran into was listening for events fired by child elements created at runtime by the custom element we are consuming (and there not available to bind to by the framework directly at edit time):

ie at edit time:

render(html`<my-element @test=${handleTest}></my-element>`, container);

and at runtime my-element creates children custom elements that fire events. If these were web components leveraging shadow dom I'd expect the events fired by my-elements's children custom elements to be re-targetted to my-element. But as just custom elements not leveraging shadow dom their behavior seems perfectly reasonable not attempting to re-target (?).

Since the repo is custom-elements-everywhere and not web-components-everywhere 😉 then maybe it would be useful for frameworks to be able to handle this case generally (which would require being able to configure event listener's capture mode). Hopefully that clarifies the intention of this issue.

coryrylan commented 2 years ago

Another example of where a test around detecting bubbling/non-bubbling events would be the use of .NET Blazor. I can use custom elements however Blazor only captures global or bubbled events. This means for most of the web components I work on that don't bubble, Blazor will never catch.

Here is a snippet from the Blazor docs https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor

If you're attempting to fire a custom event, bubbles must be enabled by setting its value to true. Otherwise, the event doesn't reach the Blazor handler for processing into the C# custom EventHandlerAttribute method. For more information, see MDN Web Docs: Event bubbling.

Without access to the underlying web component and setting it to bubble, it makes it very difficult to use web components. I have to hack the custom event to bubble for Blazor to see it. Also to compound the issue, since Blazor only understands global events it can't distinguish the event detail types if the event has the same name as another component custom event. https://github.com/coryrylan/clarity-blazor/blob/main/EventHandlers.cs