From 14bf6674d9d363b3d45a01aae3ea99f42dc124c2 Mon Sep 17 00:00:00 2001 From: Abasov Akif Date: Fri, 22 Oct 2021 17:11:03 +0300 Subject: [PATCH] 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 --- .../jetbrains/compose/web/css/CSSBuilder.kt | 7 ++- .../compose/web/css/selectors/CSSSelectors.kt | 51 ++++++++++++++++++- .../src/jsTest/kotlin/CSSStylesheetTests.kt | 46 +++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/CSSBuilder.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/CSSBuilder.kt index ba5943fd41..e35a540892 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/CSSBuilder.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/CSSBuilder.kt @@ -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 { val root: CSSSelector @@ -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.() -> Unit) = diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/selectors/CSSSelectors.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/selectors/CSSSelectors.kt index 4ad911013e..4161c5e978 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/selectors/CSSSelectors.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/selectors/CSSSelectors.kt @@ -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() { @@ -69,26 +73,44 @@ open class CSSSelector { } data class Combine(val selectors: MutableList) : 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() { + 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" } @@ -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" } } @@ -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() } } @@ -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) @@ -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, strict: Boolean): Boolean { + return that === other || // exactly same selector + children.any { it.contains(other, strict) } || // contains it in children + (!strict && that == other) // equals structurally +} diff --git a/web/core/src/jsTest/kotlin/CSSStylesheetTests.kt b/web/core/src/jsTest/kotlin/CSSStylesheetTests.kt index 4fabc6189f..a00bcabd0a 100644 --- a/web/core/src/jsTest/kotlin/CSSStylesheetTests.kt +++ b/web/core/src/jsTest/kotlin/CSSStylesheetTests.kt @@ -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() @@ -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) } } + + @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" + ) + } }