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