Skip to content

Commit

Permalink
Merge pull request #85 from chromaui/56-selecting-specific-element-al…
Browse files Browse the repository at this point in the history
…so-applies-pseudostate-to-all-its-descendants-alternate

Selecting specific element also applies pseudo state to all its descendants alternate
  • Loading branch information
JonathanKolnik authored Aug 21, 2023
2 parents 0610a5b + 0bc3584 commit 479fae3
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 45 deletions.
16 changes: 8 additions & 8 deletions src/preview/rewriteStyleSheet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("rewriteStyleSheet", () => {
it("adds alternative selector targeting an ancestor", () => {
const sheet = new Sheet("a:hover { color: red }")
rewriteStyleSheet(sheet as any)
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-hover a")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-hover-all a")
})

it("does not add .pseudo-<class> to pseudo-class, which does not support classes", () => {
Expand All @@ -56,8 +56,8 @@ describe("rewriteStyleSheet", () => {
rewriteStyleSheet(sheet as any)
expect(sheet.cssRules[0].selectorText).toContain("a.pseudo-hover")
expect(sheet.cssRules[0].selectorText).toContain("a.pseudo-focus")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-hover a")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-focus a")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-hover-all a")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-focus-all a")
})

it("keeps non-pseudo selectors as-is", () => {
Expand All @@ -71,21 +71,21 @@ describe("rewriteStyleSheet", () => {
const sheet = new Sheet("a:hover:focus { color: red }")
rewriteStyleSheet(sheet as any)
expect(sheet.cssRules[0].selectorText).toContain("a.pseudo-hover.pseudo-focus")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-hover.pseudo-focus a")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-hover-all.pseudo-focus-all a")
})

it("supports combined pseudo selectors with classes", () => {
const sheet = new Sheet(".hiOZqY:hover { color: red }")
rewriteStyleSheet(sheet as any)
expect(sheet.cssRules[0].selectorText).toContain(".hiOZqY:hover")
expect(sheet.cssRules[0].selectorText).toContain(".hiOZqY.pseudo-hover")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-hover .hiOZqY")
expect(sheet.cssRules[0].selectorText).toContain(".pseudo-hover-all .hiOZqY")
})

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) { color: red }")
expect(sheet.cssRules[0].cssText).toEqual(":host(:hover), :host(.pseudo-hover), :host(.pseudo-hover-all) { color: red }")
})

