Skip to content

node.nextActiveElement & node.previousActiveElement #10

@ddamato

Description

@ddamato

I saw your talk last night at JSConf about this issue and I resonate with your pain. I've also had questions about how to get focus information and was up for most of the night thinking about it and your proposal. I appreciate all the leg work you've done to expose this issue.

I'd like you to consider changing a viewpoint for a moment. As someone well versed in custom elements, think about how that API is structured. It's very low-level and able to be built on top of for more robust and custom solutions. I propose that we only ask browser vendors to expose two properties for which I believe everything else you are asking for can be built from: node.nextActiveElement and node.previousActiveElement.

node.nextActiveElement is akin to node.nextElementSibling; it is a reference to the next element that would receive focus if the user was to tab to it from the current node. This is basically what the .next(el) method is asking for in your spec. The node in this case can also hold context. Meaning if the node represented a <dialog-box/>, we could trap the focus within the node. If you simply called dialogBox.nextActiveElement it would point to a reference to the next element to receive focus in the <dialog-box/>. In the case of custom elements, when we want to continue outside of the custom element to the next focusable element, it's just a call to document.nextActiveElement.

One of the reasons why I gravitate toward this approach is that the elements of the DOM may change over the lifecycle. What was once queried in the list of focusable elements at page load might not be the same as when you need to know the next element. React is a good example, where entire DOM trees may (dis)appear. Similar to node.nextElementSibling, it'll know which element that is next when referencing the current element in the current tree structure.

As for extending to an API that could be made outside of the browser vendors (as a package for example like your polyfill):

  • .hasFocusable() - node === document.activeElement
  • .isFocusable() - node.focus() && node === document.activeElement (you could return focus back to the previous node if you'd like by storing it before this check).
  • .next() - node.nextActiveElement
  • .previous() - node.previousActiveElement
  • .forward() - node.nextActiveElement.focus()
  • .backward() - node.previousActiveElement.focus()
  • .first() - On page load, document.activeElement and store it.
  • .last() - One page load, document.activeElement.previousActiveElement and store it.

I'm not sure what you'd use .isFocusable() for as a check prior to moving focus. I'd rather just attempt to focus the element that needs focus and then check if it was focused. Then I can move on accordingly if the focus was or wasn't achieved.

The .first() and .last() references might change over the lifecycle (as explained above) but I honestly can't find a use case for when you'd need the first or last focusable element on the page. The primary use case is exactly what you've described. Once the user has completed a task, you want to move them to the next task. You can dictate what the starting point is using tabindex="1" and then the last element should be the .previousActiveElement node at tabindex="1".

Managing a history should be trivial, you could just push and pop element references in an array as you listen to 'focusin' events on the document by reading from the event object provided in the callback.

document.addEventListener('focusin', (ev) => focusHistory.push(ev.target));

Naming the methods I'm certainly open to ideas for. I strayed from using the word "focus" because your API was using it heavily and I wanted to separate the ideas from each other in this explainer. I'd be fine with calling these references something more descriptive. I believe nextFocusableElement will work but nextElementFocus probably wont (nextElementFocus.focus() just sounds odd).

What do you think?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions