1 | function cssHasPseudo(document) {
|
---|
2 | const observedItems = []; // document.createAttribute() doesn't support `:` in the name. innerHTML does
|
---|
3 |
|
---|
4 | const attributeElement = document.createElement('x'); // walk all stylesheets to collect observed css rules
|
---|
5 |
|
---|
6 | [].forEach.call(document.styleSheets, walkStyleSheet);
|
---|
7 | transformObservedItems(); // observe DOM modifications that affect selectors
|
---|
8 |
|
---|
9 | const mutationObserver = new MutationObserver(mutationsList => {
|
---|
10 | mutationsList.forEach(mutation => {
|
---|
11 | [].forEach.call(mutation.addedNodes || [], node => {
|
---|
12 | // walk stylesheets to collect observed css rules
|
---|
13 | if (node.nodeType === 1 && node.sheet) {
|
---|
14 | walkStyleSheet(node.sheet);
|
---|
15 | }
|
---|
16 | }); // transform observed css rules
|
---|
17 |
|
---|
18 | cleanupObservedCssRules();
|
---|
19 | transformObservedItems();
|
---|
20 | });
|
---|
21 | });
|
---|
22 | mutationObserver.observe(document, {
|
---|
23 | childList: true,
|
---|
24 | subtree: true
|
---|
25 | }); // observe DOM events that affect pseudo-selectors
|
---|
26 |
|
---|
27 | document.addEventListener('focus', transformObservedItems, true);
|
---|
28 | document.addEventListener('blur', transformObservedItems, true);
|
---|
29 | document.addEventListener('input', transformObservedItems); // transform observed css rules
|
---|
30 |
|
---|
31 | function transformObservedItems() {
|
---|
32 | requestAnimationFrame(() => {
|
---|
33 | observedItems.forEach(item => {
|
---|
34 | const nodes = [];
|
---|
35 | [].forEach.call(document.querySelectorAll(item.scopeSelector), element => {
|
---|
36 | const nthChild = [].indexOf.call(element.parentNode.children, element) + 1;
|
---|
37 | const relativeSelectors = item.relativeSelectors.map(relativeSelector => item.scopeSelector + ':nth-child(' + nthChild + ') ' + relativeSelector).join(); // find any relative :has element from the :scope element
|
---|
38 |
|
---|
39 | const relativeElement = element.parentNode.querySelector(relativeSelectors);
|
---|
40 | const shouldElementMatch = item.isNot ? !relativeElement : relativeElement;
|
---|
41 |
|
---|
42 | if (shouldElementMatch) {
|
---|
43 | // memorize the node
|
---|
44 | nodes.push(element); // set an attribute with an irregular attribute name
|
---|
45 | // document.createAttribute() doesn't support special characters
|
---|
46 |
|
---|
47 | attributeElement.innerHTML = '<x ' + item.attributeName + '>';
|
---|
48 | element.setAttributeNode(attributeElement.children[0].attributes[0].cloneNode()); // trigger a style refresh in IE and Edge
|
---|
49 |
|
---|
50 | document.documentElement.style.zoom = 1;
|
---|
51 | document.documentElement.style.zoom = null;
|
---|
52 | }
|
---|
53 | }); // remove the encoded attribute from all nodes that no longer match them
|
---|
54 |
|
---|
55 | item.nodes.forEach(node => {
|
---|
56 | if (nodes.indexOf(node) === -1) {
|
---|
57 | node.removeAttribute(item.attributeName); // trigger a style refresh in IE and Edge
|
---|
58 |
|
---|
59 | document.documentElement.style.zoom = 1;
|
---|
60 | document.documentElement.style.zoom = null;
|
---|
61 | }
|
---|
62 | }); // update the
|
---|
63 |
|
---|
64 | item.nodes = nodes;
|
---|
65 | });
|
---|
66 | });
|
---|
67 | } // remove any observed cssrules that no longer apply
|
---|
68 |
|
---|
69 |
|
---|
70 | function cleanupObservedCssRules() {
|
---|
71 | [].push.apply(observedItems, observedItems.splice(0).filter(item => item.rule.parentStyleSheet && item.rule.parentStyleSheet.ownerNode && document.documentElement.contains(item.rule.parentStyleSheet.ownerNode)));
|
---|
72 | } // walk a stylesheet to collect observed css rules
|
---|
73 |
|
---|
74 |
|
---|
75 | function walkStyleSheet(styleSheet) {
|
---|
76 | try {
|
---|
77 | // walk a css rule to collect observed css rules
|
---|
78 | [].forEach.call(styleSheet.cssRules || [], rule => {
|
---|
79 | if (rule.selectorText) {
|
---|
80 | // decode the selector text in all browsers to:
|
---|
81 | // [1] = :scope, [2] = :not(:has), [3] = :has relative, [4] = :scope relative
|
---|
82 | const selectors = decodeURIComponent(rule.selectorText.replace(/\\(.)/g, '$1')).match(/^(.*?)\[:(not-)?has\((.+?)\)\](.*?)$/);
|
---|
83 |
|
---|
84 | if (selectors) {
|
---|
85 | const attributeName = ':' + (selectors[2] ? 'not-' : '') + 'has(' + // encode a :has() pseudo selector as an attribute name
|
---|
86 | encodeURIComponent(selectors[3]).replace(/%3A/g, ':').replace(/%5B/g, '[').replace(/%5D/g, ']').replace(/%2C/g, ',') + ')';
|
---|
87 | observedItems.push({
|
---|
88 | rule,
|
---|
89 | scopeSelector: selectors[1],
|
---|
90 | isNot: selectors[2],
|
---|
91 | relativeSelectors: selectors[3].split(/\s*,\s*/),
|
---|
92 | attributeName,
|
---|
93 | nodes: []
|
---|
94 | });
|
---|
95 | }
|
---|
96 | } else {
|
---|
97 | walkStyleSheet(rule);
|
---|
98 | }
|
---|
99 | });
|
---|
100 | } catch (error) {
|
---|
101 | /* do nothing and continue */
|
---|
102 | }
|
---|
103 | }
|
---|
104 | }
|
---|
105 |
|
---|
106 | export default cssHasPseudo;
|
---|
107 | //# sourceMappingURL=index.mjs.map
|
---|