it('supports ":not"', () => {
Expand Down Expand Up @@ -115,10 +115,10 @@ describe("rewriteStyleSheet", () => {
expect(sheet.cssRules[1].selectorText).toContain(".test")
expect(sheet.cssRules[2].selectorText).toContain(".test:hover")
expect(sheet.cssRules[2].selectorText).toContain(".test.pseudo-hover")
expect(sheet.cssRules[2].selectorText).toContain(".pseudo-hover .test")
expect(sheet.cssRules[2].selectorText).toContain(".pseudo-hover-all .test")
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 .test2")
expect(sheet.cssRules[3].selectorText).toContain(".pseudo-hover-all .test2")

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

const classAllSelector = states.reduce((acc, state) => {
if (isExcludedPseudoElement(selector, state)) return ""
return acc.replace(new RegExp(`:${state}`, "g"), `.pseudo-${state}-all`)
}, selector)


if (selector.startsWith(":host(") || selector.startsWith("::slotted(")) {
return [selector, classSelector].filter(Boolean)
return [selector, classSelector, classAllSelector].filter(Boolean)
}

const ancestorSelector = shadowRoot
? `:host(${states.map((s) => `.pseudo-${s}`).join("")}) ${plainSelector}`
: `${states.map((s) => `.pseudo-${s}`).join("")} ${plainSelector}`
? `:host(${states.map((s) => `.pseudo-${s}-all`).join("")}) ${plainSelector}`
: `${states.map((s) => `.pseudo-${s}-all`).join("")} ${plainSelector}`

return [selector, classSelector, ancestorSelector].filter(
(selector) => selector && !selector.includes(":not()")
Expand Down
16 changes: 13 additions & 3 deletions src/preview/withPseudoState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ const shadowHosts = new Set<Element>()
// Drops any existing pseudo state classnames that carried over from a previously viewed story
// before adding the new classnames. We use forEach for IE compatibility.
const applyClasses = (element: Element, classnames: Set<string>) => {
Object.values(PSEUDO_STATES).forEach((state) => element.classList.remove(`pseudo-${state}`))
Object.values(PSEUDO_STATES).forEach((state) => {
element.classList.remove(`pseudo-${state}`)
element.classList.remove(`pseudo-${state}-all`)
})
classnames.forEach((classname) => element.classList.add(classname))
}

Expand All @@ -37,7 +40,7 @@ const applyParameter = (rootElement: Element, parameter: PseudoStateConfig = {})
;(Object.entries(parameter || {}) as [PseudoState, any]).forEach(([state, value]) => {
if (typeof value === "boolean") {
// default API - applying pseudo class to root element.
if (value) add(rootElement, state)
if (value) add(rootElement, `${state}-all` as PseudoState)
} else if (typeof value === "string") {
// explicit selectors API - applying pseudo class to a specific element
rootElement.querySelectorAll(value).forEach((el) => add(el, state))
Expand All @@ -49,7 +52,14 @@ const applyParameter = (rootElement: Element, parameter: PseudoStateConfig = {})

map.forEach((states, target) => {
const classnames = new Set<string>()
states.forEach((key) => PSEUDO_STATES[key] && classnames.add(`pseudo-${PSEUDO_STATES[key]}`))
states.forEach((key) => {
const keyWithoutAll = key.replace('-all', '') as PseudoState
if (PSEUDO_STATES[key]) {
classnames.add(`pseudo-${PSEUDO_STATES[key]}`)
} else if (PSEUDO_STATES[keyWithoutAll]) {
classnames.add(`pseudo-${PSEUDO_STATES[keyWithoutAll]}-all`)
}
})
applyClasses(target, classnames)
})
}
Expand Down
31 changes: 24 additions & 7 deletions stories/Button.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,25 @@ export const All = () => (
<div>
<Button>Normal</Button>
</div>
<div className="pseudo-hover">
<div className="pseudo-hover-all">
<Button>Hover</Button>
</div>
<div className="pseudo-focus">
<div className="pseudo-focus-all">
<Button>Focus</Button>
</div>
<div className="pseudo-active">
<div className="pseudo-active-all">
<Button>Active</Button>
</div>
<div className="pseudo-hover pseudo-focus">
<div className="pseudo-hover-all pseudo-focus-all">
<Button>Hover Focus</Button>
</div>
<div className="pseudo-hover pseudo-active">
<div className="pseudo-hover-all pseudo-active-all">
<Button>Hover Active</Button>
</div>
<div className="pseudo-focus pseudo-active">
<div className="pseudo-focus-all pseudo-active-all">
<Button>Focus Active</Button>
</div>
<div className="pseudo-hover pseudo-focus pseudo-active">
<div className="pseudo-hover-all pseudo-focus-all pseudo-active-all">
<Button>Hover Focus Active</Button>
</div>
</div>
Expand Down Expand Up @@ -86,3 +86,20 @@ DirectSelector.parameters = {
active: ["[data-active]"],
},
}

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

<div id='foo'>
<Button>Not Hovered 1 </Button>
<Button>Not Hovered 2</Button>
</div>
</>
)

DirectSelectorParentDoesNotAffectDescendants.parameters = {
pseudo: {
hover: ["#foo"],
},
}
14 changes: 7 additions & 7 deletions stories/CustomElement.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@ export const All = () => (
<div>
<custom-element>Normal</custom-element>
</div>
<div className="pseudo-hover">
<div className="pseudo-hover-all">
<custom-element>Hover</custom-element>
</div>
<div className="pseudo-focus">
<div className="pseudo-focus-all">
<custom-element>Focus</custom-element>
</div>
<div className="pseudo-active">
<div className="pseudo-active-all">
<custom-element>Active</custom-element>
</div>
<div className="pseudo-hover pseudo-focus">
<div className="pseudo-hover-all pseudo-focus-all">
<custom-element>Hover Focus</custom-element>
</div>
<div className="pseudo-hover pseudo-active">
<div className="pseudo-hover-all pseudo-active-all">
<custom-element>Hover Active</custom-element>
</div>
<div className="pseudo-focus pseudo-active">
<div className="pseudo-focus-all pseudo-active-all">
<custom-element>Focus Active</custom-element>
</div>
<div className="pseudo-hover pseudo-focus pseudo-active">
<div className="pseudo-hover-all pseudo-focus-all pseudo-active-all">
<custom-element>Hover Focus Active</custom-element>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions stories/Input.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ export const All = () => (
<div>
<Input defaultValue="Normal" />
</div>
<div className="pseudo-hover">
<div className="pseudo-hover-all">
<Input defaultValue="Hover" />
</div>
<div className="pseudo-focus">
<div className="pseudo-focus-all">
<Input defaultValue="Focus" />
</div>
<div className="pseudo-hover pseudo-focus">
<div className="pseudo-hover-all pseudo-focus-all">
<Input defaultValue="Hover Focus" />
</div>
</div>
Expand Down
14 changes: 7 additions & 7 deletions stories/ShadowRoot.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,25 @@ export const All = () => (
<div>
<ShadowRoot label="Normal" />
</div>
<div className="pseudo-hover">
<div className="pseudo-hover-all">
<ShadowRoot label="Hover" />
</div>
<div className="pseudo-focus">
<div className="pseudo-focus-all">
<ShadowRoot label="Focus" />
</div>
<div className="pseudo-active">
<div className="pseudo-active-all">
<ShadowRoot label="Active" />
</div>
<div className="pseudo-hover pseudo-focus">
<div className="pseudo-hover-all pseudo-focus-all">
<ShadowRoot label="Hover Focus" />
</div>
<div className="pseudo-hover pseudo-active">
<div className="pseudo-hover-all pseudo-active-all">
<ShadowRoot label="Hover Active" />
</div>
<div className="pseudo-focus pseudo-active">
<div className="pseudo-focus-all pseudo-active-all">
<ShadowRoot label="Focus Active" />
</div>
<div className="pseudo-hover pseudo-focus pseudo-active">
<div className="pseudo-hover-all pseudo-focus-all pseudo-active-all">
<ShadowRoot label="Hover Focus Active" />
</div>
</div>
Expand Down
14 changes: 7 additions & 7 deletions stories/grid.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,37 @@
grid-area: normal;
justify-self: center;
}
.story-grid > .pseudo-hover,
.story-grid > .pseudo-hover-all,
.story-grid > [data-hover] {
grid-area: hover;
justify-self: right;
}
.story-grid > .pseudo-focus,
.story-grid > .pseudo-focus-all,
.story-grid > [data-focus] {
grid-area: focus;
justify-self: center;
}
.story-grid > .pseudo-active,
.story-grid > .pseudo-active-all,
.story-grid > [data-active] {
grid-area: active;
justify-self: left;
}
.story-grid > .pseudo-hover.pseudo-focus,
.story-grid > .pseudo-hover-all.pseudo-focus-all,
.story-grid > [data-hover][data-focus] {
grid-area: hover-focus;
justify-self: right;
}
.story-grid > .pseudo-hover.pseudo-active,
.story-grid > .pseudo-hover-all.pseudo-active-all,
.story-grid > [data-hover][data-active] {
grid-area: hover-active;
justify-self: center;
}
.story-grid > .pseudo-focus.pseudo-active,
.story-grid > .pseudo-focus-all.pseudo-active-all,
.story-grid > [data-focus][data-active] {
grid-area: focus-active;
justify-self: left;
}
.story-grid > .pseudo-hover.pseudo-focus.pseudo-active,
.story-grid > .pseudo-hover-all.pseudo-focus-all.pseudo-active-all,
.story-grid > [data-hover][data-focus][data-active] {
grid-area: hover-focus-active;
justify-self: center;
Expand Down

0 comments on commit 479fae3

Please sign in to comment.