5

I've been trying to detect whether :focus-within class was lost. I tried to detect click-outside of by using addEventListener for 'click', 'auxclick', 'blur', 'mouseup'. But I can't figure out how to detect a click outside of the actual document. For example, click on URL input. How can I solve it?

Daniel
  • 839
  • 10
  • 20
  • Could you explain more in details so that everyone can understand the question in an easy way and provide you best solution on it ? – kunal panchal Feb 04 '21 at 14:47
  • 3
    The event you need is `focusout` and the test you want to do when the event occurs is `event.target.matches(':focus-within')`. Actions that don't actually remove focus (like you describe) cannot be listened to. – connexo Feb 04 '21 at 14:59

1 Answers1

7

To detect whether a DOM element has lost :focus-within, you can use the focusout event like this:

containerElement.addEventListener('focusout', function(e) {
    if (e.currentTarget.contains(e.relatedTarget)) {
        /* Focus will still be within the container */
    } else {
        /* Focus will leave the container */
    }
});

When focus is lost by the page altogether (visiting an URL, switching tabs, etc.), e.relatedTarget doesn't exist so the code just works. If you want to ignore when the page loses focus, you can use document.hasFocus() to check:

containerElement.addEventListener('focusout', function(e) {
    /*
        If the document has lost focus,
        skip the containment check 
        and keep the element visible.
     */
    if (!document.hasFocus()) {
        return;
    }
    if (!e.currentTarget.contains(e.relatedTarget)) {
        hideSelf();
    }
});

...but then you have to react when the focus does get back to the page, so the full(er) solution looks something like this:

containerElement.addEventListener('focusout', function(e) {
    const self = e.currentTarget;
    /*
        If the document has lost focus,
        don't hide the container just yet,
        wait until the focus is returned.
     */
    if (!document.hasFocus()) {
        window.addEventListener('focus', function focusReturn() {
            /* 
                We want the listener to be triggered just once,
                so we have it remove itself from the `focus` event.
            */
            window.removeEventListener('focus', focusReturn);

            /*
                Test whether the active element 
                is within our container.
             */
            if (!self.contains(document.activeElement)) {
                hideSelf();
            }
        });
        return;
    }
    if (!self.contains(e.relatedTarget)) {
        hideSelf();
    }
});
Dan
  • 9,912
  • 18
  • 49
  • 70