diff --git a/build.gradle.kts b/build.gradle.kts index 3183702..5dec0a2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.5.21" + kotlin("multiplatform") version "1.5.30" `maven-publish` } @@ -7,9 +7,9 @@ version = "0.4.29-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenLocal() mavenCentral() + maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } } kotlin { diff --git a/build.gradle.kts b/build.gradle.kts index 3183702..5dec0a2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.5.21" + kotlin("multiplatform") version "1.5.30" `maven-publish` } @@ -7,9 +7,9 @@ version = "0.4.29-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenLocal() mavenCentral() + maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } } kotlin { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/build.gradle.kts b/build.gradle.kts index 3183702..5dec0a2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.5.21" + kotlin("multiplatform") version "1.5.30" `maven-publish` } @@ -7,9 +7,9 @@ version = "0.4.29-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenLocal() mavenCentral() + maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } } kotlin { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 6cc8d95..a22324a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,4 +10,4 @@ rootProject.name = "kotlin-css-generator" -enableFeaturePreview("GRADLE_METADATA") +//enableFeaturePreview("GRADLE_METADATA") diff --git a/build.gradle.kts b/build.gradle.kts index 3183702..5dec0a2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.5.21" + kotlin("multiplatform") version "1.5.30" `maven-publish` } @@ -7,9 +7,9 @@ version = "0.4.29-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenLocal() mavenCentral() + maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } } kotlin { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 6cc8d95..a22324a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,4 +10,4 @@ rootProject.name = "kotlin-css-generator" -enableFeaturePreview("GRADLE_METADATA") +//enableFeaturePreview("GRADLE_METADATA") diff --git a/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt b/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt index 5941199..9231f62 100644 --- a/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt +++ b/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt @@ -4,6 +4,7 @@ NONE, PX, EM, + REL, REM, PC, PRC, @@ -16,12 +17,23 @@ val uom: MeasurementUoM = MeasurementUoM.NONE ) : CssProperty(value) { + override fun toString(): String = super.value + companion object { val auto = Measurement("auto") val initial = Measurement("initial") val inherit = Measurement("inherit") val normal = Measurement("normal") + fun fromString(value:String): Measurement = when { + value == "0" -> Measurement("0", MeasurementUoM.PX) + value.endsWith("px") -> Measurement(value.slice(0..(value.length-2)), MeasurementUoM.PX) + value.endsWith("rel") -> Measurement(value.slice(0..(value.length-3)), MeasurementUoM.REL) + else -> { + TODO("Unable to parse $value") + } + } + fun px(nr: Int) = if (nr == 0) { Measurement( "0", diff --git a/build.gradle.kts b/build.gradle.kts index 3183702..5dec0a2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.5.21" + kotlin("multiplatform") version "1.5.30" `maven-publish` } @@ -7,9 +7,9 @@ version = "0.4.29-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenLocal() mavenCentral() + maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } } kotlin { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 6cc8d95..a22324a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,4 +10,4 @@ rootProject.name = "kotlin-css-generator" -enableFeaturePreview("GRADLE_METADATA") +//enableFeaturePreview("GRADLE_METADATA") diff --git a/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt b/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt index 5941199..9231f62 100644 --- a/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt +++ b/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt @@ -4,6 +4,7 @@ NONE, PX, EM, + REL, REM, PC, PRC, @@ -16,12 +17,23 @@ val uom: MeasurementUoM = MeasurementUoM.NONE ) : CssProperty(value) { + override fun toString(): String = super.value + companion object { val auto = Measurement("auto") val initial = Measurement("initial") val inherit = Measurement("inherit") val normal = Measurement("normal") + fun fromString(value:String): Measurement = when { + value == "0" -> Measurement("0", MeasurementUoM.PX) + value.endsWith("px") -> Measurement(value.slice(0..(value.length-2)), MeasurementUoM.PX) + value.endsWith("rel") -> Measurement(value.slice(0..(value.length-3)), MeasurementUoM.REL) + else -> { + TODO("Unable to parse $value") + } + } + fun px(nr: Int) = if (nr == 0) { Measurement( "0", diff --git a/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt b/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt index 60dd4c9..81a5986 100644 --- a/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt +++ b/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt @@ -61,32 +61,140 @@ return "$paddedName$builder;\n" } - fun generatePropertyCss(indent: String): String { + fun generatePropertyCss( + indent: String, + sortProperties: Boolean + ): String { val builder = StringBuilder() - for ((name, prop) in props) { - builder.append(propertyCss(indent, name, prop)) + if (sortProperties) { + for (name in props.keys.sorted()) { + val prop = props[name] ?: error("$name not found in properties after sorting!") + + builder.append(propertyCss(indent, name, prop)) + } + } else { + for ((name, prop) in props) { + builder.append(propertyCss(indent, name, prop)) + } } return builder.toString() } open fun generateCss( + indent: String = "", + minified: Boolean = false, + warnOnRedeclaration: Boolean = true, + allowCommaInSelector: Boolean = false, + combineEqualBlocks: Boolean = false, + sortProperties: Boolean = false + ): String { + val blocks = generateCssBlocks( + indent = indent, + minified = minified, + warnOnRedeclaration = warnOnRedeclaration, + allowCommaInSelector = allowCommaInSelector, + sortProperties = sortProperties + ) + + val builder = StringBuilder() + + fun StringBuilder.generateBlock( + indent: String, + selectors: List, + block: CssBlock? + ) { + if (selectors.isNotEmpty() && block != null) { + append(indent) + append(selectors.joinToString(",\n")) + append(" {\n") + append(block.content) + append(indent) + append("}\n\n") + } + } + + if (!combineEqualBlocks) { + var first = true + val selectors = mutableListOf() + var lastBlock: CssBlock? = null + + for (block in blocks) { + if (first) { + first = false + selectors.add(block.selector) + lastBlock = block + } else { + lastBlock = if (lastBlock != null && lastBlock.content == block.content) { + selectors.add(block.selector) + block + } else { + builder.generateBlock(indent, selectors, lastBlock) + + selectors.clear() + selectors.add(block.selector) + block + } + } + } + + builder.generateBlock(indent, selectors, lastBlock) + } else { + val blockHashes: MutableMap> = mutableMapOf() + + for (block in blocks) { + blockHashes.getOrPut(block.content.hashCode()) { + mutableListOf() + }.add(block) + } + val done = mutableSetOf() + + for(block in blocks) { + val hashCode = block.content.hashCode() + + if (!done.contains(hashCode)) { + blockHashes[hashCode]?.let { + val slctrs = it.map { blk -> + blk.selector + } + builder.generateBlock(indent, slctrs, block) + done.add(hashCode) + } + } + } + } + + return if (minified) { + val stripped = StringBuilder() + val skip = arrayOf(' ', '\t', '\n', '\r') + for (char in builder) { + if (!skip.contains(char)) { + stripped.append(char) + } + } + stripped.toString() + } else { + builder.toString() + } + } + + open fun generateCssBlocks( namespace: String = "", indent: String = "", minified: Boolean = false, warnOnRedeclaration: Boolean = true, - allowCommaInSelector: Boolean = false - ): String { + allowCommaInSelector: Boolean = false, + sortProperties: Boolean = false + ): List { val blocks = mutableListOf() - val builder = StringBuilder() for (name in definitions.keys) { val props = definitions[name]!! val css = StringBuilder() if (warnOnRedeclaration && props.size > 1) { - css.append("/* style '$name' is defined ${props.size} times! */\n") + css.append(" $indent/* style '$name' is defined ${props.size} times! */\n") } val finalStyle = Style() @@ -95,22 +203,24 @@ prop(finalStyle) } - css.append(finalStyle.generatePropertyCss(" $indent")) + css.append(finalStyle.generatePropertyCss(" $indent", sortProperties)) if (css.isNotBlank()) { + val builder = StringBuilder() + check (allowCommaInSelector || !name.contains(',')) { "Comma is not allowed in selector (option is set in generateCss call)" } - builder.append("\n$namespace$name".trim()) + //builder.append("\n$namespace$name".trim()) //builder.append(" $indent") - builder.append(" {\n") + //builder.append(" {\n") finalStyle.fontFace?.let { ff -> builder.append(" $indent") builder.append("@font-face {\n") - builder.append(ff.generatePropertyCss(" $indent")) + builder.append(ff.generatePropertyCss(" $indent", sortProperties)) builder.append(" $indent") builder.append("}\n") } @@ -134,7 +244,7 @@ style(finalStyle) - builder.append(finalStyle.generatePropertyCss(" $indent")) + builder.append(finalStyle.generatePropertyCss(" $indent", sortProperties)) builder.append(" $indent") builder.append("}\n") @@ -147,13 +257,21 @@ } builder.append(css) - builder.append("}\n\n") + //builder.append("}\n\n") + + blocks.add(CssBlock( + "$namespace$name".trim(), + builder.toString() + )) } - builder.append(finalStyle.generateCss( + blocks.addAll(finalStyle.generateCssBlocks( "$namespace$name".trim(), indent, - allowCommaInSelector = allowCommaInSelector + minified = minified, + allowCommaInSelector = allowCommaInSelector, + warnOnRedeclaration = warnOnRedeclaration, + sortProperties = sortProperties )) } @@ -162,24 +280,22 @@ mq.keys.sorted().forEach { mediaName -> val css = mq[mediaName] - builder.append(indent) - builder.append("@media ") - builder.append(mediaName) - builder.append(" {\n") css?.let { css -> val mediaStyle = ConditionalStyle() css(mediaStyle) - builder.append(mediaStyle.generateCss( - "", - " $indent", - allowCommaInSelector = allowCommaInSelector + blocks.add(CssBlock( + "$indent@media $mediaName".trim(), + mediaStyle.generateCss( + " $indent", + minified = minified, + allowCommaInSelector = allowCommaInSelector, + warnOnRedeclaration = warnOnRedeclaration, + sortProperties = sortProperties + ) )) } - - builder.append(indent) - builder.append("}\n") } } @@ -187,40 +303,27 @@ mq.keys.sorted().forEach { mediaName -> val css = mq[mediaName] - builder.append(indent) - builder.append("@supports ") - builder.append(mediaName) - builder.append(" {\n") css?.let { css -> val mediaStyle = ConditionalStyle() css(mediaStyle) - builder.append(mediaStyle.generateCss( - "", - " $indent", - allowCommaInSelector = allowCommaInSelector + blocks.add(CssBlock( + "$indent@supports $mediaName".trim(), + mediaStyle.generateCss( + " $indent", + minified = minified, + allowCommaInSelector = allowCommaInSelector, + warnOnRedeclaration = warnOnRedeclaration, + sortProperties = sortProperties + ) )) } - - builder.append(indent) - builder.append("}\n") } } } - return if (minified) { - val stripped = StringBuilder() - val skip = arrayOf(' ', '\t', '\n', '\r') - for (char in builder) { - if (!skip.contains(char)) { - stripped.append(char) - } - } - stripped.toString() - } else { - builder.toString() - } + return blocks } } @@ -382,7 +485,19 @@ addStyle(":hover", style) } - fun pseudo(selector: DescriptionProvider, style: Css) { + fun firstChild(style: Css) { + addStyle(":first-child", style) + } + + fun lastChild(style: Css) { + addStyle(":last-child", style) + } + + fun pseudoElement(selector: DescriptionProvider, style: Css) { + addStyle(":${selector.description()}", style) + } + + fun pseudoChild(selector: DescriptionProvider, style: Css) { addStyle("::${selector.description()}", style) } @@ -486,6 +601,10 @@ props["background-image"] = prp(image) } + fun backgroundImage(value: String) { + props["background-image"] = prp(value) + } + fun backgroundOrigin(origin: ClipOrigin) { props["background-origin"] = prp(origin) } @@ -506,6 +625,14 @@ props["border"] = prp(border) } + fun border( + width: Measurement, + style: BorderStyle, + color: Color + ) { + props["border"] = prp(width, style, color) + } + fun borderBottom(borderBottom: String) { props["border-bottom"] = prp(borderBottom) } @@ -514,10 +641,18 @@ props["border-bottom-color"] = prp(color) } + fun borderBottomLeftRadius(vararg radius: Measurement) { + props["border-bottom-left-radius"] = prp(*radius) + } + fun borderBottomLeftRadius(vararg radius: BorderRadius) { props["border-bottom-left-radius"] = prp(*radius) } + fun borderBottomRightRadius(vararg radius: Measurement) { + props["border-bottom-right-radius"] = prp(*radius) + } + fun borderBottomRightRadius(vararg radius: BorderRadius) { props["border-bottom-right-radius"] = prp(*radius) } @@ -656,10 +791,18 @@ props["border-top-color"] = prp(color) } + fun borderTopLeftRadius(radius: Measurement) { + props["border-top-left-radius"] = prp(radius) + } + fun borderTopLeftRadius(radius: BorderRadius) { props["border-top-left-radius"] = prp(radius) } + fun borderTopRightRadius(radius: Measurement) { + props["border-top-right-radius"] = prp(radius) + } + fun borderTopRightRadius(radius: BorderRadius) { props["border-top-right-radius"] = prp(radius) } diff --git a/build.gradle.kts b/build.gradle.kts index 3183702..5dec0a2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.5.21" + kotlin("multiplatform") version "1.5.30" `maven-publish` } @@ -7,9 +7,9 @@ version = "0.4.29-SNAPSHOT" repositories { - maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } mavenLocal() mavenCentral() + maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } } kotlin { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 6cc8d95..a22324a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,4 +10,4 @@ rootProject.name = "kotlin-css-generator" -enableFeaturePreview("GRADLE_METADATA") +//enableFeaturePreview("GRADLE_METADATA") diff --git a/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt b/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt index 5941199..9231f62 100644 --- a/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt +++ b/src/commonMain/kotlin/nl/astraeus/css/properties/Measurement.kt @@ -4,6 +4,7 @@ NONE, PX, EM, + REL, REM, PC, PRC, @@ -16,12 +17,23 @@ val uom: MeasurementUoM = MeasurementUoM.NONE ) : CssProperty(value) { + override fun toString(): String = super.value + companion object { val auto = Measurement("auto") val initial = Measurement("initial") val inherit = Measurement("inherit") val normal = Measurement("normal") + fun fromString(value:String): Measurement = when { + value == "0" -> Measurement("0", MeasurementUoM.PX) + value.endsWith("px") -> Measurement(value.slice(0..(value.length-2)), MeasurementUoM.PX) + value.endsWith("rel") -> Measurement(value.slice(0..(value.length-3)), MeasurementUoM.REL) + else -> { + TODO("Unable to parse $value") + } + } + fun px(nr: Int) = if (nr == 0) { Measurement( "0", diff --git a/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt b/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt index 60dd4c9..81a5986 100644 --- a/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt +++ b/src/commonMain/kotlin/nl/astraeus/css/style/Style.kt @@ -61,32 +61,140 @@ return "$paddedName$builder;\n" } - fun generatePropertyCss(indent: String): String { + fun generatePropertyCss( + indent: String, + sortProperties: Boolean + ): String { val builder = StringBuilder() - for ((name, prop) in props) { - builder.append(propertyCss(indent, name, prop)) + if (sortProperties) { + for (name in props.keys.sorted()) { + val prop = props[name] ?: error("$name not found in properties after sorting!") + + builder.append(propertyCss(indent, name, prop)) + } + } else { + for ((name, prop) in props) { + builder.append(propertyCss(indent, name, prop)) + } } return builder.toString() } open fun generateCss( + indent: String = "", + minified: Boolean = false, + warnOnRedeclaration: Boolean = true, + allowCommaInSelector: Boolean = false, + combineEqualBlocks: Boolean = false, + sortProperties: Boolean = false + ): String { + val blocks = generateCssBlocks( + indent = indent, + minified = minified, + warnOnRedeclaration = warnOnRedeclaration, + allowCommaInSelector = allowCommaInSelector, + sortProperties = sortProperties + ) + + val builder = StringBuilder() + + fun StringBuilder.generateBlock( + indent: String, + selectors: List, + block: CssBlock? + ) { + if (selectors.isNotEmpty() && block != null) { + append(indent) + append(selectors.joinToString(",\n")) + append(" {\n") + append(block.content) + append(indent) + append("}\n\n") + } + } + + if (!combineEqualBlocks) { + var first = true + val selectors = mutableListOf() + var lastBlock: CssBlock? = null + + for (block in blocks) { + if (first) { + first = false + selectors.add(block.selector) + lastBlock = block + } else { + lastBlock = if (lastBlock != null && lastBlock.content == block.content) { + selectors.add(block.selector) + block + } else { + builder.generateBlock(indent, selectors, lastBlock) + + selectors.clear() + selectors.add(block.selector) + block + } + } + } + + builder.generateBlock(indent, selectors, lastBlock) + } else { + val blockHashes: MutableMap> = mutableMapOf() + + for (block in blocks) { + blockHashes.getOrPut(block.content.hashCode()) { + mutableListOf() + }.add(block) + } + val done = mutableSetOf() + + for(block in blocks) { + val hashCode = block.content.hashCode() + + if (!done.contains(hashCode)) { + blockHashes[hashCode]?.let { + val slctrs = it.map { blk -> + blk.selector + } + builder.generateBlock(indent, slctrs, block) + done.add(hashCode) + } + } + } + } + + return if (minified) { + val stripped = StringBuilder() + val skip = arrayOf(' ', '\t', '\n', '\r') + for (char in builder) { + if (!skip.contains(char)) { + stripped.append(char) + } + } + stripped.toString() + } else { + builder.toString() + } + } + + open fun generateCssBlocks( namespace: String = "", indent: String = "", minified: Boolean = false, warnOnRedeclaration: Boolean = true, - allowCommaInSelector: Boolean = false - ): String { + allowCommaInSelector: Boolean = false, + sortProperties: Boolean = false + ): List { val blocks = mutableListOf() - val builder = StringBuilder() for (name in definitions.keys) { val props = definitions[name]!! val css = StringBuilder() if (warnOnRedeclaration && props.size > 1) { - css.append("/* style '$name' is defined ${props.size} times! */\n") + css.append(" $indent/* style '$name' is defined ${props.size} times! */\n") } val finalStyle = Style() @@ -95,22 +203,24 @@ prop(finalStyle) } - css.append(finalStyle.generatePropertyCss(" $indent")) + css.append(finalStyle.generatePropertyCss(" $indent", sortProperties)) if (css.isNotBlank()) { + val builder = StringBuilder() + check (allowCommaInSelector || !name.contains(',')) { "Comma is not allowed in selector (option is set in generateCss call)" } - builder.append("\n$namespace$name".trim()) + //builder.append("\n$namespace$name".trim()) //builder.append(" $indent") - builder.append(" {\n") + //builder.append(" {\n") finalStyle.fontFace?.let { ff -> builder.append(" $indent") builder.append("@font-face {\n") - builder.append(ff.generatePropertyCss(" $indent")) + builder.append(ff.generatePropertyCss(" $indent", sortProperties)) builder.append(" $indent") builder.append("}\n") } @@ -134,7 +244,7 @@ style(finalStyle) - builder.append(finalStyle.generatePropertyCss(" $indent")) + builder.append(finalStyle.generatePropertyCss(" $indent", sortProperties)) builder.append(" $indent") builder.append("}\n") @@ -147,13 +257,21 @@ } builder.append(css) - builder.append("}\n\n") + //builder.append("}\n\n") + + blocks.add(CssBlock( + "$namespace$name".trim(), + builder.toString() + )) } - builder.append(finalStyle.generateCss( + blocks.addAll(finalStyle.generateCssBlocks( "$namespace$name".trim(), indent, - allowCommaInSelector = allowCommaInSelector + minified = minified, + allowCommaInSelector = allowCommaInSelector, + warnOnRedeclaration = warnOnRedeclaration, + sortProperties = sortProperties )) } @@ -162,24 +280,22 @@ mq.keys.sorted().forEach { mediaName -> val css = mq[mediaName] - builder.append(indent) - builder.append("@media ") - builder.append(mediaName) - builder.append(" {\n") css?.let { css -> val mediaStyle = ConditionalStyle() css(mediaStyle) - builder.append(mediaStyle.generateCss( - "", - " $indent", - allowCommaInSelector = allowCommaInSelector + blocks.add(CssBlock( + "$indent@media $mediaName".trim(), + mediaStyle.generateCss( + " $indent", + minified = minified, + allowCommaInSelector = allowCommaInSelector, + warnOnRedeclaration = warnOnRedeclaration, + sortProperties = sortProperties + ) )) } - - builder.append(indent) - builder.append("}\n") } } @@ -187,40 +303,27 @@ mq.keys.sorted().forEach { mediaName -> val css = mq[mediaName] - builder.append(indent) - builder.append("@supports ") - builder.append(mediaName) - builder.append(" {\n") css?.let { css -> val mediaStyle = ConditionalStyle() css(mediaStyle) - builder.append(mediaStyle.generateCss( - "", - " $indent", - allowCommaInSelector = allowCommaInSelector + blocks.add(CssBlock( + "$indent@supports $mediaName".trim(), + mediaStyle.generateCss( + " $indent", + minified = minified, + allowCommaInSelector = allowCommaInSelector, + warnOnRedeclaration = warnOnRedeclaration, + sortProperties = sortProperties + ) )) } - - builder.append(indent) - builder.append("}\n") } } } - return if (minified) { - val stripped = StringBuilder() - val skip = arrayOf(' ', '\t', '\n', '\r') - for (char in builder) { - if (!skip.contains(char)) { - stripped.append(char) - } - } - stripped.toString() - } else { - builder.toString() - } + return blocks } } @@ -382,7 +485,19 @@ addStyle(":hover", style) } - fun pseudo(selector: DescriptionProvider, style: Css) { + fun firstChild(style: Css) { + addStyle(":first-child", style) + } + + fun lastChild(style: Css) { + addStyle(":last-child", style) + } + + fun pseudoElement(selector: DescriptionProvider, style: Css) { + addStyle(":${selector.description()}", style) + } + + fun pseudoChild(selector: DescriptionProvider, style: Css) { addStyle("::${selector.description()}", style) } @@ -486,6 +601,10 @@ props["background-image"] = prp(image) } + fun backgroundImage(value: String) { + props["background-image"] = prp(value) + } + fun backgroundOrigin(origin: ClipOrigin) { props["background-origin"] = prp(origin) } @@ -506,6 +625,14 @@ props["border"] = prp(border) } + fun border( + width: Measurement, + style: BorderStyle, + color: Color + ) { + props["border"] = prp(width, style, color) + } + fun borderBottom(borderBottom: String) { props["border-bottom"] = prp(borderBottom) } @@ -514,10 +641,18 @@ props["border-bottom-color"] = prp(color) } + fun borderBottomLeftRadius(vararg radius: Measurement) { + props["border-bottom-left-radius"] = prp(*radius) + } + fun borderBottomLeftRadius(vararg radius: BorderRadius) { props["border-bottom-left-radius"] = prp(*radius) } + fun borderBottomRightRadius(vararg radius: Measurement) { + props["border-bottom-right-radius"] = prp(*radius) + } + fun borderBottomRightRadius(vararg radius: BorderRadius) { props["border-bottom-right-radius"] = prp(*radius) } @@ -656,10 +791,18 @@ props["border-top-color"] = prp(color) } + fun borderTopLeftRadius(radius: Measurement) { + props["border-top-left-radius"] = prp(radius) + } + fun borderTopLeftRadius(radius: BorderRadius) { props["border-top-left-radius"] = prp(radius) } + fun borderTopRightRadius(radius: Measurement) { + props["border-top-right-radius"] = prp(radius) + } + fun borderTopRightRadius(radius: BorderRadius) { props["border-top-right-radius"] = prp(radius) } diff --git a/src/commonTest/kotlin/nl/astraeus/css/TestCssBuilder.kt b/src/commonTest/kotlin/nl/astraeus/css/TestCssBuilder.kt index c500441..7d47ad3 100644 --- a/src/commonTest/kotlin/nl/astraeus/css/TestCssBuilder.kt +++ b/src/commonTest/kotlin/nl/astraeus/css/TestCssBuilder.kt @@ -1,5 +1,6 @@ package nl.astraeus.css +import nl.astraeus.css.properties.BoxSizing import nl.astraeus.css.properties.Color import nl.astraeus.css.properties.Count import nl.astraeus.css.properties.Display @@ -24,6 +25,32 @@ fun testBuilder() { val css = style { + select("*", "*::before", "*::after") { + boxSizing(BoxSizing.borderBox) + } + + select("html") { + transition("background-color 1s ease") + + margin(0.px) + padding(0.px) + + focus { + backgroundColor(Color.blue) + } + } + + select("body") { + margin(0.px) + + padding(0.px) + + focus { + backgroundColor(Color.blue) + } + transition("background-color 1s ease") + } + select(".test") { top(10.px) left(4.em) @@ -77,7 +104,7 @@ } } - println(css.generateCss()) + println(css.generateCss(combineEqualBlocks = true, sortProperties = true)) } @Test