index.js 4.5 KB

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