From f4e2014f137c0abc88acf1eb9b4a94c6b9f9e5e4 Mon Sep 17 00:00:00 2001 From: AomiVel Date: Tue, 24 Jun 2025 10:05:05 +0900 Subject: [PATCH 01/10] add: css support --- .../kotlin/net/kigawa/renlin/css/CssColor.kt | 104 ++++++++++++++++++ .../kotlin/net/kigawa/renlin/css/CssDsl.kt | 79 +++++++++++++ .../net/kigawa/renlin/css/CssExtensions.kt | 28 +++++ .../net/kigawa/renlin/css/CssManager.kt | 40 +++++++ .../kotlin/net/kigawa/renlin/css/CssUnit.kt | 18 +++ .../kotlin/net/kigawa/renlin/css/CssValue.kt | 15 +++ .../kotlin/net/kigawa/renlin/dsl/DslBase.kt | 26 ++++- .../renlin/dsl/state/BasicDslStateBase.kt | 17 ++- .../net/kigawa/renlin/dsl/state/DslState.kt | 4 + .../renlin/dsl/state/RootDslStateBase.kt | 5 + .../renlin/dsl/state/SubBasicDslState.kt | 10 ++ .../net/kigawa/renlin/element/TagNode.kt | 1 + .../kotlin/net/kigawa/renlin/tag/Div.kt | 9 +- .../net/kigawa/renlin/css/JsCssManager.kt | 47 ++++++++ .../kigawa/renlin/element/DomTagElement.kt | 11 ++ .../net/kigawa/renlin/element/TagNode.kt | 1 + .../net/kigawa/renlin/css/JvmCssManager.kt | 80 ++++++++++++++ .../net/kigawa/renlin/element/TagNode.kt | 1 + 18 files changed, 490 insertions(+), 6 deletions(-) create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssValue.kt create mode 100644 renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt create mode 100644 renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt new file mode 100644 index 0000000..a328a9b --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt @@ -0,0 +1,104 @@ +package net.kigawa.renlin.css + +/** + * CSS色の基底インターフェース + */ +sealed interface CssColor : CssValue + +/** + * 名前付きの色 + */ +enum class NamedColor(private val colorName: String) : CssColor { + RED("red"), + GREEN("green"), + BLUE("blue"), + BLACK("black"), + WHITE("white"), + TRANSPARENT("transparent"), + GRAY("gray"), + LIGHTGRAY("lightgray"), + DARKGRAY("darkgray"), + YELLOW("yellow"), + ORANGE("orange"), + PURPLE("purple"), + PINK("pink"), + BROWN("brown"), + CYAN("cyan"), + MAGENTA("magenta"); + + override fun toCssString(): String = colorName +} + +/** + * 16進数表記 + */ +data class HexColor(val hex: String) : CssColor { + init { + require(hex.matches(Regex("^#[0-9A-Fa-f]{3}$|^#[0-9A-Fa-f]{6}$"))) { + "Invalid hex color format: $hex" + } + } + + override fun toCssString(): String = hex +} + +/** + * RGB表記 + */ +data class RgbColor(val r: Int, val g: Int, val b: Int) : CssColor { + init { + require(r in 0..255 && g in 0..255 && b in 0..255) { + "RGB values must be between 0 and 255" + } + } + + override fun toCssString(): String = "rgb($r, $g, $b)" +} + +/** + * RGBA表記(透明度付き) + */ +data class RgbaColor(val r: Int, val g: Int, val b: Int, val a: Double) : CssColor { + init { + require(r in 0..255 && g in 0..255 && b in 0..255) { + "RGB values must be between 0 and 255" + } + require(a in 0.0..1.0) { + "Alpha value must be between 0.0 and 1.0" + } + } + + override fun toCssString(): String = "rgba($r, $g, $b, $a)" +} + +/** + * 色のファクトリオブジェクト + */ +object Color { + // よく使う色の定数 + val RED = NamedColor.RED + val GREEN = NamedColor.GREEN + val BLUE = NamedColor.BLUE + val BLACK = NamedColor.BLACK + val WHITE = NamedColor.WHITE + val TRANSPARENT = NamedColor.TRANSPARENT + val GRAY = NamedColor.GRAY + val LIGHTGRAY = NamedColor.LIGHTGRAY + val DARKGRAY = NamedColor.DARKGRAY + val YELLOW = NamedColor.YELLOW + val ORANGE = NamedColor.ORANGE + val PURPLE = NamedColor.PURPLE + val PINK = NamedColor.PINK + val BROWN = NamedColor.BROWN + val CYAN = NamedColor.CYAN + val MAGENTA = NamedColor.MAGENTA + + // ファクトリメソッド + fun hex(hex: String): HexColor = HexColor(hex) + fun rgb(r: Int, g: Int, b: Int): RgbColor = RgbColor(r, g, b) + fun rgba(r: Int, g: Int, b: Int, a: Double): RgbaColor = RgbaColor(r, g, b, a) +} + +// 文字列からの便利な変換 +val String.color: HexColor + get() = HexColor(if (startsWith("#")) this else "#$this") \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt new file mode 100644 index 0000000..9f39816 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt @@ -0,0 +1,79 @@ +package net.kigawa.renlin.css + +import net.kigawa.renlin.Html + +/** + * CSS記述用のDSL(基本プロパティのみ) + */ +@Html +class CssDsl { + private val properties = mutableMapOf() + + // Color + var color: CssColor? + get() = properties["color"] as? CssColor + set(value) { if (value != null) properties["color"] = value } + + var backgroundColor: CssColor? + get() = properties["background-color"] as? CssColor + set(value) { if (value != null) properties["background-color"] = value } + + // Size + var width: CssValue? + get() = properties["width"] + set(value) { if (value != null) properties["width"] = value } + + var height: CssValue? + get() = properties["height"] + set(value) { if (value != null) properties["height"] = value } + + // Font + var fontSize: CssValue? + get() = properties["font-size"] + set(value) { if (value != null) properties["font-size"] = value } + + // Margin + var margin: CssValue? + get() = properties["margin"] + set(value) { if (value != null) properties["margin"] = value } + + var marginTop: CssValue? + get() = properties["margin-top"] + set(value) { if (value != null) properties["margin-top"] = value } + + var marginRight: CssValue? + get() = properties["margin-right"] + set(value) { if (value != null) properties["margin-right"] = value } + + var marginBottom: CssValue? + get() = properties["margin-bottom"] + set(value) { if (value != null) properties["margin-bottom"] = value } + + var marginLeft: CssValue? + get() = properties["margin-left"] + set(value) { if (value != null) properties["margin-left"] = value } + + // Padding + var padding: CssValue? + get() = properties["padding"] + set(value) { if (value != null) properties["padding"] = value } + + var paddingTop: CssValue? + get() = properties["padding-top"] + set(value) { if (value != null) properties["padding-top"] = value } + + var paddingRight: CssValue? + get() = properties["padding-right"] + set(value) { if (value != null) properties["padding-right"] = value } + + var paddingBottom: CssValue? + get() = properties["padding-bottom"] + set(value) { if (value != null) properties["padding-bottom"] = value } + + var paddingLeft: CssValue? + get() = properties["padding-left"] + set(value) { if (value != null) properties["padding-left"] = value } + + // 内部で使用:プロパティマップを取得 + internal fun getProperties(): Map = properties.toMap() +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt new file mode 100644 index 0000000..836e83e --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt @@ -0,0 +1,28 @@ +package net.kigawa.renlin.css + +import net.kigawa.renlin.dsl.Dsl +import net.kigawa.renlin.category.ContentCategory + +/** + * DSLにCSS機能を追加する拡張関数 + */ +fun Dsl.css(block: CssDsl.() -> Unit) { + val cssDsl = CssDsl() + cssDsl.block() + val properties = cssDsl.getProperties() + + if (properties.isNotEmpty()) { + // dslStateがnullの場合は、CSS情報を一時保存 + if (this is CssCapable) { + this.pendingCssProperties = properties + } + } +} + +/** + * CSS対応可能なDSLを示すインターフェース + */ +interface CssCapable { + var cssClassName: String? + var pendingCssProperties: Map? +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt new file mode 100644 index 0000000..db25bfd --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt @@ -0,0 +1,40 @@ +package net.kigawa.renlin.css + +/** + * CSSクラス管理のインターフェース + */ +interface CssManager { + fun getOrCreateClass(properties: Map): String + fun updateStyles() +} + +/** + * プラットフォーム固有のCssManagerを作成するファクトリ関数 + */ +expect fun createCssManager(): CssManager + +/** + * CSS管理のユーティリティ関数 + */ +object CssUtils { + fun generateCssString(properties: Map): String { + return properties.entries.joinToString("; ") { (key, value) -> + "$key: ${value.toCssString()}" + } + } + + /** + * CSSプロパティからReact風のクラス名を生成 + */ + fun generateClassName(properties: Map): String { + // プロパティの内容からハッシュ値を生成 + val content = properties.entries + .sortedBy { it.key } // 順序を固定化 + .joinToString("|") { "${it.key}:${it.value.toCssString()}" } + + val hash = content.hashCode() + + // 負の値も正の値として扱い、36進数で短縮 + return "renlin-" + hash.toUInt().toString(36) + } +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt new file mode 100644 index 0000000..b39f5d1 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt @@ -0,0 +1,18 @@ +package net.kigawa.renlin.css + +/** + * 単位付きの値 + */ +data class CssUnit(val value: Number, val unit: String) : CssValue { + override fun toCssString(): String = "$value$unit" +} + +// 便利な拡張プロパティ +val Int.px: CssUnit get() = CssUnit(this, "px") +val Double.px: CssUnit get() = CssUnit(this, "px") +val Int.em: CssUnit get() = CssUnit(this, "em") +val Double.em: CssUnit get() = CssUnit(this, "em") +val Int.rem: CssUnit get() = CssUnit(this, "rem") +val Double.rem: CssUnit get() = CssUnit(this, "rem") +val Int.percent: CssUnit get() = CssUnit(this, "%") +val Double.percent: CssUnit get() = CssUnit(this, "%") \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssValue.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssValue.kt new file mode 100644 index 0000000..6e523ef --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssValue.kt @@ -0,0 +1,15 @@ +package net.kigawa.renlin.css + +/** + * CSS値の基底インターフェース + */ +sealed interface CssValue { + fun toCssString(): String +} + +/** + * 文字列値 + */ +data class CssString(val value: String) : CssValue { + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt index ee74621..3df65f4 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt @@ -2,13 +2,17 @@ package net.kigawa.renlin.dsl import net.kigawa.hakate.api.state.State import net.kigawa.renlin.category.ContentCategory +import net.kigawa.renlin.css.CssCapable +import net.kigawa.renlin.css.CssValue import net.kigawa.renlin.dsl.state.DslState import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid -abstract class DslBase : Dsl { +abstract class DslBase : Dsl, CssCapable { override var dslState: DslState? = null override var key: String? = null + override var cssClassName: String? = null + override var pendingCssProperties: Map? = null private val subDsls = mutableListOf() override val states = mutableSetOf>() @@ -29,6 +33,10 @@ abstract class DslBase : Dsl : Dsl + val cssManager = dslState?.getCssManager() + if (cssManager != null) { + cssClassName = cssManager.getOrCreateClass(properties) + // 処理完了後はクリア + pendingCssProperties = null + } + } + } +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/BasicDslStateBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/BasicDslStateBase.kt index 4009bed..e85f887 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/BasicDslStateBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/BasicDslStateBase.kt @@ -1,6 +1,9 @@ package net.kigawa.renlin.dsl.state import net.kigawa.hakate.api.state.StateContext +import net.kigawa.renlin.css.CssCapable +import net.kigawa.renlin.css.CssManager +import net.kigawa.renlin.css.createCssManager import net.kigawa.renlin.dsl.Dsl import net.kigawa.renlin.dsl.RegisteredDslData import net.kigawa.renlin.element.TagNode @@ -12,6 +15,7 @@ abstract class BasicDslStateBase( ) : DslState { protected var subStates = mutableListOf() abstract override val ownElement: TagNode? + protected var cssManager: CssManager? = null override fun subDslState(key: String, component: Component): DslState { return subStates.firstOrNull { it.key == key } ?: SubBasicDslState( @@ -57,11 +61,22 @@ abstract class BasicDslStateBase( subStates.forEach { it.remove() } } - override fun applyDsl(dsl: Dsl<*>, registeredDslData: RegisteredDslData) { + // CSS適用処理を追加 + if (dsl is CssCapable && dsl.cssClassName != null) { + ownElement?.setClassName(dsl.cssClassName!!) + } throw NotImplementedError("BasicDslState not implemented.") } abstract fun newElement(tag: Tag<*>): TagNode + // CSS機能の追加 + override fun getCssManager(): CssManager? = cssManager + + protected fun initializeCssManager() { + if (cssManager == null) { + cssManager = createCssManager() + } + } } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt index d7f9463..a2c603c 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt @@ -1,5 +1,6 @@ package net.kigawa.renlin.dsl.state +import net.kigawa.renlin.css.CssManager import net.kigawa.renlin.dsl.Dsl import net.kigawa.renlin.dsl.RegisteredDslData import net.kigawa.renlin.element.TagNode @@ -11,4 +12,7 @@ interface DslState { fun subDslState(key: String, second: Component): DslState fun setSubDsls(dsls: List) fun applyDsl(dsl: Dsl<*>, registeredDslData: RegisteredDslData) + + // CSS機能の追加 + fun getCssManager(): CssManager? } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/RootDslStateBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/RootDslStateBase.kt index 06a4701..03bf5d0 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/RootDslStateBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/RootDslStateBase.kt @@ -9,6 +9,11 @@ class RootDslStateBase( override val ownElement: TagNode, stateContext: StateContext, ) : BasicDslStateBase(stateContext) { + init { + // ルートではCssManagerを初期化 + initializeCssManager() + } + override fun newElement(tag: Tag<*>): TagNode { return ownElement.newNode(tag) } diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/SubBasicDslState.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/SubBasicDslState.kt index 76d32cf..53b4e00 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/SubBasicDslState.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/SubBasicDslState.kt @@ -3,6 +3,7 @@ package net.kigawa.renlin.dsl.state import net.kigawa.hakate.api.multi.mergeState import net.kigawa.hakate.api.state.State import net.kigawa.hakate.api.state.StateContext +import net.kigawa.renlin.css.CssCapable import net.kigawa.renlin.dsl.Dsl import net.kigawa.renlin.dsl.RegisteredDslData import net.kigawa.renlin.element.TagNode @@ -25,6 +26,12 @@ class SubBasicDslState( override fun applyDsl(dsl: Dsl<*>, registeredDslData: RegisteredDslData) { latestRegisteredDslData = registeredDslData val index = parent.getIndex(this) + + // CSS適用処理を追加 + if (dsl is CssCapable && dsl.cssClassName != null) { + ownElement?.setClassName(dsl.cssClassName!!) + } + if (ownElement != null) { dsl.applyElement(ownElement) parent.setElements(index, listOf(ownElement)) @@ -63,4 +70,7 @@ class SubBasicDslState( override fun newElement(tag: Tag<*>): TagNode { return ownElement?.newNode(tag) ?: parent.newElement(tag) } + + // CSS機能:親のCssManagerを継承 + override fun getCssManager() = parent.getCssManager() } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt index e6b85d9..d294c3e 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt @@ -9,4 +9,5 @@ expect interface TagNode { fun newNode(tag: Tag<*>): TagNode fun remove() fun setNodes(index: Int, nodes: List) + fun setClassName(className: String) } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Div.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Div.kt index 91ed264..1a3d6e2 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Div.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Div.kt @@ -3,14 +3,19 @@ package net.kigawa.renlin.tag import net.kigawa.renlin.category.FlowContent import net.kigawa.renlin.category.FlowPhrasingDsl import net.kigawa.renlin.category.FlowPhrasingIntersection +import net.kigawa.renlin.css.CssCapable import net.kigawa.renlin.dsl.DslBase import net.kigawa.renlin.dsl.Dsl import net.kigawa.renlin.element.TagNode import net.kigawa.renlin.tag.component.TagComponent1 class DivDsl : - DslBase(), Dsl, FlowPhrasingDsl { + DslBase(), Dsl, FlowPhrasingDsl, CssCapable { + override fun applyElement(element: TagNode) { + cssClassName?.let { className -> + element.setClassName(className) + } } } @@ -19,4 +24,4 @@ val div = TagComponent1(Div, ::DivDsl) object Div : Tag { override val name: String get() = "div" -} +} \ No newline at end of file diff --git a/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt b/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt new file mode 100644 index 0000000..d23152d --- /dev/null +++ b/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt @@ -0,0 +1,47 @@ +package net.kigawa.renlin.css + +import kotlinx.browser.document +import org.w3c.dom.HTMLStyleElement + +/** + * JS版のCSSマネージャー実装 + */ +class JsCssManager : CssManager { + private val styleElement: HTMLStyleElement = document.createElement("style") as HTMLStyleElement + private val cssClasses = mutableMapOf() // クラス名 -> CSS文字列 + private val propertyHashToClassName = mutableMapOf() // プロパティハッシュ -> クラス名 + + init { + styleElement.id = "renlin-css-styles" + document.head?.appendChild(styleElement) + } + + override fun getOrCreateClass(properties: Map): String { + if (properties.isEmpty()) return "" + + // React風のクラス名を生成 + val className = CssUtils.generateClassName(properties) + + // 既に同じクラス名が存在する場合はそれを返す + return if (cssClasses.containsKey(className)) { + className + } else { + val cssString = CssUtils.generateCssString(properties) + cssClasses[className] = cssString + updateStyles() + className + } + } + + override fun updateStyles() { + val cssText = cssClasses.entries.joinToString("\n") { (className, css) -> + ".$className { $css }" + } + styleElement.textContent = cssText + } +} + +/** + * JS版のCssManagerファクトリ関数 + */ +actual fun createCssManager(): CssManager = JsCssManager() \ No newline at end of file diff --git a/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/DomTagElement.kt b/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/DomTagElement.kt index bd1cd47..9d1e112 100644 --- a/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/DomTagElement.kt +++ b/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/DomTagElement.kt @@ -3,6 +3,7 @@ package net.kigawa.renlin.element import kotlinx.browser.document import net.kigawa.renlin.tag.Tag import net.kigawa.renlin.tag.TextTag +import org.w3c.dom.Element import org.w3c.dom.Node import org.w3c.dom.Text @@ -42,4 +43,14 @@ class DomTagElement( last = it.node.nextSibling } } + + override fun setClassName(className: String) { + if (node is Element) { + // 既存のクラス名から renlin- プレフィックスのクラス名を削除 + val existingClasses = node.className.split(" ").filter { it.isNotEmpty() } + val nonRenlinClasses = existingClasses.filter { !it.startsWith("renlin-") } + val newClasses = (nonRenlinClasses + className).distinct() + node.className = newClasses.joinToString(" ") + } + } } \ No newline at end of file diff --git a/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/TagNode.kt b/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/TagNode.kt index 87c58b7..7e34a00 100644 --- a/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/TagNode.kt +++ b/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/TagNode.kt @@ -11,4 +11,5 @@ actual interface TagNode { actual fun newNode(tag: Tag<*>): TagNode actual fun remove() actual fun setNodes(index: Int, nodes: List) + actual fun setClassName(className: String) } \ No newline at end of file diff --git a/renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt b/renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt new file mode 100644 index 0000000..f2fbd9d --- /dev/null +++ b/renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt @@ -0,0 +1,80 @@ +package net.kigawa.renlin.css + +/** + * JVM版のCSSマネージャー実装 + */ +class JvmCssManager : CssManager { + private val cssClasses = mutableMapOf() // クラス名 -> CSS文字列 + + override fun getOrCreateClass(properties: Map): String { + if (properties.isEmpty()) return "" + + // React風のクラス名を生成 + val className = CssUtils.generateClassName(properties) + + // 既に同じクラス名が存在する場合はそれを返す + return if (cssClasses.containsKey(className)) { + className + } else { + val cssString = CssUtils.generateCssString(properties) + cssClasses[className] = cssString + updateStyles() + className + } + } + + override fun updateStyles() { + // JVM版では何もしない(HTMLファイル生成時にまとめて出力) + } + + /** + * スタイルタグを生成 + */ + fun generateStyleTag(): String { + return if (cssClasses.isEmpty()) { + "" + } else { + "" + } + } + + /** + * HTMLにスタイルを埋め込んだ完全なHTMLを生成 + */ + fun generateHtmlWithStyles(htmlContent: String): String { + val styleTag = generateStyleTag() + return if (styleTag.isNotEmpty()) { + """ + + + + + $styleTag + + + $htmlContent + + + """.trimIndent() + } else { + """ + + + + + + + $htmlContent + + + """.trimIndent() + } + } +} + +/** + * JVM版のCssManagerファクトリ関数 + */ +actual fun createCssManager(): CssManager = JvmCssManager() \ No newline at end of file diff --git a/renlin/src/jvmMain/kotlin/net/kigawa/renlin/element/TagNode.kt b/renlin/src/jvmMain/kotlin/net/kigawa/renlin/element/TagNode.kt index 7bca34a..0cf8f15 100644 --- a/renlin/src/jvmMain/kotlin/net/kigawa/renlin/element/TagNode.kt +++ b/renlin/src/jvmMain/kotlin/net/kigawa/renlin/element/TagNode.kt @@ -9,4 +9,5 @@ actual interface TagNode { actual fun newNode(tag: Tag<*>): TagNode actual fun remove() actual fun setNodes(index: Int, nodes: List) + actual fun setClassName(className: String) } \ No newline at end of file From b24bc106c69dea4c89cc44b2bead55b4ea97d493 Mon Sep 17 00:00:00 2001 From: AomiVel Date: Tue, 24 Jun 2025 10:05:17 +0900 Subject: [PATCH 02/10] update: css sample --- .../kotlin/net/kigawa/renlin/sample/Sub.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt index dcc542e..170294f 100644 --- a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt +++ b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt @@ -6,6 +6,10 @@ import net.kigawa.renlin.category.FlowContent import net.kigawa.renlin.category.FlowPhrasingIntersection import net.kigawa.renlin.category.PhrasingContent import net.kigawa.renlin.category.t +import net.kigawa.renlin.css.Color +import net.kigawa.renlin.css.css +import net.kigawa.renlin.css.percent +import net.kigawa.renlin.css.px import net.kigawa.renlin.tag.div import net.kigawa.renlin.tag.fragment import net.kigawa.renlin.tag.p @@ -15,7 +19,8 @@ interface MarginValue class Sub { - val state: MutableState = HakateInitializer().newStateDispatcher().newState("state") + val state: MutableState = HakateInitializer().newStateDispatcher().newState("state 0") + val numState: MutableState = HakateInitializer().newStateDispatcher().newState(0) val display = div.component { t("display") @@ -33,6 +38,10 @@ class Sub { text { margin = "asd" } + css { + color = if (value.last().digitToInt() % 2 == 0) Color.RED else Color.BLUE + backgroundColor = if (value.last().digitToInt() % 2 == 0) Color.BLUE else Color.RED + } } } } @@ -47,6 +56,13 @@ class Sub { div { t("display3") key = "uuid aawaaaaaaaa" + css{ + color = Color.YELLOW + backgroundColor = Color.BLUE + fontSize = 24.px + height = 100.px + padding = 1.percent + } } } val controller = div.component { From c042bc9d37d84b7b749c7548431e273d7a5a837c Mon Sep 17 00:00:00 2001 From: AomiVel Date: Tue, 24 Jun 2025 10:06:56 +0900 Subject: [PATCH 03/10] fix: import and unused property --- .../src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt index 170294f..20d20e0 100644 --- a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt +++ b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt @@ -6,10 +6,7 @@ import net.kigawa.renlin.category.FlowContent import net.kigawa.renlin.category.FlowPhrasingIntersection import net.kigawa.renlin.category.PhrasingContent import net.kigawa.renlin.category.t -import net.kigawa.renlin.css.Color -import net.kigawa.renlin.css.css -import net.kigawa.renlin.css.percent -import net.kigawa.renlin.css.px +import net.kigawa.renlin.css.* import net.kigawa.renlin.tag.div import net.kigawa.renlin.tag.fragment import net.kigawa.renlin.tag.p @@ -20,7 +17,6 @@ interface MarginValue class Sub { val state: MutableState = HakateInitializer().newStateDispatcher().newState("state 0") - val numState: MutableState = HakateInitializer().newStateDispatcher().newState(0) val display = div.component { t("display") From 31d8936bb5de109746f3e93400a94c2015b37c3a Mon Sep 17 00:00:00 2001 From: AomiVel Date: Tue, 24 Jun 2025 10:14:12 +0900 Subject: [PATCH 04/10] fix: suppress "unused" warning on required constants --- .../commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt | 1 + .../src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt | 1 + .../commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt index a328a9b..fe90c86 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt @@ -74,6 +74,7 @@ data class RgbaColor(val r: Int, val g: Int, val b: Int, val a: Double) : CssCol /** * 色のファクトリオブジェクト */ +@Suppress("unused") object Color { // よく使う色の定数 val RED = NamedColor.RED diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt index 9f39816..f945f2e 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt @@ -6,6 +6,7 @@ import net.kigawa.renlin.Html * CSS記述用のDSL(基本プロパティのみ) */ @Html +@Suppress("unused") class CssDsl { private val properties = mutableMapOf() diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt index b39f5d1..ccf8bad 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt @@ -8,11 +8,19 @@ data class CssUnit(val value: Number, val unit: String) : CssValue { } // 便利な拡張プロパティ +@Suppress("unused") val Int.px: CssUnit get() = CssUnit(this, "px") +@Suppress("unused") val Double.px: CssUnit get() = CssUnit(this, "px") +@Suppress("unused") val Int.em: CssUnit get() = CssUnit(this, "em") +@Suppress("unused") val Double.em: CssUnit get() = CssUnit(this, "em") +@Suppress("unused") val Int.rem: CssUnit get() = CssUnit(this, "rem") +@Suppress("unused") val Double.rem: CssUnit get() = CssUnit(this, "rem") +@Suppress("unused") val Int.percent: CssUnit get() = CssUnit(this, "%") +@Suppress("unused") val Double.percent: CssUnit get() = CssUnit(this, "%") \ No newline at end of file From f25585e750e30333e58dad0fabac7c87c568a9da Mon Sep 17 00:00:00 2001 From: AomiVel Date: Tue, 24 Jun 2025 10:14:31 +0900 Subject: [PATCH 05/10] fix: remove unused interface --- sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt index 20d20e0..a9ab717 100644 --- a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt +++ b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt @@ -12,8 +12,6 @@ import net.kigawa.renlin.tag.fragment import net.kigawa.renlin.tag.p import net.kigawa.renlin.tag.text -interface MarginValue - class Sub { val state: MutableState = HakateInitializer().newStateDispatcher().newState("state 0") From c8e60bd162c1d966ff493bf88902d1ca4c710cbc Mon Sep 17 00:00:00 2001 From: AomiVel Date: Tue, 24 Jun 2025 10:15:42 +0900 Subject: [PATCH 06/10] fix: suppress "unused" warning on required constants --- renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt index fe90c86..65c6f3e 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt @@ -101,5 +101,6 @@ object Color { } // 文字列からの便利な変換 +@Suppress("unused") val String.color: HexColor get() = HexColor(if (startsWith("#")) this else "#$this") \ No newline at end of file From a55d216948ba5877a24b032467a2553e5cb7d395 Mon Sep 17 00:00:00 2001 From: AomiVel Date: Thu, 26 Jun 2025 12:59:35 +0900 Subject: [PATCH 07/10] update: add some css properties and pseudo classes --- .../net/kigawa/renlin/css/CssBackground.kt | 63 ++ .../kotlin/net/kigawa/renlin/css/CssBorder.kt | 31 + .../kotlin/net/kigawa/renlin/css/CssColor.kt | 1 + .../kotlin/net/kigawa/renlin/css/CssDsl.kt | 607 +++++++++++++++++- .../net/kigawa/renlin/css/CssExtensions.kt | 9 +- .../net/kigawa/renlin/css/CssFlexbox.kt | 85 +++ .../kotlin/net/kigawa/renlin/css/CssFont.kt | 46 ++ .../kotlin/net/kigawa/renlin/css/CssGrid.kt | 68 ++ .../kotlin/net/kigawa/renlin/css/CssLayout.kt | 85 +++ .../net/kigawa/renlin/css/CssManager.kt | 70 +- .../kotlin/net/kigawa/renlin/css/CssMisc.kt | 84 +++ .../net/kigawa/renlin/css/CssPseudoDsl.kt | 211 ++++++ .../kotlin/net/kigawa/renlin/css/CssText.kt | 80 +++ .../kotlin/net/kigawa/renlin/css/CssUnit.kt | 20 +- .../kotlin/net/kigawa/renlin/dsl/DslBase.kt | 17 +- .../net/kigawa/renlin/css/JsCssManager.kt | 27 +- .../net/kigawa/renlin/css/JvmCssManager.kt | 22 +- 17 files changed, 1475 insertions(+), 51 deletions(-) create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssBackground.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssBorder.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssFlexbox.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssFont.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssGrid.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssLayout.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssMisc.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssPseudoDsl.kt create mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssText.kt diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssBackground.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssBackground.kt new file mode 100644 index 0000000..5b0f026 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssBackground.kt @@ -0,0 +1,63 @@ +package net.kigawa.renlin.css + +/** + * Background Repeat プロパティ + */ +@Suppress("unused") +enum class BackgroundRepeat(private val value: String) : CssValue { + REPEAT("repeat"), + REPEAT_X("repeat-x"), + REPEAT_Y("repeat-y"), + NO_REPEAT("no-repeat"), + SPACE("space"), + ROUND("round"); + + override fun toCssString(): String = value +} + +/** + * Background Position プロパティ + */ +@Suppress("unused") +enum class BackgroundPosition(private val value: String) : CssValue { + LEFT("left"), + CENTER("center"), + RIGHT("right"), + TOP("top"), + BOTTOM("bottom"), + LEFT_TOP("left top"), + LEFT_CENTER("left center"), + LEFT_BOTTOM("left bottom"), + CENTER_TOP("center top"), + CENTER_CENTER("center center"), + CENTER_BOTTOM("center bottom"), + RIGHT_TOP("right top"), + RIGHT_CENTER("right center"), + RIGHT_BOTTOM("right bottom"); + + override fun toCssString(): String = value +} + +/** + * Background Size プロパティ + */ +@Suppress("unused") +enum class BackgroundSize(private val value: String) : CssValue { + AUTO("auto"), + COVER("cover"), + CONTAIN("contain"); + + override fun toCssString(): String = value +} + +/** + * Background Attachment プロパティ + */ +@Suppress("unused") +enum class BackgroundAttachment(private val value: String) : CssValue { + SCROLL("scroll"), + FIXED("fixed"), + LOCAL("local"); + + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssBorder.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssBorder.kt new file mode 100644 index 0000000..69b4c82 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssBorder.kt @@ -0,0 +1,31 @@ +package net.kigawa.renlin.css + +/** + * Border Style プロパティ + */ +@Suppress("unused") +enum class BorderStyle(private val value: String) : CssValue { + NONE("none"), + HIDDEN("hidden"), + DOTTED("dotted"), + DASHED("dashed"), + SOLID("solid"), + DOUBLE("double"), + GROOVE("groove"), + RIDGE("ridge"), + INSET("inset"), + OUTSET("outset"); + + override fun toCssString(): String = value +} + +/** + * Box Sizing プロパティ + */ +@Suppress("unused") +enum class BoxSizing(private val value: String) : CssValue { + CONTENT_BOX("content-box"), + BORDER_BOX("border-box"); + + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt index 65c6f3e..a7103fb 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt @@ -8,6 +8,7 @@ sealed interface CssColor : CssValue /** * 名前付きの色 */ +@Suppress("unused") enum class NamedColor(private val colorName: String) : CssColor { RED("red"), GREEN("green"), diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt index f945f2e..447d39c 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt @@ -3,14 +3,116 @@ package net.kigawa.renlin.css import net.kigawa.renlin.Html /** - * CSS記述用のDSL(基本プロパティのみ) + * CSS記述用のDSL(全プロパティ対応 + 疑似クラス対応) */ @Html @Suppress("unused") class CssDsl { private val properties = mutableMapOf() + private val pseudoClasses = mutableListOf() - // Color + // === 疑似クラスメソッド === + + /** + * :hover 疑似クラス + */ + fun hover(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("hover", pseudoDsl.getProperties())) + } + + /** + * :focus 疑似クラス + */ + fun focus(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("focus", pseudoDsl.getProperties())) + } + + /** + * :active 疑似クラス + */ + fun active(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("active", pseudoDsl.getProperties())) + } + + /** + * :disabled 疑似クラス + */ + fun disabled(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("disabled", pseudoDsl.getProperties())) + } + + /** + * :first-child 疑似クラス + */ + fun firstChild(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("first-child", pseudoDsl.getProperties())) + } + + /** + * :last-child 疑似クラス + */ + fun lastChild(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("last-child", pseudoDsl.getProperties())) + } + + /** + * :nth-child(n) 疑似クラス + */ + fun nthChild(n: Int, block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("nth-child($n)", pseudoDsl.getProperties())) + } + + /** + * :nth-child(odd) 疑似クラス + */ + fun nthChildOdd(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("nth-child(odd)", pseudoDsl.getProperties())) + } + + /** + * :nth-child(even) 疑似クラス + */ + fun nthChildEven(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("nth-child(even)", pseudoDsl.getProperties())) + } + + /** + * :visited 疑似クラス(リンク用) + */ + fun visited(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("visited", pseudoDsl.getProperties())) + } + + /** + * :checked 疑似クラス(フォーム要素用) + */ + fun checked(block: CssPseudoDsl.() -> Unit) { + val pseudoDsl = CssPseudoDsl() + pseudoDsl.block() + pseudoClasses.add(PseudoClassRule("checked", pseudoDsl.getProperties())) + } + + // === Color Properties === var color: CssColor? get() = properties["color"] as? CssColor set(value) { if (value != null) properties["color"] = value } @@ -19,7 +121,7 @@ class CssDsl { get() = properties["background-color"] as? CssColor set(value) { if (value != null) properties["background-color"] = value } - // Size + // === Size Properties === var width: CssValue? get() = properties["width"] set(value) { if (value != null) properties["width"] = value } @@ -28,12 +130,81 @@ class CssDsl { get() = properties["height"] set(value) { if (value != null) properties["height"] = value } - // Font + var minWidth: CssValue? + get() = properties["min-width"] + set(value) { if (value != null) properties["min-width"] = value } + + var minHeight: CssValue? + get() = properties["min-height"] + set(value) { if (value != null) properties["min-height"] = value } + + var maxWidth: CssValue? + get() = properties["max-width"] + set(value) { if (value != null) properties["max-width"] = value } + + var maxHeight: CssValue? + get() = properties["max-height"] + set(value) { if (value != null) properties["max-height"] = value } + + // === Font Properties === var fontSize: CssValue? get() = properties["font-size"] set(value) { if (value != null) properties["font-size"] = value } - // Margin + var fontWeight: FontWeight? + get() = properties["font-weight"] as? FontWeight + set(value) { if (value != null) properties["font-weight"] = value } + + var fontStyle: FontStyle? + get() = properties["font-style"] as? FontStyle + set(value) { if (value != null) properties["font-style"] = value } + + var fontVariant: FontVariant? + get() = properties["font-variant"] as? FontVariant + set(value) { if (value != null) properties["font-variant"] = value } + + var fontFamily: CssValue? + get() = properties["font-family"] + set(value) { if (value != null) properties["font-family"] = value } + + var lineHeight: CssValue? + get() = properties["line-height"] + set(value) { if (value != null) properties["line-height"] = value } + + // === Text Properties === + var textAlign: TextAlign? + get() = properties["text-align"] as? TextAlign + set(value) { if (value != null) properties["text-align"] = value } + + var textDecoration: TextDecoration? + get() = properties["text-decoration"] as? TextDecoration + set(value) { if (value != null) properties["text-decoration"] = value } + + var textTransform: TextTransform? + get() = properties["text-transform"] as? TextTransform + set(value) { if (value != null) properties["text-transform"] = value } + + var letterSpacing: CssValue? + get() = properties["letter-spacing"] + set(value) { if (value != null) properties["letter-spacing"] = value } + + var wordSpacing: CssValue? + get() = properties["word-spacing"] + set(value) { if (value != null) properties["word-spacing"] = value } + + var whiteSpace: WhiteSpace? + get() = properties["white-space"] as? WhiteSpace + set(value) { if (value != null) properties["white-space"] = value } + + var wordBreak: WordBreak? + get() = properties["word-break"] as? WordBreak + set(value) { if (value != null) properties["word-break"] = value } + + var textOverflow: TextOverflow? + get() = properties["text-overflow"] as? TextOverflow + set(value) { if (value != null) properties["text-overflow"] = value } + + // === Margin Properties === var margin: CssValue? get() = properties["margin"] set(value) { if (value != null) properties["margin"] = value } @@ -54,7 +225,7 @@ class CssDsl { get() = properties["margin-left"] set(value) { if (value != null) properties["margin-left"] = value } - // Padding + // === Padding Properties === var padding: CssValue? get() = properties["padding"] set(value) { if (value != null) properties["padding"] = value } @@ -75,6 +246,430 @@ class CssDsl { get() = properties["padding-left"] set(value) { if (value != null) properties["padding-left"] = value } + // === Border Properties === + var border: CssValue? + get() = properties["border"] + set(value) { if (value != null) properties["border"] = value } + + var borderWidth: CssValue? + get() = properties["border-width"] + set(value) { if (value != null) properties["border-width"] = value } + + var borderStyle: BorderStyle? + get() = properties["border-style"] as? BorderStyle + set(value) { if (value != null) properties["border-style"] = value } + + var borderColor: CssColor? + get() = properties["border-color"] as? CssColor + set(value) { if (value != null) properties["border-color"] = value } + + var borderRadius: CssValue? + get() = properties["border-radius"] + set(value) { if (value != null) properties["border-radius"] = value } + + // Border individual sides + var borderTop: CssValue? + get() = properties["border-top"] + set(value) { if (value != null) properties["border-top"] = value } + + var borderRight: CssValue? + get() = properties["border-right"] + set(value) { if (value != null) properties["border-right"] = value } + + var borderBottom: CssValue? + get() = properties["border-bottom"] + set(value) { if (value != null) properties["border-bottom"] = value } + + var borderLeft: CssValue? + get() = properties["border-left"] + set(value) { if (value != null) properties["border-left"] = value } + + // === Layout Properties === + var display: Display? + get() = properties["display"] as? Display + set(value) { if (value != null) properties["display"] = value } + + var position: Position? + get() = properties["position"] as? Position + set(value) { if (value != null) properties["position"] = value } + + var top: CssValue? + get() = properties["top"] + set(value) { if (value != null) properties["top"] = value } + + var right: CssValue? + get() = properties["right"] + set(value) { if (value != null) properties["right"] = value } + + var bottom: CssValue? + get() = properties["bottom"] + set(value) { if (value != null) properties["bottom"] = value } + + var left: CssValue? + get() = properties["left"] + set(value) { if (value != null) properties["left"] = value } + + var zIndex: CssValue? + get() = properties["z-index"] + set(value) { if (value != null) properties["z-index"] = value } + + var overflow: Overflow? + get() = properties["overflow"] as? Overflow + set(value) { if (value != null) properties["overflow"] = value } + + var overflowX: Overflow? + get() = properties["overflow-x"] as? Overflow + set(value) { if (value != null) properties["overflow-x"] = value } + + var overflowY: Overflow? + get() = properties["overflow-y"] as? Overflow + set(value) { if (value != null) properties["overflow-y"] = value } + + var visibility: Visibility? + get() = properties["visibility"] as? Visibility + set(value) { if (value != null) properties["visibility"] = value } + + var float: Float? + get() = properties["float"] as? Float + set(value) { if (value != null) properties["float"] = value } + + var clear: Clear? + get() = properties["clear"] as? Clear + set(value) { if (value != null) properties["clear"] = value } + + var boxSizing: BoxSizing? + get() = properties["box-sizing"] as? BoxSizing + set(value) { if (value != null) properties["box-sizing"] = value } + + // === Flexbox Properties === + var flex: CssValue? + get() = properties["flex"] + set(value) { if (value != null) properties["flex"] = value } + + var flexDirection: FlexDirection? + get() = properties["flex-direction"] as? FlexDirection + set(value) { if (value != null) properties["flex-direction"] = value } + + var flexWrap: FlexWrap? + get() = properties["flex-wrap"] as? FlexWrap + set(value) { if (value != null) properties["flex-wrap"] = value } + + var justifyContent: JustifyContent? + get() = properties["justify-content"] as? JustifyContent + set(value) { if (value != null) properties["justify-content"] = value } + + var alignItems: AlignItems? + get() = properties["align-items"] as? AlignItems + set(value) { if (value != null) properties["align-items"] = value } + + var alignContent: AlignContent? + get() = properties["align-content"] as? AlignContent + set(value) { if (value != null) properties["align-content"] = value } + + var alignSelf: AlignSelf? + get() = properties["align-self"] as? AlignSelf + set(value) { if (value != null) properties["align-self"] = value } + + var flexGrow: CssValue? + get() = properties["flex-grow"] + set(value) { if (value != null) properties["flex-grow"] = value } + + var flexShrink: CssValue? + get() = properties["flex-shrink"] + set(value) { if (value != null) properties["flex-shrink"] = value } + + var flexBasis: CssValue? + get() = properties["flex-basis"] + set(value) { if (value != null) properties["flex-basis"] = value } + + var order: CssValue? + get() = properties["order"] + set(value) { if (value != null) properties["order"] = value } + + // === Grid Properties === + var grid: CssValue? + get() = properties["grid"] + set(value) { if (value != null) properties["grid"] = value } + + var gridTemplate: CssValue? + get() = properties["grid-template"] + set(value) { if (value != null) properties["grid-template"] = value } + + var gridTemplateColumns: CssValue? + get() = properties["grid-template-columns"] + set(value) { if (value != null) properties["grid-template-columns"] = value } + + var gridTemplateRows: CssValue? + get() = properties["grid-template-rows"] + set(value) { if (value != null) properties["grid-template-rows"] = value } + + var gridTemplateAreas: CssValue? + get() = properties["grid-template-areas"] + set(value) { if (value != null) properties["grid-template-areas"] = value } + + var gridColumn: CssValue? + get() = properties["grid-column"] + set(value) { if (value != null) properties["grid-column"] = value } + + var gridRow: CssValue? + get() = properties["grid-row"] + set(value) { if (value != null) properties["grid-row"] = value } + + var gridColumnStart: CssValue? + get() = properties["grid-column-start"] + set(value) { if (value != null) properties["grid-column-start"] = value } + + var gridColumnEnd: CssValue? + get() = properties["grid-column-end"] + set(value) { if (value != null) properties["grid-column-end"] = value } + + var gridRowStart: CssValue? + get() = properties["grid-row-start"] + set(value) { if (value != null) properties["grid-row-start"] = value } + + var gridRowEnd: CssValue? + get() = properties["grid-row-end"] + set(value) { if (value != null) properties["grid-row-end"] = value } + + var gridArea: CssValue? + get() = properties["grid-area"] + set(value) { if (value != null) properties["grid-area"] = value } + + var gridAutoFlow: GridAutoFlow? + get() = properties["grid-auto-flow"] as? GridAutoFlow + set(value) { if (value != null) properties["grid-auto-flow"] = value } + + var gridAutoColumns: CssValue? + get() = properties["grid-auto-columns"] + set(value) { if (value != null) properties["grid-auto-columns"] = value } + + var gridAutoRows: CssValue? + get() = properties["grid-auto-rows"] + set(value) { if (value != null) properties["grid-auto-rows"] = value } + + var gap: CssValue? + get() = properties["gap"] + set(value) { if (value != null) properties["gap"] = value } + + var columnGap: CssValue? + get() = properties["column-gap"] + set(value) { if (value != null) properties["column-gap"] = value } + + var rowGap: CssValue? + get() = properties["row-gap"] + set(value) { if (value != null) properties["row-gap"] = value } + + var justifyItems: JustifyItems? + get() = properties["justify-items"] as? JustifyItems + set(value) { if (value != null) properties["justify-items"] = value } + + var justifySelf: JustifySelf? + get() = properties["justify-self"] as? JustifySelf + set(value) { if (value != null) properties["justify-self"] = value } + + var placeItems: PlaceItems? + get() = properties["place-items"] as? PlaceItems + set(value) { if (value != null) properties["place-items"] = value } + + var placeSelf: PlaceSelf? + get() = properties["place-self"] as? PlaceSelf + set(value) { if (value != null) properties["place-self"] = value } + + // === Background Properties === + var background: CssValue? + get() = properties["background"] + set(value) { if (value != null) properties["background"] = value } + + var backgroundImage: CssValue? + get() = properties["background-image"] + set(value) { if (value != null) properties["background-image"] = value } + + var backgroundRepeat: BackgroundRepeat? + get() = properties["background-repeat"] as? BackgroundRepeat + set(value) { if (value != null) properties["background-repeat"] = value } + + var backgroundPosition: BackgroundPosition? + get() = properties["background-position"] as? BackgroundPosition + set(value) { if (value != null) properties["background-position"] = value } + + var backgroundSize: BackgroundSize? + get() = properties["background-size"] as? BackgroundSize + set(value) { if (value != null) properties["background-size"] = value } + + var backgroundAttachment: BackgroundAttachment? + get() = properties["background-attachment"] as? BackgroundAttachment + set(value) { if (value != null) properties["background-attachment"] = value } + + // === Transform & Animation Properties === + var transform: CssValue? + get() = properties["transform"] + set(value) { if (value != null) properties["transform"] = value } + + var transformOrigin: CssValue? + get() = properties["transform-origin"] + set(value) { if (value != null) properties["transform-origin"] = value } + + var transition: CssValue? + get() = properties["transition"] + set(value) { if (value != null) properties["transition"] = value } + + var transitionProperty: CssValue? + get() = properties["transition-property"] + set(value) { if (value != null) properties["transition-property"] = value } + + var transitionDuration: CssValue? + get() = properties["transition-duration"] + set(value) { if (value != null) properties["transition-duration"] = value } + + var transitionTimingFunction: CssValue? + get() = properties["transition-timing-function"] + set(value) { if (value != null) properties["transition-timing-function"] = value } + + var transitionDelay: CssValue? + get() = properties["transition-delay"] + set(value) { if (value != null) properties["transition-delay"] = value } + + var animation: CssValue? + get() = properties["animation"] + set(value) { if (value != null) properties["animation"] = value } + + var animationName: CssValue? + get() = properties["animation-name"] + set(value) { if (value != null) properties["animation-name"] = value } + + var animationDuration: CssValue? + get() = properties["animation-duration"] + set(value) { if (value != null) properties["animation-duration"] = value } + + var animationTimingFunction: CssValue? + get() = properties["animation-timing-function"] + set(value) { if (value != null) properties["animation-timing-function"] = value } + + var animationDelay: CssValue? + get() = properties["animation-delay"] + set(value) { if (value != null) properties["animation-delay"] = value } + + var animationIterationCount: CssValue? + get() = properties["animation-iteration-count"] + set(value) { if (value != null) properties["animation-iteration-count"] = value } + + var animationDirection: CssValue? + get() = properties["animation-direction"] + set(value) { if (value != null) properties["animation-direction"] = value } + + var animationFillMode: CssValue? + get() = properties["animation-fill-mode"] + set(value) { if (value != null) properties["animation-fill-mode"] = value } + + var animationPlayState: CssValue? + get() = properties["animation-play-state"] + set(value) { if (value != null) properties["animation-play-state"] = value } + + // === Miscellaneous Properties === + var opacity: CssValue? + get() = properties["opacity"] + set(value) { if (value != null) properties["opacity"] = value } + + var cursor: Cursor? + get() = properties["cursor"] as? Cursor + set(value) { if (value != null) properties["cursor"] = value } + + var userSelect: UserSelect? + get() = properties["user-select"] as? UserSelect + set(value) { if (value != null) properties["user-select"] = value } + + var pointerEvents: PointerEvents? + get() = properties["pointer-events"] as? PointerEvents + set(value) { if (value != null) properties["pointer-events"] = value } + + var boxShadow: CssValue? + get() = properties["box-shadow"] + set(value) { if (value != null) properties["box-shadow"] = value } + + var textShadow: CssValue? + get() = properties["text-shadow"] + set(value) { if (value != null) properties["text-shadow"] = value } + + var outline: CssValue? + get() = properties["outline"] + set(value) { if (value != null) properties["outline"] = value } + + var outlineWidth: CssValue? + get() = properties["outline-width"] + set(value) { if (value != null) properties["outline-width"] = value } + + var outlineStyle: CssValue? + get() = properties["outline-style"] + set(value) { if (value != null) properties["outline-style"] = value } + + var outlineColor: CssColor? + get() = properties["outline-color"] as? CssColor + set(value) { if (value != null) properties["outline-color"] = value } + + var outlineOffset: CssValue? + get() = properties["outline-offset"] + set(value) { if (value != null) properties["outline-offset"] = value } + + var objectFit: ObjectFit? + get() = properties["object-fit"] as? ObjectFit + set(value) { if (value != null) properties["object-fit"] = value } + + var objectPosition: CssValue? + get() = properties["object-position"] + set(value) { if (value != null) properties["object-position"] = value } + + var verticalAlign: VerticalAlign? + get() = properties["vertical-align"] as? VerticalAlign + set(value) { if (value != null) properties["vertical-align"] = value } + + var resize: CssValue? + get() = properties["resize"] + set(value) { if (value != null) properties["resize"] = value } + + var content: CssValue? + get() = properties["content"] + set(value) { if (value != null) properties["content"] = value } + + var listStyle: CssValue? + get() = properties["list-style"] + set(value) { if (value != null) properties["list-style"] = value } + + var listStyleType: CssValue? + get() = properties["list-style-type"] + set(value) { if (value != null) properties["list-style-type"] = value } + + var listStylePosition: CssValue? + get() = properties["list-style-position"] + set(value) { if (value != null) properties["list-style-position"] = value } + + var listStyleImage: CssValue? + get() = properties["list-style-image"] + set(value) { if (value != null) properties["list-style-image"] = value } + + // === Helper Methods === + fun fontFamily(value: String) { + fontFamily = CssString("\"$value\"") + } + + fun border(width: CssValue, style: BorderStyle, color: CssColor) { + border = CssString("${width.toCssString()} ${style.toCssString()} ${color.toCssString()}") + } + + fun boxShadow(offsetX: CssValue, offsetY: CssValue, blurRadius: CssValue, color: CssColor) { + boxShadow = CssString("${offsetX.toCssString()} ${offsetY.toCssString()} ${blurRadius.toCssString()} ${color.toCssString()}") + } + + fun boxShadow(offsetX: CssValue, offsetY: CssValue, blurRadius: CssValue, spreadRadius: CssValue, color: CssColor) { + boxShadow = CssString("${offsetX.toCssString()} ${offsetY.toCssString()} ${blurRadius.toCssString()} ${spreadRadius.toCssString()} ${color.toCssString()}") + } + + fun textShadow(offsetX: CssValue, offsetY: CssValue, blurRadius: CssValue, color: CssColor) { + textShadow = CssString("${offsetX.toCssString()} ${offsetY.toCssString()} ${blurRadius.toCssString()} ${color.toCssString()}") + } + // 内部で使用:プロパティマップを取得 internal fun getProperties(): Map = properties.toMap() + + // 内部で使用:CSS規則セットを取得 + internal fun getRuleSet(): CssRuleSet = CssRuleSet(properties.toMap(), pseudoClasses.toList()) } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt index 836e83e..bdaf756 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt @@ -4,17 +4,17 @@ import net.kigawa.renlin.dsl.Dsl import net.kigawa.renlin.category.ContentCategory /** - * DSLにCSS機能を追加する拡張関数 + * DSLにCSS機能を追加する拡張関数(疑似クラス対応) */ fun Dsl.css(block: CssDsl.() -> Unit) { val cssDsl = CssDsl() cssDsl.block() - val properties = cssDsl.getProperties() + val ruleSet = cssDsl.getRuleSet() - if (properties.isNotEmpty()) { + if (!ruleSet.isEmpty()) { // dslStateがnullの場合は、CSS情報を一時保存 if (this is CssCapable) { - this.pendingCssProperties = properties + this.pendingCssRuleSet = ruleSet } } } @@ -25,4 +25,5 @@ fun Dsl.css(block: CssDsl interface CssCapable { var cssClassName: String? var pendingCssProperties: Map? + var pendingCssRuleSet: CssRuleSet? } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssFlexbox.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssFlexbox.kt new file mode 100644 index 0000000..654cc8b --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssFlexbox.kt @@ -0,0 +1,85 @@ +package net.kigawa.renlin.css + +/** + * Flex Direction プロパティ + */ +@Suppress("unused") +enum class FlexDirection(private val value: String) : CssValue { + ROW("row"), + ROW_REVERSE("row-reverse"), + COLUMN("column"), + COLUMN_REVERSE("column-reverse"); + + override fun toCssString(): String = value +} + +/** + * Flex Wrap プロパティ + */ +@Suppress("unused") +enum class FlexWrap(private val value: String) : CssValue { + NOWRAP("nowrap"), + WRAP("wrap"), + WRAP_REVERSE("wrap-reverse"); + + override fun toCssString(): String = value +} + +/** + * Justify Content プロパティ + */ +@Suppress("unused") +enum class JustifyContent(private val value: String) : CssValue { + FLEX_START("flex-start"), + FLEX_END("flex-end"), + CENTER("center"), + SPACE_BETWEEN("space-between"), + SPACE_AROUND("space-around"), + SPACE_EVENLY("space-evenly"); + + override fun toCssString(): String = value +} + +/** + * Align Items プロパティ + */ +@Suppress("unused") +enum class AlignItems(private val value: String) : CssValue { + STRETCH("stretch"), + FLEX_START("flex-start"), + FLEX_END("flex-end"), + CENTER("center"), + BASELINE("baseline"); + + override fun toCssString(): String = value +} + +/** + * Align Content プロパティ + */ +@Suppress("unused") +enum class AlignContent(private val value: String) : CssValue { + STRETCH("stretch"), + FLEX_START("flex-start"), + FLEX_END("flex-end"), + CENTER("center"), + SPACE_BETWEEN("space-between"), + SPACE_AROUND("space-around"); + + override fun toCssString(): String = value +} + +/** + * Align Self プロパティ + */ +@Suppress("unused") +enum class AlignSelf(private val value: String) : CssValue { + AUTO("auto"), + STRETCH("stretch"), + FLEX_START("flex-start"), + FLEX_END("flex-end"), + CENTER("center"), + BASELINE("baseline"); + + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssFont.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssFont.kt new file mode 100644 index 0000000..22bd8e7 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssFont.kt @@ -0,0 +1,46 @@ +package net.kigawa.renlin.css + +/** + * フォントウェイト + */ +@Suppress("unused") +enum class FontWeight(private val value: String) : CssValue { + NORMAL("normal"), + BOLD("bold"), + BOLDER("bolder"), + LIGHTER("lighter"), + W100("100"), + W200("200"), + W300("300"), + W400("400"), + W500("500"), + W600("600"), + W700("700"), + W800("800"), + W900("900"); + + override fun toCssString(): String = value +} + +/** + * フォントスタイル + */ +@Suppress("unused") +enum class FontStyle(private val value: String) : CssValue { + NORMAL("normal"), + ITALIC("italic"), + OBLIQUE("oblique"); + + override fun toCssString(): String = value +} + +/** + * フォントバリアント + */ +@Suppress("unused") +enum class FontVariant(private val value: String) : CssValue { + NORMAL("normal"), + SMALLCAPS("small-caps"); + + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssGrid.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssGrid.kt new file mode 100644 index 0000000..9445848 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssGrid.kt @@ -0,0 +1,68 @@ +package net.kigawa.renlin.css + +/** + * Grid Auto Flow プロパティ + */ +@Suppress("unused") +enum class GridAutoFlow(private val value: String) : CssValue { + ROW("row"), + COLUMN("column"), + ROW_DENSE("row dense"), + COLUMN_DENSE("column dense"); + + override fun toCssString(): String = value +} + +/** + * Justify Items プロパティ + */ +@Suppress("unused") +enum class JustifyItems(private val value: String) : CssValue { + START("start"), + END("end"), + CENTER("center"), + STRETCH("stretch"); + + override fun toCssString(): String = value +} + +/** + * Justify Self プロパティ + */ +@Suppress("unused") +enum class JustifySelf(private val value: String) : CssValue { + AUTO("auto"), + START("start"), + END("end"), + CENTER("center"), + STRETCH("stretch"); + + override fun toCssString(): String = value +} + +/** + * Place Items プロパティ + */ +@Suppress("unused") +enum class PlaceItems(private val value: String) : CssValue { + START("start"), + END("end"), + CENTER("center"), + STRETCH("stretch"); + + override fun toCssString(): String = value +} + +/** + * Place Self プロパティ + */ +@Suppress("unused") +enum class PlaceSelf(private val value: String) : CssValue { + AUTO("auto"), + START("start"), + END("end"), + CENTER("center"), + STRETCH("stretch"); + + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssLayout.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssLayout.kt new file mode 100644 index 0000000..127974b --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssLayout.kt @@ -0,0 +1,85 @@ +package net.kigawa.renlin.css + +/** + * Display プロパティ + */ +@Suppress("unused") +enum class Display(private val value: String) : CssValue { + BLOCK("block"), + INLINE("inline"), + INLINE_BLOCK("inline-block"), + FLEX("flex"), + INLINE_FLEX("inline-flex"), + GRID("grid"), + INLINE_GRID("inline-grid"), + TABLE("table"), + TABLE_CELL("table-cell"), + TABLE_ROW("table-row"), + NONE("none"); + + override fun toCssString(): String = value +} + +/** + * Position プロパティ + */ +@Suppress("unused") +enum class Position(private val value: String) : CssValue { + STATIC("static"), + RELATIVE("relative"), + ABSOLUTE("absolute"), + FIXED("fixed"), + STICKY("sticky"); + + override fun toCssString(): String = value +} + +/** + * Overflow プロパティ + */ +@Suppress("unused") +enum class Overflow(private val value: String) : CssValue { + VISIBLE("visible"), + HIDDEN("hidden"), + SCROLL("scroll"), + AUTO("auto"); + + override fun toCssString(): String = value +} + +/** + * Float プロパティ + */ +@Suppress("unused") +enum class Float(private val value: String) : CssValue { + NONE("none"), + LEFT("left"), + RIGHT("right"); + + override fun toCssString(): String = value +} + +/** + * Clear プロパティ + */ +@Suppress("unused") +enum class Clear(private val value: String) : CssValue { + NONE("none"), + LEFT("left"), + RIGHT("right"), + BOTH("both"); + + override fun toCssString(): String = value +} + +/** + * Visibility プロパティ + */ +@Suppress("unused") +enum class Visibility(private val value: String) : CssValue { + VISIBLE("visible"), + HIDDEN("hidden"), + COLLAPSE("collapse"); + + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt index db25bfd..71755f7 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt @@ -1,10 +1,28 @@ package net.kigawa.renlin.css +/** + * CSS疑似クラス情報を保持するデータクラス + */ +data class PseudoClassRule( + val pseudoClass: String, + val properties: Map +) + +/** + * CSS規則(ベース + 疑似クラス)を管理するデータクラス + */ +data class CssRuleSet( + val baseProperties: Map, + val pseudoClasses: List +) { + fun isEmpty(): Boolean = baseProperties.isEmpty() && pseudoClasses.isEmpty() +} + /** * CSSクラス管理のインターフェース */ interface CssManager { - fun getOrCreateClass(properties: Map): String + fun getOrCreateClass(ruleSet: CssRuleSet): String fun updateStyles() } @@ -24,17 +42,53 @@ object CssUtils { } /** - * CSSプロパティからReact風のクラス名を生成 + * CSSルールセットからReact風のクラス名を生成 */ - fun generateClassName(properties: Map): String { - // プロパティの内容からハッシュ値を生成 - val content = properties.entries - .sortedBy { it.key } // 順序を固定化 + fun generateClassName(ruleSet: CssRuleSet): String { + // ベースプロパティと疑似クラスを含めた内容でハッシュ値を生成 + val baseContent = ruleSet.baseProperties.entries + .sortedBy { it.key } .joinToString("|") { "${it.key}:${it.value.toCssString()}" } + val pseudoContent = ruleSet.pseudoClasses + .sortedBy { it.pseudoClass } + .joinToString("|") { rule -> + val props = rule.properties.entries + .sortedBy { it.key } + .joinToString(",") { "${it.key}:${it.value.toCssString()}" } + "${rule.pseudoClass}($props)" + } + + val content = listOf(baseContent, pseudoContent) + .filter { it.isNotEmpty() } + .joinToString("||") + val hash = content.hashCode() + val hashString = hash.toUInt().toString(36) + + return "renlin-$hashString" + } + + /** + * CSS規則セットから完全なCSS文字列を生成 + */ + fun generateFullCssString(className: String, ruleSet: CssRuleSet): String { + val cssRules = mutableListOf() + + // ベースルール + if (ruleSet.baseProperties.isNotEmpty()) { + val baseRule = ".$className { ${generateCssString(ruleSet.baseProperties)} }" + cssRules.add(baseRule) + } + + // 疑似クラスルール + ruleSet.pseudoClasses.forEach { pseudoRule -> + if (pseudoRule.properties.isNotEmpty()) { + val pseudoCssRule = ".$className:${pseudoRule.pseudoClass} { ${generateCssString(pseudoRule.properties)} }" + cssRules.add(pseudoCssRule) + } + } - // 負の値も正の値として扱い、36進数で短縮 - return "renlin-" + hash.toUInt().toString(36) + return cssRules.joinToString("\n") } } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssMisc.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssMisc.kt new file mode 100644 index 0000000..817f3ce --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssMisc.kt @@ -0,0 +1,84 @@ +package net.kigawa.renlin.css + +/** + * Cursor プロパティ + */ +@Suppress("unused") +enum class Cursor(private val value: String) : CssValue { + AUTO("auto"), + DEFAULT("default"), + POINTER("pointer"), + TEXT("text"), + WAIT("wait"), + HELP("help"), + CROSSHAIR("crosshair"), + MOVE("move"), + NOT_ALLOWED("not-allowed"), + GRAB("grab"), + GRABBING("grabbing"), + RESIZE_E("e-resize"), + RESIZE_N("n-resize"), + RESIZE_NE("ne-resize"), + RESIZE_NW("nw-resize"), + RESIZE_S("s-resize"), + RESIZE_SE("se-resize"), + RESIZE_SW("sw-resize"), + RESIZE_W("w-resize"); + + override fun toCssString(): String = value +} + +/** + * User Select プロパティ + */ +@Suppress("unused") +enum class UserSelect(private val value: String) : CssValue { + AUTO("auto"), + NONE("none"), + TEXT("text"), + ALL("all"); + + override fun toCssString(): String = value +} + +/** + * Pointer Events プロパティ + */ +@Suppress("unused") +enum class PointerEvents(private val value: String) : CssValue { + AUTO("auto"), + NONE("none"); + + override fun toCssString(): String = value +} + +/** + * Object Fit プロパティ + */ +@Suppress("unused") +enum class ObjectFit(private val value: String) : CssValue { + FILL("fill"), + CONTAIN("contain"), + COVER("cover"), + NONE("none"), + SCALE_DOWN("scale-down"); + + override fun toCssString(): String = value +} + +/** + * Vertical Align プロパティ + */ +@Suppress("unused") +enum class VerticalAlign(private val value: String) : CssValue { + BASELINE("baseline"), + TOP("top"), + MIDDLE("middle"), + BOTTOM("bottom"), + TEXT_TOP("text-top"), + TEXT_BOTTOM("text-bottom"), + SUB("sub"), + SUPER("super"); + + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssPseudoDsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssPseudoDsl.kt new file mode 100644 index 0000000..a60f5d2 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssPseudoDsl.kt @@ -0,0 +1,211 @@ +package net.kigawa.renlin.css + +import net.kigawa.renlin.Html + +/** + * 疑似クラス用のDSL + */ +@Html +@Suppress("unused") +class CssPseudoDsl { + private val properties = mutableMapOf() + + // === Color Properties === + var color: CssColor? + get() = properties["color"] as? CssColor + set(value) { if (value != null) properties["color"] = value } + + var backgroundColor: CssColor? + get() = properties["background-color"] as? CssColor + set(value) { if (value != null) properties["background-color"] = value } + + // === Size Properties === + var width: CssValue? + get() = properties["width"] + set(value) { if (value != null) properties["width"] = value } + + var height: CssValue? + get() = properties["height"] + set(value) { if (value != null) properties["height"] = value } + + // === Font Properties === + var fontSize: CssValue? + get() = properties["font-size"] + set(value) { if (value != null) properties["font-size"] = value } + + var fontWeight: FontWeight? + get() = properties["font-weight"] as? FontWeight + set(value) { if (value != null) properties["font-weight"] = value } + + var fontStyle: FontStyle? + get() = properties["font-style"] as? FontStyle + set(value) { if (value != null) properties["font-style"] = value } + + // === Transform & Animation === + var transform: CssValue? + get() = properties["transform"] + set(value) { if (value != null) properties["transform"] = value } + + var transition: CssValue? + get() = properties["transition"] + set(value) { if (value != null) properties["transition"] = value } + + // === Border Properties === + var border: CssValue? + get() = properties["border"] + set(value) { if (value != null) properties["border"] = value } + + var borderColor: CssColor? + get() = properties["border-color"] as? CssColor + set(value) { if (value != null) properties["border-color"] = value } + + var borderWidth: CssValue? + get() = properties["border-width"] + set(value) { if (value != null) properties["border-width"] = value } + + var borderRadius: CssValue? + get() = properties["border-radius"] + set(value) { if (value != null) properties["border-radius"] = value } + + // === Layout Properties === + var display: Display? + get() = properties["display"] as? Display + set(value) { if (value != null) properties["display"] = value } + + var position: Position? + get() = properties["position"] as? Position + set(value) { if (value != null) properties["position"] = value } + + var top: CssValue? + get() = properties["top"] + set(value) { if (value != null) properties["top"] = value } + + var left: CssValue? + get() = properties["left"] + set(value) { if (value != null) properties["left"] = value } + + var right: CssValue? + get() = properties["right"] + set(value) { if (value != null) properties["right"] = value } + + var bottom: CssValue? + get() = properties["bottom"] + set(value) { if (value != null) properties["bottom"] = value } + + // === Visual Effects === + var opacity: CssValue? + get() = properties["opacity"] + set(value) { if (value != null) properties["opacity"] = value } + + var boxShadow: CssValue? + get() = properties["box-shadow"] + set(value) { if (value != null) properties["box-shadow"] = value } + + var textShadow: CssValue? + get() = properties["text-shadow"] + set(value) { if (value != null) properties["text-shadow"] = value } + + // === Outline Properties === + var outline: CssValue? + get() = properties["outline"] + set(value) { if (value != null) properties["outline"] = value } + + var outlineColor: CssColor? + get() = properties["outline-color"] as? CssColor + set(value) { if (value != null) properties["outline-color"] = value } + + var outlineWidth: CssValue? + get() = properties["outline-width"] + set(value) { if (value != null) properties["outline-width"] = value } + + var outlineOffset: CssValue? + get() = properties["outline-offset"] + set(value) { if (value != null) properties["outline-offset"] = value } + + // === Spacing Properties === + var margin: CssValue? + get() = properties["margin"] + set(value) { if (value != null) properties["margin"] = value } + + var marginTop: CssValue? + get() = properties["margin-top"] + set(value) { if (value != null) properties["margin-top"] = value } + + var marginRight: CssValue? + get() = properties["margin-right"] + set(value) { if (value != null) properties["margin-right"] = value } + + var marginBottom: CssValue? + get() = properties["margin-bottom"] + set(value) { if (value != null) properties["margin-bottom"] = value } + + var marginLeft: CssValue? + get() = properties["margin-left"] + set(value) { if (value != null) properties["margin-left"] = value } + + var padding: CssValue? + get() = properties["padding"] + set(value) { if (value != null) properties["padding"] = value } + + var paddingTop: CssValue? + get() = properties["padding-top"] + set(value) { if (value != null) properties["padding-top"] = value } + + var paddingRight: CssValue? + get() = properties["padding-right"] + set(value) { if (value != null) properties["padding-right"] = value } + + var paddingBottom: CssValue? + get() = properties["padding-bottom"] + set(value) { if (value != null) properties["padding-bottom"] = value } + + var paddingLeft: CssValue? + get() = properties["padding-left"] + set(value) { if (value != null) properties["padding-left"] = value } + + // === UI Properties === + var cursor: Cursor? + get() = properties["cursor"] as? Cursor + set(value) { if (value != null) properties["cursor"] = value } + + var userSelect: UserSelect? + get() = properties["user-select"] as? UserSelect + set(value) { if (value != null) properties["user-select"] = value } + + var pointerEvents: PointerEvents? + get() = properties["pointer-events"] as? PointerEvents + set(value) { if (value != null) properties["pointer-events"] = value } + + // === Text Properties === + var textAlign: TextAlign? + get() = properties["text-align"] as? TextAlign + set(value) { if (value != null) properties["text-align"] = value } + + var textDecoration: TextDecoration? + get() = properties["text-decoration"] as? TextDecoration + set(value) { if (value != null) properties["text-decoration"] = value } + + var textTransform: TextTransform? + get() = properties["text-transform"] as? TextTransform + set(value) { if (value != null) properties["text-transform"] = value } + + // === Helper Methods === + fun border(width: CssValue, style: BorderStyle, color: CssColor) { + border = CssString("${width.toCssString()} ${style.toCssString()} ${color.toCssString()}") + } + + fun boxShadow(offsetX: CssValue, offsetY: CssValue, blurRadius: CssValue, color: CssColor) { + boxShadow = CssString("${offsetX.toCssString()} ${offsetY.toCssString()} ${blurRadius.toCssString()} ${color.toCssString()}") + } + + fun boxShadow(offsetX: CssValue, offsetY: CssValue, blurRadius: CssValue, spreadRadius: CssValue, color: CssColor) { + boxShadow = CssString("${offsetX.toCssString()} ${offsetY.toCssString()} ${blurRadius.toCssString()} ${spreadRadius.toCssString()} ${color.toCssString()}") + } + + fun textShadow(offsetX: CssValue, offsetY: CssValue, blurRadius: CssValue, color: CssColor) { + textShadow = CssString("${offsetX.toCssString()} ${offsetY.toCssString()} ${blurRadius.toCssString()} ${color.toCssString()}") + } + + // 内部で使用:プロパティマップを取得 + internal fun getProperties(): Map = properties.toMap() +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssText.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssText.kt new file mode 100644 index 0000000..a64022d --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssText.kt @@ -0,0 +1,80 @@ +package net.kigawa.renlin.css + +/** + * Text Align プロパティ + */ +@Suppress("unused") +enum class TextAlign(private val value: String) : CssValue { + LEFT("left"), + RIGHT("right"), + CENTER("center"), + JUSTIFY("justify"), + START("start"), + END("end"); + + override fun toCssString(): String = value +} + +/** + * Text Decoration プロパティ + */ +@Suppress("unused") +enum class TextDecoration(private val value: String) : CssValue { + NONE("none"), + UNDERLINE("underline"), + OVERLINE("overline"), + LINE_THROUGH("line-through"); + + override fun toCssString(): String = value +} + +/** + * Text Transform プロパティ + */ +@Suppress("unused") +enum class TextTransform(private val value: String) : CssValue { + NONE("none"), + UPPERCASE("uppercase"), + LOWERCASE("lowercase"), + CAPITALIZE("capitalize"); + + override fun toCssString(): String = value +} + +/** + * White Space プロパティ + */ +@Suppress("unused") +enum class WhiteSpace(private val value: String) : CssValue { + NORMAL("normal"), + NOWRAP("nowrap"), + PRE("pre"), + PRE_LINE("pre-line"), + PRE_WRAP("pre-wrap"); + + override fun toCssString(): String = value +} + +/** + * Word Break プロパティ + */ +@Suppress("unused") +enum class WordBreak(private val value: String) : CssValue { + NORMAL("normal"), + BREAK_ALL("break-all"), + KEEP_ALL("keep-all"), + BREAK_WORD("break-word"); + + override fun toCssString(): String = value +} + +/** + * Text Overflow プロパティ + */ +@Suppress("unused") +enum class TextOverflow(private val value: String) : CssValue { + CLIP("clip"), + ELLIPSIS("ellipsis"); + + override fun toCssString(): String = value +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt index ccf8bad..372e4a8 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt @@ -23,4 +23,22 @@ val Double.rem: CssUnit get() = CssUnit(this, "rem") @Suppress("unused") val Int.percent: CssUnit get() = CssUnit(this, "%") @Suppress("unused") -val Double.percent: CssUnit get() = CssUnit(this, "%") \ No newline at end of file +val Double.percent: CssUnit get() = CssUnit(this, "%") +@Suppress("unused") +val Int.vh: CssUnit get() = CssUnit(this, "vh") +@Suppress("unused") +val Double.vh: CssUnit get() = CssUnit(this, "vh") +@Suppress("unused") +val Int.vw: CssUnit get() = CssUnit(this, "vw") +@Suppress("unused") +val Double.vw: CssUnit get() = CssUnit(this, "vw") + +// 文字列をCssValueに変換する拡張プロパティ +@Suppress("unused") +val String.cssValue: CssValue get() = CssString(this) + +// 数値をCssValueに変換する拡張プロパティ +@Suppress("unused") +val Int.cssValue: CssValue get() = CssString(this.toString()) +@Suppress("unused") +val Double.cssValue: CssValue get() = CssString(this.toString()) \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt index 3df65f4..9c066fc 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt @@ -3,6 +3,7 @@ package net.kigawa.renlin.dsl import net.kigawa.hakate.api.state.State import net.kigawa.renlin.category.ContentCategory import net.kigawa.renlin.css.CssCapable +import net.kigawa.renlin.css.CssRuleSet import net.kigawa.renlin.css.CssValue import net.kigawa.renlin.dsl.state.DslState import kotlin.uuid.ExperimentalUuidApi @@ -13,6 +14,7 @@ abstract class DslBase : Dsl? = null + override var pendingCssRuleSet: CssRuleSet? = null private val subDsls = mutableListOf() override val states = mutableSetOf>() @@ -55,10 +57,23 @@ abstract class DslBase : Dsl + val cssManager = dslState?.getCssManager() + if (cssManager != null) { + cssClassName = cssManager.getOrCreateClass(ruleSet) + // 処理完了後はクリア + pendingCssRuleSet = null + return + } + } + + // 後方互換性のため、古いproperties形式も処理 pendingCssProperties?.let { properties -> val cssManager = dslState?.getCssManager() if (cssManager != null) { - cssClassName = cssManager.getOrCreateClass(properties) + val ruleSet = CssRuleSet(properties, emptyList()) + cssClassName = cssManager.getOrCreateClass(ruleSet) // 処理完了後はクリア pendingCssProperties = null } diff --git a/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt b/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt index d23152d..b11620b 100644 --- a/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt +++ b/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt @@ -3,45 +3,34 @@ package net.kigawa.renlin.css import kotlinx.browser.document import org.w3c.dom.HTMLStyleElement -/** - * JS版のCSSマネージャー実装 - */ class JsCssManager : CssManager { private val styleElement: HTMLStyleElement = document.createElement("style") as HTMLStyleElement - private val cssClasses = mutableMapOf() // クラス名 -> CSS文字列 - private val propertyHashToClassName = mutableMapOf() // プロパティハッシュ -> クラス名 + private val cssRules = mutableMapOf() init { styleElement.id = "renlin-css-styles" document.head?.appendChild(styleElement) } - override fun getOrCreateClass(properties: Map): String { - if (properties.isEmpty()) return "" + override fun getOrCreateClass(ruleSet: CssRuleSet): String { + if (ruleSet.isEmpty()) return "" - // React風のクラス名を生成 - val className = CssUtils.generateClassName(properties) + val className = CssUtils.generateClassName(ruleSet) - // 既に同じクラス名が存在する場合はそれを返す - return if (cssClasses.containsKey(className)) { + return if (cssRules.containsKey(className)) { className } else { - val cssString = CssUtils.generateCssString(properties) - cssClasses[className] = cssString + val cssString = CssUtils.generateFullCssString(className, ruleSet) + cssRules[className] = cssString updateStyles() className } } override fun updateStyles() { - val cssText = cssClasses.entries.joinToString("\n") { (className, css) -> - ".$className { $css }" - } + val cssText = cssRules.values.joinToString("\n") styleElement.textContent = cssText } } -/** - * JS版のCssManagerファクトリ関数 - */ actual fun createCssManager(): CssManager = JsCssManager() \ No newline at end of file diff --git a/renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt b/renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt index f2fbd9d..0a04aac 100644 --- a/renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt +++ b/renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt @@ -1,23 +1,23 @@ package net.kigawa.renlin.css /** - * JVM版のCSSマネージャー実装 + * JVM版のCSSマネージャー実装(疑似クラス対応) */ class JvmCssManager : CssManager { - private val cssClasses = mutableMapOf() // クラス名 -> CSS文字列 + private val cssRules = mutableMapOf() // クラス名 -> 完全なCSS文字列 - override fun getOrCreateClass(properties: Map): String { - if (properties.isEmpty()) return "" + override fun getOrCreateClass(ruleSet: CssRuleSet): String { + if (ruleSet.isEmpty()) return "" // React風のクラス名を生成 - val className = CssUtils.generateClassName(properties) + val className = CssUtils.generateClassName(ruleSet) // 既に同じクラス名が存在する場合はそれを返す - return if (cssClasses.containsKey(className)) { + return if (cssRules.containsKey(className)) { className } else { - val cssString = CssUtils.generateCssString(properties) - cssClasses[className] = cssString + val cssString = CssUtils.generateFullCssString(className, ruleSet) + cssRules[className] = cssString updateStyles() className } @@ -31,12 +31,10 @@ class JvmCssManager : CssManager { * スタイルタグを生成 */ fun generateStyleTag(): String { - return if (cssClasses.isEmpty()) { + return if (cssRules.isEmpty()) { "" } else { - "" + "" } } From 5fa5cc4f9722492e1dc7a9f08ccfac63cd51e3e2 Mon Sep 17 00:00:00 2001 From: AomiVel Date: Thu, 26 Jun 2025 12:59:55 +0900 Subject: [PATCH 08/10] update: test css --- .../kotlin/net/kigawa/renlin/sample/Sub.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt index a9ab717..a03bc9e 100644 --- a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt +++ b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt @@ -35,6 +35,9 @@ class Sub { css { color = if (value.last().digitToInt() % 2 == 0) Color.RED else Color.BLUE backgroundColor = if (value.last().digitToInt() % 2 == 0) Color.BLUE else Color.RED + hover { + cursor = Cursor.GRABBING + } } } } @@ -45,6 +48,17 @@ class Sub { div { t("display2-1") key = "uuid aadaaaaaaa" + css { + userSelect = UserSelect.NONE + hover { + cursor = Cursor.POINTER + backgroundColor = Color.rgba(0, 255, 255, 0.3) + } + active { + color = Color.RED + fontWeight = FontWeight.BOLD + } + } } } div { From b30cd76e5ccf80bc5601f71b3d9d3fa6690446c1 Mon Sep 17 00:00:00 2001 From: kigawa Date: Fri, 4 Jul 2025 00:23:35 +0900 Subject: [PATCH 09/10] add: click event --- .../net/kigawa/renlin/css/CssExtensions.kt | 6 +++--- .../kotlin/net/kigawa/renlin/dsl/DslBase.kt | 10 ++++------ .../net/kigawa/renlin/dsl/state/DslState.kt | 18 ------------------ .../net/kigawa/renlin/element/TagNode.kt | 13 ------------- .../kotlin/net/kigawa/renlin/state/DslState.kt | 4 ++++ .../kigawa/renlin/w3c/element/TagNodeCommon.kt | 2 ++ .../net/kigawa/renlin/element/TagNode.kt | 15 --------------- .../net/kigawa/renlin/element/TagNode.kt | 13 ------------- .../kotlin/net/kigawa/renlin/sample/Sub.kt | 9 +-------- 9 files changed, 14 insertions(+), 76 deletions(-) delete mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt delete mode 100644 renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt delete mode 100644 renlin/src/jsMain/kotlin/net/kigawa/renlin/element/TagNode.kt delete mode 100644 renlin/src/jvmMain/kotlin/net/kigawa/renlin/element/TagNode.kt diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt index bdaf756..98ba88c 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt @@ -1,12 +1,12 @@ package net.kigawa.renlin.css -import net.kigawa.renlin.dsl.Dsl -import net.kigawa.renlin.category.ContentCategory +import net.kigawa.renlin.dsl.StatedDsl +import net.kigawa.renlin.w3c.category.ContentCategory /** * DSLにCSS機能を追加する拡張関数(疑似クラス対応) */ -fun Dsl.css(block: CssDsl.() -> Unit) { +fun StatedDsl.css(block: CssDsl.() -> Unit) { val cssDsl = CssDsl() cssDsl.block() val ruleSet = cssDsl.getRuleSet() diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt index 2e652d9..5975377 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt @@ -1,18 +1,16 @@ package net.kigawa.renlin.dsl import net.kigawa.hakate.api.state.State -import net.kigawa.renlin.state.DslState import net.kigawa.renlin.css.CssCapable import net.kigawa.renlin.css.CssRuleSet import net.kigawa.renlin.css.CssValue +import net.kigawa.renlin.state.DslState import net.kigawa.renlin.state.DslStateData import net.kigawa.renlin.w3c.category.ContentCategory abstract class DslBase( override val dslState: DslState, -) : StatedDsl { -abstract class DslBase : Dsl, CssCapable { - override var key: String? = null +) : StatedDsl, CssCapable { override var cssClassName: String? = null override var pendingCssProperties: Map? = null override var pendingCssRuleSet: CssRuleSet? = null @@ -58,7 +56,7 @@ abstract class DslBase : Dsl - val cssManager = dslState?.getCssManager() + val cssManager = dslState.getCssManager() if (cssManager != null) { cssClassName = cssManager.getOrCreateClass(ruleSet) // 処理完了後はクリア @@ -69,7 +67,7 @@ abstract class DslBase : Dsl - val cssManager = dslState?.getCssManager() + val cssManager = dslState.getCssManager() if (cssManager != null) { val ruleSet = CssRuleSet(properties, emptyList()) cssClassName = cssManager.getOrCreateClass(ruleSet) diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt deleted file mode 100644 index a2c603c..0000000 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt +++ /dev/null @@ -1,18 +0,0 @@ -package net.kigawa.renlin.dsl.state - -import net.kigawa.renlin.css.CssManager -import net.kigawa.renlin.dsl.Dsl -import net.kigawa.renlin.dsl.RegisteredDslData -import net.kigawa.renlin.element.TagNode -import net.kigawa.renlin.tag.component.Component - -interface DslState { - val ownElement: TagNode? - val latestRegisteredDslData: RegisteredDslData? - fun subDslState(key: String, second: Component): DslState - fun setSubDsls(dsls: List) - fun applyDsl(dsl: Dsl<*>, registeredDslData: RegisteredDslData) - - // CSS機能の追加 - fun getCssManager(): CssManager? -} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt deleted file mode 100644 index d294c3e..0000000 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt +++ /dev/null @@ -1,13 +0,0 @@ -package net.kigawa.renlin.element - -import net.kigawa.renlin.tag.Tag - -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") -expect interface TagNode { - val isEmpty: Boolean - fun setTextContent(text: String?) - fun newNode(tag: Tag<*>): TagNode - fun remove() - fun setNodes(index: Int, nodes: List) - fun setClassName(className: String) -} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt index f2112cb..3d9b977 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt @@ -1,5 +1,6 @@ package net.kigawa.renlin.state +import net.kigawa.renlin.css.CssManager import net.kigawa.renlin.dsl.StatedDsl import net.kigawa.renlin.dsl.RegisteredDslData import net.kigawa.renlin.w3c.element.TagNode @@ -53,4 +54,7 @@ interface DslState { */ fun applyDsl(dsl: StatedDsl<*>, registeredDslData: RegisteredDslData) fun dslStateData(): DslStateData? + + // CSS機能の追加 + fun getCssManager(): CssManager? } diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/w3c/element/TagNodeCommon.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/w3c/element/TagNodeCommon.kt index e3267f4..020bd95 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/w3c/element/TagNodeCommon.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/w3c/element/TagNodeCommon.kt @@ -23,4 +23,6 @@ interface TagNodeCommon { dslStateData.onClick?.let { addEventListener(EventNames.click, it) } ).let { dslStateData.setAdditionalData(this::class, it) } } + + fun setClassName(className: String) } \ No newline at end of file diff --git a/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/TagNode.kt b/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/TagNode.kt deleted file mode 100644 index 7e34a00..0000000 --- a/renlin/src/jsMain/kotlin/net/kigawa/renlin/element/TagNode.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.kigawa.renlin.element - -import net.kigawa.renlin.tag.Tag -import org.w3c.dom.Node - -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") -actual interface TagNode { - actual val isEmpty: Boolean - val node: Node - actual fun setTextContent(text: String?) - actual fun newNode(tag: Tag<*>): TagNode - actual fun remove() - actual fun setNodes(index: Int, nodes: List) - actual fun setClassName(className: String) -} \ No newline at end of file diff --git a/renlin/src/jvmMain/kotlin/net/kigawa/renlin/element/TagNode.kt b/renlin/src/jvmMain/kotlin/net/kigawa/renlin/element/TagNode.kt deleted file mode 100644 index 0cf8f15..0000000 --- a/renlin/src/jvmMain/kotlin/net/kigawa/renlin/element/TagNode.kt +++ /dev/null @@ -1,13 +0,0 @@ -package net.kigawa.renlin.element - -import net.kigawa.renlin.tag.Tag - -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") -actual interface TagNode { - actual val isEmpty: Boolean - actual fun setTextContent(text: String?) - actual fun newNode(tag: Tag<*>): TagNode - actual fun remove() - actual fun setNodes(index: Int, nodes: List) - actual fun setClassName(className: String) -} \ No newline at end of file diff --git a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt index 1ec619d..08ed7ad 100644 --- a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt +++ b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt @@ -2,10 +2,6 @@ package net.kigawa.renlin.sample import net.kigawa.hakate.api.HakateInitializer import net.kigawa.hakate.api.state.MutableState -import net.kigawa.renlin.category.FlowContent -import net.kigawa.renlin.category.FlowPhrasingIntersection -import net.kigawa.renlin.category.PhrasingContent -import net.kigawa.renlin.category.t import net.kigawa.renlin.css.* import net.kigawa.renlin.tag.div import net.kigawa.renlin.tag.fragment @@ -17,7 +13,6 @@ import net.kigawa.renlin.w3c.category.native.PhrasingContent import net.kigawa.renlin.w3c.category.t - class Sub { val state: MutableState = HakateInitializer().newStateDispatcher().newState("state 0") @@ -51,7 +46,6 @@ class Sub { div("uuid aadaaaaaaa") { t("display2-1") - key = "uuid aadaaaaaaa" css { userSelect = UserSelect.NONE hover { @@ -67,8 +61,7 @@ class Sub { } div("uuid aawaaaaaaaa") { t("display3") - key = "uuid aawaaaaaaaa" - css{ + css { color = Color.YELLOW backgroundColor = Color.BLUE fontSize = 24.px From 3aa33654881ff9b57fb058c7f8a482d850d48910 Mon Sep 17 00:00:00 2001 From: kigawa Date: Fri, 4 Jul 2025 01:48:32 +0900 Subject: [PATCH 10/10] refactor: replace `getCssManager()` with `cssManager` property and update related logic --- .run/renlin_sample [allTests].run.xml | 24 +++++++++++++++++++ ...n_sample [jsBrowserDevelopmentRun].run.xml | 24 +++++++++++++++++++ .../kotlin/net/kigawa/renlin/dsl/DslBase.kt | 4 ++-- .../kigawa/renlin/state/BasicDslStateBase.kt | 15 ++++++------ .../net/kigawa/renlin/state/DslState.kt | 6 ++--- .../kigawa/renlin/state/SubBasicDslState.kt | 7 +++--- 6 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 .run/renlin_sample [allTests].run.xml create mode 100644 .run/renlin_sample [jsBrowserDevelopmentRun].run.xml diff --git a/.run/renlin_sample [allTests].run.xml b/.run/renlin_sample [allTests].run.xml new file mode 100644 index 0000000..831ceae --- /dev/null +++ b/.run/renlin_sample [allTests].run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.run/renlin_sample [jsBrowserDevelopmentRun].run.xml b/.run/renlin_sample [jsBrowserDevelopmentRun].run.xml new file mode 100644 index 0000000..1db4ee6 --- /dev/null +++ b/.run/renlin_sample [jsBrowserDevelopmentRun].run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt index 5975377..91c244b 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt @@ -56,7 +56,7 @@ abstract class DslBase( private fun processPendingCss() { // 新しいCssRuleSet形式を優先的に処理 pendingCssRuleSet?.let { ruleSet -> - val cssManager = dslState.getCssManager() + val cssManager = dslState.cssManager if (cssManager != null) { cssClassName = cssManager.getOrCreateClass(ruleSet) // 処理完了後はクリア @@ -67,7 +67,7 @@ abstract class DslBase( // 後方互換性のため、古いproperties形式も処理 pendingCssProperties?.let { properties -> - val cssManager = dslState.getCssManager() + val cssManager = dslState.cssManager if (cssManager != null) { val ruleSet = CssRuleSet(properties, emptyList()) cssClassName = cssManager.getOrCreateClass(ruleSet) diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt index 732d4f7..7e4d313 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt @@ -4,18 +4,20 @@ import net.kigawa.hakate.api.state.StateContext import net.kigawa.renlin.css.CssCapable import net.kigawa.renlin.css.CssManager import net.kigawa.renlin.css.createCssManager -import net.kigawa.renlin.dsl.StatedDsl import net.kigawa.renlin.dsl.RegisteredDslData -import net.kigawa.renlin.w3c.element.TagNode +import net.kigawa.renlin.dsl.StatedDsl import net.kigawa.renlin.tag.Tag import net.kigawa.renlin.tag.component.Component +import net.kigawa.renlin.w3c.element.TagNode abstract class BasicDslStateBase( protected val stateContext: StateContext, ) : DslState { protected var subStates = mutableListOf() abstract override val ownElement: TagNode? - protected var cssManager: CssManager? = null + protected var internalCssManager: CssManager? = null + override val cssManager: CssManager? + get() = internalCssManager override fun getOrCreateSubDslState(key: String, second: Component): DslState { return subStates.firstOrNull { it.key == key } ?: SubBasicDslState( @@ -71,12 +73,9 @@ abstract class BasicDslStateBase( abstract fun newElement(tag: Tag<*>): TagNode - // CSS機能の追加 - override fun getCssManager(): CssManager? = cssManager - protected fun initializeCssManager() { - if (cssManager == null) { - cssManager = createCssManager() + if (internalCssManager == null) { + internalCssManager = createCssManager() } } } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt index 3d9b977..b3ba2e1 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt @@ -1,10 +1,10 @@ package net.kigawa.renlin.state import net.kigawa.renlin.css.CssManager -import net.kigawa.renlin.dsl.StatedDsl import net.kigawa.renlin.dsl.RegisteredDslData -import net.kigawa.renlin.w3c.element.TagNode +import net.kigawa.renlin.dsl.StatedDsl import net.kigawa.renlin.tag.component.Component +import net.kigawa.renlin.w3c.element.TagNode /** * `DslState` インターフェースは、DSLの状態を管理するための機能を定義します。 @@ -56,5 +56,5 @@ interface DslState { fun dslStateData(): DslStateData? // CSS機能の追加 - fun getCssManager(): CssManager? + val cssManager: CssManager? } diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt index 1a20bf7..07b923e 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt @@ -4,7 +4,7 @@ import net.kigawa.hakate.api.multi.mergeState import net.kigawa.hakate.api.state.State import net.kigawa.hakate.api.state.StateContext import net.kigawa.renlin.css.CssCapable -import net.kigawa.renlin.dsl.Dsl +import net.kigawa.renlin.css.CssManager import net.kigawa.renlin.dsl.RegisteredDslData import net.kigawa.renlin.dsl.StatedDsl import net.kigawa.renlin.tag.Tag @@ -24,6 +24,8 @@ class SubBasicDslState( override var latestRegisteredDslData: RegisteredDslData? = null private var latestDslStateData: DslStateData? = DslStateData(key) + override val cssManager: CssManager? + get() = parent. cssManager var latestStateContext: StateContext? = null override fun applyDsl(dsl: StatedDsl<*>, registeredDslData: RegisteredDslData) { @@ -79,7 +81,4 @@ class SubBasicDslState( override fun newElement(tag: Tag<*>): TagNode { return ownElement?.newNode(tag) ?: parent.newElement(tag) } - - // CSS機能:親のCssManagerを継承 - override fun getCssManager() = parent.getCssManager() } \ No newline at end of file