css-has-pseudo
Advanced tools
Comparing version
@@ -1,1 +0,1 @@ | ||
function cssHas(e){var t=/^(.*?)\[:(not-)?has\((.+?)\)\]/,o=[];function n(){o.forEach(function(t){var o=[];Array.prototype.forEach.call(e.querySelectorAll(t.elementSelector),function(n){var r=Array.prototype.indexOf.call(n.parentNode.children,n)+1,c=t.scopedSelectors.map(function(e){return t.elementSelector+":nth-child("+r+") "+e}).join(),l=n.parentNode.querySelector(c);(t.hasSelectorNot?!l:l)&&(o.push(n),function(t,o){var n="<x "+o+">",r=e.createElement("x");r.innerHTML=n;var c=r.firstChild.attributes[0].cloneNode();t.attributes.setNamedItem(c)}(n,t.encodedAttributeName),e.documentElement.style.zoom=1,e.documentElement.style.zoom=null)}),t.nodes.splice(0).forEach(function(n){-1===o.indexOf(n)&&(n.removeAttribute(t.encodedAttributeName),e.documentElement.style.zoom=1,e.documentElement.style.zoom=null)}),t.nodes=o})}function r(e){Array.prototype.forEach.call(e.cssRules,c)}function c(e){t.lastIndex=0;var n,r,c=(n=e.selectorText,decodeURIComponent(n.replace(/\\([^\\])/g,"$1"))).match(t);if(c){var l=c[2],a=c[1],s=c[3].split(/\s*,\s*/),u=":"+(l?"not-":"")+"has("+(r=c[3],encodeURIComponent(r).replace(/%3A/g,":").replace(/%5B/g,"[").replace(/%5D/g,"]").replace(/%2C/g,","))+")";o.push({cssRule:e,hasSelectorNot:l,elementSelector:a,scopedSelectors:s,encodedAttributeName:u,nodes:[]})}}Array.prototype.forEach.call(e.styleSheets,r),n(),new MutationObserver(function(t){t.forEach(function(t){Array.prototype.forEach.call(t.addedNodes||[],function(t){1===t.nodeType&&t.sheet&&(Array.prototype.push.apply(o,o.splice(0).filter(function(t){return t.cssRule.parentStyleSheet&&t.cssRule.parentStyleSheet.ownerNode&&e.contains(t.cssRule.parentStyleSheet.ownerNode)})),Array.prototype.forEach.call(e.styleSheets,r))}),n()})}).observe(e,{childList:!0,subtree:!0}),e.addEventListener("focus",function(){return setImmediate(n)},!0),e.addEventListener("blur",function(){return setImmediate(n)},!0),e.addEventListener("input",function(){return setImmediate(n)})} | ||
function cssHasPseudo(n){var t=[],e=n.createElement("x");function r(){t.forEach(function(t){var r=[];Array.prototype.forEach.call(n.querySelectorAll(t.n),function(u){var o=Array.prototype.indexOf.call(u.parentNode.children,u)+1,i=t.t.map(function(n){return t.n+":nth-child("+o+") "+n}).join(),c=u.parentNode.querySelector(i);(t.e?!c:c)&&(r.push(u),e.innerHTML="<x "+t.r+">",u.attributes.setNamedItem(e.firstChild.attributes[0].cloneNode()),n.documentElement.style.zoom=1,n.documentElement.style.zoom=null)}),t.u.forEach(function(e){-1===r.indexOf(e)&&(e.removeAttribute(t.r),n.documentElement.style.zoom=1,n.documentElement.style.zoom=null)}),t.u=r})}function u(n){Array.prototype.forEach.call(n.cssRules,function(n){var e=decodeURIComponent(n.selectorText.replace(/\\(.)/g,"$1")).match(/^(.*?)\[:(not-)?has\((.+?)\)\](.*?)$/);if(e){var r=":"+(e[2]?"not-":"")+"has("+e[3].replace(/%3A/g,":").replace(/%5B/g,"[").replace(/%5D/g,"]").replace(/%2C/g,",")+")";t.push({o:n,n:e[1],e:e[2],t:e[3].split(/\s*,\s*/),r:r,u:[]})}})}Array.prototype.forEach.call(n.styleSheets,u),r(),new MutationObserver(function(e){e.forEach(function(e){Array.prototype.forEach.call(e.i||[],function(n){1===n.nodeType&&n.sheet&&u(n.sheet)}),Array.prototype.push.apply(t,t.splice(0).filter(function(t){return t.o.parentStyleSheet&&t.o.parentStyleSheet.ownerNode&&n.documentElement.contains(t.o.parentStyleSheet.ownerNode)})),r()})}).observe(n,{childList:!0,subtree:!0}),n.addEventListener("focus",function(){return setImmediate(r)},!0),n.addEventListener("blur",function(){return setImmediate(r)},!0),n.addEventListener("input",function(){return setImmediate(r)})}module.c=cssHasPseudo; |
# Changes to CSS Has Pseudo | ||
### 0.2.0 (November 21, 2018) | ||
- Improved browser compatibility with updated parsers, encoders, and decoders | ||
- Improved performance by walking the DOM less | ||
- Fixed the misnamed function name for the browser-ready script | ||
- Reduced script size by 9%; from 855 bytes to 775 bytes | ||
### 0.1.0 (November 20, 2018) | ||
- Initial version |
117
index.js
'use strict'; | ||
function cssHas(document) { | ||
const hasPseudoRegExp = /^(.*?)\[:(not-)?has\((.+?)\)\]/; | ||
const observedCssRules = []; // walk all stylesheets to collect observed css rules | ||
function cssHasPseudo(document) { | ||
const observedItems = []; | ||
const attributeElement = document.createElement('x'); // walk all stylesheets to collect observed css rules | ||
Array.prototype.forEach.call(document.styleSheets, walkStyleSheet); | ||
transformObservedCssRules(); // observe DOM modifications that affect selectors | ||
transformObservedItems(); // observe DOM modifications that affect selectors | ||
new MutationObserver(mutationsList => { | ||
const mutationObserver = new MutationObserver(mutationsList => { | ||
mutationsList.forEach(mutation => { | ||
@@ -15,10 +15,11 @@ Array.prototype.forEach.call(mutation.addedNodes || [], node => { | ||
if (node.nodeType === 1 && node.sheet) { | ||
cleanupObservedCssRules(); | ||
Array.prototype.forEach.call(document.styleSheets, walkStyleSheet); | ||
walkStyleSheet(node.sheet); | ||
} | ||
}); // transform observed css rules | ||
transformObservedCssRules(); | ||
cleanupObservedCssRules(); | ||
transformObservedItems(); | ||
}); | ||
}).observe(document, { | ||
}); | ||
mutationObserver.observe(document, { | ||
childList: true, | ||
@@ -28,21 +29,22 @@ subtree: true | ||
document.addEventListener('focus', () => setImmediate(transformObservedCssRules), true); | ||
document.addEventListener('blur', () => setImmediate(transformObservedCssRules), true); | ||
document.addEventListener('input', () => setImmediate(transformObservedCssRules)); // transform observed css rules | ||
document.addEventListener('focus', () => setImmediate(transformObservedItems), true); | ||
document.addEventListener('blur', () => setImmediate(transformObservedItems), true); | ||
document.addEventListener('input', () => setImmediate(transformObservedItems)); // transform observed css rules | ||
function transformObservedCssRules() { | ||
observedCssRules.forEach(item => { | ||
function transformObservedItems() { | ||
observedItems.forEach(item => { | ||
const nodes = []; | ||
Array.prototype.forEach.call(document.querySelectorAll(item.elementSelector), element => { | ||
const index = Array.prototype.indexOf.call(element.parentNode.children, element) + 1; | ||
const nextSelector = item.scopedSelectors.map(scopedSelector => item.elementSelector + ':nth-child(' + index + ') ' + scopedSelector).join(); // find the :has element from the scope of the element | ||
Array.prototype.forEach.call(document.querySelectorAll(item.scopeSelector), element => { | ||
const nthChild = Array.prototype.indexOf.call(element.parentNode.children, element) + 1; | ||
const relativeSelectors = item.relativeSelectors.map(relativeSelector => item.scopeSelector + ':nth-child(' + nthChild + ') ' + relativeSelector).join(); // find any relative :has element from the :scope element | ||
const scopedElement = element.parentNode.querySelector(nextSelector); | ||
const shouldContinue = item.hasSelectorNot ? !scopedElement : scopedElement; | ||
const relativeElement = element.parentNode.querySelector(relativeSelectors); | ||
const shouldElementMatch = item.isNot ? !relativeElement : relativeElement; | ||
if (shouldContinue) { | ||
if (shouldElementMatch) { | ||
// memorize the node | ||
nodes.push(element); // set the encoded attribute on the node | ||
nodes.push(element); // set an attribute with an irregular attribute name | ||
setAttribute(element, item.encodedAttributeName); // trigger a style refresh in IE and Edge | ||
attributeElement.innerHTML = '<x ' + item.attributeName + '>'; | ||
element.attributes.setNamedItem(attributeElement.firstChild.attributes[0].cloneNode()); // trigger a style refresh in IE and Edge | ||
@@ -54,5 +56,5 @@ document.documentElement.style.zoom = 1; | ||
item.nodes.splice(0).forEach(node => { | ||
item.nodes.forEach(node => { | ||
if (nodes.indexOf(node) === -1) { | ||
node.removeAttribute(item.encodedAttributeName); // trigger a style refresh in IE and Edge | ||
node.removeAttribute(item.attributeName); // trigger a style refresh in IE and Edge | ||
@@ -62,3 +64,4 @@ document.documentElement.style.zoom = 1; | ||
} | ||
}); | ||
}); // update the | ||
item.nodes = nodes; | ||
@@ -70,3 +73,3 @@ }); | ||
function cleanupObservedCssRules() { | ||
Array.prototype.push.apply(observedCssRules, observedCssRules.splice(0).filter(item => item.cssRule.parentStyleSheet && item.cssRule.parentStyleSheet.ownerNode && document.contains(item.cssRule.parentStyleSheet.ownerNode))); | ||
Array.prototype.push.apply(observedItems, observedItems.splice(0).filter(item => item.rule.parentStyleSheet && item.rule.parentStyleSheet.ownerNode && document.documentElement.contains(item.rule.parentStyleSheet.ownerNode))); | ||
} // walk a stylesheet to collect observed css rules | ||
@@ -76,49 +79,25 @@ | ||
function walkStyleSheet(styleSheet) { | ||
Array.prototype.forEach.call(styleSheet.cssRules, walkCssRule); | ||
} // walk a css rule to collect observed css rules | ||
// walk a css rule to collect observed css rules | ||
Array.prototype.forEach.call(styleSheet.cssRules, rule => { | ||
// decode the selector text in all browsers to: | ||
// [1] = :scope, [2] = :not(:has), [3] = :has relative, [4] = :scope relative | ||
const selectors = decodeURIComponent(rule.selectorText.replace(/\\(.)/g, '$1')).match(/^(.*?)\[:(not-)?has\((.+?)\)\](.*?)$/); | ||
function walkCssRule(cssRule) { | ||
hasPseudoRegExp.lastIndex = 0; // decode the selector text, unifying their design between most browsers and IE/Edge | ||
const selectorText = decodeHasPseudoSelector(cssRule.selectorText); | ||
const selectorTextMatches = selectorText.match(hasPseudoRegExp); | ||
if (selectorTextMatches) { | ||
const hasSelectorNot = selectorTextMatches[2]; | ||
const elementSelector = selectorTextMatches[1]; | ||
const scopedSelectors = selectorTextMatches[3].split(/\s*,\s*/); | ||
const encodedAttributeName = ':' + (hasSelectorNot ? 'not-' : '') + 'has(' + encodeAttributeName(selectorTextMatches[3]) + ')'; | ||
observedCssRules.push({ | ||
cssRule, | ||
hasSelectorNot, | ||
elementSelector, | ||
scopedSelectors, | ||
encodedAttributeName, | ||
nodes: [] | ||
}); | ||
} | ||
} // set an attribute with an irregular attribute name | ||
function setAttribute(target, attributeName) { | ||
const innerHTML = '<x ' + attributeName + '>'; | ||
const x = document.createElement('x'); | ||
x.innerHTML = innerHTML; | ||
const attribute = x.firstChild.attributes[0].cloneNode(); | ||
target.attributes.setNamedItem(attribute); | ||
} // encode a :has() pseudo selector as an attribute name | ||
function encodeAttributeName(attributeName) { | ||
return encodeURIComponent(attributeName).replace(/%3A/g, ':').replace(/%5B/g, '[').replace(/%5D/g, ']').replace(/%2C/g, ','); | ||
} // decode the :has() pseudo selector | ||
function decodeHasPseudoSelector(hasPseudoText) { | ||
return decodeURIComponent(hasPseudoText.replace(/\\([^\\])/g, '$1')); | ||
if (selectors) { | ||
const attributeName = ':' + (selectors[2] ? 'not-' : '') + 'has(' + // encode a :has() pseudo selector as an attribute name | ||
selectors[3].replace(/%3A/g, ':').replace(/%5B/g, '[').replace(/%5D/g, ']').replace(/%2C/g, ',') + ')'; | ||
observedItems.push({ | ||
rule, | ||
scopeSelector: selectors[1], | ||
isNot: selectors[2], | ||
relativeSelectors: selectors[3].split(/\s*,\s*/), | ||
attributeName, | ||
nodes: [] | ||
}); | ||
} | ||
}); | ||
} | ||
} | ||
module.exports = cssHas; | ||
module.exports = cssHasPseudo; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "css-has-pseudo", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Style elements relative to other elements in CSS", | ||
@@ -5,0 +5,0 @@ "author": "Jonathan Neal <jonathantneal@hotmail.com>", |
@@ -41,6 +41,6 @@ # CSS Has Pseudo [<img src="http://jonathantneal.github.io/js-logo.svg" alt="" width="90" height="90" align="right">][CSS Has Pseudo] | ||
<script src="https://unpkg.com/css-has-pseudo/browser"></script> | ||
<script>cssHasPseudo(document)</script> | ||
<script>cssHas(document)</script> | ||
``` | ||
That’s it. The script is 855 bytes and works in all browsers, including | ||
That’s it. The script is 775 bytes and works in all browsers, including | ||
Internet Explorer 11. With a [Mutation Observer polyfill], the script will work | ||
@@ -47,0 +47,0 @@ down to Internet Explorer 9. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
53962
-6.84%370
-6.8%