Browse Source

Add implicit self for nested css selector (#1284)

* Deprecate `descendant` and introduce `desc` util to combine selectors

* Add internal `contains` method to `CSSSelector`

* Add implicit self for nested css selector
pull/1305/head v1.0.0-beta1
Abasov Akif 3 years ago committed by GitHub
parent
commit
14bf6674d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/CSSBuilder.kt
  2. 51
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/selectors/CSSSelectors.kt
  3. 46
      web/core/src/jsTest/kotlin/CSSStylesheetTests.kt

7
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/CSSBuilder.kt

@ -1,6 +1,7 @@
package org.jetbrains.compose.web.css package org.jetbrains.compose.web.css
import org.jetbrains.compose.web.css.selectors.CSSSelector import org.jetbrains.compose.web.css.selectors.CSSSelector
import org.jetbrains.compose.web.css.selectors.desc
interface CSSBuilder : CSSStyleRuleBuilder, GenericStyleSheetBuilder<CSSBuilder> { interface CSSBuilder : CSSStyleRuleBuilder, GenericStyleSheetBuilder<CSSBuilder> {
val root: CSSSelector val root: CSSSelector
@ -15,7 +16,11 @@ class CSSBuilderImpl(
override fun style(selector: CSSSelector, cssRule: CSSBuilder.() -> Unit) { override fun style(selector: CSSSelector, cssRule: CSSBuilder.() -> Unit) {
val (style, rules) = buildCSS(root, selector, cssRule) val (style, rules) = buildCSS(root, selector, cssRule)
rules.forEach { add(it) } 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) = override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSBuilder>.() -> Unit) =

51
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/selectors/CSSSelectors.kt

@ -23,7 +23,11 @@ sealed class Nth {
open class CSSSelector { open class CSSSelector {
override fun equals(other: Any?): Boolean { 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() { data class Raw(val selector: String) : CSSSelector() {
@ -69,26 +73,44 @@ open class CSSSelector {
} }
data class Combine(val selectors: MutableList<CSSSelector>) : 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("") override fun toString(): String = selectors.joinToString("")
} }
data class Group(val selectors: List<CSSSelector>) : CSSSelector() { 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(", ") override fun toString(): String = selectors.joinToString(", ")
} }
data class Descendant(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() { 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" override fun toString(): String = "$parent $selected"
} }
data class Child(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() { 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" override fun toString(): String = "$parent > $selected"
} }
data class Sibling(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() { 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" override fun toString(): String = "$prev ~ $selected"
} }
data class Adjacent(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() { 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" override fun toString(): String = "$prev + $selected"
} }
@ -177,11 +199,17 @@ open class CSSSelector {
override fun argsStr() = "$nth" override fun argsStr() = "$nth"
} }
class Host(val selector: CSSSelector) : PseudoClass("host") { 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" override fun argsStr() = "$selector"
} }
// Etc // Etc
class Not(val selector: CSSSelector) : PseudoClass("not") { 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" override fun argsStr() = "$selector"
} }
} }
@ -207,6 +235,9 @@ open class CSSSelector {
} }
class Slotted(val selector: CSSSelector) : PseudoElement("slotted") { 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() override fun argsStr() = selector.toString()
} }
} }
@ -231,8 +262,19 @@ fun attr(
caseSensitive: Boolean = true caseSensitive: Boolean = true
) = CSSSelector.Attribute(name, value, operator, caseSensitive) ) = CSSSelector.Attribute(name, value, operator, caseSensitive)
fun group(vararg selectors: CSSSelector) = CSSSelector.Group(selectors.toList()) fun group(vararg selectors: CSSSelector) = CSSSelector.Group(selectors.toList())
@Deprecated("Replaced with `desc`", ReplaceWith("desc(parent, selected)"))
fun descendant(parent: CSSSelector, selected: CSSSelector) = fun descendant(parent: CSSSelector, selected: CSSSelector) =
desc(parent, selected)
fun desc(parent: CSSSelector, selected: CSSSelector) =
CSSSelector.Descendant(parent, selected) 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) = fun child(parent: CSSSelector, selected: CSSSelector) =
CSSSelector.Child(parent, selected) CSSSelector.Child(parent, selected)
fun sibling(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Descendant(sibling, selected) fun sibling(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Descendant(sibling, selected)
@ -241,3 +283,10 @@ fun adjacent(sibling: CSSSelector, selected: CSSSelector) = CSSSelector.Adjacent
fun not(selector: CSSSelector) = CSSSelector.PseudoClass.Not(selector) fun not(selector: CSSSelector) = CSSSelector.PseudoClass.Not(selector)
fun hover() = CSSSelector.PseudoClass.hover fun hover() = CSSSelector.PseudoClass.hover
fun hover(selector: CSSSelector) = selector + 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
web/core/src/jsTest/kotlin/CSSStylesheetTests.kt

@ -7,12 +7,15 @@ package org.jetbrains.compose.web.core.tests
import kotlinx.browser.window import kotlinx.browser.window
import org.jetbrains.compose.web.css.* 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.Div
import org.jetbrains.compose.web.dom.stringPresentation
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.get import org.w3c.dom.get
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import org.jetbrains.compose.web.testutils.* import org.jetbrains.compose.web.testutils.*
import kotlin.test.assertContains
object AppCSSVariables { object AppCSSVariables {
val width by variable<CSSUnitValue>() val width by variable<CSSUnitValue>()
@ -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)
}
}
} }
@ -144,4 +160,34 @@ class CSSVariableTests {
assertEquals("rgb(0, 128, 0)", window.getComputedStyle(el).backgroundColor) 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"
)
}
} }

Loading…
Cancel
Save