[6a3a178] | 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
|
---|