Skip to content

Commit

Permalink
Merge pull request #87 from chromaui/ghengeveld/ap-3505-pseudo-states…
Browse files Browse the repository at this point in the history
…-addon-doesnt-handle-dynamic-stylesheets

Check for rewritten pseudo states at rule-level rather than sheet-level
  • Loading branch information
ghengeveld committed Oct 11, 2023
2 parents 4b7f441 + 9ed8103 commit 2d5328b
Show file tree
Hide file tree
Showing 9 changed files with 2,871 additions and 1,127 deletions.
1 change: 1 addition & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"../",
"@chromaui/addon-visual-tests"
],
framework: {
name: "@storybook/react-webpack5",
Expand Down
5 changes: 5 additions & 0 deletions chromatic.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"buildScriptName": "build:storybook",
"projectId": "Project:6008aabce49a640021858011",
"projectToken": "uskf6kz75lb"
}
25 changes: 13 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,31 @@
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@storybook/addon-essentials": "^7.0.0",
"@storybook/addon-interactions": "^7.0.0",
"@storybook/addon-links": "^7.0.0",
"@storybook/react": "^7.0.0",
"@storybook/react-webpack5": "^7.0.0",
"@chromaui/addon-visual-tests": "^0.0.105",
"@storybook/addon-essentials": "^7.4.6",
"@storybook/addon-interactions": "^7.4.6",
"@storybook/addon-links": "^7.4.6",
"@storybook/react": "^7.4.6",
"@storybook/react-webpack5": "^7.4.6",
"@storybook/testing-library": "^0.0.13",
"@storybook/types": "^7.0.0",
"@storybook/types": "^7.4.6",
"@types/jest": "^29.2.4",
"auto": "^10.16.8",
"concurrently": "^5.3.0",
"jest": "^27.5.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rimraf": "^3.0.2",
"storybook": "^7.0.0",
"storybook": "^7.4.6",
"tsup": "^6.5.0",
"typescript": "^4.9.4"
},
"peerDependencies": {
"@storybook/components": "^7.0.0",
"@storybook/core-events": "^7.0.0",
"@storybook/manager-api": "^7.0.0",
"@storybook/preview-api": "^7.0.0",
"@storybook/theming": "^7.0.0",
"@storybook/components": "^7.4.6",
"@storybook/core-events": "^7.4.6",
"@storybook/manager-api": "^7.4.6",
"@storybook/preview-api": "^7.4.6",
"@storybook/theming": "^7.4.6",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
Expand Down
51 changes: 29 additions & 22 deletions src/preview/rewriteStyleSheet.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { rewriteStyleSheet } from "./rewriteStyleSheet"

class Rule {
__pseudoStatesRewritten: boolean
cssText: string
selectorText?: string

constructor(cssText: string) {
this.__pseudoStatesRewritten = false
if (cssText.trim().startsWith("@")) {
this.cssText = cssText
return
Expand All @@ -16,19 +19,21 @@ class Rule {
}
}

class Sheet {
type CSSRule = CSSStyleRule & {
__pseudoStatesRewritten: boolean
cssRules: CSSStyleRule[]
}

class Sheet {
cssRules: CSSRule[]

constructor(...rules: string[]) {
this.__pseudoStatesRewritten = false
this.cssRules = rules.map((cssText) => new Rule(cssText) as CSSStyleRule)
this.cssRules = rules.map((cssText) => new Rule(cssText) as CSSRule)
}
deleteRule(index: number) {
this.cssRules.splice(index, 1)
}
insertRule(cssText: string, index: number) {
this.cssRules.splice(index, 0, new Rule(cssText) as CSSStyleRule)
this.cssRules.splice(index, 0, new Rule(cssText) as CSSRule)
}
}

Expand Down Expand Up @@ -85,7 +90,9 @@ describe("rewriteStyleSheet", () => {
it('supports ":host"', () => {
const sheet = new Sheet(":host(:hover) { color: red }")
rewriteStyleSheet(sheet as any)
expect(sheet.cssRules[0].cssText).toEqual(":host(:hover), :host(.pseudo-hover), :host(.pseudo-hover-all) { color: red }")
expect(sheet.cssRules[0].cssText).toEqual(
":host(:hover), :host(.pseudo-hover), :host(.pseudo-hover-all) { color: red }"
)
})

it('supports ":not"', () => {
Expand All @@ -96,21 +103,22 @@ describe("rewriteStyleSheet", () => {

it("override correct rules with media query present", () => {
const sheet = new Sheet(
`@media (max-width: 790px) {
.test {
background-color: green;
}
}`,
`.test {
background-color: blue;
}`,
`.test:hover {
background-color: red;
}`,
`.test2:hover {
background-color: white;
}`)
rewriteStyleSheet(sheet)
`@media (max-width: 790px) {
.test {
background-color: green;
}
}`,
`.test {
background-color: blue;
}`,
`.test:hover {
background-color: red;
}`,
`.test2:hover {
background-color: white;
}`
)
rewriteStyleSheet(sheet as any)
expect(sheet.cssRules[0].cssText).toContain("@media (max-width: 790px)")
expect(sheet.cssRules[1].selectorText).toContain(".test")
expect(sheet.cssRules[2].selectorText).toContain(".test:hover")
Expand All @@ -119,6 +127,5 @@ describe("rewriteStyleSheet", () => {
expect(sheet.cssRules[3].selectorText).toContain(".test2:hover")
expect(sheet.cssRules[3].selectorText).toContain(".test2.pseudo-hover")
expect(sheet.cssRules[3].selectorText).toContain(".pseudo-hover-all .test2")

})
})
15 changes: 8 additions & 7 deletions src/preview/rewriteStyleSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ const rewriteRule = ({ cssText, selectorText }: CSSStyleRule, shadowRoot?: Shado
return acc.replace(new RegExp(`:${state}`, "g"), `.pseudo-${state}-all`)
}, selector)


if (selector.startsWith(":host(") || selector.startsWith("::slotted(")) {
return [selector, classSelector, classAllSelector].filter(Boolean)
}
Expand All @@ -67,23 +66,25 @@ export const rewriteStyleSheet = (
shadowRoot?: ShadowRoot,
shadowHosts?: Set<Element>
) => {
// @ts-expect-error
if (sheet.__pseudoStatesRewritten) return
// @ts-expect-error
sheet.__pseudoStatesRewritten = true

try {
let index = -1
for (const cssRule of sheet.cssRules) {
index++
if (!("selectorText" in cssRule)) continue

// @ts-expect-error
if (cssRule.__pseudoStatesRewritten || !("selectorText" in cssRule)) continue

const styleRule = cssRule as CSSStyleRule
if (matchOne.test(styleRule.selectorText)) {
const newRule = rewriteRule(styleRule, shadowRoot)
sheet.deleteRule(index)
sheet.insertRule(newRule, index)
if (shadowRoot && shadowHosts) shadowHosts.add(shadowRoot.host)
}

// @ts-expect-error
cssRule.__pseudoStatesRewritten = true

if (index > 1000) {
warnOnce("Reached maximum of 1000 pseudo selectors per sheet, skipping the rest.")
break
Expand Down
8 changes: 7 additions & 1 deletion src/preview/withPseudoState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* eslint-env browser */
import {
DOCS_RENDERED,
FORCE_REMOUNT,
FORCE_RE_RENDER,
GLOBALS_UPDATED,
STORY_CHANGED,
STORY_RENDERED,
UPDATE_GLOBALS,
Expand Down Expand Up @@ -53,7 +56,7 @@ const applyParameter = (rootElement: Element, parameter: PseudoStateConfig = {})
map.forEach((states, target) => {
const classnames = new Set<string>()
states.forEach((key) => {
const keyWithoutAll = key.replace('-all', '') as PseudoState
const keyWithoutAll = key.replace("-all", "") as PseudoState
if (PSEUDO_STATES[key]) {
classnames.add(`pseudo-${PSEUDO_STATES[key]}`)
} else if (PSEUDO_STATES[keyWithoutAll]) {
Expand Down Expand Up @@ -153,6 +156,9 @@ channel.on(STORY_CHANGED, () => shadowHosts.clear())

// Reinitialize CSS enhancements every time the story changes
channel.on(STORY_RENDERED, () => rewriteStyleSheets())
channel.on(GLOBALS_UPDATED, () => rewriteStyleSheets())
channel.on(FORCE_RE_RENDER, () => rewriteStyleSheets())
channel.on(FORCE_REMOUNT, () => rewriteStyleSheets())

// Reinitialize CSS enhancements every time a docs page is rendered
channel.on(DOCS_RENDERED, () => rewriteStyleSheets())
Expand Down
4 changes: 3 additions & 1 deletion stories/Button.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from "react"
import "./button.css"

export const Button = (props) => <button {...props} className="button" />
export const Button = (props) => (
<button {...props} className={["button", props.className].filter(Boolean).join(" ")} />
)
41 changes: 29 additions & 12 deletions stories/Button.stories.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FORCE_REMOUNT } from "@storybook/core-events"
import { useChannel, useStoryContext } from "@storybook/preview-api"
import React from "react"

import { Button } from "./Button"
Expand All @@ -10,31 +12,31 @@ export default {

const Template = (args) => <Button {...args}>Label</Button>

export const All = () => (
export const All = (args) => (
<div className="story-grid">
<div>
<Button>Normal</Button>
<Button {...args}>Normal</Button>
</div>
<div className="pseudo-hover-all">
<Button>Hover</Button>
<Button {...args}>Hover</Button>
</div>
<div className="pseudo-focus-all">
<Button>Focus</Button>
<Button {...args}>Focus</Button>
</div>
<div className="pseudo-active-all">
<Button>Active</Button>
<Button {...args}>Active</Button>
</div>
<div className="pseudo-hover-all pseudo-focus-all">
<Button>Hover Focus</Button>
<Button {...args}>Hover Focus</Button>
</div>
<div className="pseudo-hover-all pseudo-active-all">
<Button>Hover Active</Button>
<Button {...args}>Hover Active</Button>
</div>
<div className="pseudo-focus-all pseudo-active-all">
<Button>Focus Active</Button>
<Button {...args}>Focus Active</Button>
</div>
<div className="pseudo-hover-all pseudo-focus-all pseudo-active-all">
<Button>Hover Focus Active</Button>
<Button {...args}>Hover Focus Active</Button>
</div>
</div>
)
Expand Down Expand Up @@ -89,9 +91,9 @@ DirectSelector.parameters = {

export const DirectSelectorParentDoesNotAffectDescendants = () => (
<>
<Button id='foo'>Hovered 1</Button>
<Button id="foo">Hovered 1</Button>

<div id='foo'>
<div id="foo">
<Button>Not Hovered 1 </Button>
<Button>Not Hovered 2</Button>
</div>
Expand All @@ -102,4 +104,19 @@ DirectSelectorParentDoesNotAffectDescendants.parameters = {
pseudo: {
hover: ["#foo"],
},
}
}

export const DynamicStyles = {
render: () => {
const emit = useChannel({})
const { id: storyId } = useStoryContext()
setTimeout(() => {
if (window.__dynamicRuleInjected) return
window.__dynamicRuleInjected = true
const sheet = Array.from(document.styleSheets).at(-1)
sheet.insertRule(".dynamic.button:hover { background-color: tomato }")
emit(FORCE_REMOUNT, { storyId })
}, 100)
return <All className="dynamic" />
},
}

0 comments on commit 2d5328b

Please sign in to comment.