From d85bd8c941b46f2e579fa7cb5dbdcfc9359647dc Mon Sep 17 00:00:00 2001 From: Ayfri Date: Mon, 12 Sep 2022 10:19:22 +0200 Subject: [PATCH] Add support for transition in CSS api. (#2228) * web: Support for transition in CSS api. * Use proper name for Transitions class, fix formatting. * web: add ExperimentalComposeWebApi on transitions CSS API. * web: Add documentation for transitions CSS API. * fix: Apply all suggested changes. * web: Add unit tests to transitions CSS API. * fix: Update documentation with new methods names. --- .../compose/web/css/properties/transitions.kt | 105 ++++++++++++++++++ .../src/jsTest/kotlin/css/TransitionsTests.kt | 80 +++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/properties/transitions.kt create mode 100644 web/core/src/jsTest/kotlin/css/TransitionsTests.kt diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/properties/transitions.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/properties/transitions.kt new file mode 100644 index 0000000000..d07a424268 --- /dev/null +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/properties/transitions.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.web.css + +import org.jetbrains.compose.web.ExperimentalComposeWebApi + +@ExperimentalComposeWebApi +data class Transition( + var property: String? = null, + var duration: CSSSizeValue? = null, + var timingFunction: AnimationTimingFunction? = null, + var delay: CSSSizeValue? = null, +) { + override fun toString(): String { + if (property == null) return "" + var result = property!! + + duration?.let { result += " $it" } + timingFunction?.let { result += " $it" } + delay?.let { result += " $it" } + + return result + } +} + +@ExperimentalComposeWebApi +fun Transition.duration(value: CSSSizeValue): Unit = run { duration = value } + +@ExperimentalComposeWebApi +fun Transition.timingFunction(value: AnimationTimingFunction): Unit = run { timingFunction = value } + +@ExperimentalComposeWebApi +fun Transition.delay(value: CSSSizeValue): Unit = run { delay = value } + +@ExperimentalComposeWebApi +data class Transitions( + var transitions: List = emptyList(), + private var defaultDuration: CSSSizeValue? = null, + private var defaultTimingFunction: AnimationTimingFunction? = null, + private var defaultDelay: CSSSizeValue? = null, +) { + override fun toString() = + transitions.distinctBy { + it.property + }.joinToString(", ") { + it.apply { + if (defaultDelay != null && delay == null) delay = defaultDelay!! + if (defaultDuration != null && duration == null) duration = defaultDuration!! + if (defaultTimingFunction != null && timingFunction == null) timingFunction = defaultTimingFunction!! + }.toString() + } + + inline operator fun String.invoke(block: Transition.() -> Unit): Unit = Transition().apply(block).let { + it.property = this + transitions += it + } + + inline operator fun Iterable.invoke(block: Transition.() -> Unit): Unit = Transition().apply(block).let { transition -> + forEach { + transitions += transition.copy(property = it) + } + } + + inline operator fun Array.invoke(block: Transition.() -> Unit): Unit = Transition().apply(block).let { transition -> + forEach { + transitions += transition.copy(property = it) + } + } + + inline fun properties(vararg properties: String, block: Transition.() -> Unit = {}): Unit = properties.invoke(block) + + inline fun all(block: Transition.() -> Unit): Unit = Transition().apply(block).let { transition -> + transition.property = "all" + transitions += transition + } + + fun defaultDuration(value: CSSSizeValue): Unit = run { defaultDuration = value } + fun defaultTimingFunction(value: AnimationTimingFunction): Unit = run { defaultTimingFunction = value } + fun defaultDelay(value: CSSSizeValue): Unit = run { defaultDelay = value } +} + +@ExperimentalComposeWebApi +/** + * Examples: + * ``` + * transitions { + * "width" { duration(1.s) } + * } + * + * transitions { + * defaultDuration(.5.s) + * defaultTimingFunction(AnimationTimingFunction.EaseInOut) + * defaultProperties("width", "height") + * } + * ``` + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transition + */ +inline fun StyleScope.transitions(transitions: Transitions.() -> Unit) { + val transitionsValue = Transitions().apply(transitions) + property("transition", transitionsValue.toString()) +} \ No newline at end of file diff --git a/web/core/src/jsTest/kotlin/css/TransitionsTests.kt b/web/core/src/jsTest/kotlin/css/TransitionsTests.kt new file mode 100644 index 0000000000..e461709cea --- /dev/null +++ b/web/core/src/jsTest/kotlin/css/TransitionsTests.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2020-2022 JetBrains s.r.o. and respective authors and developers. + * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. + */ + +package org.jetbrains.compose.web.core.tests.css + +import org.jetbrains.compose.web.ExperimentalComposeWebApi +import org.jetbrains.compose.web.css.AnimationTimingFunction +import org.jetbrains.compose.web.css.delay +import org.jetbrains.compose.web.css.duration +import org.jetbrains.compose.web.css.s +import org.jetbrains.compose.web.css.timingFunction +import org.jetbrains.compose.web.css.transitions +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.testutils.runTest +import kotlin.test.Test +import kotlin.test.assertEquals + +@ExperimentalComposeWebApi +class TransitionsTests { + @Test + fun duration() = runTest { + composition { + Div({ style { transitions { "width" { duration(1.s) } }}}) + } + + assertEquals("width 1s ease 0s", nextChild().style.transition) + } + + @Test + fun multipleProperties() = runTest { + composition { + Div({ style { transitions { "width" { duration(1.s) }; "height" { duration(2.s) } }}}) + } + + assertEquals("width 1s ease 0s, height 2s ease 0s", nextChild().style.transition) + } + + @Test + fun allProperties() = runTest { + composition { + Div({ style { transitions { all { duration(1.s) } }}}) + } + + assertEquals("all 1s ease 0s", nextChild().style.transition) + } + + @Test + fun timingFunction() = runTest { + composition { + Div({ style { transitions { "width" { duration(1.s); timingFunction(AnimationTimingFunction.EaseInOut) }}}}) + } + + assertEquals("width 1s ease-in-out 0s", nextChild().style.transition) + } + + @Test + fun delay() = runTest { + composition { + Div({ style { transitions { "width" { duration(1.s); delay(2.s) }}}}) + } + + assertEquals("width 1s ease 2s", nextChild().style.transition) + } + + @Test + fun properties() = runTest { + composition { + Div({ style { transitions { defaultDuration(1.s); properties("width", "height") }}}) + Div({ style { transitions { defaultDuration(1.s); properties("width, height"); "width" { duration(2.s) }}}}) + val myList = listOf("width", "height") + Div({ style { transitions { defaultDuration(1.s); myList { duration(2.s) }}}}) + } + + assertEquals("width 1s ease 0s, height 1s ease 0s", nextChild().style.transition) + assertEquals("width 0s ease 0s, height 1s ease 0s, width 2s ease 0s", nextChild().style.transition) + assertEquals("width 2s ease 0s, height 2s ease 0s", nextChild().style.transition) + } +} \ No newline at end of file