Browse Source

CSS Animations (#810)

pull/845/head
Abasov Akif 3 years ago committed by GitHub
parent
commit
e2dd256235
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      web/core/src/jsMain/kotlin/androidx/compose/web/css/BrowserAPI.kt
  2. 71
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSEnums.kt
  3. 73
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSKeyframeRule.kt
  4. 6
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSMediaRule.kt
  5. 67
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSProperties.kt
  6. 19
      web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSRules.kt
  7. 31
      web/core/src/jsMain/kotlin/androidx/compose/web/css/StyleSheet.kt
  8. 19
      web/core/src/jsMain/kotlin/androidx/compose/web/elements/Style.kt
  9. 15
      web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/Sample.kt

13
web/core/src/jsMain/kotlin/androidx/compose/web/css/BrowserAPI.kt

@ -6,6 +6,19 @@
@file:Suppress("UNUSED", "NOTHING_TO_INLINE", "FunctionName")
package org.jetbrains.compose.web.css
import org.w3c.dom.css.CSSRule
import org.w3c.dom.css.CSSRuleList
external class CSSKeyframesRule: CSSRule {
val name: String
val cssRules: CSSRuleList
}
inline fun CSSKeyframesRule.appendRule(cssRule: String) {
this.asDynamic().appendRule(cssRule)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T : Any> jsObject(): T =
js("({})")

71
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSEnums.kt

@ -181,3 +181,74 @@ external interface Position: StylePropertyEnum {
inline fun Position(value: String) = value.unsafeCast<Position>()
typealias LanguageCode = String
external interface StepPosition: StylePropertyEnum {
companion object {
inline val JumpStart get() = StepPosition("jump-start")
inline val JumpEnd get() = StepPosition("jump-end")
inline val JumpNone get() = StepPosition("jump-none")
inline val JumpBoth get() = StepPosition("jump-both")
inline val Start get() = StepPosition("start")
inline val End get() = StepPosition("end")
}
}
inline fun StepPosition(value: String) = value.unsafeCast<StepPosition>()
external interface AnimationTimingFunction: StylePropertyEnum {
companion object {
inline val Ease get() = AnimationTimingFunction("ease")
inline val EaseIn get() = AnimationTimingFunction("ease-in")
inline val EaseOut get() = AnimationTimingFunction("ease-out")
inline val EaseInOut get() = AnimationTimingFunction("ease-in-out")
inline val Linear get() = AnimationTimingFunction("linear")
inline val StepStart get() = AnimationTimingFunction("step-start")
inline val StepEnd get() = AnimationTimingFunction("step-end")
inline fun cubicBezier(x1: Double, y1: Double, x2: Double, y2: Double) = AnimationTimingFunction("cubic-bezier($x1, $y1, $x2, $y2)")
inline fun steps(count: Int, stepPosition: StepPosition) = AnimationTimingFunction("steps($count, $stepPosition)")
inline fun steps(count: Int) = AnimationTimingFunction("steps($count)")
inline val Inherit get() = AnimationTimingFunction("inherit")
inline val Initial get() = AnimationTimingFunction("initial")
inline val Unset get() = AnimationTimingFunction("unset")
}
}
inline fun AnimationTimingFunction(value: String) = value.unsafeCast<AnimationTimingFunction>()
external interface AnimationDirection: StylePropertyEnum {
companion object {
inline val Normal get() = AnimationDirection("normal")
inline val Reverse get() = AnimationDirection("reverse")
inline val Alternate get() = AnimationDirection("alternate")
inline val AlternateReverse get() = AnimationDirection("alternate-reverse")
inline val Inherit get() = AnimationDirection("inherit")
inline val Initial get() = AnimationDirection("initial")
inline val Unset get() = AnimationDirection("unset")
}
}
inline fun AnimationDirection(value: String) = value.unsafeCast<AnimationDirection>()
external interface AnimationFillMode: StylePropertyEnum {
companion object {
inline val None get() = AnimationFillMode("none")
inline val Forwards get() = AnimationFillMode("forwards")
inline val Backwards get() = AnimationFillMode("backwards")
inline val Both get() = AnimationFillMode("both")
}
}
inline fun AnimationFillMode(value: String) = value.unsafeCast<AnimationFillMode>()
external interface AnimationPlayState: StylePropertyEnum {
companion object {
inline val Running get() = AnimationPlayState("running")
inline val Paused get() = AnimationPlayState("Paused")
inline val Backwards get() = AnimationPlayState("backwards")
inline val Both get() = AnimationPlayState("both")
inline val Inherit get() = AnimationPlayState("inherit")
inline val Initial get() = AnimationPlayState("initial")
inline val Unset get() = AnimationPlayState("unset")
}
}
inline fun AnimationPlayState(value: String) = value.unsafeCast<AnimationPlayState>()

73
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSKeyframeRule.kt

@ -0,0 +1,73 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package org.jetbrains.compose.web.css
interface CSSNamedKeyframes {
val name: String
}
data class CSSKeyframesRuleDeclaration(
override val name: String,
val keys: CSSKeyframeRuleDeclarationList
) : CSSRuleDeclaration, CSSNamedKeyframes {
override val header: String
get() = "@keyframes $name"
}
typealias CSSKeyframeRuleDeclarationList = List<CSSKeyframeRuleDeclaration>
abstract class CSSKeyframe {
abstract override fun toString(): String
object From: CSSKeyframe() {
override fun toString(): String = "from"
}
object To: CSSKeyframe() {
override fun toString(): String = "to"
}
data class Percentage(val value: CSSSizeValue<CSSUnit.percent>): CSSKeyframe() {
override fun toString(): String = value.toString()
}
data class Combine(val values: List<CSSSizeValue<CSSUnit.percent>>): CSSKeyframe() {
override fun toString(): String = values.joinToString(", ")
}
}
data class CSSKeyframeRuleDeclaration(
val keyframe: CSSKeyframe,
override val style: StyleHolder
) : CSSRuleDeclaration, CSSStyledRuleDeclaration {
override val header: String
get() = keyframe.toString()
}
class CSSKeyframesBuilder() {
constructor(init: CSSKeyframesBuilder.() -> Unit) : this() {
init()
}
val frames: MutableList<CSSKeyframeRuleDeclaration> = mutableListOf()
fun from(style: CSSStyleRuleBuilder.() -> Unit) {
frames += CSSKeyframeRuleDeclaration(CSSKeyframe.From, buildCSSStyleRule(style))
}
fun to(style: CSSStyleRuleBuilder.() -> Unit) {
frames += CSSKeyframeRuleDeclaration(CSSKeyframe.To, buildCSSStyleRule(style))
}
fun each(vararg keys: CSSSizeValue<CSSUnit.percent>, style: CSSStyleRuleBuilder.() -> Unit) {
frames += CSSKeyframeRuleDeclaration(CSSKeyframe.Combine(keys.toList()), buildCSSStyleRule(style))
}
operator fun CSSSizeValue<CSSUnit.percent>.invoke(style: CSSStyleRuleBuilder.() -> Unit) {
frames += CSSKeyframeRuleDeclaration(CSSKeyframe.Percentage(this), buildCSSStyleRule(style))
}
}
fun buildKeyframes(name: String, builder: CSSKeyframesBuilder.() -> Unit): CSSKeyframesRuleDeclaration {
val frames = CSSKeyframesBuilder(builder).frames
return CSSKeyframesRuleDeclaration(name, frames)
}

6
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSMediaRule.kt

@ -18,7 +18,7 @@ interface CSSMediaQuery {
}
@Suppress("EqualsOrHashCode")
data class MediaFeature(
class MediaFeature(
val name: String,
val value: StylePropertyValue? = null
) : CSSMediaQuery, Atomic {
@ -61,8 +61,8 @@ interface CSSMediaQuery {
@Suppress("EqualsOrHashCode")
class CSSMediaRuleDeclaration(
val query: CSSMediaQuery,
rules: CSSRuleDeclarationList
) : CSSGroupingRuleDeclaration(rules) {
override val rules: CSSRuleDeclarationList
) : CSSGroupingRuleDeclaration {
override val header: String
get() = "@media $query"

67
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSProperties.kt

@ -237,3 +237,70 @@ fun StyleBuilder.padding(value: CSSNumeric) {
// padding hasn't Typed OM yet
property("padding", value)
}
@Suppress("EqualsOrHashCode")
data class CSSAnimation(
val keyframesName: String,
var duration: List<CSSSizeValue<out CSSUnitTime>>? = null,
var timingFunction: List<AnimationTimingFunction>? = null,
var delay: List<CSSSizeValue<out CSSUnitTime>>? = null,
var iterationCount: List<Int?>? = null,
var direction: List<AnimationDirection>? = null,
var fillMode: List<AnimationFillMode>? = null,
var playState: List<AnimationPlayState>? = null
) : CSSStyleValue {
override fun toString(): String {
val values = listOfNotNull(
keyframesName,
duration?.joinToString(", "),
timingFunction?.joinToString(", "),
delay?.joinToString(", "),
iterationCount?.joinToString(", ") { it?.toString() ?: "infinite" },
direction?.joinToString(", "),
fillMode?.joinToString(", "),
playState?.joinToString(", ")
)
return values.joinToString(" ")
}
}
inline fun CSSAnimation.duration(vararg values: CSSSizeValue<out CSSUnitTime>) {
this.duration = values.toList()
}
inline fun CSSAnimation.timingFunction(vararg values: AnimationTimingFunction) {
this.timingFunction = values.toList()
}
inline fun CSSAnimation.delay(vararg values: CSSSizeValue<out CSSUnitTime>) {
this.delay = values.toList()
}
inline fun CSSAnimation.iterationCount(vararg values: Int?) {
this.iterationCount = values.toList()
}
inline fun CSSAnimation.direction(vararg values: AnimationDirection) {
this.direction = values.toList()
}
inline fun CSSAnimation.fillMode(vararg values: AnimationFillMode) {
this.fillMode = values.toList()
}
inline fun CSSAnimation.playState(vararg values: AnimationPlayState) {
this.playState = values.toList()
}
fun StyleBuilder.animation(
keyframesName: String,
builder: CSSAnimation.() -> Unit
) {
val animation = CSSAnimation(keyframesName).apply(builder)
property("animation", animation)
}
inline fun StyleBuilder.animation(
keyframes: CSSNamedKeyframes,
noinline builder: CSSAnimation.() -> Unit
) = animation(keyframes.name, builder)

19
web/core/src/jsMain/kotlin/androidx/compose/web/css/CSSRules.kt

@ -6,23 +6,28 @@ interface CSSStyleRuleBuilder : StyleBuilder
open class CSSRuleBuilderImpl : CSSStyleRuleBuilder, StyleBuilderImpl()
abstract class CSSRuleDeclaration {
abstract val header: String
@Suppress("EqualsOrHashCode")
interface CSSRuleDeclaration {
val header: String
abstract override fun equals(other: Any?): Boolean
override fun equals(other: Any?): Boolean
}
interface CSSStyledRuleDeclaration {
val style: StyleHolder
}
data class CSSStyleRuleDeclaration(
val selector: CSSSelector,
val style: StyleHolder
) : CSSRuleDeclaration() {
override val style: StyleHolder
) : CSSRuleDeclaration, CSSStyledRuleDeclaration {
override val header
get() = selector.toString()
}
abstract class CSSGroupingRuleDeclaration(
interface CSSGroupingRuleDeclaration: CSSRuleDeclaration {
val rules: CSSRuleDeclarationList
) : CSSRuleDeclaration()
}
typealias CSSRuleDeclarationList = List<CSSRuleDeclaration>
typealias MutableCSSRuleDeclarationList = MutableList<CSSRuleDeclaration>

31
web/core/src/jsMain/kotlin/androidx/compose/web/css/StyleSheet.kt

@ -14,6 +14,7 @@ class CSSRulesHolderState : CSSRulesHolder {
override var cssRules: CSSRuleDeclarationList by mutableStateOf(listOf())
override fun add(cssRule: CSSRuleDeclaration) {
@Suppress("SuspiciousCollectionReassignment")
cssRules += cssRule
}
}
@ -39,17 +40,21 @@ class CSSRulesHolderState : CSSRulesHolder {
* ```
*/
open class StyleSheet(
private val rulesHolder: CSSRulesHolder = CSSRulesHolderState()
private val rulesHolder: CSSRulesHolder = CSSRulesHolderState(),
val usePrefix: Boolean = true,
) : StyleSheetBuilder, CSSRulesHolder by rulesHolder {
private val boundClasses = mutableMapOf<String, CSSRuleDeclarationList>()
protected fun style(cssRule: CSSBuilder.() -> Unit) = CSSHolder(cssRule)
protected fun style(cssRule: CSSBuilder.() -> Unit) = CSSHolder(usePrefix, cssRule)
protected fun keyframes(cssKeyframes: CSSKeyframesBuilder.() -> Unit) = CSSKeyframesHolder(usePrefix, cssKeyframes)
companion object {
var counter = 0
}
data class CSSSelfSelector(var selector: CSSSelector? = null) : CSSSelector() {
@Suppress("EqualsOrHashCode")
class CSSSelfSelector(var selector: CSSSelector? = null) : CSSSelector() {
override fun toString(): String = selector.toString()
override fun equals(other: Any?): Boolean {
return other is CSSSelfSelector
@ -77,12 +82,12 @@ open class StyleSheet(
}
}
protected class CSSHolder(val cssBuilder: CSSBuilder.() -> Unit) {
protected class CSSHolder(private val usePrefix: Boolean, private val cssBuilder: CSSBuilder.() -> Unit) {
operator fun provideDelegate(
sheet: StyleSheet,
property: KProperty<*>
): ReadOnlyProperty<Any?, String> {
val sheetName = "${sheet::class.simpleName}-"
val sheetName = if (usePrefix) "${sheet::class.simpleName}-" else ""
val selector = className("$sheetName${property.name}")
val (properties, rules) = buildCSS(selector, selector, cssBuilder)
sheet.add(selector, properties)
@ -94,6 +99,22 @@ open class StyleSheet(
}
}
protected class CSSKeyframesHolder(private val usePrefix: Boolean, 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 rule = buildKeyframes(keyframesName, keyframesBuilder)
sheet.add(rule)
return ReadOnlyProperty { _, _ ->
rule
}
}
}
override fun buildRules(rulesBuild: GenericStyleSheetBuilder<CSSStyleRuleBuilder>.() -> Unit) =
StyleSheet().apply(rulesBuild).cssRules
}

19
web/core/src/jsMain/kotlin/androidx/compose/web/elements/Style.kt

@ -24,6 +24,11 @@ private fun CSSStyleSheet.addRule(cssRule: String): CSSRule? {
return this.cssRules.item(cssRuleIndex)
}
private fun CSSKeyframesRule.addRule(cssRule: String): CSSRule? {
appendRule(cssRule)
return this.cssRules.item(this.cssRules.length - 1)
}
private fun CSSStyleSheet.addRule(cssRuleDeclaration: CSSRuleDeclaration) {
addRule("${cssRuleDeclaration.header} {}")?.let { cssRule ->
fillRule(cssRuleDeclaration, cssRule)
@ -41,12 +46,18 @@ private fun CSSGroupingRule.addRule(cssRuleDeclaration: CSSRuleDeclaration) {
}
}
private fun CSSKeyframesRule.addRule(cssRuleDeclaration: CSSKeyframeRuleDeclaration) {
addRule("${cssRuleDeclaration.header} {}")?.let { cssRule ->
fillRule(cssRuleDeclaration, cssRule)
}
}
private fun fillRule(
cssRuleDeclaration: CSSRuleDeclaration,
cssRule: CSSRule
) {
when (cssRuleDeclaration) {
is CSSStyleRuleDeclaration -> {
is CSSStyledRuleDeclaration -> {
val cssStyleRule = cssRule.unsafeCast<CSSStyleRule>()
cssRuleDeclaration.style.properties.forEach { (name, value) ->
setProperty(cssStyleRule.style, name, value)
@ -61,6 +72,12 @@ private fun fillRule(
cssGroupingRule.addRule(childRuleDeclaration)
}
}
is CSSKeyframesRuleDeclaration -> {
val cssGroupingRule = cssRule.unsafeCast<CSSKeyframesRule>()
cssRuleDeclaration.keys.forEach { childRuleDeclaration ->
cssGroupingRule.addRule(childRuleDeclaration)
}
}
}
}

15
web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/Sample.kt

@ -44,8 +44,23 @@ object MyCSSVariables : CSSVariables {
}
object AppStyleSheet : StyleSheet() {
val bounce by keyframes {
from {
property("transform", "translateX(50%)")
}
to {
property("transform", "translateX(-50%)")
}
}
val myClass by style {
color("green")
animation(bounce) {
duration(2.s)
timingFunction(AnimationTimingFunction.EaseIn)
direction(AnimationDirection.Alternate)
}
}
val classWithNested by style {

Loading…
Cancel
Save