Skip to content

Instantly share code, notes, and snippets.

@jerinisready
Forked from tomhodgins/ancestor-selector.js
Created November 29, 2018 12:17
Show Gist options
  • Save jerinisready/b2434d0de834d2a52a71e5d21558ee1c to your computer and use it in GitHub Desktop.
Save jerinisready/b2434d0de834d2a52a71e5d21558ee1c to your computer and use it in GitHub Desktop.
Download ZIP to view and edit, or preview index.html online at: http://staticresource.com/helpers
// Ancestor Selector
function ancestor(selector, ancestor, rule) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(ancestor))
.filter(tag => tag.querySelector(selector))
.forEach(tag => {
const attr = (selector+ancestor).replace(/\W/g, '')
tag.setAttribute(`data-ancestor-${attr}`, count)
styles += `${ancestor}[data-ancestor-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
// Aspect Ratio
function aspectRatio(selector, ratio) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
const attr = selector.replace(/\W/g, '')
const width = tag.offsetWidth
const height = `${width/ratio}px`
tag.setAttribute(`data-aspect-${attr}`, count)
styles += `${selector}[data-aspect-${attr}="${count}"] { height: ${height} }\n`
count++
})
return styles
}
// Auto Expand
function autoExpand(selector, option) {
let styles = ''
let count = 0
const features = {
width: tag => {
const computed = getComputedStyle(tag)
tag.style.width = 'inherit'
const width = parseInt(computed.getPropertyValue('border-left-width'), 10)
+ parseInt(computed.getPropertyValue('padding-left'), 10)
+ tag.scrollWidth
+ parseInt(computed.getPropertyValue('padding-right'), 10)
+ parseInt(computed.getPropertyValue('border-right-width'), 10)
tag.style.width = ''
return `width: ${width}px;`
},
height: tag => {
const computed = getComputedStyle(tag)
tag.style.height = 'inherit'
const height = parseInt(computed.getPropertyValue('border-top-width'), 10)
+ parseInt(computed.getPropertyValue('padding-top'), 10)
+ tag.scrollHeight
+ parseInt(computed.getPropertyValue('padding-bottom'), 10)
+ parseInt(computed.getPropertyValue('border-bottom-width'), 10)
tag.style.height = ''
return `height: ${height}px;`
},
both: tag => {
return features.width(tag) + features.height(tag)
}
}
document.querySelectorAll(selector).forEach(tag => {
const attr = selector.replace(/\W/g, '')
const evaluated = features[option](tag)
tag.setAttribute(`data-${attr}`, count)
styles += `${selector}[data-${attr}="${count}"] { ${evaluated} }\n`
count++
})
return styles
}
// Closest Selector
function closest(selector, ancestor, rule) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(selector))
.filter(tag => tag.closest(ancestor))
.forEach(tag => {
const attr = (selector+ancestor).replace(/\W/g, '')
tag.closest(ancestor).setAttribute(`data-closest-${attr}`, count)
styles += `${ancestor}[data-closest-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
// Compare attribute values
function compareAttribute(selector, attribute, test, stylesheet) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(selector))
.filter(tag => test(attribute === 'value' ? tag.value : tag.getAttribute(attribute)))
.forEach(tag => {
const attr = selector.replace(/\W/g, '')
styles += stylesheet.replace(/:self|\$this/g, `[data-compare-${attr}="${count}"]`)
tag.setAttribute(`data-compare-${attr}`, count)
count++
})
return styles
}
// Container Queries
function container(selector, conditions, stylesheet) {
let styles = ''
let count = 0
const features = {
minWidth: (el, number) => number <= el.offsetWidth,
maxWidth: (el, number) => number >= el.offsetWidth,
minHeight: (el, number) => number <= el.offsetHeight,
maxHeight: (el, number) => number >= el.offsetHeight,
minChildren: (el, number) => number <= el.children.length,
maxChildren: (el, number) => number >= el.children.length,
minCharacters: (el, number) => number <= ((el.value && el.value.length) || el.textContent.length),
maxCharacters: (el, number) => number >= ((el.value && el.value.length) || el.textContent.length),
minScrollX: (el, number) => number <= el.scrollLeft,
maxScrollX: (el, number) => number >= el.scrollLeft,
minScrollY: (el, number) => number <= el.scrollTop,
maxScrollY: (el, number) => number >= el.scrollTop,
minAspectRatio: (el, number) => number <= el.offsetWidth / el.offsetHeight,
maxAspectRatio: (el, number) => number >= el.offsetWidth / el.offsetHeight,
orientation: (el, string) => {
switch (string) {
case 'portrait': return el.offsetWidth < el.offsetHeight
case 'square': return el.offsetWidth === el.offsetHeight
case 'landscape': return el.offsetWidth > el.offsetHeight
}
}
}
document.querySelectorAll(selector).forEach(tag => {
const attr = (selector
+ Object.keys(conditions)
+ Object.values(conditions)).replace(/\W/g, '')
const results = []
for (let test in conditions) {
results.push(features[test](tag, conditions[test]) ? true : false)
}
if (!results.includes(false)) {
tag.setAttribute(`data-container-${attr}`, count)
styles += stylesheet.replace(/:self|\$this/g, `[data-container-${attr}="${count}"]`)
count++
} else {
tag.setAttribute(`data-container-${attr}`, '')
}
})
return styles
}
// Days of the week
function days(options, stylesheet) {
const day = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']
const results = []
for (let test in options) {
results.push(day[new Date().getDay()] === options[test] ? true : false)
}
return results.includes(true) ? stylesheet : ''
}
// Elder Selector
function elder(selector, rule) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
const attr = selector.replace(/\W/g, '')
const siblings = Array.from(tag.parentNode.getElementsByTagName('*'))
const index = siblings.indexOf(tag)
siblings
.filter(sibling => siblings.indexOf(sibling) < index)
.forEach(sibling => {
sibling.setAttribute(`data-elder-${attr}`, count)
styles += `[data-elder-${attr}="${count}"] { ${rule} }\n`
count++
})
})
return styles
}
// Element Query
function element(selector, test, stylesheet) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
const attr = (selector+test).replace(/\W/g, '')
if (test(tag)) {
tag.setAttribute(`data-${attr}`, count)
styles += stylesheet.replace(/:self|\$this/g, `[data-${attr}="${count}"]`)
count++
} else {
tag.setAttribute(`data-${attr}`, '')
}
})
return styles
}
// Element-based units
function eunit(selector, rule) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
const attr = selector.replace(/\W/g, '')
rule = rule.replace(/(\d*\.?\d+)(?:\s*)(ew|eh|emin|emax)/gi,
(match, number, unit) => {
switch(unit) {
case 'ew':
return tag.offsetWidth / 100 * number + 'px'
case 'eh':
return tag.offsetHeight / 100 * number + 'px'
case 'emin':
return Math.min(tag.offsetWidth, tag.offsetHeight) / 100 * number + 'px'
case 'emax':
return Math.max(tag.offsetWidth, tag.offsetHeight) / 100 * number + 'px'
}
})
tag.setAttribute(`data-eunit-${attr}`, count)
styles += `[data-eunit-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
// First Ever
function firstEver(selector, rule) {
let styles = ''
const tag = document.querySelector(selector)
if (tag) {
const attr = selector.replace(/\W/g, '')
tag.setAttribute(`data-first-${attr}`, '')
styles += `${selector}[data-first-${attr}] { ${rule} }\n`
}
return tag ? styles : ''
}
// :has() selector
function has(selector, child, rule) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(selector))
.filter(tag => tag.querySelector(child))
.forEach(tag => {
const attr = (selector+child).replace(/\W/g, '')
styles += `[data-has-${attr}="${count}"] { ${rule} }\n`
tag.setAttribute(`data-has-${attr}`, count)
count++
})
return styles
}
// HEXA Colors
function hexa(hex, opacity) {
let color = hex.replace(/#/, '')
if (color.length === 3) {
color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2]
}
let red = parseInt(color.substring(0, 2), 16)
let green = parseInt(color.substring(2, 4), 16)
let blue = parseInt(color.substring(4, 6), 16)
return `rgba(${red}, ${green}, ${blue}, ${opacity})`
}
// Visible Vertically Within Parent
function inSight(selector, rule) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
const attr = selector.replace(/\W/g, '')
let underTop = tag.offsetTop + tag.offsetHeight >= tag.parentElement.scrollTop
let aboveBottom = tag.offsetTop < tag.parentElement.scrollTop + tag.parentElement.offsetHeight
if (underTop && aboveBottom) {
styles += `[data-insight-${attr}="${count}"] { ${rule} }\n`
tag.setAttribute(`data-insight-${attr}`, count)
count++
} else {
tag.setAttribute(`data-insight-${attr}`, '')
}
})
return styles
}
// String Match
function includes(selector, string, rule) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(selector))
.filter(tag => tag.textContent.includes(string))
.forEach(tag => {
const attr = (selector+string).replace(/\W/g, '')
tag.setAttribute(`data-contains-${attr}`, count)
styles += `[data-contains-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
<!DOCTYPE html>
<meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1">
<title>JS-in-CSS Helper Functions in ES6</title>
<!-- JS-in-CSS Stylesheet -->
<link rel=stylesheet href=styles.jic type=text/css>
<!-- JS-in-CSS plugin -->
<script src=https://staticasset.s3.amazonaws.com/jic.js></script>
<!-- JS-in-CSS Helper Functions -->
<script src=ancestor-selector.js></script>
<script src=aspect-ratio.js></script>
<script src=auto-expand.js></script>
<script src=closest-selector.js></script>
<script src=compare-attribute.js></script>
<script src=container-query.js></script>
<script src=days.js></script>
<script src=elder-selector.js></script>
<script src=element-query.js></script>
<script src=eunit.js></script>
<script src=first-ever.js></script>
<script src=has.js></script>
<script src=hexa.js></script>
<script src=includes.js></script>
<script src=in-sight.js></script>
<script src=last-ever.js></script>
<script src=next-sibling.js></script>
<script src=non-empty.js></script>
<script src=overflow.js></script>
<script src=out-of-sight.js></script>
<script src=parent.js></script>
<script src=previous-sibling.js></script>
<script src=protocol.js></script>
<script src=regex.js></script>
<script src=tag-count.js></script>
<script src=scoped-eval.js></script>
<script src=specificity.js></script>
<script src=variables.js></script>
<script src=viewport.js></script>
<script src=xpath-selector.js></script>
<h1>JS-in-CSS Helper Functions in ES6</h1>
<p><a href=https://gist.github.com/tomhodgins/a106d938cbab066f1c9621aed1dde883>Click here view on Github</a></p>
<h2>First Ever</h2>
<ul class=first>
<li>item
<li>item
<li class=target>target
<li>item
<li>item
</ul>
<h2>Last Ever</h2>
<ul class=last>
<li>item
<li>item
<li class=target>target
<li>item
<li>item
</ul>
<h2>Previous Element</h2>
<ul class=prev>
<li>item
<li>item
<li class=target>target
<li>item
<li>item
</ul>
<h2>Parent Element</h2>
<ul class=parent>
<li>item
<li>item
<li class=target>target
<li>item
<li>item
</ul>
<h2>Ancestor Selector</h2>
<ul class=ancestor>
<li>item with <span>span</span>
<li>item
<li class=target>target
<li>item
<li>item
</ul>
<h2>Next Element</h2>
<ul class=next>
<li>item
<li>item
<li class=target>target
<li>item
<li>item
</ul>
<h2>Auto Expand Demo</h2>
<div class=auto-expand>
<h3>Width</h3>
<input>
<h3>Height</h3>
<textarea></textarea>
</div>
<h2>Closest Selector</h2>
<div class=closest>
<div>
<div class=demo>
<div>
<div>
<div class=demo>
<div>
<div>
<div class=target>target</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<h2>Elder Siblings</h2>
<ul class=elder>
<li>item
<li>item
<li class=target>target
<li>item
<li>item
</ul>
<h2>XPath Selector</h2>
<div class=xpath>
<p>This paragraph contains the word 'test'</p>
<p>This paragraph does not</p>
</div>
<h2>Scoped Eval</h2>
<div class=scoped>
<textarea placeholder="Type to change the background"></textarea>
</div>
<h2>Element Queries</h2>
<div class=element-query>
<input placeholder="type more than 5 characters">
</div>
<h2>Container Queries</h2>
<div class=container-query>
<input placeholder="type more than 5 characters">
</div>
<h2>String Match</h2>
<ul class=includes>
<li>item
<li>target
<li>item
</ul>
<h2>Regex Match</h2>
<ul class=regex>
<li>item
<li>target
<li>item
</ul>
<h2>Element Based Units</h2>
<ul class=eunit>
<li class=ew>EW Units
<li class=eh>EH Units
<li class=emin>EMIN Units
<li class=emax>EMAX Units
</ul>
<h2>Aspect Ratio</h2>
<div class=aspect-ratio>
<div></div>
</div>
<h2>Frontend Variables</h2>
<ul class=variables data-background="gold">
<li>item
<li data-color="red" data-background="lime">item
<li>item
<li>item
</ul>
<h2>Custom Specificity</h2>
<ul class=specificity>
<li>regular
<li class=target>class
<li id=target>id
</ul>
<h2>Viewport Visibility</h2>
<div class=viewport>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
</div>
<script>
/*
Using a separate JS-in-CSS stylesheet to test this function so we're not reprocessing all of the rest of the styles on window.scroll
*/
window.addEventListener('load', viewportStyles)
window.addEventListener('resize', viewportStyles)
window.addEventListener('scroll', viewportStyles)
function viewportStyles() {
var tag = document.querySelector('#viewportStyles')
if (!tag) {
tag = document.createElement('style')
tag.id = 'viewportStyles'
document.head.appendChild(tag)
}
tag.innerHTML = `
.viewport p {
padding: 2em;
}
${viewport('.viewport p', 'partly', `
color: red;
`)}
${viewport('.viewport p', 'fully', `
background: lime;
`)}
`
}
</script>
<h2>Days of the week</h2>
<div class=days>
<div class=sunday>sunday</div>
<div class=monday>monday</div>
<div class=tuesday>tuesday</div>
<div class=wednesday>wednesday</div>
<div class=thursday>thursday</div>
<div class=friday>friday</div>
<div class=saturday>saturday</div>
<h3></h3>
</div>
<h2>Protocol Sniffer</h2>
<h3 class=protocol></h3>
<h2>Tag Count</h2>
<ul class=tagCount>
<li>item
<li>item
<li>item
<li>item
<li>item
<li>item
<li>item
<li>item
<li>item
<li>item
</ul>
<h2>HEXA Colors</h2>
<div class=hexa>Hover me to switch opacities</div>
<h2>Horizontal Overflow</h2>
<div class=overflow>
<pre>Lorem ipsum dolor sit amet.</pre>
<span class=left></span>
<span class=right></span>
</div>
<div class=overflow>
<pre>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</pre>
<span class=left></span>
<span class=right></span>
</div>
<div class=overflow>
<pre>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor.</pre>
<span class=left></span>
<span class=right></span>
</div>
<div class=overflow>
<pre>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore.</pre>
<span class=left></span>
<span class=right></span>
</div>
<div class=overflow>
<pre>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.</pre>
<span class=left></span>
<span class=right></span>
</div>
<script>
window.addEventListener('load', overflowDemo)
window.addEventListener('resize', overflowDemo)
document.querySelectorAll('.overflow pre').forEach(tag => {
tag.addEventListener('scroll', overflowDemo)
})
function overflowDemo() {
var tag = document.querySelector('#overflowDemo')
if (!tag) {
tag = document.createElement('style')
tag.id = 'overflowDemo'
document.head.appendChild(tag)
}
tag.innerHTML = `
.overflow {
margin: 1em;
position: relative;
border-radius: 3px;
border: 1px solid;
}
.overflow pre {
margin: 0;
padding: 2em;
white-space: pre;
overflow: auto;
overflow-x: scroll;
}
.overflow .left,
.overflow .right {
display: block;
width: 50px;
height: 100%;
position: absolute;
top: 0;
opacity: 0;
pointer-events: none;
transition: .5s ease-in-out;
}
.overflow .left {
left: 0;
background: linear-gradient(90deg, rgba(0,0,0,.3) 0%, rgba(0,0,0,0) 100%);
}
.overflow .right {
right: 0;
background: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.3) 100%);
}
${overflow('.overflow pre', 'left', `
:self ~ .left {
opacity: 1;
}
`)}
${overflow('.overflow pre', 'right', `
:self ~ .right {
opacity: 1;
}
`)}
`
}
</script>
<h2>:has() selector</h2>
<div class=has>
<ul>
<li>item
</ul>
<ul>
<li>item
<li>item
</ul>
<ul>
<li>item
<li>item
<li>item
</ul>
<ul>
<li>item
<li>item
<li>item
<li>item with <span>span</span>
</ul>
</div>
<h2>In-Sight &amp; Out-of-Sight Vertically within parent</h2>
<div class=sight>Lorem ipsum <a href=#>dolor sit</a> amet, consectetur adipisicing elit, <input> sed do eiusmod tempor incididunt ut labore et dolore magna <a href=#>aliqua</a>. Ut enim ad minim veniam, quis nostrud <input> exercitation ullamco laboris nisi ut aliquip ex ea commodo <a href=#>consequat</a>. Duis aute irure dolor in reprehenderit <input> in voluptate velit esse cillum dolore eu fugiat nulla <a href=#>pariatur</a>. Excepteur sint occaecat cupidatat non <input> proident, sunt in culpa qui officia deserunt mollit anim id <a href=#>est laborum.</a></div>
<script>
window.addEventListener('load', sightDemo)
window.addEventListener('resize', sightDemo)
document.querySelector('.sight').addEventListener('scroll', sightDemo)
function sightDemo() {
var tag = document.querySelector('#sightDemo')
if (!tag) {
tag = document.createElement('style')
tag.id = 'sightDemo'
document.head.appendChild(tag)
}
tag.innerHTML = `
${inSight('.sight *', `
background: lime;
`)}
${outOfSight('.sight *', `
visibility: hidden;
`)}
`
}
</script>
<h2>Non-Empty Values</h2>
<select class=non-empty>
<option value>Select an option
<option value=1>Option 1
<option value=2>Option 2
<option value=3>Option 3
</select>
<select class=non-empty>
<option value>Select an option
<option value=1>Option 1
<option value=2>Option 2
<option value=3>Option 3
</select>
<h2>Compare Attribute Values</h2>
<div class=compare>
<input type=range min=0 max=100 step=1>
<div></div>
</div>
// Last Ever
function lastEver(selector, rule) {
let styles = ''
const tag = document.querySelectorAll(selector)
if (tag) {
const attr = selector.replace(/\W/g, '')
tag[tag.length - 1].setAttribute(`data-last-${attr}`, '')
styles += `${selector}[data-last-${attr}] { ${rule} }\n`
}
return tag ? styles : ''
}
// Next Sibling
function nextSibling(selector, rule) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(selector))
.filter(tag => tag.nextElementSibling)
.forEach(tag => {
const attr = selector.replace(/\W/g, '')
tag.nextElementSibling.setAttribute(`data-next-${attr}`, count)
styles += `[data-next-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
// Non-empty value
function nonEmpty(selector, rule) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
let attr = selector.replace(/\W/g, '')
if (tag.value && tag.value !== '') {
styles += `[data-nonEmpty-${attr}="${count}"] { ${rule} }\n`
tag.setAttribute(`data-nonEmpty-${attr}`, count)
count++
} else {
tag.setAttribute(`data-nonEmpty-${attr}`, '')
}
})
return styles
}
// Out of Sight Vertically in Parent
function outOfSight(selector, rule) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
const attr = selector.replace(/\W/g, '')
let aboveTop = tag.offsetTop + tag.offsetHeight < tag.parentElement.scrollTop
let underBottom = tag.offsetTop > tag.parentElement.scrollTop + tag.parentElement.offsetHeight
if (aboveTop || underBottom) {
styles += `[data-outofsight-${attr}="${count}"] { ${rule} }\n`
tag.setAttribute(`data-outofsight-${attr}`, count)
count++
} else {
tag.setAttribute(`data-outofsight-${attr}`, '')
}
})
return styles
}
// Horizontal Overflow
function overflow(selector, option, stylesheet) {
let styles = ''
let count = 0
const features = {
left: tag => 0 < tag.scrollLeft,
right: tag => (tag.scrollLeft + tag.offsetWidth) !== tag.scrollWidth
}
Array.from(document.querySelectorAll(selector)).forEach(tag => {
const attr = (selector+option).replace(/\W/g, '')
if (features[option](tag)) {
styles += stylesheet.replace(/:self|\$this/g, `[data-overflow-${attr}="${count}"]`)
tag.setAttribute(`data-overflow-${attr}`, count)
count++
} else {
tag.setAttribute(`data-overflow-${attr}`, '')
}
})
return styles
}
// Parent Selector
function parent(selector, rule) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(selector))
.filter(tag => tag.parentElement)
.forEach(tag => {
const attr = selector.replace(/\W/g, '')
tag.parentElement.setAttribute(`data-parent-${attr}`, count)
styles += `[data-parent-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
// Previous Sibling
function previousSibling(selector, rule) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(selector))
.filter(tag => tag.previousElementSibling)
.forEach(tag => {
const attr = selector.replace(/\W/g, '')
tag.previousElementSibling.setAttribute(`data-prev-${attr}`, count)
styles += `[data-prev-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
// Protocol Sniffer
function protocol(options, stylesheet) {
const results = []
for (let test in options) {
results.push(location.protocol === `${options[test]}:` ? true : false)
}
return results.includes(true) ? stylesheet : ''
}
// Regex Match
function regex(selector, regex, rule) {
let styles = ''
let count = 0
Array.from(document.querySelectorAll(selector))
.filter(tag => regex.test(tag.textContent))
.forEach(tag => {
const attr = (selector+regex).replace(/\W/g, '')
tag.setAttribute(`data-regex-${attr}`, count)
styles += `[data-regex-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
// Scoped Eval
function scoped(selector, rule) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
const attr = selector.replace(/\W/g, '')
const evaluated =
rule.replace(/eval\( *((".*?")|('.*?')) *\)/g, (string, match) =>
new Function(`return ${match.slice(1, -1)}`).call(tag) || ''
)
tag.setAttribute(`data-scoped-${attr}`, count)
styles += `[data-scoped-${attr}="${count}"] { ${evaluated} }\n`
count++
})
return styles
}
// Custom Specificity
function specificity(selector, number, rule) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
const attr = (selector+number).replace(/\W+/g, '')
let partial = `[data-specificity-${attr}="${count}"]`
let repeated = partial + partial.repeat(number)
tag.setAttribute(`data-specificity-${attr}`, count)
styles += `${repeated} { ${rule} }\n`
count++
})
return styles
}
${firstEver('.first li', `
background: lime;
`)}
${previousSibling('.prev .target', `
background: teal;
`)}
${parent('.parent .target', `
border: 4px dashed red;
`)}
${ancestor('.ancestor span', 'li', `
background: orange;
`)}
${nextSibling('.next .target', `
background: dodgerblue;
`)}
${lastEver('.last li', `
background: hotpink;
`)}
.auto-expand input,
.auto-expand textarea {
margin: 1em 0;
display: block;
}
${autoExpand('.auto-expand input', 'width')}
${autoExpand('.auto-expand textarea', 'height')}
.closest, .closest div {
border: 1px solid;
padding: 1em;
}
${closest('.closest .target', '.demo', `
border: 4px dashed red;
`)}
${elder('.elder .target', `
background: violet;
`)}
${xpath('//*[@class="xpath"]/*[contains(text(), "test")]', `
background: yellow;
`)}
${scoped('.scoped textarea', `
background: hsl(eval('this.value.length * 5'), 75%, 75%);
`)}
${element('.element-query input', el => el.value.length > 5, `
:self {
background: lime;
}
`)}
${container('.container-query input', {minCharacters: 6}, `
:self {
background: hotpink;
}
`)}
${includes('.includes li', 'target', `
background: blue;
`)}
${regex('.regex li', /target/, `
background: plum;
`)}
${eunit('.eunit .ew', `
font-size: 10ew;
`)}
${eunit('.eunit .eh', `
font-size: 90eh;
max-height: 100px;
`)}
${eunit('.eunit .emin', `
font-size: 90emin;
max-height: 100px;
`)}
${eunit('.eunit .emax', `
font-size: 10emax;
`)}
.aspect-ratio div {
margin: 0 auto;
background: lime;
}
${aspectRatio('.aspect-ratio div', 16/9)}
${variables('.variables ', '--color: blue')}
${variables('.variables li', `
color: var(--color);
background: var(--background);
`)}
${specificity('.specificity li', 2, `
background: hotpink;
`)}
${specificity('.specificity li.target', 2, `
background: red;
`)}
${specificity('.specificity li#target', 1, `
background: blue;
`)}
.days div {
margin: 1em 0;
padding: 1em;
border: 1px solid black;
}
${days(['sunday'], `
.days .sunday {
background: red;
}
`)}
${days(['monday'], `
.days .monday {
background: orange;
}
`)}
${days(['tuesday'], `
.days .tuesday {
background: yellow;
}
`)}
${days(['wednesday'], `
.days .wednesday {
background: green;
}
`)}
${days(['thursday'], `
.days .thursday {
background: blue;
}
`)}
${days(['friday'], `
.days .friday {
background: indigo;
}
`)}
${days(['saturday'], `
.days .saturday {
background: violet;
}
`)}
/* On weekdays they are red */
${days(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], `
.days h3:after {
content: 'It is a weekday'
}
`)}
/* On weekends they are yellow */
${days(['saturday', 'sunday'], `
.days h3:after {
content: 'It is the weekend'
}
`)}
${protocol(['file'],`
.protocol:before {
content: "You're on FILE://";
}
`)}
${protocol(['http'],`
.protocol:before {
content: "You're on HTTP://"
}
`)}
${protocol(['https'],`
.protocol:before {
content: "You're on HTTPS://"
}
`)}
${protocol(['file'],`
.protocol:after {
content: " and this site is in DEVELOPMENT";
}
`)}
${protocol(['http', 'https'],`
.protocol:after {
content: " and this site is in PRODUCTION";
}
`)}
${scoped('.tagCount li', `
background: hsl(eval("tagCount('.tagCount li', this) * 50 + 50"), 75%, 75%)
`)}
.hexa {
font-size: 18pt;
color: #c00;
background: ${hexa('#07f', .3)};
transition:
color .2s ease-in-out,
background .2s ease-in-out
;
}
.hexa:hover {
color: ${hexa('#c00', .3)};
background: #07f;
}
.has span {
background: red;
}
${has('.has ul', 'li:nth-child(3)', `
background: cyan;
`)}
${has('.has *', 'span', `
padding-top: 5px;
padding-bottom: 5px;
border: 4px dashed red;
`)}
.sight {
width: 200px;
height: 200px;
border: 4px solid purple;
overflow: auto;
position: relative;
}
${nonEmpty('.non-empty', `
color: white;
background: orange;
`)}
.compare div {
width: 200px;
height: 200px;
}
${compareAttribute('.compare [type="range"]', 'value', num => num < 25, `
:self + div {
background: red;
}
`)}
${compareAttribute('.compare [type="range"]', 'value', num => 25 <= num && num < 50, `
:self + div {
background: orange;
}
`)}
${compareAttribute('.compare [type="range"]', 'value', num => 50 <= num && num < 75, `
:self + div {
background: yellow;
}
`)}
${compareAttribute('.compare [type="range"]', 'value', num => 75 <= num && num <= 100, `
:self + div {
background: lime;
}
`)}
// Tag Count
function tagCount(selector, tag) {
return [].indexOf.call(document.querySelectorAll(selector), tag)
}
// Cascading Frontend Variables
function variables(selector, rule) {
let styles = ''
let count = 0
document.querySelectorAll(selector).forEach(tag => {
// Apply variables defined in supplied rule
rule.replace(/--([^;]+):(.+)[;}]*/gm, (string, property, value) => {
tag.setAttribute(`data-${property}`, value)
})
const attr = selector.replace(/\W+/g, '')
const evaluated = rule.replace(/var\(--([^)]+)\)/g, (string, match) => {
// Check if has data attribute on self
if (tag.getAttribute(`data-${match}`) !== null) {
return tag.getAttribute(`data-${match}`)
// Check if parent has data attribute
} else if (tag.closest(`[data-${match}]`) && tag.closest(`[data-${match}]`).getAttribute(`data-${match}`) !== null) {
return tag.closest(`[data-${match}]`).getAttribute(`data-${match}`)
// Otherwise return global value
} else {
if (match in window) {
return (new Function(`return ${match}`))() || ''
}
}
})
tag.setAttribute(`data-variable-${attr}`, count)
styles += `[data-variable-${attr}="${count}"] { ${evaluated} }\n`
count++
})
return styles
}
// Viewport Visibility
function viewport(selector, option, rule) {
let styles = ''
let count = 0
const features = {
partly: (tag, rule) => {
const top = tag.offsetTop - innerHeight
const bottom = top + tag.offsetHeight
return (scrollY < tag.offsetTop + tag.offsetHeight)
&& (top < scrollY)
&& (bottom < scrollY + tag.offsetHeight)
? rule
: ''
},
fully: (tag, rule) => {
const top = tag.offsetTop - innerHeight
const bottom = top + tag.offsetHeight
return (scrollY < tag.offsetTop)
&& (top < scrollY)
&& (bottom < scrollY)
? rule
: ''
}
}
document.querySelectorAll(selector).forEach(tag => {
const attr = selector.replace(/\W/g, '')
const evaluated = features[option](tag, rule)
tag.setAttribute(`data-viewport-${attr}`, count)
styles += `[data-viewport-${attr}="${count}"] { ${evaluated} }\n`
count++
})
return styles
}
// XPath Selector
function xpath(selector, rule) {
let styles = ''
let count = 0
const tags = []
const result = document.evaluate(
selector,
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
)
for (var i=0; i < result.snapshotLength; i++) {
tags.push(result.snapshotItem(i))
}
tags.forEach(tag => {
const attr = selector.replace(/\W/g, '')
tag.setAttribute(`data-xpath-${attr}`, count)
styles += `[data-xpath-${attr}="${count}"] { ${rule} }\n`
count++
})
return styles
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment