-
Notifications
You must be signed in to change notification settings - Fork 10k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Control event propagation and default action. Implements #5545 #14509
Control event propagation and default action. Implements #5545 #14509
Conversation
Also, the E2E tests hard-code the magic-named attributes. This can be replaced with |
…ons about storage formats
…e's no real value in deleting that tracking info later
dc10e71
to
395e2f2
Compare
Ping to reviewers @rynowak @pranavkm. Even though the tooling side won't be in preview 2, it would be valuable to have the runtime part in, so that (1) it doesn't get out of sync and need more work later and (2) for extra verification that my refactoring hasn't broken anybody. There's no drawback to shipping the runtime part that I'm aware of. |
/// </summary> | ||
public static class RenderTreeBuilderExtensions | ||
{ | ||
// The "prevent default" and "stop propagation" flags behave like attributes, in that: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI the compiler support for this will need to do feature detection to see if these APIs are defined. Also the compiler won't likely codegen them as extension methods just because it's not something we tend to do - I'll leave that up to @ajaybhargavb though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rynowak @SteveSandersonMS, do we plan to do the compiler work for this in preview2?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3
/// <param name="value">True if the default action is to be prevented, otherwise false.</param> | ||
public static void AddEventPreventDefaultAttribute(this RenderTreeBuilder builder, int sequence, string eventName, bool value) | ||
{ | ||
builder.AddAttribute(sequence, $"__internal_preventDefault_{eventName}", value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing this repeated allocation is another good reason to build a feature for this (if we ever get around to it).
<div __internal_preventDefault_onclick="@ancestorPreventDefault"> | ||
<p> | ||
<label> | ||
<input type="checkbox" id="checkbox-with-preventDefault-true" __internal_preventDefault_onclick @onclick="@(() => LogEvent("Checkbox click"))" @onchange="@(() => LogEvent("Checkbox change"))" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scrolling through these - it looks like this is the only coverage of the minimized form. @ajaybhargavb - let's make sure we preserve the semantics of this.
This PR is the runtime part of #5545, which is hopefully the vast majority of it. The remaining tooling work is to add directive attributes for
@oneventname:stopPropagation
and@oneventname:preventDefault
.Design notes
This closely follows the plan described here.
The first major decision point here was to make this entirely a feature of
.Web
/.Web.JS
and not part of.Components
core at all. I think that's the right thing to do, because the vast majority of the implementation is in.Web.JS
and there's no reason to say that some other non-web UI platform would have any meaning for "prevent default", and another platform need not necessarily have a concept of event bubbling/propagation. The semantics of both are extremely closely tied to DOM semantics. As such, the rendertree APIs for attaching this information are given as extension methods that live in.Web
, so the compiler is only going to be able to use them if you're referencing.Web
.The second major decision point was how to represent these flags in the rendertree. Note that these flags behave like attributes:
I think eventually we will want to introduce a concept of "attribute type". Today there are several types of attributes (regular string/bool attributes, event handler attributes, component parameters, and now stopPropagation/preventDefault ones), but we don't explicitly declare their types anywhere, and instead handle things based on conventions around where they are used and what their value types are. There is a slot in
RenderTreeFrame
where we could put anAttributeType
enum value and hence be more explicit about all this. At that point, it would make sense for a "stop propagation" attribute to haveAttributeType.StopPropagation
and name=nameOfTheEvent. Given the attribute type discriminator, we could overload the memory slots with the "attribute" frame type to have different meanings for different types, letting us scale this whole system up further without consuming more memory.However, I don't think attribute types are useful for this purpose until we also solve "allow multiple same-named attributes" (#14365). So in the short term, my plan is:
builder.AddEventPreventDefaultAttribute
andbuilder.AddEventStopBubblingAttribute
, but you don't get to know what they are doing to set that information. How they represent themselves in the tree can change in the future non-breakingly, as long as the APIs and their resulting behavior remains unchanged.If/when we later do #14365, we could change the internal behavior of
AddEventPreventDefaultAttribute
andAddEventStopBubblingAttribute
to make use of it.Review notes
Most of the complicated-looking noise in
EventDelegator.ts
is just refactoring. Previously it used a very loosely-typed scheme for tracking event data, with lots of duplicated checks likeObject.hasOwnProperty('something')
. Now it's more explicitly typed. I did this just because I'm storing more information here now, and didn't like to expand the ad-hoc loose-typeness.I'm not trying to override or change DOM semantics for event propagation and its interaction with navigation. So,
These are all browser behaviors and not controlled by Blazor.
Similarly, stopPropagation does not block navigation. You might think that stopping a "click" from propagating up to an
<a href...>
would mean the browser doesn't navigate. But you'd be wrong: the native behavior is that it navigates anyway. So I've reproduced the same with Blazor's synthetic event bubbling mechanism and its link click interception. There are E2E tests for this.