Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jhancock532/05b59514651558e14dd71ab7dc80f74a to your computer and use it in GitHub Desktop.
Save jhancock532/05b59514651558e14dd71ab7dc80f74a to your computer and use it in GitHub Desktop.
Storybook pseudo states addon code for preview.js to get nested classes working
let pseudoStyleRules: any = {
':hover': [],
':visited': [],
};
function removeMatchingSubstrings(str: string, patterns: string[]) {
const regex = new RegExp(patterns.join('|'), 'g');
return str.replace(regex, '');
}
function matchCSSSelectors(element: Element, rules: any[]): any {
const matchedSelectors = [];
for (let i = 0; i < element.children.length; i++) {
const child = element.children[i];
const matches = rules.filter(rule => {
// Remove pseudo classes from selector
const selectorWithoutPseudoClass = removeMatchingSubstrings(
rule.selectorText,
Object.keys(pseudoStyleRules)
);
return child.matches(selectorWithoutPseudoClass);
});
if (matches.length > 0) {
matchedSelectors.push(...matches);
}
// Recursively check child's children
matchedSelectors.push(...matchCSSSelectors(child, rules));
}
return matchedSelectors;
}
document.addEventListener('DOMContentLoaded', function () {
const rules = [];
// Iterate over all the style sheets in the document
for (let i = 0; i < document.styleSheets.length; i++) {
const styleSheet = document.styleSheets[i];
// Check if the style sheet is accessible
try {
styleSheet.cssRules;
} catch (e: any) {
if (e.name === 'SecurityError') {
console.log('The style sheet is not accessible');
continue;
}
}
for (let j = 0; j < styleSheet.cssRules.length; j++) {
rules.push(styleSheet.cssRules[j]);
}
}
const filteredRules = rules.filter(rule => {
if (rule.type !== CSSRule.STYLE_RULE) {
return false;
}
return Object.keys(pseudoStyleRules).some(r => (rule as CSSStyleRule).selectorText.includes(r));
});
let componentContainer = document.getElementById('root');
const rulesRelatedToComponent = matchCSSSelectors(componentContainer as Element, filteredRules);
for (let i = 0; i < rulesRelatedToComponent.length; i++) {
//console.log(rulesRelatedToComponent[i].selectorText);
// split the selector text by `,`
let splitSelectorTextRules = rulesRelatedToComponent[i].selectorText.split(',');
// for each selector text,
for (let j = 0; j < splitSelectorTextRules.length; j++) {
// find which pseudo state it is related to
let pseudoState = Object.keys(pseudoStyleRules).find(pseudoState => {
return splitSelectorTextRules[j].includes(pseudoState);
});
// If a split rule does not include a pseudo state, skip it
if (!pseudoState) {
continue;
}
// remove the pseudo state from the selector text
let selectorWithoutPseudoState = removeMatchingSubstrings(splitSelectorTextRules[j], [
pseudoState as string,
]);
// trim the whitespace from the selector text
selectorWithoutPseudoState = selectorWithoutPseudoState.trim();
// append each split selector paired with the desired styles to the pseudoState dictionary
pseudoStyleRules[pseudoState as string].push({
selector: selectorWithoutPseudoState,
style: rulesRelatedToComponent[i].style,
});
}
}
console.log(pseudoStyleRules);
// for each pseudo state in the dictionary
for (let pseudoState in pseudoStyleRules) {
// create a new class
let pseudoStateClass = `pseudo-${pseudoState.replace(':', '')}`;
// create a new style element
let style = document.createElement('style');
// for each style rule in the pseudo state
for (let i = 0; i < pseudoStyleRules[pseudoState].length; i++) {
// create a new style rule
let pseudoStateStyleRule = `.${pseudoStateClass} `;
// append the selector to the new style rule
pseudoStateStyleRule += `${pseudoStyleRules[pseudoState][i].selector} {`;
// for each style in the style rule
for (let j = 0; j < pseudoStyleRules[pseudoState][i].style.length; j++) {
// append the style to the new style rule
pseudoStateStyleRule += `${pseudoStyleRules[pseudoState][i].style[j]}: ${pseudoStyleRules[pseudoState][
i
].style.getPropertyValue(pseudoStyleRules[pseudoState][i].style[j])};`;
}
// close the style rule
pseudoStateStyleRule += '}\n';
// add the new style rule to the style element
style.innerHTML = pseudoStateStyleRule;
}
// append the style element to the document
document.head.appendChild(style);
// add the new class to the component container
componentContainer?.classList.add(pseudoStateClass);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment