Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Open Web Components profile imageWestbrook Johnson
Westbrook Johnson forOpen Web Components

Posted on

     

Mind the `document.activeElement`!

The element that currently hasfocus in your HTML at any point in time can be accessed asdocument.activeElement. If you don't know, now you know!

What's more, while it can be difficult to capture the value of this property while debugging, at least without changing it, you can leverage browsers that allow you to"watch live expressions" to keep the current value of this property available at all times, 😱. No, really, go check it out right now!

There are lots of ways you can leverage this in your work, whether in functional code, unit tests or debugging, but I'm not looking to walk you through all the things that should be, can be, or will be in this area. However, if you're already using this value, I'd love to hear more about it in the comments. My usage can definitely be super-powered by hearing great workflows from others, so I look forward to hearing what you've got up your sleeves.

This isn't the  raw `document` endraw  you're looking for...

We are gathered here, today, to go a little bit deeper on whatdocument means and whenthedocument isn't the “document”0 you're looking for and what to do in that case.

Out of the shadows a newdocument rises...

Do you find yourself using code like the following to attach a shadow root to elements in your application?

el.attachShadow({mode:'open'});
Enter fullscreen modeExit fullscreen mode

Do you find yourself attaching that shadow root to custom elements that you've defined?

classCustomElementextendsHTMLElement{}customElement.define('custom-element',CustomElement);
Enter fullscreen modeExit fullscreen mode

Then you're already using web components.

If not, I highly recommend them in many and varied use cases! The benefits I've gained from working with custom elements and shadow DOM from well before both APIs were even supported by two browsers,let alone all of them, are all positive, and the full possibilities of this sometimes wholely different paradigm of client-side development are still only beginning to be fully explored.

If you're ready to start exploring them too, check outWeb Components: from zero to hero, an amazing introduction to these technologies byPascal Schilp, and you'll be well on the way.

When creating your own custom element with their own shadow roots, you're getting a "DOM subtree that is rendered separately from a document's main DOM tree". A subtree that is separate from thedocument: adocument to itself. Inside of that subtree, you get encapsulation for whatever DOM lives therein from external selectors, a special HTMLslot API for composing DOM from the outside of the element, and much more. However, when minding thedocument.activeElement, it is important to look a little deeper at the specific cost that we pay to get these new capabilities.

document.activeElement points to the element in thedocument that currently hasfocus, but what happens when that element isn't actually in thedocument? If your shadow DOM has focusable elements internal to it, and one of those elements currently hasfocus,document.activeElement (like all other selectors) will not be able to point directly to it. What it will point to is the first element in thedocument that includes a shadow DOM. So, taking the following tree into account:

<document><body><h1>Title</h1><custom-element>      #shadow-root<h2>Sub-title</h2><other-custom-element>          #shadow-root<ahref="#">This is a link</a><!-- The link _has_ focus -->
Enter fullscreen modeExit fullscreen mode

When the<a> element above is focused anddocument.activeElement is referenced, the value returned will point to the<custom-element> just below the<h1>; not the<a>, not the<other-custom-element> that is its parent, and likely, not what you expected.

A brave new world

Oh no, shadow DOM broke the internet!
- alarmist JS (framework) user

Well, in a word, "no".

With more nuance... shadow DOM has broken the assumption that the specifics offocus in any one component will bleed into all other components, so yes the fragile, fly by night, shoot from the hip internet that was previously the only option available to use isbroken if you choose to use shadow DOM and the shadow boundaries that they create. However, if you choose to use shadow DOM and the shadow boundaries that they create, you now have access to a more nuanced, controllable, and refined DOM than ever before. Yes, some things that you may have taken for granted in the past may be a little different than you remember, but you also have access to capabilities that were previously impossible or prohibitively complex.

But... if I can't see what the currently focused element is, whatwill I do?

While inside a shadow root,document.activeElement will not allow you to see if any other elements in the subtree are currently focused, yes. However, from the root of a subtree, we now haveshadowRoot.activeElement available to us when we desire to find the focused element in our current subtree. This means that instead of having to worry about the entire document (both above and below your current component), you can take into consideration only the DOM belonging to the subtree related to the current component.

OK, how do I leverage this?

I feel you start to think, "ok, that sounds like I could find a way to process this as being cool after ruminating on it for a while, but how do I figure out what shadow root I'm in?", and that's a great question! The answer is in thegetRootNode() method that has been added toElement as part of the introduction of shadow DOM. With this method, you will be given the root of the DOM tree in which the element you calledgetRootNode() on lives. Whether what is returned is the actualdocument or an individualshadowRoot its member propertyactiveElement will allow you to know what element in that tree is currently focused.

Let's revisit our sample document from above to better understand what this means...

