-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Prevent default when handling keydown events #836
Conversation
Hi @trevorsg, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!
TTYL, MSBOT; |
By the way, this PR is submitted on behalf of Craig Harry, who cannot log into GitHub because he does not have a phone for 2FA. |
@@ -459,6 +459,7 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext | |||
|
|||
if (ev.which === openKey) { | |||
this._onItemSubMenuExpand(item, ev.currentTarget as HTMLElement); | |||
ev.preventDefault(); |
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.
shouldn't this also have ev.stopPropagation() ?
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.
I think preventDefault makes sense, maybe for logging or something like that? Where it should continue to propagate but no other native actions should take place?
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.
I think from the description it should propagate so that the parent can handle
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.
No. I'd argue that stopPropagation is almost never what you want. The parent should look at the defaultPrevented property on the event object in most cases to determine if action should be taken. If propagation is stopped, the parent doesn't even get that choice.
@joschect You mention native actions, but is that what you mean? The preventDefault() call obviously prevents native actions being handled, but more importantly parent event handlers can look at the defaultPrevented property on the event to see if the default action has already been taken by a child handler.
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.
Yeah, preventing native actions is all I meant.
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.
I talked with @dzearing and I agree with him that it should have both preventDefault and stopPropagation. The code should prevent the browser from handling the event as well as preventing it from getting to other handlers.
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.
I think it would be probably make the most sense to add it. It would at least be consistent with other places where we handle events.
I'd be interested in how it's cleaner to avoid stopPropagation
, it's a bit unclear to me, I might be naive here tho! I thought about the purposes of the 2 methods:
preventDefault
: Prevents the default browser behavior. It is intended to prevent things like scrollbars moving, buttons submitting forms, and anchors from navigating.
stopPropagation
: Prevents the event from bubbling, which avoids user code. It doesn't affect browser behaviors; it simply avoids user code from executing. This is important when an event has been handled and it is intended for no other actions to handle the event.
So interpretting defaultPrevented
as a signal for avoiding user code might be presumptuous or wrong.
Again, possible to watch for defaultPrevented
, it just may not be the right signal indicating that user code executed an action and subsequent user code actions should be avoided.
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.
The API gives us two methods, preventDefault
and stopPropagation
. Given the above argument, it is hard to imagine a scenario where it would be useful to call one of the two methods, but not both. This makes me think they are not necessarily intended to be called as a pair in all/most cases.
Here's an example scenario where it might be useful for a parent to react to events that have been handled by the child:
Say you have a ContextMenu that is configured to hide when the user "clicks away". That is, the user clicks any part of the UI that is not part of the menu. The user might click a button or some other element that handles the click. But you still want the menu to close. If the event propagation was stopped, that event would not be able to make it up to the document
triggering the closing of the ContextMenu.
Anyway, I'm not trying to be argumentative here. I think calling both preventDefault
and stopPropagation
is the more common pattern, so it may be best to follow 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.
I don't think we should blindly use both of them. stopPropagation
can be hazardous as evident from the example by @trevorsg. Infact, we just fixed a bug in Dropdown to avoid stop propagating event if it doesn't handle the keypress
and give chance to parent components (dialog) to act on it.
preventDefault
is mostly used when we want to avoid browser default behavior and handle it ourselves. As in this PR, with custom context menu we don't want browsers to open its default context menu for some element. Or say, we want to avoid link click navigation and handle it ourselves. That said, preventDefault
and defaultPrevented
can be used as an alternative to stopPropagation
. It is explained at dangers of stopping event propagation as one of the best practices to follow.
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.
Thanks for finding that article, Manish. It organizes my thoughts on the subject very nicely!
Hi @trevorsg, to get this checked in, could you just run rush change to generate a change file? |
@cliffkoh Is there a doc I'm supposed to read about how to do that? I npm installed @microsoft/rush, then ran rush change, but I get this error...
|
@trevorsg I just run |
@cschleiden I still get the same error. Is there an order of operations? I made a commit then ran |
@trevorsg Hm, not that I'm aware of. That's how it worked for me last night :) |
@trevorsg, for the issue you encountered, @qinweiz and @pgonzal can help. An alternative is to just manually create a change file in |
Can you run Also, do you have a repro? (e.g. enlist in branch X and run these commands, something like that?) I wasn't following this thread, so I don't have much context, but if there's a Rush bug, we're interested. |
@pgonzal
Repro:
Let me know if I can get you any more info. |
@pgonzal Updated :) I forgot to escape the < and > |
@trevorsg Thanks, however I still can't repro the bug. I tried to find your branch trevorsg:patch-1, but it looks like it got squashed when the PR was merged. :-( |
@trevorsg 1 thought - what version of node are you running on? (node -v), and what OS? :) |
@cliffkoh I was on 7.1.0. Looks like I was hitting something related to this issue: nodejs/node#9542 I updated to 7.2.1 and now everything is working. Thanks @pgonzal for helping debug. |
@cliffkoh @dzearing I noticed that your rush.json file has this line: "nodeSupportedVersionRange": ">=4.3.0", I would suggest to restrict it to something like this: "nodeSupportedVersionRange": ">=6.9.0 <7.0.0", Supporting a wide range of NodeJS versions has been a nuisance for us in the past. For our projects, we generally require people to be on the recent LTS version range. NodeJS rolls them pretty often, so you won't be missing any killer features, and it's hardly any hassle if people are using nodist or nvm (which I strongly recommend). |
@pgonzal Created an issue to track this, will get to it later today or tomorrow. Thanks! |
@pgonzal thanks! |
…ft#836) * Prevent default when handling keydown events * Update ContextualMenu.tsx
Pull request checklist
Description of changes
Adds preventDefault() when handling keydown event on a ContextualMenu. This is necessary so that event handlers on parent elements know whether or not the event was handled. In our case, an arrow key pressed in a menu might not have an effect (e.g. there is no right sub-menu), in which case the parent needs to handle the user's intent (move to the next horizontal menu item, for example).
Focus areas to test
(optional)