Skip to content

Instantly share code, notes, and snippets.

@barneycarroll
Created July 17, 2024 14:08
Show Gist options
  • Save barneycarroll/8f49070bab171034d82fadd8615d7c8c to your computer and use it in GitHub Desktop.
Save barneycarroll/8f49070bab171034d82fadd8615d7c8c to your computer and use it in GitHub Desktop.
A CSS @scope polyfill, extracted from Jon Neals pen here: https://codepen.io/jonneal/pen/xxpqdpJ
/** Return a unique selector for a specific element. */
const getUniqueSelector = (/** @type {Element} */ element) => {
/** Unique selector for this element */
let selector = ''
/** @type {Element} */
let parent
while (parent = element.parentElement) {
/** @type {number} Nth-child order of the element. */
const nthChild = Array.prototype.indexOf.call(parent.children, element) + 1
selector = ` > :nth-child(${nthChild})${selector}`
element = parent
}
return ':root' + selector
}
let supportMap = new WeakMap()
let polyfillScope = (document) => {
for (let sheet of document.styleSheets) {
for (let index = 0; index < sheet.cssRules.length; ++index) {
let rule = sheet.cssRules[index]
if (rule.type === 12) {
let { conditionText } = rule
let match = conditionText.match(/^\(polyfill @scope \((.+) to\((.+)\)\)\)$/)
if (match) {
if (supportMap.has(rule)) continue
let [ , from, to ] = match
let mediaRule = sheet.cssRules[sheet.insertRule(`@media ${`@scope ${from} to(${to})`.replace(/[^\w]/g, '\\$&')},all` + rule.cssText.slice('@supports '.length + rule.conditionText.length), index++)]
supportMap.set(rule, mediaRule)
supportMap.set(mediaRule, rule)
let fromElements = Array.from(document.querySelectorAll(from))
for (let innerRule of mediaRule.cssRules) {
if (innerRule.type === 1) {
let narrElements = fromElements.map(
from => Array.from(
from.querySelectorAll(':is(' + innerRule.selectorText + '):not(:scope :is(' + to + ') *)')
)
).flat()
let selectors = narrElements.map(
element => getUniqueSelector(element)
)
innerRule.selectorText += ':where(' + selectors + ')'
}
}
}
}
}
}
}
let observer = new MutationObserver(() => polyfillScope(document));
observer.observe(document.documentElement, { childList: true, subtree: true });
polyfillScope(document);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment