Browse Source

Simplify inner CSS API, remove css fun completely (was experimental, will be redesigned)

pull/1889/head
Shagen Ogandzhanian 2 years ago committed by GitHub
parent
commit
cd7691a53f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/Attrs.kt
  2. 2
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/CSSBuilder.kt
  3. 44
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt
  4. 80
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheetBuilder.kt
  5. 18
      web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/selectors/CSSSelectors.kt
  6. 8
      web/core/src/jsTest/kotlin/elements/AttributesTests.kt
  7. 10
      web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/Sample.kt

13
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/Attrs.kt

@ -158,6 +158,12 @@ fun AttrsScope<HTMLInputElement>.height(value: Int) =
fun AttrsScope<HTMLInputElement>.width(value: Int) = fun AttrsScope<HTMLInputElement>.width(value: Int) =
attr("width", value.toString()) // image only attr("width", value.toString()) // image only
fun AttrsScope<HTMLCanvasElement>.width(value: Int) =
attr("width", value.toString())
fun AttrsScope<HTMLCanvasElement>.height(value: Int) =
attr("height", value.toString())
fun AttrsScope<HTMLInputElement>.list(dataListId: String) = fun AttrsScope<HTMLInputElement>.list(dataListId: String) =
attr("list", dataListId) attr("list", dataListId)
@ -330,10 +336,3 @@ fun AttrsScope<HTMLTableCellElement>.colspan(value: Int): AttrsScope<HTMLTableCe
fun AttrsScope<HTMLTableCellElement>.rowspan(value: Int): AttrsScope<HTMLTableCellElement> = fun AttrsScope<HTMLTableCellElement>.rowspan(value: Int): AttrsScope<HTMLTableCellElement> =
attr("rowspan", value.toString()) attr("rowspan", value.toString())
/* Canvas attributes */
fun AttrsScope<HTMLCanvasElement>.width(value: CSSSizeValue<out CSSUnitLengthOrPercentage>) =
attr("width", value.toString())
fun AttrsScope<HTMLCanvasElement>.height(value: CSSSizeValue<out CSSUnitLengthOrPercentage>) =
attr("height", value.toString())

2
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/CSSBuilder.kt