<document><body><h1>Title</h1><custom-element>      #shadow-root<h2>Sub-title</h2><other-custom-element>          #shadow-root<ahref="#">This is a link</a><!-- The link _has_ focus -->
Enter fullscreen modeExit fullscreen mode

When you have a reference to the<a> element therein:

constroot=a.getRootNode();console.log(root);// otherCustomElement#shadowRootconstactiveElement=root.activeElement;console.log(activeElement);// <a href="#"></a>
Enter fullscreen modeExit fullscreen mode

When you have a reference to the<h2> element therein:

constroot=h2.getRootNode();console.log(root);// customElement#shadowRootconstactiveElement=root.activeElement;console.log(activeElement);// <other-custom-element></other-custom-element>
Enter fullscreen modeExit fullscreen mode

And, when you have a reference to the<body> element therein:

constroot=body.getRootNode();console.log(root);// documentconstactiveElement=root.activeElement;console.log(activeElement);// <custom-element></custom-element>
Enter fullscreen modeExit fullscreen mode

But, a componentshould have some control of its children, right?

I completely agree! But, in the context of a free and singledocument "some" control becomescomplete and total control.

Not just

In the case of shadow DOM encapsulated subtrees, the control that a parent has over its children is only the control that said child offers in the form of its public API. If you don't want to cede any control to a parent element implementing your custom element, you do not have to. Much like the first night you stayed out past curfew, this will surprise most parents accustomed to a level of control they maybe never should have had.

  • Will they get the number to your new cell phone?
  • Will you pick up when they call?
  • Will you still come home for dinner on Sunday nights?

All these questions and more are yours to answer via the attributes, properties, and methods that your elements surface to the public. Take care to respect your parents, but don't think that you have to become a doctor/lawyer/the President just because your mother said you should.

The components are alright

In this way, we might address the following simplification of the DOM we've reviewed through much of this article:

<document><body><h1>Title</h1><other-custom-element>      #shadow-root<ahref="#">This is a link</a><!-- The link _has_ focus -->
Enter fullscreen modeExit fullscreen mode

When accessingdocument.activeElement from the outside, again we will be returnedother-custom-element in reverence of the constrained control we now have over our once singulardocument. In this context, we may want to forward aclick event into our focused element, however not having direct access to the anchor tag through the shadow boundary, we'd be callingclick() onother-custom-element. By default, this type of interaction on the shadow DOM ofother-custom-element would be prevented. In the case that we wanted this sort of thing to be possible, we could build the following extension of theclick() method into ourother-custom-element element to pass theclick into its child:

click() {  this.shadowRoot.querySelector('a').click();}
Enter fullscreen modeExit fullscreen mode

But what about the case where there are more than one anchor tags inside of another-custom-element?

<other-custom-element>  #shadow-root    <a href="#">This is a link</a>    <a href="#">This is also a link</a> <!-- The link _has_ focus -->
Enter fullscreen modeExit fullscreen mode

In this case, we can take advantage of theactiveElement accessor on a shadow root and target the correct anchor tag as follows to make an even more flexible custom element implementation:

click() {  this.shadowRoot.activeElement.click();}
Enter fullscreen modeExit fullscreen mode

From here, there are any number of next steps that you can take to produce your own powerful custom elements that leverage the encapsulation offered by the shadow DOM to structure more nuanced, yet eminently powerful APIs to surface to users of your components. As you find patterns that work well for you, I'd love to hear about them in the comments below. If you're interested in uses of theactiveElement property in the wild, I invite you to checkoutSpectrum Web Components where we are actively reviewing the use of this and many other practices to power our growing web component implementation of theSpectrum, Abode's design system.

Top comments(3)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
ianemv profile image
Ian Villanueva
  • Joined

Is there an event inside the web component to detect that is not the current activeElement?

CollapseExpand
 
westbrook profile image
Westbrook Johnson
  • Location
    Brooklyn
  • Work
    Founding Frontend Engineer at something new
  • Joined

Afocusout event will be dispatched (and bubble, whereasblur events do not) when an element loses focus. This mirrors thefocusin event that would have told you that an element has gained focus. You can checkoutevent.composedPath()[0] to ensure you reference the element from which this even originated. Check outopen-wc.org/faq/events.html for more info on handling events in custom elements and across shadow DOM boundaries.

Is that what you're looking for?

At any one time, the following code can compare that:

const el = ... ; // how ever you gain access to the element in questionconst root = el.getRootNode();const { activeElement } = root;const isElFocused = el === activeElement;
CollapseExpand
 
ianemv profile image
Ian Villanueva
  • Joined

I think this is it. What I wanted is, to know that my component loses focus so I can toggle or trigger other events.

For now what I did is add a focusable element such as button to check if that is on focus and if focus out, trigger the event I wanted to do (basically just hide a pop-up div)

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

More fromOpen Web Components

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp