Skip to content
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

Programmatically setting focus navigation start point? #5326

Open
alice opened this issue Mar 2, 2020 · 41 comments
Open

Programmatically setting focus navigation start point? #5326

alice opened this issue Mar 2, 2020 · 41 comments
Labels

Comments

@alice
Copy link
Contributor

alice commented Mar 2, 2020

For a while, @robdodson and I have been noting use cases where setting the focus navigation start point, as opposed to the active element, would be preferable.

One motivating example is skip links - currently the technique may require setting focus to a non-interactive element purely to effectively set the focus navigation start point.

Another example is focus management for transient UI - for example, a side menu which appears as a result of a keyboard shortcut. It may not be appropriate to focus any specific element in the newly visible UI, but the user should be able to easily move focus within that UI, even if the UI is not modal.

Strawman API proposal:

el.focus({ setActiveElement: false });

This would unfocus the current active element, and set the focus navigation start point to el.

Additional naming proposals from @othermaciej in #5326 (comment):

element.setSequentialFocusStartPoint();
element.startSequentialFocusHere();
element.setFocusStartPoint();
document.setFocusStartPoint(element); // maybe more obvious that this clears focus,
                                      // & if there was a getter it'd be on document

More name suggestions from @muan in #5326 (comment)

document.setSequentialFocusStartingPoint(element);
document.setTabFocusStartingPoint(element);
document.setSequentialNavigationStartingPoint(element);
document.setNavigationStartingPoint(element);

Another example from @muan in #5326 (comment):

I've been working on adding focus management to the file list on GitHub's repository page. Currently when user navigates through the file directories with a keyboard, each navigation drops user's focus back to the top of the page.

The most basic solution would be to set the file table as the start point. However, with the current recommended pattern, we have to go through 5 steps:

  1. Setting tabindex="-1" on the file table
  2. Call focus() on the file table
  3. Ensure focus outline does not apply to this element
  4. Install a one time blur handler to remove the tabindex

With this new API, it'll be:

  1. Call table.focus({ setActiveElement: false })

This one line solution would immediately make the experience 10 times better.

@hober
Copy link
Collaborator

hober commented Mar 2, 2020

cc @whatwg/a11y

@domenic
Copy link
Member

domenic commented Mar 2, 2020

Can you describe the negative impacts of setting focus to a non-interactive element? I kind of was under the impression that's why almost everything is programmatically focusable, is so as to accomplish the use cases you describe here. But I guess something is not good enough about the existing solution?

I worry about having a separate active element and focus navigation starting point, which seems like it would be confusing. So I think it's worth getting a sense of what's wrong with the current coupling of the two so we can evaluate the tradeoff.

@patrickhlauke
Copy link
Member

for clarity, what is the focus navigation start point? the equivalent of the reading position if you were using a screen reader? is it something internal to the browser?

@domenic
Copy link
Member

domenic commented Mar 3, 2020

https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation-starting-point

@alice
Copy link
Contributor Author

alice commented Mar 3, 2020

I kind of was under the impression that's why almost everything is programmatically focusable, is so as to accomplish the use cases you describe here.

Most things are not programmatically focusable, unless you add tabindex=-1, so I'm not sure what you mean here.

This would work on any element, without requiring an opt-in on the element.

Can you describe the negative impacts of setting focus to a non-interactive element?

It can trigger confusing focus styling/indication - both from :focus but additionally in some situations focus indication cannot be opted out of (such as when an operating system preference to always show focus is enabled).

Focus indication is only helpful (IMO) when the focused element has some kind of keyboard interactivity.

I worry about having a separate active element and focus navigation starting point, which seems like it would be confusing.

Apologies, I was unclear about my intent there: my intent was that it would still blur the active element, so document.activeElement would be body.

@domenic
Copy link
Member

domenic commented Mar 3, 2020

Most things are not programmatically focusable, unless you add tabindex=-1, so I'm not sure what you mean here.

Right, I meant if you add that. So we're comparing this new primitive vs. using the existing focus primitive on a tabindex=-1 element.

It can trigger confusing focus styling/indication - both from :focus but additionally in some situations focus indication cannot be opted out of (such as when an operating system preference to always show focus is enabled).

:focus-visible is meant to handle this, right?

Focus indication is only helpful (IMO) when the focused element has some kind of keyboard interactivity.

In the case of skip links (for example), it seems like the keyboard interactivity is "you can use the keyboard to navigate focus within the main content". E.g. pressing Tab takes you to the first focusable piece of main content. Similarly for side menus.

Perhaps I'm not understanding what you mean by keyboard interactivity?

Apologies, I was unclear about my intent there: my intent was that it would still blur the active element, so document.activeElement would be body.

I see. This does seem to reduce the potential confusion by making it similar to how browsers seem to behave in the existing case, e.g. of clicking on a non-click-focusable element. So, this complexity does already exist in the platform.

The question is whether it's worth exposing this complexity to JavaScript, and thus letting authors trigger it instead of users. I can see how when used for good, it probably aligns with user expectations. But it feels a bit like we're solving the problem of "people are using :focus instead of the new :focus-visible technology" by introducing yet another new technology and hoping they'll use that instead?

Ultimately what I'm not quite grasping is why it's better for users to have the body focused than to have the container element focused, in these cases. This is likely just a matter of me not having enough experience in these areas, so please take all of this in the spirit of me trying to strenghten your case, and not just being skeptical or resistant to change.

@bkardell
Copy link
Contributor

bkardell commented Mar 3, 2020

Perhaps I'm not understanding what you mean by keyboard interactivity?

As in an interactive control that can be/is operated via keyboard...

@othermaciej
Copy link
Collaborator

Setting aside whether this functionality is useful or necessary, I think a way to set focus navigation start point without actually focusing anything should be a new method, not a new parameter to focus. The proposal overloads focus beyond its plain meaning. Often in such cases a new method is better. Furthermore, whole new methods are easier to feature test for, and it seems likely this functionality would need fallback when not supported.

@alice
Copy link
Contributor Author

alice commented Mar 3, 2020

@othermaciej

I think a way to set focus navigation start point without actually focusing anything should be a new method, not a new parameter to focus. The proposal overloads focus beyond its plain meaning.

The reason I proposed overloading focus() is that initially my thought was that focus() on an unfocusable element should have this behaviour, but that would break any code that assume that that should be a complete no-op.

I take your point about overloading focus beyond its current meaning, but in practice focus() is already being used this way when focus is sent to a non-interactive element in order to simulate this exact behaviour.

Adding a new method is definitely a possibility, although it risks being stuck forever in a naming bikeshed, for what I see as debatable value.

@domenic

It can trigger confusing focus styling/indication - both from :focus but additionally in some situations focus indication cannot be opted out of (such as when an operating system preference to always show focus is enabled).

:focus-visible is meant to handle this, right?

  1. Not in the case where the operating system preference to always show focus is enabled
  2. :focus-visible heuristics can be "fooled" by using keyboard shortcuts to show modal UI:
    Suppose you use a keyboard shortcut in a page to show a piece of modal UI, and focus is moved to a container div in order to simulate moving focus navigation start point to that div. Should :focus-visible match, or not? How could you know reliably?

... keyboard interactivity is "you can use the keyboard to navigate focus within the main content". E.g. pressing Tab takes you to the first focusable piece of main content. Similarly for side menus.

I agree with @bkardell's framing. Meaningful keyboard interactivity in this case is, approximately, "handles keyboard events" (modulo event delegation).

@alice
Copy link
Contributor Author

alice commented Mar 3, 2020

@othermaciej Apologies for not addressing this in my comment above:

seems likely this functionality would need fallback when not supported.

This is a good point.

The fallback, I believe, would default to either focusing the element (if it is focusable), or a no-op (if it is not).

Since the latter is what we want to avoid, we would need to seriously consider how to address that if we didn't add a new method.

@othermaciej
Copy link
Collaborator

othermaciej commented Mar 4, 2020

@alice I find I am confused about the situation. What is the current effect of focus() on a non-focusable element? Is it to move the focus navigation position without setting focus? Or is it a no-op? I understood your two recent comments to say both of those different things, so I'm probably not understanding correctly.

(Or is the distinction that some elements are focusable but non-interactive? And focusing interactive elements has an undesirable side effect? In which case, I think a new method is still a better design.)

@alice
Copy link
Contributor Author

alice commented Mar 4, 2020

@othermaciej Sorry for the confusion, let me clarify the comments which probably read that way:

in practice focus() is already being used this way when focus is sent to a non-interactive element in order to simulate this exact behaviour.

That looks like this:

<!-- non-interactive element with tabindex -->
<div id="container" style="display: none" tabindex="-1"> 
  <!-- interactive content goes in here -->
</div>
function openModal() {
    container.style.display = "block";
    // focus the container to allow moving focus into interactive content
    container.focus();  
}

So focus() on container is being used to get a similar effect to moving the FNSP, but only moves the FNSP in practice because the (non-interactive) element is also being focused.

The fallback, I believe, would default to either focusing the element (if it is focusable), or a no-op (if it is not).

The former case looks like:

<button id="button">Focusable element</button>
// if the setActiveElement option isn't supported, this will focus button as usual
button.focus({ setActiveElement: false });

The latter looks like:

<div id="container">  <!-- no tabindex -->
    <!-- interactive content goes here -->
</div>
// if the setActiveElement option isn't supported, this is a no-op:
// focus stays on the previous activeElement
container.focus({ setActiveElement: false });

Hope that clarifies things!

@othermaciej
Copy link
Collaborator

OK. So part of this is avoiding the need to carefully prepare a non-interactive focusable element, such by setting tabindex=-1 on a container. Other than that, is there an undesirable side effect from focusing such an element? Does it do something unwanted other than setting the focus navigation position?

@alice
Copy link
Contributor Author

alice commented Mar 4, 2020

@othermaciej

is there an undesirable side effect from focusing such an element?

I address that in earlier comments:

#5326 (comment)

It can trigger confusing focus styling/indication - both from :focus but additionally in some situations focus indication cannot be opted out of (such as when an operating system preference to always show focus is enabled).

Focus indication is only helpful (IMO) when the focused element has some kind of keyboard interactivity.

#5326 (comment)

:focus-visible is meant to handle this, right?

Not in the case where the operating system preference to always show focus is enabled.

:focus-visible heuristics can be "fooled" by using keyboard shortcuts to show modal UI:
Suppose you use a keyboard shortcut in a page to show a piece of modal UI, and focus is moved to a container div in order to simulate moving focus navigation start point to that div. Should :focus-visible match, or not? How could you know reliably?

@patrickhlauke
Copy link
Member

patrickhlauke commented Mar 9, 2020

wondering naively if all that would be required (but of course, throughout all user agents) is changing the behavior of focus() itself to be more like "if it's a focusable element, move active element and focus navigation start point; if the target isn't focusable (it's not an interactive control, or an arbitrary element blessed with tabindex) just move the focus navigation start point and unfocus the currently active element" ?

