npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

shadow-observer

v0.1.7

Published

A MutationObserver that automatically picks up Shadow Roots when/if needed

Readme

shadow-observer

Cover image: AI-generated art (Cursor), 2026. This file is documentation-only and is not covered by the MIT license on the package source code; see LICENSE for software terms.


TL;DR - I got tired in here, I needed to move forward, enjoy 👋

MutationObserver does not cross into shadow trees. This package provides ShadowObserver, a small subclass that patches Element.prototype.attachShadow so that when you observe a node with subtree: true and a shadow mask, matching author shadow roots created later under that subtree are observed with the same options.

Use ShadowObserver instead of the global MutationObserver everywhere: shadow forwarding is strictly opt-in. Unless observe is called with subtree: true and a shadow property set to a supported mask (true, OPEN, CLOSED, or OPEN | CLOSED), the class does not register any shadow hook for that observation—it just delegates to MutationObserver.prototype.observe, so callbacks and options behave like the native observer.

Loading the module still installs one global attachShadow wrapper (so the feature can work when you do opt in); with no matching registrations, that wrapper is effectively a no-op aside from a small loop over empty state.

Use it when you control load order and care about programmatic attachShadow in the same JavaScript realm (for example observing document or a known app root).

Usage

Load the module before application code that might call attachShadow (or keep a reference to the native function), so the patch is in place.

import { ShadowObserver, OPEN, CLOSED } from 'shadow-observer';

const observer = new ShadowObserver((records) => {
  // …same as MutationObserver
});

observer.observe(document.documentElement, {
  subtree: true,
  childList: true,
  shadow: OPEN | CLOSED, // follow open and closed author roots
});

subtree still does not reach into existing shadow trees from the outer target: each MutationObserverInit flag applies per observed root. This package only adds a matching observe on new programmatic shadow roots, passing the same options there—so for example attributes: true affects attribute mutations on that root’s subtree inside that shadow tree, not “through” the light-DOM subtree from document alone.

shadow option

Registration only runs when subtree is true and the options object includes the shadow key (even if the value is later ignored).

  • true — same as OPEN only.
  • OPEN — forward only when attachShadow creates an open root (including omitted mode, which defaults to "open" per the DOM spec).
  • CLOSED — forward only for closed roots.
  • OPEN | CLOSED — forward for both open and closed programmatic roots (bitmask of the two exports).

Other values are ignored for shadow forwarding (the normal observe call still runs).

Exports

  • ShadowObserver — extends MutationObserver; use observe as above.
  • OPEN, CLOSED — bit flags for the shadow option.

In scope

  • Programmatic Element.attachShadow on hosts that are descendants (in the composed ancestor sense) of the node you passed to observe.
  • Omitted ShadowRootInit.mode treated as "open".
  • Closed author shadow roots when the shadow mask includes CLOSED (or OPEN | CLOSED). For mode: "closed", Element.shadowRoot is null for outside code, but this module still observes the ShadowRoot return value from inside the attachShadow patch—it does not rely on querying the host.

Out of scope

  • Declarative Shadow DOM and any shadow tree that is not created through the patched attachShadow (no hook runs, so no automatic observe on that root).
  • Non-author (user-agent) and other shadow trees that are never created through author Element.prototype.attachShadow in this realm, so no ShadowRoot is returned through the patch (open vs. closed is irrelevant to this limitation).
  • Other realms (e.g. iframes): each frame has its own Element.prototype; this patch applies only where the module runs.
  • Shadow roots already attached before you call observe (no retroactive attach event).

License

MIT