use-theme-editor
Version:
Zero configuration CSS variables based theme editor
63 lines (54 loc) • 2.42 kB
JavaScript
import { compare } from 'specificity';
import { definedValues } from './collectRuleVars';
import { allStateSelectorsRegexp } from './getMatchingVars';
import { getMaxMatchingSpecificity } from './getOnlyMostSpecific';
export function getMatchingScopes(target, vars) {
const matchingSelectors = Object.keys(definedValues).filter((rawSelector) => {
if (rawSelector === ':root') {
// We're only interested in local scopes here.
// Technically this should return true, as there may be definitions in the code for the global scope.
// But so far the editor just considers these the same as default values.
return false;
}
const selector = rawSelector.replace(allStateSelectorsRegexp, '').replace(/:?:(before|after|first\-letter)/g, '');
const withInnerElementsSelector = `${selector}, ${selector.replace(',', ' *,')} *`;
try {
return target.matches(withInnerElementsSelector);
} catch (e) {
console.log('Failed testing scope selector:', withInnerElementsSelector);
}
});
// Matches that actually can affect a custom property
const withMatchingVars = matchingSelectors.reduce((all, selector) => {
const scopeVars = vars.filter(({ name }) => name in definedValues[selector]);
if (scopeVars.length > 0) {
all.push({ selector, scopeVars });
}
return all;
}, []);
const withMostSpecific = withMatchingVars.map((scope) => {
const { selector } = scope;
if (!/,/.test(selector)) {
scope.matchingSelector = selector;
} else {
let max = null, currentParent = target;
// Travel up in case scope was from parent.
while (max === null && currentParent?.parentNode) {
// Quick hack using an existing function that checks if the property exists on the element's style.
// We're only matching the whole scope and don't care about the property.
max = getMaxMatchingSpecificity([{ selector, property: '__definitely_not_exists__' }], currentParent);
currentParent = currentParent.parentNode;
}
scope.matchingSelector = max.winningSelector;
}
return scope;
});
return withMostSpecific.sort((a,b) => {
const result = compare(a.matchingSelector, b.matchingSelector);
if (result === 0) {
return -1;
}
// Sort opposite direction.
return result * -1;
});
};