I spent some time searching but have only seen too many regular "walk the DOM" blogs or answers that only go one level UP with getRootnode()
Pseudo code:
HTML
<element-x>
//# shadow-root
    <element-y>
        <element-z>
        //# shadow-root
        let container = this.closest('element-x');
        </element-z>
    </element-y>
</element-x>
The standard element.closest() function does not pierce shadow boundaries;
So this.closest('element-x') returns null because there is no <element-x> within <element-z> shadowDom
Goal:
Find <element-x> from inside descendant <element z> (any nested level)
Required:
A (recursive) .closest() function that walks up the (shadow) DOMs and finds <element-x>
Note: elements may or may not have ShadowDOM (see <element y>: only lightDOM)
I can and will do it myself tomorrow; just wondered if some bright mind had already done it.
Resources:
This is the UNminified code from the answer below:
        closestElement(selector, base = this) {
            function __closestFrom(el) {
                if (!el || el === document || el === window) return null;
                let found = el.closest(selector);
                if (found)
                  return found;
                else
                  __closestFrom(el.getRootNode().host);
            }
            return __closestFrom(base);
        }
I changed it to a method on my BaseElement:
  closestElement(selector, el = this) {
    return (
      (el && el != document && el != window && el.closest(selector)) ||
      this.closestElement(selector, el.getRootNode().host)
    );
  }
                This does the same as .closest() from inside any child (shadow)DOM
but walking up the DOM crossing shadowroot Boundaries
Optimized for (extreme) minification
//declared as method on a Custom Element:
closestElement(
    selector,      // selector like in .closest()
    base = this,   // extra functionality to skip a parent
    __Closest = (el, found = el && el.closest(selector)) => 
        !el || el === document || el === window
            ? null // standard .closest() returns null for non-found selectors also
            : found 
                ? found // found a selector INside this element
                : __Closest(el.getRootNode().host) // recursion!! break out to parent DOM
) {
    return __Closest(base);
}
Note: the __Closest function is declared as 'parameter' to avoid an extra let declaration... better for minification, and keeps your IDE from complaining
Called from inside a Custom Element:
<element-x>
//# shadow-root
    <element-y>
        <element-z>
        //# shadow-root
        let container = this.closestElement('element-x');
        </element-z>
    </element-y>
</element-x>
                        Excellent examples! Wanted to contribute a TypeScript version that has a minor difference -- it follows assignedSlot while traversing up the shadow roots, so you can find the closest matching element in a chain of nested, slotted custom elements. It's not the fanciest way to write the TypeScript, but it gets the job done.
closestElement(selector: string, base: Element = this) {
  function __closestFrom(el: Element | Window | Document): Element {
    if (!el || el === document || el === window) return null;
    if ((el as Slotable).assignedSlot) el = (el as Slotable).assignedSlot;
    let found = (el as Element).closest(selector);
    return found
      ? found
      : __closestFrom(((el as Element).getRootNode() as ShadowRoot).host);
  }
  return __closestFrom(base);
}
The equvalent in JS is:
closestElement(selector, base = this) {
    function __closestFrom(el) {
        if (!el || el === document || el === window)
            return null;
        if (el.assignedSlot)
            el = el.assignedSlot;
        let found = el.closest(selector);
        return found
            ? found
            : __closestFrom(el.getRootNode().host);
    }
    return __closestFrom(base);
}
                        Something like this should do the trick
function closestPassShadow(node, selector) {
    if (!node) {
        return null;
    }
    if (node instanceof ShadowRoot) {
        return this.closestPassShadow(node.host, selector);
    }
    if (node instanceof HTMLElement) {
        if (node.matches(selector)) {
            return node;
        } else {
            return this.closestPassShadow(node.parentNode, selector);
        }
    }
    return this.closestPassShadow(node.parentNode, selector);
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With