Skip to content

Commit

Permalink
Merge pull request #117 from m-akinc/fix-part-regression
Browse files Browse the repository at this point in the history
Fix regression in pseudo-element handling
  • Loading branch information
ghengeveld committed May 3, 2024
2 parents 64a04cb + a8f0437 commit 59cb994
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 36 deletions.
2 changes: 2 additions & 0 deletions src/preview/rewriteStyleSheet.test.ts
Expand Up @@ -141,6 +141,7 @@ describe("rewriteStyleSheet", () => {
const sheet = new Sheet("::-webkit-scrollbar-thumb:hover { border-color: transparent; }")
rewriteStyleSheet(sheet as any)
expect(sheet.cssRules[0].getSelectors()).not.toContain("::-webkit-scrollbar-thumb.pseudo-hover")
expect(sheet.cssRules[0].getSelectors()).toContain(".pseudo-hover-all ::-webkit-scrollbar-thumb")
})

it("adds alternative selector when ::-webkit-scrollbar-thumb follows :hover", () => {
Expand All @@ -153,6 +154,7 @@ describe("rewriteStyleSheet", () => {
const sheet = new Sheet("::part(foo bar):hover { border-color: transparent; }")
rewriteStyleSheet(sheet as any)
expect(sheet.cssRules[0].getSelectors()).not.toContain("::part(foo bar).pseudo-hover")
expect(sheet.cssRules[0].getSelectors()).toContain(".pseudo-hover-all ::part(foo bar)")
})

it("adds alternative selector when ::part() follows :hover", () => {
Expand Down
26 changes: 17 additions & 9 deletions src/preview/rewriteStyleSheet.ts
@@ -1,13 +1,11 @@
import { PSEUDO_STATES, EXCLUDED_PSEUDO_ELEMENT_PATTERNS } from "../constants"
import { splitSelectors } from "./splitSelectors"

const pseudoStateRegExp = (global: boolean, pseudoStates: string[]) =>
new RegExp(`(?<!(?:${EXCLUDED_PSEUDO_ELEMENT_PATTERNS.join("|")})\\S*):(${pseudoStates.join("|")})`, global ? "g" : undefined)
const pseudoStates = Object.values(PSEUDO_STATES)
const matchOne = pseudoStateRegExp(false, pseudoStates)
const matchAll = pseudoStateRegExp(true, pseudoStates)
const replacementRegExp = (pseudoState: string) => pseudoStateRegExp(true, [pseudoState])

const pseudoStatesPattern = `:(${pseudoStates.join("|")})`
const matchOne = new RegExp(pseudoStatesPattern)
const matchAll = new RegExp(pseudoStatesPattern, "g")
const warnings = new Set()
const warnOnce = (message: string) => {
if (warnings.has(message)) return
Expand All @@ -17,7 +15,11 @@ const warnOnce = (message: string) => {
}

const replacePseudoStates = (selector: string, allClass?: boolean) => {
return pseudoStates.reduce((acc, state) => acc.replace(replacementRegExp(state), `.pseudo-${state}${allClass ? "-all" : ""}`), selector)
const negativeLookbehind = `(?<!(?:${EXCLUDED_PSEUDO_ELEMENT_PATTERNS.join("|")})\\S*)`
return pseudoStates.reduce((acc, state) => acc.replace(
new RegExp(`${negativeLookbehind}:${state}`, "g"),
`.pseudo-${state}${allClass ? "-all" : ""}`
), selector)
}

// Does not handle :host() or :not() containing pseudo-states. Need to call replaceNotSelectors on the input first.
Expand Down Expand Up @@ -77,11 +79,16 @@ const rewriteRule = ({ cssText, selectorText }: CSSStyleRule, forShadowDOM: bool
if (selector.includes(".pseudo-")) {
return []
}
const replacementSelectors = [selector]
if (!matchOne.test(selector)) {
return [selector]
return replacementSelectors
}

const classSelector = replacePseudoStates(selector)
if (classSelector !== selector) {
replacementSelectors.push(classSelector)
}

let ancestorSelector = ""

if (selector.startsWith(":host(")) {
Expand All @@ -108,8 +115,9 @@ const rewriteRule = ({ cssText, selectorText }: CSSStyleRule, forShadowDOM: bool
const withNotsReplaced = rewriteNotSelectors(selector, forShadowDOM)
ancestorSelector = replacePseudoStatesWithAncestorSelector(withNotsReplaced, forShadowDOM)
}
replacementSelectors.push(ancestorSelector)

return [selector, classSelector, ancestorSelector]
return replacementSelectors
})
.join(", ")
)
Expand Down
23 changes: 23 additions & 0 deletions stories/ShadowRootWithPart.css
@@ -0,0 +1,23 @@
::part(foo) {
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 700;
border: 0;
border-radius: 3em;
cursor: pointer;
display: inline-block;
line-height: 1;
color: white;
background-color: tomato;
font-size: 14px;
padding: 11px 20px;
}
::part(foo):hover {
text-decoration: underline;
}
::part(foo):focus {
box-shadow: inset 0 0 0 2px maroon;
outline: 0;
}
::part(foo):active {
background-color: firebrick;
}
28 changes: 1 addition & 27 deletions stories/ShadowRootWithPart.js
@@ -1,4 +1,5 @@
import React from "react"
import "./ShadowRootWithPart.css"

export const ShadowRoot = ({ label = "Hello from shadow DOM" }) => {
const ref = React.useRef()
Expand All @@ -9,33 +10,6 @@ export const ShadowRoot = ({ label = "Hello from shadow DOM" }) => {
ref.current.shadowRoot.innerHTML = `
<button part="foo">${label}</button>
`
ref.current.innerHTML = `
<style>
::part(foo) {
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 700;
border: 0;
border-radius: 3em;
cursor: pointer;
display: inline-block;
line-height: 1;
color: white;
background-color: tomato;
font-size: 14px;
padding: 11px 20px;
}
::part(foo):hover {
text-decoration: underline;
}
::part(foo):focus {
box-shadow: inset 0 0 0 2px maroon;
outline: 0;
}
::part(foo):active {
background-color: firebrick;
}
</style>
`
}, [])

return <div ref={ref} />
Expand Down

0 comments on commit 59cb994

Please sign in to comment.