Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add opportunity to use custom prefixes in StyleSheet #3015

Merged
merged 9 commits into from
Jan 31, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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<String, CSSRuleDeclarationList>()
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:
Expand All @@ -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
Expand All @@ -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<Any?, String> {
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}"
}
Expand All @@ -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<Any?, CSSNamedKeyframes> {
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)

Expand Down
49 changes: 49 additions & 0 deletions html/core/src/jsTest/kotlin/css/AnimationTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
)
}
}
30 changes: 30 additions & 0 deletions html/core/src/jsTest/kotlin/css/StyleSheetTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
}

}