css-has-pseudo
Advanced tools
Comparing version 0.1.0 to 0.2.0
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
53962
370