-
Notifications
You must be signed in to change notification settings - Fork 376
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
Focus delegation from shadow host should not behave like sequential focus navigation #830
Comments
It seems reasonable to me to change the default. I think ideally the delegation is exposed to script (either by being able to set an element or some kind of callback). (Out of curiosity, what happens when the host is focusable itself?) |
This indeed seems like a serious compat risk for Chrome although the new proposed behavior is a lot simpler to understand & implement so I support the proposed change. |
@annevk to be clear, changing the default isn't quite what we raised this issue for. The question is just what the default behavior should be. In Chrome's implementation click/programmatic-focusing a delegatesFocus host chooses a shadow-child to delegate to based on the shadow-children's sequential-focusability. This is strange. The proposed change is to choose a shadow-child based on the shadow-children's click/programmatic focusability. There might be a separate discussion about giving more explicit control over focus delegation, instead of "pick the appropriate child that is focusable", but I think that gets complicated for different reasons... |
One thing I just clarified for myself:
This predicate, and its consequences, only hold if you click on the host directly. If you click on one of the children, then that child will get focus directly, regardless of ordering. That is, consider an element implemented like Chromium's (This is true with both the Chromium behavior and the proposed change.) |
Let me try to come up with a semi-realistic example. This is fairly hard to do, because we need to find situations where:
You can easily come up with contrived examples, like the OP, by using Consider a control that is a kind of "color well", with a button that pops open a color picker, plus a textbox for direct control entry. We want this control to delegate focus, similar to <span tabindex="0">Before</span>
<color-picker>
<!!shadow-root delegatesFocus=true!!>
<button style="background: #0033FF;"></button>
#<input type="text" value="0033FF">
</!!shadow-root!!>
</color-picker>
<span tabindex="0">After</span> On Chrome with the current Chromium behavior:
On Safari (where buttons are only programmatically focusable), with the current Chromium behavior:
On Chrome with the proposed behavior: no change, but the reasoning changes...
On Safari with the proposed behavior:
Make sense? |
I haven't used But reading the details (thanks for the very clear explanation @rakina), it doesn't sound to me like any choice is going to be all that great. We have to choose either the first sequential or first clickable focusable element - and in either case we're only inferring what the component author wants in the face of no explicit The thing I find particularly strange about the proposed change is that we'd be picking the first click-focusable element, ie sequentially, where order doesn't matter otherwise. At least with Chrome's current behavior the author already expects that tabindex ordering and tree-order matter. All that said, I don't feel particularly strongly here. |
Right, after talking with @s4y I realized that's probably the justification for Chrome's current behavior. That is, Chrome's current behavior allows you to get some control over where focus gets delegated to in all cases, whereas the proposed behavior only gives you such control for sequential focus. The weird thing is, the way that Chrome's proposed behavior gives you such control is by co-opting the This gets back to @annevk's point about giving better control, and my comment about it being complicated. It feels the most powerful thing we could do would be three separate APIs, for controlling delegation for each of click, programmatic, and sequential focus, instead of assuming you want the same delegation in all cases. But that's tricky in at least two ways:
|
Do we not already have an API with the this.addEventListener('focus', (e) => {
e.preventDefault();
this.shadowRoot.querySelector('#default-input').focus();
}); |
CC @atanassov and @boggydigital |
Yeah, both of these are highly problematic. What if we let In the case when the user uses sequential focus navigation, the UA should probably decide where the focus should go based on what it decides to be focusable within the shadow tree. Otherwise, we'd end up with a bunch of components that try to implement Windows behavior to delegate the focus to hyperlinks and bunch others that try to match macOS to skip hyperlinks and only delegate to editable text fields. |
I've been spending some time on a delegatesFocus polyfill at Salesforce and I support the proposed behavior. I think it makes sense that click and programmatic focus behavior are not influenced by the sequential focus navigation order because the default user experience should be consistent. It's considered a bad practice to go crazy with In terms of control, components could do the following for programmatic: class Foo extends HTMLElement {
focus() {
this.shadowRoot.querySelector('input').focus();
}
} This is what our internal components are required to do so that we don't break them once the expected behavior is specified and our framework is ready to implement it. I'm not sure if anything can be done for clicks using existing APIs but I wonder if the component really needs control over that? |
What does "consistent" mean here? The more I think about it the more I think that the current Chrome behavior makes sense. Via |
@justinfagnani I think "consistent" means that the user can reasoning about how the UI respond to interactions for the most part. And yes, the developer can control some aspects of it, e.g.: programmatic focus, but the rest should behave as expected. @ekashida has been our champion when it comes to focus related bug, and it is important to also notice that focus is the biggest honeypot for Lightning Web Components bugs, it has been like that for a long time, probably because we have a lot of complex forms and inputable components. |
I was notified, asked to give a comment in this discussion, so let me do it. :) I remember that I and @TakayoshiKochi designed and implemented the current behavior.
I can guess that the current behavior is not perfect, and there is a situation that the current behavior is not desired, however, I think the current behavior should make sense in most use cases with consistent behaviors between click and sequential navigation. If we want to address a use case where this default consistent behavior is not desired, I think we should add another API (or any workaround) so that web component authors explicitly use it; web components authors should be aware that they might break an end user's expectation by using it. |
The disconnect here is that it's highly desirable for JS API Also, I'm not certain that using the keyboard-based sequential focus navigation order for when a host element is clicked makes much sense. If the user is clicking somewhere a more sensible thing for the component might be to select the nearest element there is to where the user clicked so perhaps some kind of an event-based focus delegation is needed anyway. In the case where keyboard is used to move focus, however, it obviously makes sense that |
@justinfagnani - As @caridy mentioned, what I meant by "consistent" is that the resulting outcome is easier to reason about for the end-user as they interact with different components over time. Thinking more about it though--as @rniwa mentioned--the most sensible thing to happen for the click case would probably be for the nearest focusable element to be focused, and from that perspective, any other option seems kind of arbitrary. My only issue with focusing on the first sequentially focusable element is that we would be ignoring all click-focusable elements for clicks, and all programmatically-focusable elements for programmatic, and that seems very strange. |
@hayatoito - Could you elaborate on the "inconsistency" that you mentioned in your second point? |
I think the first element is not always nearest element from an end user's perspective. |
Right. I don’t think the current behavior of Chrome makes much sense in this regard. |
It's still unclear to me why the first clickable element (in a node tree) is better. Could someone explain what would happen if the first clickable element is assigned to a slot? For example, given the following tree of trees:
Then, the flat tree would be:
If an end user clicks host-1, an end user's expectation is that clickable-element1 gets focused, I think. If we always choose the first clickable element in the shadow tree, ignoring slot assigments at all, we will focus clickable-element2. Is that acceptable behavior? I think the current behavior is not perfect, but it works in most cases, handling this case too, without any further tricks. Comments are welcome! |
FWIW we can definitely use the flat tree even if we're not using the "sequential focus navigation" delegation anymore.
This might have some problems with slotted elements. What if we use the actual elements instead? So like |
Indeed, an explicit setter might better although it brings its own can of worms by allowing random node outside of shadow host's flat tree to be specified. However, that might not matter. We can reject whatever node which is not a focusable element in the flat tree at the time of focusing, and treat as if it's not specified in which case we can fallback to the default (maybe the first focusable element). Now that I've been thinking about this issue for a while, I'm pretty convinced that mouse based focusing & keyboard based focusing need to be treated differently. In the case of keyboard based sequential focus navigation, we should simply find the first element with the In the case of mouse based focusing, however, it's unclear to me if a component can specify which element to be focused priori. As I stated above, some components may need to make a decision knowing where the user had clicked to trigger such focusing. I think a component can do this by listening to As I've noted multiple times above, we don't want |
So I feel like we have something close to consensus on the following:
We are still discussing the desired behavior for clicking, e.g. using flat-tree-first focusable element vs. tabIndex-order-first element vs. closest-to-cursor focusable element. However, getting this exactly nailed down in the spec seems less urgent, because there will always be user agent discretion involved. We could even leave it explicitly up to the UA in the spec. We also are discussing potential additions:
Although, it's also been noted that there are alternative ways of accomplishing this, e.g. overriding If the above is accurate, I propose:
Does this sound good? Thoughts on OP vs. the "leave click focusing up to the UA" variant? |
That proposal doesn't look quite right. We should define the order in which UA should try to find the focusable element on clicking. Then leave it up to the UA to say which elements are focusable that way. (Firefox on MacOS should have quite similar behavior to Safari) |
Yeah, it seems like we've reached a consensus on these two issues as far as I could tell. I really don't get the urgency of merging this feature into the HTML spec though. All the involved parties are paying attention to this issue and related PRs so it's not like having this in the HTML specification would speed up any process. If anything, it would create an illusion of the feature being mature to web developers, which is problematic.
Adding an option to
FWIW, if this turns out be an compatibility issue, we should probably rename
Could you elaborate what you mean by this? Are you suggesting that we should define an order of focusable areas and then UA would pick the first element in which it deems focusable under that ordering? That does sound like a reasonable approach. |
I mean, this feature has been in a semi-specced state for literally years (in the shadow DOM spec). It has fairly high usage. It'd be nice to have a real spec, instead of telling people to look at a bunch of outstanding pull requests, or maybe the shadow DOM spec. |
The reality of the situation is that I find it rather frustrating that this feature got dropped on floor when people started merging shadow DOM & custom elements spec into the HTML & DOM if you think it's so important; specially because I had complained about how the merge would make reviewing & implementing these features impossible on my end. Regardless, we're making pretty good progress here. Let's focus on the technical discussion and settle on what to do on click. That's the only ting left to agree on, and I bet we can get there in a matter of days & weeks, and not months. |
Hm... if I'm not mistaken, it doesn't seem like the current HTML specification defines how clicking on an element ends up putting the focus on the element at all... If that's the case (someone should confirm it), it's kind of moot point as to what |
I recently worked on an update around that part of the spec: whatwg/html#4768 |
Indeed, that PR adds the much needed definition. It's really unfortunate that all these PRs are tied up with |
Actually both whatwg/html#4735 and whatwg/html#4768 are not really related to If you and @smaug---- approve those, then we can merge those two PRs - and after those two are merged the |
Continuing the discussion on this,
I think this approach is good. The set of things would be all the focusable elements in the host's shadow tree (and slotted ones too). Maybe the ordering will be flat-tree order, like the one suggested in the original post? Then the UA can use their own mechanisms (like skipping options). What do you think? FYI this is kinda like what |
But defining the order here doesn't help if UA could skip any element because we're picking exactly one element here. Let's say we have elements: e_1, e_2, etc... e_n inside a shadow tree. If UA wanted to focus an element e_c because it happens to be the first element in flat tree order, or because it's the first in tab index order, then UA can just skip e_1 through e_(c-1) and always pick e_c. Put it another way, for any sequence sequence S_1 and S_2 each consisting of all elements of a set {e_1, e_2, ... e_n}, you can pick k ∈ ℤ: 1 ≤ k ≤ n such that k-th element in S_1 is the first element in S_2. So defining the order then letting UA skip any element would not result in any more specific definition than saying it's entirely UA dependent. |
I agree, which is why I was a bit surprised by folks pushing to standardize a specific order. Given that different browsers explicitly want to have different choices for what elements are click-focusable, I don't see any way of avoiding the result being UA-dependent. So shall we just go with that? |
Well, there is a subtle difference between allowing UA to skip any element and skip only ones that are not mouse focusable. If we defined the order and defined so that UA can only skip non-mouse focusable element, then the first element is more or less fixed. e.g. UA can't use tab index order as opposed to flat tree order since the first mouse focusable element wouldn't be the first element in the tab index order. |
I think the intention of @rakina's OP was to only skip ones that are not click-focusable. However, I'm not sure how different that is in practice, because the UA gets to decide what is click-focusable, so in essence it seems like the UA just gets whatever freedom it wants. |
Alright. So, for clicking on a host that delegates focus. let's use "Focus on the first element in flat-tree order that is click focusable." with the definition of "click focusable" from https://whatpr.org/html/4768/2a397f6...75b4443/interaction.html#click-focusable. This ensures UA can only skip the ones it considers to be not click focusable, making it consistent with its other click behaviors. |
That sounds good. Hopefully that's what @smaug---- had in mind as well in #830 (comment). |
The focus delegation behavior might change as proposed in WICG/webcomponents#830. This CL adds use counter for when a host with delegatesFocus is focused, and when the result of the focus delegation is different than the proposed change, to see the possible impact of the change. Bug: 692678 Change-Id: I90a0a10e1e5adbd3fe018ea772e1d560fedec9a2 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1794945 Reviewed-by: Kent Tamura <tkent@chromium.org> Commit-Queue: Rakina Zata Amni <rakina@chromium.org> Cr-Commit-Position: refs/heads/master@{#695100}
That sounds reasonable |
This comment has been minimized.
This comment has been minimized.
@mfreed7 that's indeed the default behavior. This is only for when the |
Sorry - please ignore my comment. |
Sounds like there was a consensus to move forward with what's being proposed thus far:
|
Are relevant PRs being updated with the latest discussion? It's getting hard to keep track of what's being incorporated and what's not. |
Yes (mostly), also there's a list on whatwg/html#2013 (comment).
I think they should be added later on after whatwg/html#4796 is merged (which is ready for merging now I think) |
Hm... it doesn't seem like web-platform-tests/wpt#18035 contain tests for tabIndex order being ignored for |
The test for click with varying tabindex is https://github.com/web-platform-tests/wpt/blob/d50f1a32f2c84c8466e739f40cd3d9fb70668c5d/shadow-dom/focus/click-focus-delegatesFocus-tabindex-varies.html I had some tests for focus() where we still ensure it focuses things with tabindex=-1, also added the tabindex=0 vs tabindex=1 test just now https://github.com/web-platform-tests/wpt/blob/d50f1a32f2c84c8466e739f40cd3d9fb70668c5d/shadow-dom/focus/focus-method-delegatesFocus.html |
Okay, two of your tests have bugs as I pointed out in web-platform-tests/wpt#18035 but everything else looks good. Will upload a patch to implement this in WebKit once those tests are fixed. |
Fixed in whatwg/html#4796! |
A brief summary of focus to help understand the issue:
tabindex
value.tabindex
valuetabindex
value is non-zero, in increasing order. So it will move the focus to all element withtabindex
value of 1, and then all element withtabindex
value of 2, ... and so on, and finally move the focus to elements withtabindex
value of 0 last.focus()
on an element) will focus on elements even if their tabindex value is negativedelegateFocus
value to true, so that the host will delegate focus.While we were trying to move the delegatesFocus spec to the HTML spec on whatwg/html#4796, @domenic pointed out that the current behavior (which I think is only implemented in Chrome?) is quite weird: it uses sequential focus navigation even when the host gets focused with clicking on it or calling the
focus()
method on it.Currently the delegation process finds the new focus target with sequential focus navigation, this means in this case:
If we click/call
focus()
on the host, it will focus the "sequentially focusable" div instead. This is a bit weird because it's inconsistent with the method that initially makes us target the host in the first place.Proposal to change the behavior:
If we click/call
focus()
on the host, we should be as if we actually click/callfocus()
on the first element that is click/programmatically focusable, even if it's not sequentially focusable - in this case the "clickable" div. Note that if we are focusing on the host through tabbing to it (sequential focus navigation), then we will use sequential focus navigation and skip elements with negative tabindex values.Another point - the current behavior in Chrome also cares about tabindex value priority mentioned earlier. For example:
If we focus on #host, the "B" div will get focus.
Proposal to change the behavior:
Since we want to change the behavior of focus delegation to not be related to sequential focus navigation, we should probably remove the tabindex priority thing as well in this case. So we should always delegate focus to the first focusable area in DOM/composed-tree order - in this case the "A" div.
Since the sequential delegation behavior is already implemented in Chrome, there is a compatibility risk for changing to the proposed behavior. I predict this to be small and worth it for the change, but would love to hear from people who actually use this on whether this change makes sense and whether it's hard to move from the old behavior to the new one.
cc @rniwa @annevk @smaug---- and possible users of delegatesFocus that might be impacted @justinfagnani @muan?
The text was updated successfully, but these errors were encountered: