css-select
Version:
a CSS selector compiler/engine
90 lines • 3.91 kB
JavaScript
import * as boolbase from "boolbase";
import { cacheParentResults } from "../helpers/cache.js";
import { copyOptions } from "../helpers/options.js";
import { findOne, getNextSiblings } from "../helpers/querying.js";
import { includesScopePseudo, isTraversal } from "../helpers/selectors.js";
/** Used as a placeholder for :has. Will be replaced with the actual element. */
export const PLACEHOLDER_ELEMENT = {};
/**
* Check if the selector has any properties that rely on the current element.
* If not, we can cache the result of the selector.
*
* We can't cache selectors that start with a traversal (e.g. `>`, `+`, `~`),
* or include a `:scope`.
* @param selector - The selector to check.
* @returns Whether the selector has any properties that rely on the current element.
*/
function hasDependsOnCurrentElement(selector) {
return selector.some((sel) => sel.length > 0 &&
(isTraversal(sel[0]) || sel.some(includesScopePseudo)));
}
const is = (next, token, options, context, compileToken) => {
const compiledToken = compileToken(token, copyOptions(options), context);
return compiledToken === boolbase.trueFunc
? next
: compiledToken === boolbase.falseFunc
? boolbase.falseFunc
: (element) => compiledToken(element) && next(element);
};
/*
* :not, :has, :is, :matches and :where have to compile selectors
* doing this in src/pseudos.ts would lead to circular dependencies,
* so we add them here
*/
/** Pseudo selectors that compile nested selectors. */
export const subselects = {
is,
/**
* `:matches` and `:where` are aliases for `:is`.
*/
matches: is,
where: is,
not(next, token, options, context, compileToken) {
const compiledToken = compileToken(token, copyOptions(options), context);
return compiledToken === boolbase.falseFunc
? next
: compiledToken === boolbase.trueFunc
? boolbase.falseFunc
: (element) => !compiledToken(element) && next(element);
},
has(next, subselect, options, _context, compileToken) {
const { adapter } = options;
const copiedOptions = copyOptions(options);
copiedOptions.relativeSelector = true;
const context = subselect.some((s) => s.some(isTraversal))
? // Used as a placeholder. Will be replaced with the actual element.
[PLACEHOLDER_ELEMENT]
: undefined;
const skipCache = hasDependsOnCurrentElement(subselect);
const compiled = compileToken(subselect, copiedOptions, context);
if (compiled === boolbase.falseFunc) {
return boolbase.falseFunc;
}
// If `compiled` is `trueFunc`, we can skip this.
if (context && compiled !== boolbase.trueFunc) {
return skipCache
? (element) => {
if (!next(element)) {
return false;
}
context[0] = element;
const childs = adapter.getChildren(element);
return (findOne(compiled, compiled.shouldTestNextSiblings
? [
...childs,
...getNextSiblings(element, adapter),
]
: childs, options) !== null);
}
: cacheParentResults(next, options, (element) => {
context[0] = element;
return (findOne(compiled, adapter.getChildren(element), options) !== null);
});
}
const hasOne = (element) => findOne(compiled, adapter.getChildren(element), options) !== null;
return skipCache
? (element) => next(element) && hasOne(element)
: cacheParentResults(next, options, hasOne);
},
};
//# sourceMappingURL=subselects.js.map