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 🙏

© 2024 – Pkg Stats / Ryan Hefner

shadow-dom-selector

v5.0.1

Published

A very small JavaScript utility to query DOM elements through the Shadow Dom subtrees in a sync or an async way

Downloads

492

Readme

shadow-dom-selector

A very small JavaScript utility to query DOM elements through the Shadow DOM subtrees in a sync or an async way.

Deployment Status Tests Coverage Status npm version downloads

Introduction

Having a DOM tree formed of Shadow DOM subtrees like the next one:

<body>
  <section>
    #shadow-root (open)
      <h1>Title</h1>
      <article>
        #shadow-root (open)
          <h2>Subtitle</h2>
          <ul>
            <li>Item 1</li>
            <li>Item 2</li>
            <li>Item 1</li>
          </ul>
      </article>
  </section>
</body>

If one wants to query for the li elements, it is required to do this:

const firstLi = document.querySelector('section').shadowRoot.querySelector('article').shadowRoot.querySelector('ul > li');

const allLis = document.querySelector('section').shadowRoot.querySelector('article').shadowRoot.querySelectorAll('ul > li');

const shadow = document.querySelector('section').shadowRoot.querySelector('article').shadowRoot;

shadow-dom-selector allows you to do the same in the next way:

// $ character at the end of a selector means to select its Shadom DOM

import {
  querySelector,
  querySelectorAll,
  shadowRootQuerySelector,
  deepQuerySelector,
  deepQuerySelectorAll
} from 'shadow-dom-selector';

const firstLi = querySelector('section$ article$ ul > li');
const allLis = querySelectorAll('section$ article$ ul > li');
const shadow = shadowRootQuerySelector('section$ article$');

const deepFirstLi = deepQuerySelector('li');
const deepAllLi = deepQuerySelectorAll('li');

It will traverse all the Shadow DOM subtrees removing all the hassle of writing long concatenated queries.

It checks for the existence of elements

With the same previous DOM tree, if we do this:

const element = document.querySelector('article').shadowRoot.querySelector('div').shadowRoot.querySelector('section > h1');

It will throw an error, because none of the elements in those queries exist. If you don‘t know if the elements exist or not, you will require to wrap all the code in conditions or use optional chaining if your target is ES2015 or greater:

// With conditions
const article = document.querySelector('article');
if (article) {
  const articleShadowRoot = article.shadowRoot;
  if (articleShadowRoot) {
    const div = articleShadowRoot.querySelector('div');
    if (div) {
      const shadow = div.shadowRoot;
      if (shadow) {
        const element = shadow.querySelector('section > h1');
        const elements = shadow.querySelectorAll('p');
      }
    }
  }
}

// With optional chaining in ES2015+
const shadow = document.querySelector('article')?.shadowRoot?.querySelector('div')?.shadowRoot;
const element = document.querySelector('article')?.shadowRoot?.querySelector('div')?.shadowRoot?.querySelector('section > h1');
const elements = document.querySelector('article')?.shadowRoot?.querySelector('div')?.shadowRoot?.querySelectorAll('p');

Which will return undefined if some element doesn‘t exist. With shadow-dom-selector, you just need to write the query and it will return the same that is returned by the native querySelector and querySelectorAll if the query cannot be satisfied.

import {
  querySelector,
  querySelectorAll,
  shadowRootQuerySelector,
  deepQuerySelector,
  deepQuerySelectorAll
} from 'shadow-dom-selector';

const shadow = shadowRootQuerySelector('article$ div$'); // null
const element = querySelector('article$ div$ section > h1'); // null
const elements = querySelectorAll('article$ div$ p'); // empty NodeList
const deepElement = deepQuerySelector('span') // null;
const deepElements = deepQuerySelectorAll('p'); // empty NodeList

Async queries

If the elements are not already rendered into the DOM in the moment that the query is made you will receive null. shadow-dom-selector allows you to wait for the elements to appear deciding how many times it will try to query for the element before giving up and returning null or an empty NodeList.

// Using the async methods
import {
  asyncQuerySelector,
  asyncQuerySelectorAll,
  asyncShadowRootQuerySelector,
  asyncDeepQuerySelector,
  asyncDeepQuerySelectorAll
} from 'shadow-dom-selector';