i.e. i can't currently think of a situation where i'd need to move focus navigation start point to a focusable element without wanting to also set focus to it (unless i wanted to park the start point "just before" it, but then i'd generally want to target something preceding)

@alice
Copy link
Contributor Author

alice commented Mar 9, 2020

Rob and I thought it might be an issue if existing code expects focus() on an unfocusable element to be a no-op. Hence needing to opt-in with the extra parameter.

@AutoSponge
Copy link

Setting tabindex=-1 on the focus target (and removing focus indication CSS) should be all you need if JavaScript is available to call el.focus().

If JavaScript is not available and you want to set a different "start point", a new tag or attribute would be needed and processed by the browser.

For instance, let's say that I have a static site with several deep-linking pages. User starts on the homepage and gets the normal experience (start point is document.body). Then uses a link to navigate to a subpage. With "start point" set, while the referrer is the same origin and location is not the baseUrl, focus begins at the "start point" (I imagine it to be main or whatever "skip to main content" points to).

What this looks like in code could be <meta rel="start-point" content="#main">. There should also be a user setting, "prefers-natural-start-point" that ignores the directive.

@patrickhlauke
Copy link
Member

Setting tabindex=-1 on the focus target (and removing focus indication CSS) should be all you need if JavaScript is available to call el.focus().

but then the thing has focus, which is distinct from it being where the focus start point is.

i'd have to test, so i may be talking rubbish here, but from memory when a container with tabindex="-1" receives focus, AT will start to read out the entirety of the content, unbroken (or at least it will start to do so), and that behavior is, i seem to remember, different from moving the focus start point/reading cursor somewhere (in that a user in the latter case can still decide at any point to stop, backtrack, etc, which is not the case when the AT was reading out all the content of the thing that has focus).

@robdodson
Copy link

@AutoSponge

Setting tabindex=-1 on the focus target (and removing focus indication CSS) should be all you need if JavaScript is available to call el.focus().

It's true that this works today, but we would argue that it's kind of a hack that developers have to be taught because there is no standard way of setting the focus start point. Internally, browsers have the ability to move the focus start point and it would be useful to expose this to developers. Otherwise, on a large app, there might be many instances where they need to sprinkle outline: none; throughout their CSS to emulate this effect.

@hidde
Copy link
Member

hidde commented Mar 10, 2020

As someone who has found himself setting focus to <div tabindex="-1"/> with outline: none before when building UI components in design systems for clients, I see great benefit in having a specific, non-hacky feature built into HTML for this situation.

Example cases I can think of: modal window, expand/collapse functionality and tabs (if not following ARIA Authoring Practices for focus management).

  • In all of those cases, moving focus to a containing div rather than an interactive element within it, helps to build a more abstract component that needs to know little about its contents. As the person building the component (and the focus management), you don't necessarily know what kinds of contents consumers of your component (e.g. other teams) are going to include, so moving focus to a containing div is usually the safest bet.
  • In all of those cases, as a front-end developer, I would expect push back from my designer counterparts if there was visible focus indication, because the indicated area can be quite large and making it visible can be confusing for people who don't use their keyboard (Kind of a use case for focus-visible, but I agree with both of @alice's earlier comments on that)

@muan
Copy link
Member

muan commented Mar 13, 2020

I agree that this would be a very helpful API to have.

Setting tabindex="-1" has too many side-effects as @alice mentioned, and they turn something that should be as simple as "now you start from here" to an annoying "focus exception" where styles don't apply and element isn't "interactive" in the sense that native interactive content is.

Using the current pattern, developers have to consider the following steps:

  1. Marking an element to be the start point
  2. Setting tabindex="-1" on the element
  3. Call focus()
  4. Ensuring focus outline does not apply to this element
  5. Install a one time blur handler to remove the tabindex

Making this pattern re-usable is very difficult for a large scale website. In these steps, I'd say step 1 alone is already a big task and quite a burden to maintain. And for step 2 to 5, the developer will have to consider how to handle the conditional cases if the start point is already an interactive element v.s. not. This is further complicated by how "interactive element" isn't always "focusable" (#4464).


For example, I've been working on adding focus management to the file list on GitHub's repository page. Currently when user navigates through the file directories with a keyboard, each navigation drops user's focus back to the top of the page.

The most basic solution would be to set the file table as the start point. However, with the current recommended pattern, we have to go through that 5 steps above. With this new API, it'll be:

  1. Call table.focus({ setActiveElement: false })

This one line solution would immediately make the experience 10 times better.

If we want to further enhance the experience, we can write more code to track which directory user came from, and is going to, and move focus to the links themselves, but that's immediately a much more complex solution, and requires developers to know exactly what goes into this piece of UI, therefore won't be shareable to other parts of the site. Whereas the proposed solution can be easily applied to other places with/without interactive content in them.

I think this convenience is much needed considering that it is very rare for businesses to put development resource into designing a keyboard/screen reader specific experience.


With regards to using focus() v.s. introducing a new method, in my opinion this deserves a new method. Putting aside "overloading focus beyond its plain meaning" as @othermaciej mentioned, this also adds confusing expectations like "should focus event get fired?" I'd assume not, but didn't I just call focus() on something?

For clarity sake, I think it'd be the most ideal to separate their responsibilities, especially considering focus is already a complex domain.

@alice
Copy link
Contributor Author

alice commented Mar 14, 2020

@muan Thank you so much for this detailed motivating example! Would you mind if I copied parts of it up to the issue description (with a link to your comment)?

Regarding introducing a new method, good point about focus events, I hadn't considered that.

What might a good method name be for the new method?

This also prompts me to think we might want an API to get the focus navigation start point (just like we can get the active element), as well; that might play into the design.

@muan
Copy link
Member

muan commented Mar 14, 2020

Thank you so much for this detailed motivating example! Would you mind if I copied parts of it up to the issue description (with a link to your comment)?

Not at all. I’m glad it’s helpful.

What might a good method name be for the new method?

Big question 😬. To start with, I hope that it’ll communicate not just setting the start point, but also the fact that focus will be taken away from the current active element. I’ll read through the spec to see if I can think of something that makes sense.

@BoCupp-Microsoft
Copy link

I like the direction this issue is heading, but wanted to call out that is seems like not all browsers have separate state to track the location of a focus navigation starting point.

The canonical example given in the HTML spec of how a user can set a focus navigation starting point is by clicking somewhere.

Clicking somewhere also moves the selection, and in Firefox it looks like the location of selection is what represents the concept of the focus navigation starting point (either that or moving selection also moves the focus navigation starting point such that the two locations cannot be distinguished).

So one candidate for the API you want could be Selection.collapse - seems to work today in Firefox.

Some other benefits of using selection to represent the focus navigation start point:

  1. Selection also determines where the first active match will be if the user performs a find-on-page operation. Maybe one API to set the user's "point of interest" might be easier to use than an API that sets the independent states that should follow the user's point of interest.
  2. Selection represents its position with a range. Test cases like this one that remove the currently focused element or a user clicking on the text between two links make me think that the API shape should track the focus navigation starting point with something like a range and not an element. This is how its implemented in Chromium today.

So some questions to consider:

  1. Should there be an API to set a focus navigation starting point which is separate from selection? An alternative is that we standardize selection as the focus navigation starting point.
  2. If we do create a separate API, should repositioning selection be something that updates the focus navigation starting point?
  3. Also, if we create a separate API, should the inputs be a node and offset pair?

Here's a test page if you want to try out the interaction between selection and the focus navigation starting point.

@emilio
Copy link
Contributor

emilio commented Mar 9, 2022

See also #7657 about the selection vs. focus distinction.

@Rich-Harris
Copy link

At the risk of making a '+1' comment, I've also encountered this need in two separate contexts. The first is SPA navigation where the link is to /other-page#foo — the SPA will navigate to /other-page via history.pushState and scroll to an element like <h2 id="foo"> (if it exists), but the <h2> doesn't become the SFNSP as it would in an MPA navigation unless we resort to hacks. (Perhaps the Navigation API will solve this?)

The second is the case where a <dialog> is shown. We want the start point to be restored when the dialog is closed, but that's not what happens. Ideally this would happen automatically, I think (is this under discussion elsewhere, or appropriate for discussion?), but in the absence of that behaviour the ability to reset the start point would (IMHO) make <dialog> more accessible. Many examples online suggest storing document.activeElement when the dialog is opened and resetting it when the dialog is closed, but that assumes the dialog was opened as a result of interacting with a focusable element, which isn't necessarily the case.

Of course, in order to set the start point in that case, we also need to know what it was before the dialog was opened. So if we're talking about adding a new method for setting the start point, should we also consider a counterpart method for getting the current start point?

const element = document.getFocusStartPoint();

// later
document.setFocusStartPoint(element);

For anyone who stumbled upon this thread looking for existing solutions, this is what I'm currently doing, until someone tells me they're harmful:

function getFocusStartPoint() {
  return window.getSelection()?.focusNode.parentElement;
}

function setFocusStartPoint(element) {
  const tabindex = element.getAttribute('tabindex');

  element.setAttribute('tabindex', '-1');
  element.focus();
  element.blur();

  if (tabindex) {
    element.setAttribute('tabindex', tabindex);
  } else {
    element.removeAttribute('tabindex');
  }
}

@nimahkh
Copy link

nimahkh commented Jun 13, 2022

At the risk of making a '+1' comment, I've also encountered this need in two separate contexts. The first is SPA navigation where the link is to /other-page#foo — the SPA will navigate to /other-page via history.pushState and scroll to an element like <h2 id="foo"> (if it exists), but the <h2> doesn't become the SFNSP as it would in an MPA navigation unless we resort to hacks. (Perhaps the Navigation API will solve this?)

The second is the case where a <dialog> is shown. We want the start point to be restored when the dialog is closed, but that's not what happens. Ideally this would happen automatically, I think (is this under discussion elsewhere, or appropriate for discussion?), but in the absence of that behaviour the ability to reset the start point would (IMHO) make <dialog> more accessible. Many examples online suggest storing document.activeElement when the dialog is opened and resetting it when the dialog is closed, but that assumes the dialog was opened as a result of interacting with a focusable element, which isn't necessarily the case.

Of course, in order to set the start point in that case, we also need to know what it was before the dialog was opened. So if we're talking about adding a new method for setting the start point, should we also consider a counterpart method for getting the current start point?

const element = document.getFocusStartPoint();

// later
document.setFocusStartPoint(element);

For anyone who stumbled upon this thread looking for existing solutions, this is what I'm currently doing, until someone tells me they're harmful:

function getFocusStartPoint() {
  return window.getSelection()?.focusNode.parentElement;
}

function setFocusStartPoint(element) {
  const tabindex = element.getAttribute('tabindex');

  element.setAttribute('tabindex', '-1');
  element.focus();
  element.blur();

  if (tabindex) {
    element.setAttribute('tabindex', tabindex);
  } else {
    element.removeAttribute('tabindex');
  }
}

Why remove the "tabindex" when it is not there? it has two conditions,
A- If tabindex is -1, so you can remove it
B- there is no tabindex attribute in the element, no need to remove what is not exist.

@Rich-Harris
Copy link

Why remove the "tabindex" when it is not there?

element.setAttribute('tabindex', '-1');

@patrickhlauke
Copy link
Member

patrickhlauke commented Jun 13, 2022

element.blur();

unless browsers error-correct here (and some seem to do), this undoes all the good you're doing before it and loses/resets focus/reading position back to the start of the document (certainly in combinations like Chrome/JAWS)

as an example, see this zenorocha/clipboard.js#805 (which has since been fixed in clipboard.js via zenorocha/clipboard.js#807)

@muan
Copy link
Member

muan commented Sep 15, 2023

Notes for TPAC, slide for context.

how does the current experience differs from the new experience and is it ideal?

If the element which was the focus start point is removed from the DOM, its parent becomes the focus start point. No more focus whack-a-mole! - Removing Headaches from Focus Management by Rob Dodson

In Focus management still matters by @smhigley, she mentions, and I paraphrase here: having good SFNSP is a general accessibility win, but support is poor (post includes a table of AT support), therefore the old hacky script is very much still required.

The magic and benefits to set SFNSP is currently reserved to the browser, and the point of this proposal is exposing this to developers.

why not actually focus on something?

@alice:

It can trigger confusing focus styling/indication - both from :focus but additionally in some situations focus indication cannot be opted out of (such as when an operating system preference to always show focus is enabled).

Focus indication is only helpful (IMO) when the focused element has some kind of keyboard interactivity.

@patrickhlauke:

when a container with tabindex="-1" receives focus, AT will start to read out the entirety of the content, ..., and that behavior is, i seem to remember, different from moving the focus start point/reading cursor somewhere (in that a user in the latter case can still decide at any point to stop, backtrack, etc, which is not the case when the AT was reading out all the content of the thing that has focus).

Regardless, I will attempt to write a polyfill to get AT user feedback (still need to investigate if it's possible to replicate the API experience, if not, I'd hope that we could try something behind flag in one of the browsers to allow AT users to test it).

Todo:

  • Polyfill or explore the possibility for an experimental feature to get AT user feedback
  • Getting standards positions (+1 from Chrome from TPAC)
  • Writing a draft spec

@muan
Copy link
Member

muan commented Sep 15, 2023

To answer "how does the current experience differs from the new experience and is it ideal?" more, cc @zcorpan, when focus is set on a container, everything in the container is read by AT, but when a virtual cursor (SFNSP) is set on an element, the element's name is read, instead of the full content, which is much preferable. cc @patrickhlauke for more feedback on AT behaviors.

I also just talked to ARIA WG about this and realized (probably should've earlier) that similar patterns with regards to current virtual cursor behavior already exist in a similar manner. For example fragment linking sets SFNSP, and moves virtual cursor to the element. If there was an core-AAM event that can be fired on activating SFNSP, then that essentially negates the need for AT to do anything to support this. I wonder if that was just missed around 2016 when Chrome added the element removal SFNSP setting. cc @tkent-google who seemed to had a hand in the implementation for Chrome 50.

I am not seeing this in AAM spec but I will dig deeper. cc @hidde / @spectranaut if you know where I should look. I will also try to track down engine engineers to see if they know how this is happening internally.

chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Sep 15, 2023
This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
@muan
Copy link
Member

muan commented Sep 17, 2023

Relevant WebKit code since it had the best support (from 2020):

SFNSP on

  1. Node removal:
    • code
    • behavior for node was removed from the document tree is not specified in the spec

  2. Fragment linking:
  3. mousepress on page

cc @cookiecrook, would love to have your view on this as well.

chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Sep 18, 2023
This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
aarongable pushed a commit to chromium/chromium that referenced this issue Sep 18, 2023
This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhangg@chromium.org>
Commit-Queue: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1197956}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Sep 18, 2023
This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhangg@chromium.org>
Commit-Queue: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1197956}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this issue Sep 18, 2023
This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhangg@chromium.org>
Commit-Queue: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1197956}
muan added a commit to muan/sfnsp that referenced this issue Sep 26, 2023
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Sep 28, 2023
…t, a=testonly

Automatic update from web-platform-tests
Implement setSequentialFocusStartingPoint

This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhangg@chromium.org>
Commit-Queue: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1197956}

--

wpt-commits: ac2aa5a07011181a9437a071786ded31431b6b73
wpt-pr: 42006
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified-and-comments-removed that referenced this issue Sep 29, 2023
…t, a=testonly

Automatic update from web-platform-tests
Implement setSequentialFocusStartingPoint

This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhanggchromium.org>
Commit-Queue: Joey Arhar <jarharchromium.org>
Cr-Commit-Position: refs/heads/main{#1197956}

--

wpt-commits: ac2aa5a07011181a9437a071786ded31431b6b73
wpt-pr: 42006

UltraBlame original commit: e31a9e86898a1b6b91b3acb6c3b792ba4d67166f
gecko-dev-updater pushed a commit to marco-c/gecko-dev-comments-removed that referenced this issue Sep 29, 2023
…t, a=testonly

Automatic update from web-platform-tests
Implement setSequentialFocusStartingPoint

This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhanggchromium.org>
Commit-Queue: Joey Arhar <jarharchromium.org>
Cr-Commit-Position: refs/heads/main{#1197956}

--

wpt-commits: ac2aa5a07011181a9437a071786ded31431b6b73
wpt-pr: 42006

UltraBlame original commit: e31a9e86898a1b6b91b3acb6c3b792ba4d67166f
ErichDonGubler pushed a commit to ErichDonGubler/firefox that referenced this issue Sep 30, 2023
…t, a=testonly

Automatic update from web-platform-tests
Implement setSequentialFocusStartingPoint

This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhangg@chromium.org>
Commit-Queue: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1197956}

--

wpt-commits: ac2aa5a07011181a9437a071786ded31431b6b73
wpt-pr: 42006
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified that referenced this issue Sep 30, 2023
…t, a=testonly

Automatic update from web-platform-tests
Implement setSequentialFocusStartingPoint

This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhanggchromium.org>
Commit-Queue: Joey Arhar <jarharchromium.org>
Cr-Commit-Position: refs/heads/main{#1197956}

--

wpt-commits: ac2aa5a07011181a9437a071786ded31431b6b73
wpt-pr: 42006

UltraBlame original commit: e31a9e86898a1b6b91b3acb6c3b792ba4d67166f
Lightning00Blade pushed a commit to Lightning00Blade/wpt that referenced this issue Dec 11, 2023
This is proposed here: whatwg/html#5326

Change-Id: I2c0c0ac578180ce5e1cf327fb65c6450a7d49f60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4866726
Reviewed-by: Di Zhang <dizhangg@chromium.org>
Commit-Queue: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1197956}
@daun
Copy link

daun commented Mar 29, 2024

The snippet above by @Rich-Harris works great in Chrome & Firefox.

Safari apparently still requires some handholding. The snippet below, though verbose, seems to work well across browsers.

It works by creating an invisible empty node as the first child of the target element, focussing it, then removing it from the document. This will remove focus, but most modern browsers will remember where focus was lost and restore focus when initiating keyboard navigation again.

function setFocusStartPoint(element) {
 const focusElement = createInvisibleFocusElement();
 element.prepend(focusElement);
 focusElement.focus({ preventScroll: true });
 focusElement.remove();
}

function createInvisibleFocusElement() {
  const element = document.createElement('div');
  element.setAttribute('tabindex', '-1');
  element.style.position = 'absolute';
  element.style.width = '1px';
  element.style.height = '1px';
  element.style.overflow = 'hidden';
  element.style.clip = 'rect(1px, 1px, 1px, 1px)';
  element.style.clipPath = 'inset(50%)';
  element.style.outline = 'none';
  return element;
}

@patrickhlauke
Copy link
Member

patrickhlauke commented Mar 29, 2024

It works by creating an invisible empty node as the first child of the target element, focussing it, then removing it from the document. This will remove focus, but most modern browsers will remember where focus was lost and restore focus when initiating keyboard navigation again.

note that this leads to focus resetting to the very start of the page when you're running the JAWS screen reader (just tested this with a throwaway codepen in Chrome/JAWS)

@daun
Copy link

daun commented Mar 29, 2024

@patrickhlauke Good to know! I assume this would apply equally to the original suggestion of using element.focus() directly followed by element.blur()?

@patrickhlauke
Copy link
Member

as noted here #5326 (comment) and yes still happens despite browser error correction

@daun
Copy link

daun commented Mar 29, 2024

@patrickhlauke Interesting! We'll keep tightening the screws on our solution and report back 🔩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests