From d9d4bf34969f03600679597ff58cc48fa5b6329d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 31 Jan 2024 18:26:34 +0600 Subject: [PATCH] Add opportunity to use custom prefixes in StyleSheet (#3015) --- .../jetbrains/compose/web/css/StyleSheet.kt | 29 +++++++---- .../src/jsTest/kotlin/css/AnimationTests.kt | 49 +++++++++++++++++++ .../src/jsTest/kotlin/css/StyleSheetTests.kt | 30 ++++++++++++ 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/html/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt b/html/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt index 64f67feb92..3c1bb048c8 100644 --- a/html/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt +++ b/html/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt @@ -20,6 +20,9 @@ class CSSRulesHolderState : CSSRulesHolder { /** * Represents a collection of the css style rules. * StyleSheet needs to be mounted. + * + * @param customPrefix Will be used as prefix with current style. Pass `null` to use default value (classname of realization) + * * @see [Style] * * Example: @@ -38,12 +41,22 @@ class CSSRulesHolderState : CSSRulesHolder { * ``` */ open class StyleSheet( + customPrefix: String?, private val rulesHolder: CSSRulesHolder = CSSRulesHolderState(), - val usePrefix: Boolean = true, ) : StyleSheetBuilder, CSSRulesHolder by rulesHolder { private val boundClasses = mutableMapOf() + protected val prefix: String = customPrefix ?: "${this::class.simpleName}-" + + val usePrefix: Boolean = customPrefix == null + constructor( + rulesHolder: CSSRulesHolder = CSSRulesHolderState(), + usePrefix: Boolean = true + ) : this( + if (usePrefix) null else "", + rulesHolder + ) - protected fun style(cssRule: CSSBuilder.() -> Unit) = CSSHolder(usePrefix, cssRule) + protected fun style(cssRule: CSSBuilder.() -> Unit) = CSSHolder(prefix, cssRule) /** * Example: @@ -69,7 +82,7 @@ open class StyleSheet( * } * ``` */ - protected fun keyframes(cssKeyframes: CSSKeyframesBuilder.() -> Unit) = CSSKeyframesHolder(usePrefix, cssKeyframes) + protected fun keyframes(cssKeyframes: CSSKeyframesBuilder.() -> Unit) = CSSKeyframesHolder(prefix, cssKeyframes) companion object { private var counter = 0 @@ -88,13 +101,12 @@ open class StyleSheet( } } - protected class CSSHolder(private val usePrefix: Boolean, private val cssBuilder: CSSBuilder.() -> Unit) { + protected class CSSHolder(private val prefix: String, private val cssBuilder: CSSBuilder.() -> Unit) { operator fun provideDelegate( sheet: StyleSheet, property: KProperty<*> ): ReadOnlyProperty { - val sheetName = if (usePrefix) "${sheet::class.simpleName}-" else "" - val className = "$sheetName${property.name}" + val className = "$prefix${property.name}" val selector = object : CSSSelector() { override fun asString() = ".${className}" } @@ -110,15 +122,14 @@ open class StyleSheet( * See [keyframes] */ protected class CSSKeyframesHolder( - private val usePrefix: Boolean, + private val prefix: String, private val keyframesBuilder: CSSKeyframesBuilder.() -> Unit ) { operator fun provideDelegate( sheet: StyleSheet, property: KProperty<*> ): ReadOnlyProperty { - val sheetName = if (usePrefix) "${sheet::class.simpleName}-" else "" - val keyframesName = "$sheetName${property.name}" + val keyframesName = "$prefix${property.name}" val rule = buildKeyframes(keyframesName, keyframesBuilder) sheet.add(rule) diff --git a/html/core/src/jsTest/kotlin/css/AnimationTests.kt b/html/core/src/jsTest/kotlin/css/AnimationTests.kt index eae861b3e0..bbdb52b914 100644 --- a/html/core/src/jsTest/kotlin/css/AnimationTests.kt +++ b/html/core/src/jsTest/kotlin/css/AnimationTests.kt @@ -35,6 +35,28 @@ object AnimationsStyleSheet : StyleSheet() { } } +class AnimationsStyleSheetWithCustomPrefix( + customPrefix: String +) : StyleSheet(customPrefix) { + val bounce by keyframes { + from { + property("transform", "translateX(50%)") + } + + to { + property("transform", "translateX(-50%)") + } + } + + val animationClass by style { + animation(bounce) { + duration(2.s) + timingFunction(AnimationTimingFunction.EaseIn) + direction(AnimationDirection.Alternate) + } + } +} + @ExperimentalComposeWebApi class AnimationTests { @Test @@ -76,4 +98,31 @@ class AnimationTests { "Animation class wasn't injected correctly" ) } + + @Test + fun animationClassInjectedWithCustomPrefix() = runTest { + val customPrefix = "CustomPrefix-" + composition { + Style(AnimationsStyleSheetWithCustomPrefix(customPrefix)) + } + + val el = nextChild() as HTMLStyleElement + val cssRules = (el.sheet as? CSSStyleSheet)?.cssRules + val rules = (0 until (cssRules?.length ?: 0)).map { + cssRules?.item(it)?.cssText?.replace("\n", "") ?: "" + } + + // TODO: we need to come up with test that not relying on any kind of formatting + assertEquals( + "@keyframes ${customPrefix}bounce {0% { transform: translateX(50%); }100% { transform: translateX(-50%); }}", + rules[0].replace(" 0%", "0%").replace(" 100%", "100%"), + "Animation keyframes wasn't injected correctly" + ) + + assertEquals( + ".${customPrefix}animationClass { animation: 2s ease-in 0s 1 alternate none running ${customPrefix}bounce; }".trimIndent(), + rules[1], + "Animation class wasn't injected correctly" + ) + } } diff --git a/html/core/src/jsTest/kotlin/css/StyleSheetTests.kt b/html/core/src/jsTest/kotlin/css/StyleSheetTests.kt index ac0278b48e..ed5ccab253 100644 --- a/html/core/src/jsTest/kotlin/css/StyleSheetTests.kt +++ b/html/core/src/jsTest/kotlin/css/StyleSheetTests.kt @@ -42,4 +42,34 @@ class StyleSheetTests { ) } + @Test + fun stylesheetCorrectlyUsingIncomingPrefix() { + val testPrefixParent = "test_prefix_parent-" + val testPrefixChild = "test_prefix_child-" + + val styleSheet = object : StyleSheet(customPrefix = testPrefixParent) { + val someClassName by style { + color(Color.red) + } + } + + val childStyleSheet = object : StyleSheet(customPrefix = testPrefixChild, styleSheet) { + val someClassName by style { + color(Color.green) + } + } + + assertContentEquals( + listOf(".${testPrefixParent}someClassName { color: red;}", ".${testPrefixChild}someClassName { color: green;}"), + styleSheet.serializeRules(), + "styleSheet rules" + ) + + assertContentEquals( + listOf(".${testPrefixParent}someClassName { color: red;}", ".${testPrefixChild}someClassName { color: green;}"), + childStyleSheet.serializeRules(), + "childStyleSheet rules" + ) + } + } \ No newline at end of file