UNPKG

@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
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; } }