blob: 29dd6756f4aa241b510b9df981422607dc4663fe (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
import {ParseSelector} from 'typed-query-selector/parser.js';
const _isHasSelectorSupported = globalThis.CSS?.supports('selector(:has(a))');
export const isHasSelectorSupported = (): boolean => _isHasSelectorSupported;
// Adapted from https://stackoverflow.com/a/35271017/288906
const hasSelectorRegex = /:has\(((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*)\)/;
export default function selectHas<Selector extends string, ExpectedElement extends HTMLElement = ParseSelector<Selector, HTMLElement>>(selectors: Selector | Selector[], baseElement: ParentNode = document): ExpectedElement | void {
const count = [...String(selectors).matchAll(/has\(/g)].length;
if (count !== 1) { // Only one :has allowed. KISS
throw new Error(`Only one \`:has()\` required/allowed, found ${count}`);
}
const parts = String(selectors).split(hasSelectorRegex);
const [baseSelector, hasSelector, finalSelector] = parts;
if (['', '*'].includes(baseSelector.trim())) {
throw new Error('* is super inefficient in :has()');
}
if (/\s$/.test(baseSelector)) {
throw new Error('No spaces before :has() supported');
}
if (/^[+~]/.test(hasSelector.trim())) {
throw new Error('This polyfill only supports looking into the children of the base element');
}
for (const base of baseElement.querySelectorAll<ExpectedElement>(baseSelector)) {
if (base.querySelector(':scope ' + hasSelector)) {
if (!finalSelector.trim()) {
return base;
}
const finalElement = base.querySelector<ExpectedElement>(finalSelector);
if (finalElement) {
return finalElement;
}
}
}
}
|