UNPKG

storybook-addon-pseudo-states

Version:
238 lines (230 loc) • 11.7 kB
import { EXCLUDED_PSEUDO_ELEMENT_PATTERNS, EXCLUDED_PSEUDO_ESCAPE_SEQUENCE, PARAM_KEY, PSEUDO_STATES, __export } from "./chunk-FLRMKMJW.js"; // src/preview.ts var preview_exports = {}; __export(preview_exports, { decorators: () => decorators, initialGlobals: () => initialGlobals }); // src/preview/withPseudoState.ts import { DOCS_RENDERED, FORCE_REMOUNT, FORCE_RE_RENDER, GLOBALS_UPDATED, STORY_CHANGED, STORY_RENDERED, UPDATE_GLOBALS } from "storybook/internal/core-events"; import { addons, useEffect, useMemo, useRef } from "storybook/preview-api"; // src/preview/splitSelectors.ts 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; }; // src/preview/rewriteStyleSheet.ts var pseudoStates = Object.values(PSEUDO_STATES), pseudoStatesPattern = `${EXCLUDED_PSEUDO_ESCAPE_SEQUENCE}:(${pseudoStates.join("|")})`, matchOne = new RegExp(pseudoStatesPattern), matchAll = new RegExp(pseudoStatesPattern, "g"), warnings = /* @__PURE__ */ 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 = /* @__PURE__ */ new Set(), withoutPseudoStates = selector.replace(matchAll, (_, state) => (states.add(state), "")).replaceAll(/(?<!is)\(\)/g, "(*)").replace(/([>+~])\s*(?=$|[,)])/g, "$1 *").replace(/(?<=[\s(]),\s+|(,\s+)+(?=\))/g, "") || "*"; return { states: Array.from(states), withoutPseudoStates }; }, rewriteNotSelectors = (selector, forShadowDOM) => [...selector.matchAll(/:not\((?:[^()]|\([^()]+\)|\((?:[^()]|\([^()]+\))+\))+\)/g)].reduce( (acc, [originalNot]) => { let selectorList = originalNot.match(/^:not\((.+)\)$/)?.[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 { let styleRule = cssRule; if ("selectorText" in styleRule && matchOne.test(styleRule.selectorText)) { let newRule = rewriteRule(styleRule, forShadowDOM); ruleContainer.deleteRule(index), ruleContainer.insertRule(newRule, index), styleRule = ruleContainer.cssRules[index], numRewritten = 1; } "cssRules" in styleRule && styleRule.cssRules.length && (numRewritten = rewriteRuleContainer( styleRule, rewriteLimit - count, forShadowDOM )), cssRule.__processed = !0, cssRule.__pseudoStatesRewrittenCount = numRewritten; } if (count += numRewritten, count >= rewriteLimit) break; } return count; }; // src/preview/withPseudoState.ts var channel = addons.getChannel(), shadowHosts = /* @__PURE__ */ 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 = /* @__PURE__ */ new Map([[rootElement, /* @__PURE__ */ new Set()]]), add = (target, state) => map.set(target, /* @__PURE__ */ 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 = /* @__PURE__ */ 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 = /* @__PURE__ */ 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") || // Storybook 7.0+ document.getElementById("root"), [rootSelector, viewMode, id]), globalsRef = useRef(globals); return useEffect(() => { let config = pseudoConfig(parameter); viewMode === "story" && Object.keys(config).length > 0 && !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; }); // src/preview.ts var decorators = [withPseudoState], initialGlobals = { [PARAM_KEY]: !1 }; export { decorators, initialGlobals, preview_exports };