@augment-vir/web
Version:
A collection of augments, helpers types, functions, and classes only for web (frontend) JavaScript environments.
88 lines (87 loc) • 3.09 kB
JavaScript
import { check } from '@augment-vir/assert';
import { stringify } from '@augment-vir/core';
/**
* Perform [`.querySelector()`](https://developer.mozilla.org/docs/Web/API/Document/querySelector)
* on the given element with support for elements that contain an open Shadow Root.
*
* @category Web : Elements
* @category Package : @augment-vir/web
* @package [`@augment-vir/web`](https://www.npmjs.com/package/@augment-vir/web)
*/
export function queryThroughShadow(element, rawQuery, options = {}) {
if (!rawQuery) {
if (element instanceof Element) {
return element;
}
else {
return element.host;
}
}
const query = check.isString(rawQuery) ? rawQuery : rawQuery.tagName;
const splitQuery = query.split(' ').filter(check.isTruthy);
if (splitQuery.length > 1) {
return handleNestedQueries(element, query, options, splitQuery);
}
else if ('shadowRoot' in element && element.shadowRoot) {
return queryThroughShadow(element.shadowRoot, query, options);
}
const shadowRootChildren = getShadowRootChildren(element);
if (options.all) {
const outerResults = Array.from(element.querySelectorAll(query));
const nestedResults = shadowRootChildren.flatMap((shadowRootChild) => {
return queryThroughShadow(shadowRootChild, query, options);
});
return [
...outerResults,
...nestedResults,
];
}
else {
const basicResult = element.querySelector(query);
if (basicResult) {
return basicResult;
}
else {
for (const shadowRootChild of shadowRootChildren) {
const nestedResult = queryThroughShadow(shadowRootChild, query, options);
if (nestedResult) {
return nestedResult;
}
}
return undefined;
}
}
}
function getShadowRootChildren(element) {
return Array.from(element.querySelectorAll('*'))
.filter((child) => !!child.shadowRoot)
.map((child) => child.shadowRoot);
}
function handleNestedQueries(element, originalQuery, options, queries) {
const firstQuery = queries[0];
/**
* No way to intentionally trigger this edge case, we're just catching it here for type
* purposes.
*/
/* node:coverage ignore next 7 */
if (!firstQuery) {
throw new Error(`Somehow the first query was empty in '[${queries.join(',')}]' for query '${stringify(originalQuery)}'`);
}
const results = queryThroughShadow(element, firstQuery, options);
if (queries.length <= 1) {
return results;
}
if (check.isArray(results)) {
return results
.flatMap((result) => {
return handleNestedQueries(result, originalQuery, options, queries.slice(1));
})
.filter(check.isTruthy);
}
else if (results) {
return handleNestedQueries(results, originalQuery, options, queries.slice(1));
}
else {
return undefined;
}
}