@ -12,7 +12,7 @@ class CSSBuilderImpl(
rulesHolder: CSSRulesHolder rulesHolder: CSSRulesHolder
) : CSSRuleBuilderImpl(), CSSBuilder, CSSRulesHolder by rulesHolder { ) : CSSRuleBuilderImpl(), CSSBuilder, CSSRulesHolder by rulesHolder {
override fun style(selector: CSSSelector, cssRule: CSSBuilder.() -> Unit) { override fun style(selector: CSSSelector, cssRule: CSSBuilder.() -> Unit) {
val resolvedSelector = if (selector.contains(self, true) || selector.contains(currentRoot, true)) { val resolvedSelector = if (selector.contains(self) || selector.contains(currentRoot)) {
selector selector
} else { } else {
desc(self, selector) desc(self, selector)

44
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheet.kt

@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import org.jetbrains.compose.web.ExperimentalComposeWebStyleApi
import org.jetbrains.compose.web.css.selectors.CSSSelector import org.jetbrains.compose.web.css.selectors.CSSSelector
import org.jetbrains.compose.web.dom.Style import org.jetbrains.compose.web.dom.Style
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
@ -79,49 +78,32 @@ open class StyleSheet(
@Suppress("EqualsOrHashCode") @Suppress("EqualsOrHashCode")
internal class CSSSelfSelector(var selector: CSSSelector? = null) : CSSSelector() { internal class CSSSelfSelector(var selector: CSSSelector? = null) : CSSSelector() {
override fun toString(): String = throw IllegalStateException("You can't concatenate `String + CSSSelector` which contains `self` or `root`. Use `selector(<your string>)` to convert `String` to `CSSSelector` for proper work. https://github.com/JetBrains/compose-jb/issues/1440") override fun toString(): String =
override fun asString(): String = selector?.asString() ?: throw IllegalStateException("You can't instantiate self") throw IllegalStateException("You can't concatenate `String + CSSSelector` which contains `self` or `root`. Use `selector(<your string>)` to convert `String` to `CSSSelector` for proper work. https://github.com/JetBrains/compose-jb/issues/1440")
override fun asString(): String =
selector?.asString() ?: throw IllegalStateException("You can't instantiate self")
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return other is CSSSelfSelector return other is CSSSelfSelector
} }
} }
// TODO: just proof of concept, do not use it
@ExperimentalComposeWebStyleApi
fun css(cssBuild: CSSBuilder.() -> Unit): String {
val selfSelector = CSSSelfSelector()
val (style, newCssRules) = buildCSS(selfSelector, selfSelector, cssBuild)
val cssRule = cssRules.find {
it is CSSStyleRuleDeclaration &&
it.selector is CSSSelector.CSSClass && it.style == style &&
(boundClasses[it.selector.className] ?: emptyList()) == newCssRules
}.unsafeCast<CSSStyleRuleDeclaration?>()
return if (cssRule != null) {
cssRule.selector.unsafeCast<CSSSelector.CSSClass>().className
} else {
val classNameSelector = CSSSelector.CSSClass("auto-${counter++}")
selfSelector.selector = classNameSelector
add(classNameSelector, style)
newCssRules.forEach { add(it) }
boundClasses[classNameSelector.className] = newCssRules
classNameSelector.className
}
}
protected class CSSHolder(private val usePrefix: Boolean, private val cssBuilder: CSSBuilder.() -> Unit) { protected class CSSHolder(private val usePrefix: Boolean, private val cssBuilder: CSSBuilder.() -> Unit) {
operator fun provideDelegate( operator fun provideDelegate(
sheet: StyleSheet, sheet: StyleSheet,
property: KProperty<*> property: KProperty<*>
): ReadOnlyProperty<Any?, String> { ): ReadOnlyProperty<Any?, String> {
val sheetName = if (usePrefix) "${sheet::class.simpleName}-" else "" val sheetName = if (usePrefix) "${sheet::class.simpleName}-" else ""
val selector = CSSSelector.CSSClass("$sheetName${property.name}") val className = "$sheetName${property.name}"
val selector = object : CSSSelector() {
override fun asString() = ".${className}"
}
val (properties, rules) = buildCSS(selector, selector, cssBuilder) val (properties, rules) = buildCSS(selector, selector, cssBuilder)
sheet.add(selector, properties) sheet.add(selector, properties)
rules.forEach { sheet.add(it) } rules.forEach { sheet.add(it) }
return ReadOnlyProperty { _, _ -> return ReadOnlyProperty { _, _ -> className }
selector.className
}
} }
} }
@ -158,9 +140,9 @@ internal fun buildCSS(
): Pair<StyleHolder, CSSRuleDeclarationList> { ): Pair<StyleHolder, CSSRuleDeclarationList> {
val styleSheet = StyleSheetBuilderImpl() val styleSheet = StyleSheetBuilderImpl()
// workaround because of problems with plus operator overloading // workaround because of problems with plus operator overloading
val root = if (thisClass is StyleSheet.CSSSelfSelector) thisClass else StyleSheet.CSSSelfSelector(thisClass) val root = (thisClass as? StyleSheet.CSSSelfSelector) ?: StyleSheet.CSSSelfSelector(thisClass)
// workaround because of problems with plus operator overloading // workaround because of problems with plus operator overloading
val self = if (thisContext is StyleSheet.CSSSelfSelector) thisContext else StyleSheet.CSSSelfSelector(thisContext) val self = (thisContext as? StyleSheet.CSSSelfSelector) ?: StyleSheet.CSSSelfSelector(thisContext)
val builder = CSSBuilderImpl(root, self, styleSheet) val builder = CSSBuilderImpl(root, self, styleSheet)
builder.cssRule() builder.cssRule()

80
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/StyleSheetBuilder.kt

@ -26,24 +26,25 @@ interface GenericStyleSheetBuilder<TBuilder> : CSSRulesHolder, SelectorsScope {
} }
operator fun String.invoke(cssRule: TBuilder.() -> Unit) { operator fun String.invoke(cssRule: TBuilder.() -> Unit) {
style(Raw(this), cssRule) style(RawSelector(this), cssRule)
} }
infix fun String.style(cssRule: TBuilder.() -> Unit) { infix fun String.style(cssRule: TBuilder.() -> Unit) {
style(Raw(this), cssRule) style(RawSelector(this), cssRule)
} }
} }
private val Universal = RawSelector("*")
interface SelectorsScope { interface SelectorsScope {
fun selector(selector: String): CSSSelector = Raw(selector) fun selector(selector: String): CSSSelector = RawSelector(selector)
fun combine(vararg selectors: CSSSelector): CSSSelector = Combine(selectors.toMutableList()) fun combine(vararg selectors: CSSSelector): CSSSelector = Combine(selectors.toMutableList())
operator fun CSSSelector.plus(selector: CSSSelector): CSSSelector { operator fun CSSSelector.plus(selector: CSSSelector): CSSSelector {
if (this is Combine) { return if (this is Combine) {
this.selectors.add(selector) this.selectors.add(selector)
return this this
} } else if (selector is Combine) {
return if (selector is Combine) {
selector.selectors.add(0, this) selector.selectors.add(0, this)
selector selector
} else { } else {
@ -52,12 +53,12 @@ interface SelectorsScope {
} }
operator fun CSSSelector.plus(selector: String): CSSSelector { operator fun CSSSelector.plus(selector: String): CSSSelector {
if (this is Combine) { return if (this is Combine) {
this.selectors.add(selector(selector)) this.selectors.add(selector(selector))
return this this
} else {
combine(this, selector(selector))
} }
return combine(this, selector(selector))
} }
@JsName("returnUniversalSelector") @JsName("returnUniversalSelector")
@ -67,9 +68,9 @@ interface SelectorsScope {
val universal: CSSSelector val universal: CSSSelector
get() = Universal get() = Universal
fun type(type: String): CSSSelector = Type(type) fun type(type: String): CSSSelector = RawSelector(type)
fun className(className: String): CSSSelector = CSSSelector.CSSClass(className) fun className(className: String): CSSSelector = RawSelector(".$className")
fun id(id: String): CSSSelector = Id(id) fun id(id: String): CSSSelector = RawSelector("#$id")
fun attr( fun attr(
name: String, name: String,
@ -241,33 +242,21 @@ interface SelectorsScope {
fun slotted(selector: CSSSelector): CSSSelector = PseudoElementInternal.Slotted(selector) fun slotted(selector: CSSSelector): CSSSelector = PseudoElementInternal.Slotted(selector)
} }
private data class Id(val id: String) : CSSSelector() { private data class RawSelector(val selector: String) : CSSSelector() {
override fun toString(): String = "#$id"
}
private data class Type(val type: String) : CSSSelector() {
override fun toString(): String = type
}
private object Universal : CSSSelector() {
override fun toString(): String = "*"
}
private data class Raw(val selector: String) : CSSSelector() {
override fun toString(): String = selector override fun toString(): String = selector
} }
private data class Combine(val selectors: MutableList<CSSSelector>) : CSSSelector() { private data class Combine(val selectors: MutableList<CSSSelector>) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, selectors, strict) contains(this, other, selectors)
override fun toString(): String = selectors.joinToString("") override fun toString(): String = selectors.joinToString("")
override fun asString(): String = selectors.joinToString("") { it.asString() } override fun asString(): String = selectors.joinToString("") { it.asString() }
} }
private data class Group(val selectors: List<CSSSelector>) : CSSSelector() { private data class Group(val selectors: List<CSSSelector>) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, selectors, strict) contains(this, other, selectors)
override fun toString(): String = selectors.joinToString(", ") override fun toString(): String = selectors.joinToString(", ")
override fun asString(): String = selectors.joinToString(", ") { it.asString() } override fun asString(): String = selectors.joinToString(", ") { it.asString() }
@ -275,32 +264,32 @@ private data class Group(val selectors: List<CSSSelector>) : CSSSelector() {
private data class Descendant(val parent: CSSSelector, val selected: CSSSelector) : private data class Descendant(val parent: CSSSelector, val selected: CSSSelector) :
CSSSelector() { CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, listOf(parent, selected), strict) contains(this, other, listOf(parent, selected))
override fun toString(): String = "$parent $selected" override fun toString(): String = "$parent $selected"
override fun asString(): String = "${parent.asString()} ${selected.asString()}" override fun asString(): String = "${parent.asString()} ${selected.asString()}"
} }
private data class Child(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() { private data class Child(val parent: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, listOf(parent, selected), strict) contains(this, other, listOf(parent, selected))
override fun toString(): String = "$parent > $selected" override fun toString(): String = "$parent > $selected"
override fun asString(): String = "${parent.asString()} > ${selected.asString()}" override fun asString(): String = "${parent.asString()} > ${selected.asString()}"
} }
private data class Sibling(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() { private data class Sibling(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, listOf(prev, selected), strict) contains(this, other, listOf(prev, selected))
override fun toString(): String = "$prev ~ $selected" override fun toString(): String = "$prev ~ $selected"
override fun asString(): String = "${prev.asString()} ~ ${selected.asString()}" override fun asString(): String = "${prev.asString()} ~ ${selected.asString()}"
} }
private data class Adjacent(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() { private data class Adjacent(val prev: CSSSelector, val selected: CSSSelector) : CSSSelector() {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, listOf(prev, selected), strict) contains(this, other, listOf(prev, selected))
override fun toString(): String = "$prev + $selected" override fun toString(): String = "$prev + $selected"
override fun asString(): String = "${prev.asString()} + ${selected.asString()}" override fun asString(): String = "${prev.asString()} + ${selected.asString()}"
@ -327,6 +316,7 @@ private open class PseudoClassInternal(val name: String) : CSSSelector() {
name == other.name && argsStr() == other.argsStr() name == other.name && argsStr() == other.argsStr()
} else false } else false
} }
open fun argsStr(): String? = null open fun argsStr(): String? = null
override fun toString(): String = ":$name${argsStr()?.let { "($it)" } ?: ""}" override fun toString(): String = ":$name${argsStr()?.let { "($it)" } ?: ""}"
@ -353,16 +343,16 @@ private open class PseudoClassInternal(val name: String) : CSSSelector() {
} }
class Host internal constructor(val selector: CSSSelector) : PseudoClassInternal("host") { class Host internal constructor(val selector: CSSSelector) : PseudoClassInternal("host") {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, listOf(selector), strict) contains(this, other, listOf(selector))
override fun argsStr() = selector.asString() override fun argsStr() = selector.asString()
} }
// Etc // Etc
class Not internal constructor(val selector: CSSSelector) : PseudoClassInternal("not") { class Not internal constructor(val selector: CSSSelector) : PseudoClassInternal("not") {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, listOf(selector), strict) contains(this, other, listOf(selector))
override fun argsStr() = "$selector" override fun argsStr() = "$selector"
} }
@ -379,8 +369,8 @@ private open class PseudoElementInternal(val name: String) : CSSSelector() {
override fun toString(): String = "::$name${argsStr()?.let { "($it)" } ?: ""}" override fun toString(): String = "::$name${argsStr()?.let { "($it)" } ?: ""}"
class Slotted internal constructor(val selector: CSSSelector) : PseudoElementInternal("slotted") { class Slotted internal constructor(val selector: CSSSelector) : PseudoElementInternal("slotted") {
override fun contains(other: CSSSelector, strict: Boolean): Boolean = override fun contains(other: CSSSelector): Boolean =
contains(this, other, listOf(selector), strict) contains(this, other, listOf(selector))
override fun argsStr() = selector.asString() override fun argsStr() = selector.asString()
} }

18
web/core/src/jsMain/kotlin/org/jetbrains/compose/web/css/selectors/CSSSelectors.kt

@ -35,31 +35,21 @@ sealed interface Nth {
} }
abstract class CSSSelector internal constructor() { abstract class CSSSelector internal constructor() {
override fun equals(other: Any?): Boolean {
return this === other || asString() == (other as? CSSSelector)?.asString()
}
internal open fun contains(other: CSSSelector, strict: Boolean = false): Boolean { internal open fun contains(other: CSSSelector): Boolean {
return if (strict) this === other else this == other return this === other
} }
@Suppress("SuspiciousEqualsCombination") @Suppress("SuspiciousEqualsCombination")
protected fun contains(that: CSSSelector, other: CSSSelector, children: List<CSSSelector>, strict: Boolean): Boolean { protected fun contains(that: CSSSelector, other: CSSSelector, children: List<CSSSelector>): Boolean {
return that === other || // exactly same selector return (that === other) || children.any { it.contains(other) }
children.any { it.contains(other, strict) } || // contains it in children
(!strict && that == other) // equals structurally
} }
// This method made for workaround because of possible concatenation of `String + CSSSelector`, // This method made for workaround because of possible concatenation of `String + CSSSelector`,
// so `toString` is called for such operator, but we are calling `asString` for instantiation. // so `toString` is called for such operator, but we are calling `asString` for instantiation.
// `toString` is reloaded for CSSSelfSelector // `toString` is reloaded for CSSSelfSelector
internal open fun asString(): String = toString() internal open fun asString(): String = toString()
internal data class CSSClass internal constructor(val className: String) : CSSSelector() {
override fun toString(): String = ".$className"
}
object Attribute { object Attribute {
enum class Operator(val value: String) { enum class Operator(val value: String) {
Equals("="), Equals("="),

8
web/core/src/jsTest/kotlin/elements/AttributesTests.kt

@ -553,15 +553,15 @@ class AttributesTests {
fun canvasAttributeTest() = runTest { fun canvasAttributeTest() = runTest {
composition { composition {
Canvas({ Canvas({
height(400.px) height(400)
width(450.px) width(450)
}) })
} }
with(nextChild() as HTMLCanvasElement) { with(nextChild() as HTMLCanvasElement) {
val attrsMap = getAttributeNames().associateWith { getAttribute(it) } val attrsMap = getAttributeNames().associateWith { getAttribute(it) }
assertEquals(2, attrsMap.size) assertEquals(2, attrsMap.size)
assertEquals("450px", attrsMap["width"]) assertEquals("450", attrsMap["width"])
assertEquals("400px", attrsMap["height"]) assertEquals("400", attrsMap["height"])
} }
} }
} }

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

@ -249,16 +249,8 @@ fun main() {
Div( Div(
attrs = { attrs = {
classes(
Auto.css {
color(Color.pink)
hover(self) style {
color(Color.blue)
}
}
)
style { style {
color(Color.pink)
opacity(30.percent) opacity(30.percent)
} }
} }

Loading…
Cancel
Save