asyncShadowRootQuerySelector('article$ div$')
  .then((shadowRoot) => {
      // Do stuff with the shadowRoot
      // If it is not found after all the retries, it will return null
  });

asyncQuerySelector('article$ div$ section > h1')
  .then((h1) => {
      // Do stuff with the h1 element
      // If it is not found after all the retries, it will return null
  });

asyncQuerySelectorAll('article$ div$ p')
  .then((paragraphs) => {
      // Do stuff with the paragraphs
      // If they are not found after all the retries, it will return an empty NodeList
  });

asyncDeepQuerySelector('h1')
  .then((h1) => {
      // Do stuff with the h1 element
      // If it is not found after all the retries, it will return null
  });

asyncDeepQuerySelectorAll('p')
  .then((paragraphs) => {
      // Do stuff with the paragraphs
      // If they are not found after all the retries, it will return an empty NodeList
  });

// Using de AsyncSelector class
import { AsyncSelector } from 'shadow-dom-selector';

const selector = new AsyncSelector();

selector.query('article').$.query('div').$.element
  .then((shadowRoot) => {
    // Do stuff with the shadowRoot
    // If it is not found after all the retries, it will return null
  });

selector.query('article').$.query('div').$.query('section > h1').element
  .then((h1) => {
    // Do stuff with the h1 element
    // If it is not found after all the retries, it will return null
  });

selector.query('article').$.query('div').$.query('p').all
  .then((paragraphs) => {
    // Do stuff with the paragraphs
    // If they are not found after all the retries, it will return an empty NodeList
  });

selector.deepQuery('h1').element
  .then((h1) => {
    // Do stuff with the h1 element
    // If it is not found after all the retries, it will return null
  });

selector.deepQuery('p').all
  .then((paragraphs) => {
    // Do stuff with the paragraphs
    // If they are not found after all the retries, it will return an empty NodeList
  });

Either the async methods or the async dot notation allow you to to specify the amount of retries and the delay between each one of them. Consult the API section for more details.

Install

npm

npm install shadow-dom-selector

yarn

yarn add shadow-dom-selector

PNPM

pnpm add shadow-dom-selector

In the browser

It is possible to include a compiled version of the package directly in an HTML file. It will create a global ShadowDomSelector object containing all the exported functions that can be accessed from anywhere in your JavaScript code.

  1. Copy the JavaScript file shadow-dom-selector-web.js, located in the root of the dist/ folder
  2. Put it in the folder that you prefer in your web server
  3. Include it in your HTML file
<script src="wherever/you/want/to/place/shadow-dom-selector-web.js"></script>
/* There will be a global variable named ShadowDomSelector containing all the functions */
ShadowDomSelector.querySelector;
ShadowDomSelector.deepQuerySelector;
ShadowDomSelector.querySelectorAll;
ShadowDomSelector.deepQuerySelectorAll;
ShadowDomSelector.shadowRootQuerySelector;
ShadowDomSelector.asyncQuerySelector;
ShadowDomSelector.asyncDeepQuerySelector;
ShadowDomSelector.asyncQuerySelectorAll;
ShadowDomSelector.asyncDeepQuerySelectorAll;
ShadowDomSelector.asyncShadowRootQuerySelector;
ShadowDomSelector.AsyncSelector;

API

querySelector

Performs a query selection passing through the shadowRoot of elements if you add $ after them.

querySelector(selectors): Element | null;
querySelector(root, selectors): Element | null;

| Parameter | Optional | Description | | ------------ | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors may not end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document |

deepQuerySelector

Performs a query selection of an element searching for it recursively in the whole DOM tree even if it needs to pass through the shadowRoots of elements.

Note: use this method carefully, depending on the extension of your DOM tree, it could be an expensive task in terms of resources. Do this when you need to query for an element that could appear in any part of the DOM tree so you don‘t know its exact position, otherwise, the querySelector method is recommended.

deepQuerySelector(selectors): Element | null;
deepQuerySelector(root, selectors): Element | null;

| Parameter | Optional | Description | | ------------ | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors may not end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document |

querySelectorAll

Performs a querySelectionAll query passing through the shadowRoot of elements if you add $ after them.

querySelectorAll(selectors): NodeListOf<Element>;
querySelectorAll(root, selectors): NodeListOf<Element>;

| Parameter | Optional | Description | | ------------ | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors may not end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document |

deepQuerySelectorAll

Performs a querySelectionAll query of elements searching for them recursively in the whole DOM tree even if it needs to pass through the shadowRoots of elements.

Note: use this method carefully, depending on the extension of your DOM tree, it could be an expensive task in terms of resources. Do this when you need to query for elements that could appear in any part of the DOM tree so you don‘t know their exact position, otherwise, the querySelectionAll method is recommended.

deepQuerySelectorAll(selectors): NodeListOf<Element>;
deepQuerySelectorAll(root, selectors): NodeListOf<Element>;

| Parameter | Optional | Description | | ------------ | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors may not end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document |

shadowRootQuerySelector

Performs a query selection of a shadowRoot passing through the shadowRoot of elements if you add $ after them.

shadowRootQuerySelector(selectors): ShadowRoot | null;
shadowRootQuerySelector(root, selectors): ShadowRoot | null;

| Parameter | Optional | Description | | ------------ | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors must end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document |

asyncQuerySelector

Performs an asynchronous query selection passing through the shadowRoot of elements if you add $ after them.

asyncQuerySelector(selectors): Promise<Element | null>;
asyncQuerySelector(root, selectors): Promise<Element | null>;
asyncQuerySelector(selectors, asyncParams): Promise<Element | null>;
asyncQuerySelector(root, selectors, asyncParams): Promise<Element | null>;

| Parameter | Optional | Description | | ------------- | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors may not end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document | | asyncParams | yes | An object containing the parameters which control the retries |

// asyncParams properties
{
  retries?: number; // how many retries before giving up (defaults to 10)
  delay?: number; // delay between each retry (defaults to 10)
}

asyncDeepQuerySelector

Performs an asynchronous query selection of an element searching for it recursively in the whole DOM tree even if it needs to pass through the shadowRoots of elements.

Note: use this method carefully, depending on the extension of your DOM tree, it could be an expensive task in terms of resources. Do this when you need to query for an element that could appear in any part of the DOM tree so you don‘t know its exact position, otherwise, the asyncQuerySelector method is recommended.

asyncDeepQuerySelector(selectors): Promise<Element | null>;
asyncDeepQuerySelector(root, selectors): Promise<Element | null>;
asyncDeepQuerySelector(selectors, asyncParams): Promise<Element | null>;
asyncDeepQuerySelector(root, selectors, asyncParams): Promise<Element | null>;

| Parameter | Optional | Description | | ------------- | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors may not end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document | | asyncParams | yes | An object containing the parameters which control the retries |

// asyncParams properties
{
  retries?: number; // how many retries before giving up (defaults to 10)
  delay?: number; // delay between each retry (defaults to 10)
}

asyncQuerySelectorAll

Performs an asynchronous querySelectionAll query passing through the shadowRoot of elements if you add $ after them.

asyncQuerySelectorAll(selectors): Promise<NodeListOf<Element>>;
asyncQuerySelectorAll(root, selectors): Promise<NodeListOf<Element>>;
asyncQuerySelectorAll(selectors, asyncParams): Promise<NodeListOf<Element>>;
asyncQuerySelectorAll(root, selectors, asyncParams): Promise<NodeListOf<Element>>;

| Parameter | Optional | Description | | ------------- | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors may not end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document | | asyncParams | yes | An object containing the parameters which control the retries |

// asyncParams properties
{
  retries?: number; // how many retries before giving up (defaults to 10)
  delay?: number; // delay between each retry (defaults to 10)
}

asyncDeepQuerySelectorAll

Performs an asynchronous querySelectionAll query of elements searching for them recursively in the whole DOM tree even if it needs to pass through the shadowRoots of elements.

Note: use this method carefully, depending on the extension of your DOM tree, it could be an expensive task in terms of resources. Do this when you need to query for elements that could appear in any part of the DOM tree so you don‘t know their exact position, otherwise, the asyncQuerySelectorAll method is recommended.

asyncDeepQuerySelectorAll(selectors): Promise<NodeListOf<Element>>;
asyncDeepQuerySelectorAll(root, selectors): Promise<NodeListOf<Element>>;
asyncDeepQuerySelectorAll(selectors, asyncParams): Promise<NodeListOf<Element>>;
asyncDeepQuerySelectorAll(root, selectors, asyncParams): Promise<NodeListOf<Element>>;

| Parameter | Optional | Description | | ------------- | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors may not end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document | | asyncParams | yes | An object containing the parameters which control the retries |

// asyncParams properties
{
  retries?: number; // how many retries before giving up (defaults to 10)
  delay?: number; // delay between each retry (defaults to 10)
}

asyncShadowRootQuerySelector

Performs an asynchronous query selection of a shadowRoot passing through the shadowRoot of elements if you add $ after them.

asyncShadowRootQuerySelector(selectors): Promise<ShadowRoot | null>;
asyncShadowRootQuerySelector(root, selectors): Promise<ShadowRoot | null>;
asyncShadowRootQuerySelector(selectors, asyncParams): Promise<ShadowRoot | null>;
asyncShadowRootQuerySelector(root, selectors, asyncParams): Promise<ShadowRoot | null>;

| Parameter | Optional | Description | | ------------- | ------------- | -------------------------------------------------- | | selectors | no | A string containing one or more selectors to match. Selectors must end in a Shadow DOM ($) | | root | yes | The element from where the query should be performed, it defaults to document | | asyncParams | yes | An object containing the parameters which control the retries |

// asyncParams properties
{
  retries?: number; // how many retries before giving up (defaults to 10)
  delay?: number; // delay between each retry (defaults to 10)
}

AsyncSelector class

This class creates an instance of an element that could be used to perform asynchronous query selections in the DOM tree passing through the shadowRoot of elements.

new AsyncSelector();
new AsyncSelector(root);
new AsyncSelector(root, asyncParams);

| Parameter | Optional | Description | | ------------- | ------------- | -------------------------------------------------- | | root | yes | The element or shadowRoot from where the query should be performed, it defaults to document | | asyncParams | yes | An object containing the parameters which control the retries |

// asyncParams properties
{
  retries?: number; // how many retries before giving up (defaults to 10)
  delay?: number; // delay between each retry (defaults to 10)
}

The instances of this class have the next properties:

| Property | Type | Description | | ---------------- | ---------------------------------------- | ---------------------------------------------------------------- | | element | Promise<Element \| ShadowRoot \| null> | A promise that resolves in the queried element | | all | Promise<NodeListOf<Element>> | A promise that resolves in a Nodelist with all queried elements | | $ | Promise<ShadowRoot \| null> | A promise that resolves in the shadowRoot of the queried element | | asyncParams | Same asyncParams previously shown | An object containing the parameters which control the retries |

And the next methods:

| Method | Return | Description | | ----------------------------- | -------------------------- | ---------------------------------------------------------------- | | eq(index: number) | Promise<Element \| null> | Returns a promise that resolves in the element in the index position of the queried elements (startig from 0) | | query(selector: string) | AsyncSelector | Performs a query and returns a new AsyncSelector instance | | deepQuery(selector: string) | AsyncSelector | Performs a deep query (even through elements' shadowRoot) ans returns a new AsyncSelector instance |

Examples of the AsyncSelector class
const selector = new AsyncSelector(); // Starting to query in the document with the default asyncParams
await selector.element === document;
await selector.all; // Empty Node list
await selector.$; // null
await selector.eq(0); // null
const selector = AsyncSelector({
  retries: 100,
  delay: 50
}); // Starting to query in the document with custom asyncParams
await selector.query('section').$.element === document.querySelector('section').shadowRoot;
await selector.query('section').$.all; // Empty Node list
await selector.query('section').$.query('article').all === document.querySelector('section').shadowRoot.querySelectorAll('article');
await selector.query('section').$.query('ul li').eq(1) === document.querySelector('section').shadowRoot.querySelectorAll('ul li')[1];
await selector.deepQuery('li.delayed-li').element; // try to query the element deep in the shadowDOM tree until the retries/delay expire
await selector.deepQuery('li.delayed-li').all; // try to perform a querySelectorAll deep in the shadowDOM tree until the retries/delay expire
selector.query('section').$.query('article').asyncParams; // { retries: 100, delay: 50 }