storybook-addon-pseudo-states
Version:
Storybook Pseudo-states addon: Manipulate CSS pseudo states
8 lines (5 loc) • 9.96 kB
JavaScript
import { definePreviewAddon } from 'storybook/internal/csf';
import { STORY_CHANGED, STORY_RENDERED, GLOBALS_UPDATED, FORCE_RE_RENDER, FORCE_REMOUNT, DOCS_RENDERED, UPDATE_GLOBALS } from 'storybook/internal/core-events';
import { addons, useMemo, useRef, useEffect } from 'storybook/preview-api';
var __defProp=Object.defineProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0});};var preview_exports={};__export(preview_exports,{decorators:()=>decorators,initialGlobals:()=>initialGlobals});var PARAM_KEY="pseudo",EXCLUDED_PSEUDO_ELEMENT_PATTERNS=["::-(webkit|moz|ms)-[a-z-]+","::part\\([^)]+\\)"],EXCLUDED_PSEUDO_ESCAPE_SEQUENCE="(?<=(?<!\\\\)(?:\\\\\\\\)*)",PSEUDO_STATES={hover:"hover",active:"active",focusVisible:"focus-visible",focusWithin:"focus-within",focus:"focus",visited:"visited",link:"link",target:"target"};var isAtRule=selector=>selector.indexOf("@")===0,splitSelectors=selectors=>{if(isAtRule(selectors))return [selectors];let result=[],parentheses=0,brackets=0,selector="";for(let i=0,len=selectors.length;i<len;i++){let char=selectors[i];if(char==="(")parentheses+=1;else if(char===")")parentheses-=1;else if(char==="[")brackets+=1;else if(char==="]")brackets-=1;else if(char===","&&!parentheses&&!brackets){result.push(selector.trim()),selector="";continue}selector+=char;}return result.push(selector.trim()),result};var pseudoStates=Object.values(PSEUDO_STATES),pseudoStatesPattern=`${EXCLUDED_PSEUDO_ESCAPE_SEQUENCE}:(${pseudoStates.join("|")})`,matchOne=new RegExp(pseudoStatesPattern),matchAll=new RegExp(pseudoStatesPattern,"g"),warnings=new Set,warnOnce=message=>{warnings.has(message)||(console.warn(message),warnings.add(message));},replacePseudoStates=(selector,allClass)=>{let negativeLookbehind=`(?<!(?:${EXCLUDED_PSEUDO_ELEMENT_PATTERNS.join("|")})\\S*)`;return pseudoStates.reduce((acc,state)=>acc.replace(new RegExp(`${negativeLookbehind}${EXCLUDED_PSEUDO_ESCAPE_SEQUENCE}:${state}`,"g"),`.pseudo-${state}${allClass?"-all":""}`),selector)},replacePseudoStatesWithAncestorSelector=(selector,forShadowDOM,additionalHostSelectors)=>{let extracted=extractPseudoStates(selector);if(extracted.states.length===0&&!additionalHostSelectors)return selector;let selectors=`${additionalHostSelectors??""}${extracted.states.map(s=>`.pseudo-${s}-all`).join("")}`,{withoutPseudoStates}=extracted;return withoutPseudoStates=withoutPseudoStates.replace(":host-context(*)","").trimStart(),withoutPseudoStates.startsWith(":host-context(")?withoutPseudoStates.replace(/(?<=:host-context\(\S+)\)/,`)${selectors}`):forShadowDOM?`:host(${selectors}) ${withoutPseudoStates}`:`${selectors} ${withoutPseudoStates}`},extractPseudoStates=selector=>{let states=new Set,withoutPseudoStates=selector.replace(matchAll,(_,state)=>(states.add(state),"")).replaceAll(/(?<!is)\(\)/g,"(*)").replace(/(?<=[\s(]),\s+|(,\s+)+(?=\))/g,"")||"*";return {states:Array.from(states),withoutPseudoStates}},rewriteNotSelectors=(selector,forShadowDOM)=>[...selector.matchAll(/:not\(([^)]+)\)/g)].reduce((acc,match)=>{let originalNot=match[0],selectorList=match[1],rewrittenNot=rewriteNotSelector(selectorList,forShadowDOM);return acc.replace(originalNot,rewrittenNot)},selector),rewriteNotSelector=(negatedSelectorList,forShadowDOM)=>{let rewrittenSelectors=[];for(let negatedSelector of negatedSelectorList.split(/,\s*/))rewrittenSelectors.push(replacePseudoStatesWithAncestorSelector(negatedSelector,forShadowDOM));return `:not(${rewrittenSelectors.join(", ")})`},rewriteRule=({cssText,selectorText},forShadowDOM)=>cssText.replace(selectorText,splitSelectors(selectorText).flatMap(selector=>{if(selector.includes(".pseudo-"))return [];let replacementSelectors=[selector];if(!matchOne.test(selector))return replacementSelectors;let classSelector=replacePseudoStates(selector);classSelector!==selector&&replacementSelectors.push(classSelector);let ancestorSelector="";if(selector.startsWith(":host(")){let matches=selector.match(/^:host\((\S+)\)\s+(.+)$/);if(matches&&matchOne.test(matches[2])){let hostInnerSelector=matches[1],descendantSelector=matches[2];hostInnerSelector=replacePseudoStates(hostInnerSelector,!0),descendantSelector=rewriteNotSelectors(descendantSelector,!0),ancestorSelector=replacePseudoStatesWithAncestorSelector(descendantSelector,!0,hostInnerSelector);}else ancestorSelector=replacePseudoStates(selector,!0);}else {let withNotsReplaced=rewriteNotSelectors(selector,forShadowDOM);ancestorSelector=replacePseudoStatesWithAncestorSelector(withNotsReplaced,forShadowDOM);}return replacementSelectors.push(ancestorSelector),replacementSelectors}).join(", ")),rewriteStyleSheet=(sheet,forShadowDOM=!1)=>{try{let count=rewriteRuleContainer(sheet,1e3,forShadowDOM);return count>=1e3&&warnOnce("Reached maximum of 1000 pseudo selectors per sheet, skipping the rest."),count>0}catch(e){return String(e).includes("cssRules")?warnOnce(`Can't access cssRules, likely due to CORS restrictions: ${sheet.href}`):console.error(e,sheet.href),!1}},rewriteRuleContainer=(ruleContainer,rewriteLimit,forShadowDOM)=>{let count=0,index=-1;for(let cssRule of ruleContainer.cssRules){index++;let numRewritten=0;if(cssRule.__processed)numRewritten=cssRule.__pseudoStatesRewrittenCount;else {if("cssRules"in cssRule&&cssRule.cssRules.length)numRewritten=rewriteRuleContainer(cssRule,rewriteLimit-count,forShadowDOM);else {if(!("selectorText"in cssRule))continue;let styleRule=cssRule;if(matchOne.test(styleRule.selectorText)){let newRule=rewriteRule(styleRule,forShadowDOM);ruleContainer.deleteRule(index),ruleContainer.insertRule(newRule,index),numRewritten=1;}}cssRule.__processed=!0,cssRule.__pseudoStatesRewrittenCount=numRewritten;}if(count+=numRewritten,count>=rewriteLimit)break}return count};var channel=addons.getChannel(),shadowHosts=new Set,applyClasses=(element,classnames)=>{Object.values(PSEUDO_STATES).forEach(state=>{element.classList.remove(`pseudo-${state}`),element.classList.remove(`pseudo-${state}-all`);}),classnames.forEach(classname=>element.classList.add(classname));};function querySelectorPiercingShadowDOM(root,selector){let results=[];return root.querySelectorAll("*").forEach(el=>{el.shadowRoot&&results.push(...querySelectorPiercingShadowDOM(el.shadowRoot,selector));}),results.push(...root.querySelectorAll(selector).values()),results}var applyParameter=(rootElement,parameter={})=>{let map=new Map([[rootElement,new Set]]),add=(target,state)=>map.set(target,new Set([...map.get(target)||[],state]));Object.entries(parameter||{}).forEach(([state,value])=>{typeof value=="boolean"?value&&add(rootElement,`${state}-all`):typeof value=="string"?querySelectorPiercingShadowDOM(rootElement,value).forEach(el=>add(el,state)):Array.isArray(value)&&value.forEach(sel=>querySelectorPiercingShadowDOM(rootElement,sel).forEach(el=>add(el,state)));}),map.forEach((states,target)=>{let classnames=new Set;states.forEach(key=>{let keyWithoutAll=key.replace("-all","");PSEUDO_STATES[key]?classnames.add(`pseudo-${PSEUDO_STATES[key]}`):PSEUDO_STATES[keyWithoutAll]&&classnames.add(`pseudo-${PSEUDO_STATES[keyWithoutAll]}-all`);}),applyClasses(target,classnames);});},updateShadowHost=shadowHost=>{let classnames=new Set;shadowHost.className.split(" ").filter(classname=>classname.match(/^pseudo-(.(?!-all))+$/)).forEach(classname=>classnames.add(classname));for(let node=shadowHost.parentNode;node;){if(node instanceof ShadowRoot){node=node.host;continue}if(node instanceof Element){let element=node;element.className&&element.className.split(" ").filter(classname=>classname.match(/^pseudo-.+-all$/)!==null).forEach(classname=>classnames.add(classname));}node=node.parentNode;}applyClasses(shadowHost,classnames);},pseudoConfig=parameter=>{let{rootSelector,...pseudoStateConfig}=parameter||{};return pseudoStateConfig},equals=(a={},b={})=>a!==null&&b!==null&&Object.keys(a).length===Object.keys(b).length&&Object.keys(a).every(key=>JSON.stringify(a[key])===JSON.stringify(b[key])),withPseudoState=(StoryFn,{viewMode,parameters,id,globals:globalsArgs})=>{let{pseudo:parameter}=parameters,{pseudo:globals}=globalsArgs,{rootSelector}=parameter||{},rootElement=useMemo(()=>rootSelector?document.querySelector(rootSelector):viewMode==="docs"?document.getElementById(`story--${id}`):document.getElementById("storybook-root")||document.getElementById("root"),[rootSelector,viewMode,id]),globalsRef=useRef(globals);return useEffect(()=>{let config=pseudoConfig(parameter);viewMode==="story"&&!equals(config,globalsRef.current)&&channel.emit(UPDATE_GLOBALS,{globals:{pseudo:config}});},[parameter,viewMode]),useEffect(()=>{if(!rootElement)return;let timeout=setTimeout(()=>{applyParameter(rootElement,globals||pseudoConfig(parameter)),shadowHosts.forEach(updateShadowHost);},0);return ()=>clearTimeout(timeout)},[rootElement,globals,parameter]),StoryFn()},rewriteStyleSheets=shadowRoot=>{let styleSheets=Array.from(shadowRoot?shadowRoot.styleSheets:document.styleSheets);shadowRoot?.adoptedStyleSheets?.length&&(styleSheets=shadowRoot.adoptedStyleSheets),styleSheets.forEach(sheet=>rewriteStyleSheet(sheet,!!shadowRoot)),shadowRoot&&shadowHosts&&shadowHosts.add(shadowRoot.host);};channel.on(STORY_CHANGED,()=>shadowHosts.clear());channel.on(STORY_RENDERED,()=>rewriteStyleSheets());channel.on(GLOBALS_UPDATED,()=>rewriteStyleSheets());channel.on(FORCE_RE_RENDER,()=>rewriteStyleSheets());channel.on(FORCE_REMOUNT,()=>rewriteStyleSheets());channel.on(DOCS_RENDERED,()=>rewriteStyleSheets());Element.prototype.attachShadow&&(Element.prototype._attachShadow=Element.prototype.attachShadow,Element.prototype.attachShadow=function(init){let shadowRoot=this._attachShadow({...init,mode:"open"});return requestAnimationFrame(()=>{rewriteStyleSheets(shadowRoot),shadowHosts.has(shadowRoot.host)&&updateShadowHost(shadowRoot.host);}),shadowRoot});var decorators=[withPseudoState],initialGlobals={[PARAM_KEY]:!1};var index_default=()=>definePreviewAddon(preview_exports);
export { PARAM_KEY, index_default as default };