Skip to content

Commit

Permalink
Add implicit self for nested css selector (#1284)
Browse files Browse the repository at this point in the history
* Deprecate `descendant` and introduce `desc` util to combine selectors

* Add internal `contains` method to `CSSSelector`

* Add implicit self for nested css selector
  • Loading branch information
Skolotsky authored Oct 22, 2021
1 parent ae10beb commit 14bf667
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jetbrains.compose.web.css

import org.jetbrains.compose.web.css.selectors.CSSSelector
import org.jetbrains.compose.web.css.selectors.desc

interface CSSBuilder : CSSStyleRuleBuilder, GenericStyleSheetBuilder<CSSBuilder> {
val root: CSSSelector
Expand All @@ -15,7 +16,11 @@ class CSSBuilderImpl(
override fun style(selector: CSSSelector, cssRule: CSSBuilder.() -> Unit) {
val (style, rules) = buildCSS(root, selector, cssRule)
rules.forEach { add(it) }
add(selector, style)
if (selector.contains(self, true) || selector.contains(root, true)) {
add(selector, style)
} else {
add(desc(self, selector), style)
}
}

override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSBuilder>.() -> Unit) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ sealed class Nth {

open class CSSSelector {
override fun equals(other: Any?): Boolean {
return toString() == other.toString()
return this === other || toString() == other.toString()
}

internal open fun contains(other: CSSSelector, strict: Boolean = false): Boolean {
return if (strict) this === other else this == other
}

data class Raw(val selector: String) : CSSSelector() {
Expand Down Expand Up @@ -69,26 +73,44 @@ open class CSSSelector {
}

data class Combine(val selectors: MutableList<CSSSelector>) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, selectors, strict)

override fun toString(): String = selectors.joinToString("")
}

data class Group(val selectors: List<CSSSelector>) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, selectors, strict)

override fun toString(): String = selectors.joinToString(", ")
}

data class Descendant(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, listOf(parent, selected), strict)

override fun toString(): String = "$parent $selected"
}

data class Child(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, listOf(parent, selected), strict)

override fun toString(): String = "$parent > $selected"
}

data class Sibling(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, listOf(prev, selected), strict)

override fun toString(): String = "$prev ~ $selected"
}

data class Adjacent(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, listOf(prev, selected), strict)

override fun toString(): String = "$prev + $selected"
}

Expand Down Expand Up @@ -177,11 +199,17 @@ open class CSSSelector {
override fun argsStr() = "$nth"
}
class Host(val selector: CSSSelector) : PseudoClass("host") {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, listOf(selector), strict)

override fun argsStr() = "$selector"
}

// Etc
class Not(val selector: CSSSelector) : PseudoClass("not") {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, listOf(selector), strict)

override fun argsStr() = "$selector"
}
}
Expand All @@ -207,6 +235,9 @@ open class CSSSelector {
}

class Slotted(val selector: CSSSelector) : PseudoElement("slotted") {
override fun contains(other: CSSSelector, strict: Boolean): Boolean =
contains(this, other, listOf(selector), strict)

override fun argsStr() = selector.toString()
}
}
Expand All @@ -231,8 +262,19 @@ fun attr(
caseSensitive: Boolean = true
) = CSSSelector.Attribute(name, value, operator, caseSensitive)
fun group(vararg selectors: CSSSelector) = CSSSelector.Group(selectors.toList())

@Deprecated("Replaced with `desc`", ReplaceWith("desc(parent, selected)"))
fun descendant(parent: CSSSelector, selected: CSSSelector) =
desc(parent, selected)
fun desc(parent: CSSSelector, selected: CSSSelector) =
CSSSelector.Descendant(parent, selected)
fun desc(parent: CSSSelector, selected: String) =
desc(parent, selector(selected))
fun desc(parent: String, selected: CSSSelector) =
desc(selector(parent), selected)
fun desc(parent: String, selected: String) =
desc(selector(parent), selector(selected))

fun child(parent: CSSSelector, selected: CSSSelector) =
CSSSelector.Child(parent, selected)
fun sibling(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Descendant(sibling, selected)
Expand All @@ -241,3 +283,10 @@ fun adjacent(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Adjacent
fun not(selector: CSSSelector) = CSSSelector.PseudoClass.Not(selector)
fun hover() = CSSSelector.PseudoClass.hover
fun hover(selector: CSSSelector) = selector + hover()

@Suppress("SuspiciousEqualsCombination")
private fun contains(that: CSSSelector, other: CSSSelector, children: List<CSSSelector>, strict: Boolean): Boolean {
return that === other || // exactly same selector
children.any { it.contains(other, strict) } || // contains it in children
(!strict && that == other) // equals structurally
}
46 changes: 46 additions & 0 deletions web/core/src/jsTest/kotlin/CSSStylesheetTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ package org.jetbrains.compose.web.core.tests

import kotlinx.browser.window
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.css.selectors.desc
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.stringPresentation
import org.w3c.dom.HTMLElement
import org.w3c.dom.get
import kotlin.test.Test
import kotlin.test.assertEquals
import org.jetbrains.compose.web.testutils.*
import kotlin.test.assertContains

object AppCSSVariables {
val width by variable<CSSUnitValue>()
Expand Down Expand Up @@ -68,6 +71,19 @@ object AppStylesheet : StyleSheet() {
}

}

val withNestedWithImplicitSelf by style {
color(Color.green)
"h1" {
color(Color.lime)
}
}
val withNestedWithExplicitSelf by style {
color(Color.green)
desc(self, "h1") style {
color(Color.lime)
}
}
}


Expand Down Expand Up @@ -144,4 +160,34 @@ class CSSVariableTests {
assertEquals("rgb(0, 128, 0)", window.getComputedStyle(el).backgroundColor)
}
}

@Test
fun nestedStyleWithImplicitSelf() = runTest {
val generatedRules = AppStylesheet.cssRules.map { it.stringPresentation() }

assertContains(
generatedRules,
"""
.AppStylesheet-withNestedWithImplicitSelf h1 {
color: lime;
}
""".trimIndent(),
"Nested selector with implicit self isn't generated correctly"
)
}

@Test
fun nestedStyleWithExplicitSelf() = runTest {
val generatedRules = AppStylesheet.cssRules.map { it.stringPresentation() }

assertContains(
generatedRules,
"""
.AppStylesheet-withNestedWithExplicitSelf h1 {
color: lime;
}
""".trimIndent(),
"Nested selector with implicit self isn't generated correctly"
)
}
}

0 comments on commit 14bf667

Please sign in to comment.