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/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
new file mode 100644
index 0000000..a7103fb
--- /dev/null
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssColor.kt
@@ -0,0 +1,107 @@
+package net.kigawa.renlin.css
+
+/**
+ * CSS色の基底インターフェース
+ */
+sealed interface CssColor : CssValue
+
+/**
+ * 名前付きの色
+ */
+@Suppress("unused")
+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)"
+}
+
+/**
+ * 色のファクトリオブジェクト
+ */
+@Suppress("unused")
+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)
+}
+
+// 文字列からの便利な変換
+@Suppress("unused")
+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..447d39c
--- /dev/null
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssDsl.kt
@@ -0,0 +1,675 @@
+package net.kigawa.renlin.css
+
+import net.kigawa.renlin.Html
+
+/**
+ * CSS記述用のDSL(全プロパティ対応 + 疑似クラス対応)
+ */
+@Html
+@Suppress("unused")
+class CssDsl {
+ private val properties = mutableMapOf()
+ private val pseudoClasses = mutableListOf()
+
+ // === 疑似クラスメソッド ===
+
+ /**
+ * :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 }
+
+ 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 }
+
+ 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 }
+
+ 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 }
+
+ 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 Properties ===
+ 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 }
+
+ // === 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
new file mode 100644
index 0000000..98ba88c
--- /dev/null
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssExtensions.kt
@@ -0,0 +1,29 @@
+package net.kigawa.renlin.css
+
+import net.kigawa.renlin.dsl.StatedDsl
+import net.kigawa.renlin.w3c.category.ContentCategory
+
+/**
+ * DSLにCSS機能を追加する拡張関数(疑似クラス対応)
+ */
+fun StatedDsl.css(block: CssDsl.() -> Unit) {
+ val cssDsl = CssDsl()
+ cssDsl.block()
+ val ruleSet = cssDsl.getRuleSet()
+
+ if (!ruleSet.isEmpty()) {
+ // dslStateがnullの場合は、CSS情報を一時保存
+ if (this is CssCapable) {
+ this.pendingCssRuleSet = ruleSet
+ }
+ }
+}
+
+/**
+ * CSS対応可能なDSLを示すインターフェース
+ */
+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
new file mode 100644
index 0000000..71755f7
--- /dev/null
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt
@@ -0,0 +1,94 @@
+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(ruleSet: CssRuleSet): 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(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)
+ }
+ }
+
+ 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
new file mode 100644
index 0000000..372e4a8
--- /dev/null
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssUnit.kt
@@ -0,0 +1,44 @@
+package net.kigawa.renlin.css
+
+/**
+ * 単位付きの値
+ */
+data class CssUnit(val value: Number, val unit: String) : CssValue {
+ override fun toCssString(): String = "$value$unit"
+}
+
+// 便利な拡張プロパティ
+@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, "%")
+@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/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 1224348..91c244b 100644
--- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt
@@ -1,13 +1,19 @@
package net.kigawa.renlin.dsl
import net.kigawa.hakate.api.state.State
+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 {
+) : StatedDsl, CssCapable {
+ override var cssClassName: String? = null
+ override var pendingCssProperties: Map? = null
+ override var pendingCssRuleSet: CssRuleSet? = null
private val subDsls = mutableListOf()
override val states = mutableSetOf>()
override val dslStateData: DslStateData? = dslState.dslStateData()
@@ -26,6 +32,10 @@ abstract class DslBase(
}
override fun applyToDslState(state: DslState, registeredDslData: RegisteredDslData) {
+
+ // dslStateが設定されたタイミングでpendingCssPropertiesを処理
+ processPendingCss()
+
subDsls.forEach {
it.dsl.applyToDslState(
state.getOrCreateSubDslState(it.key, it.component), it
@@ -39,6 +49,31 @@ abstract class DslBase(
states.add(this)
return this.currentValue()
}
-}
+ /**
+ * 保留中のCSS情報を処理
+ */
+ private fun processPendingCss() {
+ // 新しいCssRuleSet形式を優先的に処理
+ pendingCssRuleSet?.let { ruleSet ->
+ val cssManager = dslState.cssManager
+ if (cssManager != null) {
+ cssClassName = cssManager.getOrCreateClass(ruleSet)
+ // 処理完了後はクリア
+ pendingCssRuleSet = null
+ return
+ }
+ }
+ // 後方互換性のため、古いproperties形式も処理
+ pendingCssProperties?.let { properties ->
+ val cssManager = dslState.cssManager
+ if (cssManager != null) {
+ val ruleSet = CssRuleSet(properties, emptyList())
+ cssClassName = cssManager.getOrCreateClass(ruleSet)
+ // 処理完了後はクリア
+ pendingCssProperties = null
+ }
+ }
+ }
+}
\ No newline at end of file
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 afb0312..7e4d313 100644
--- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt
@@ -1,17 +1,23 @@
package net.kigawa.renlin.state
import net.kigawa.hakate.api.state.StateContext
-import net.kigawa.renlin.dsl.StatedDsl
+import net.kigawa.renlin.css.CssCapable
+import net.kigawa.renlin.css.CssManager
+import net.kigawa.renlin.css.createCssManager
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 internalCssManager: CssManager? = null
+ override val cssManager: CssManager?
+ get() = internalCssManager
override fun getOrCreateSubDslState(key: String, second: Component): DslState {
return subStates.firstOrNull { it.key == key } ?: SubBasicDslState(
@@ -57,11 +63,19 @@ abstract class BasicDslStateBase(
subStates.forEach { it.remove() }
}
-
override fun applyDsl(dsl: StatedDsl<*>, 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
+ protected fun initializeCssManager() {
+ 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 f2112cb..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,9 +1,10 @@
package net.kigawa.renlin.state
-import net.kigawa.renlin.dsl.StatedDsl
+import net.kigawa.renlin.css.CssManager
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の状態を管理するための機能を定義します。
@@ -53,4 +54,7 @@ interface DslState {
*/
fun applyDsl(dsl: StatedDsl<*>, registeredDslData: RegisteredDslData)
fun dslStateData(): DslStateData?
+
+ // CSS機能の追加
+ val cssManager: CssManager?
}
diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/RootDslStateBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/RootDslStateBase.kt
index 6f57dde..ea9dc8e 100644
--- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/RootDslStateBase.kt
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/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/state/SubBasicDslState.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt
index 907af5e..07b923e 100644
--- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt
+++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt
@@ -3,6 +3,8 @@ package net.kigawa.renlin.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.css.CssManager
import net.kigawa.renlin.dsl.RegisteredDslData
import net.kigawa.renlin.dsl.StatedDsl
import net.kigawa.renlin.tag.Tag
@@ -22,11 +24,19 @@ 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) {
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)
ownElement.setDslStateData(latestDslStateData, dsl.dslStateData)
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/css/JsCssManager.kt b/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt
new file mode 100644
index 0000000..b11620b
--- /dev/null
+++ b/renlin/src/jsMain/kotlin/net/kigawa/renlin/css/JsCssManager.kt
@@ -0,0 +1,36 @@
+package net.kigawa.renlin.css
+
+import kotlinx.browser.document
+import org.w3c.dom.HTMLStyleElement
+
+class JsCssManager : CssManager {
+ private val styleElement: HTMLStyleElement = document.createElement("style") as HTMLStyleElement
+ private val cssRules = mutableMapOf()
+
+ init {
+ styleElement.id = "renlin-css-styles"
+ document.head?.appendChild(styleElement)
+ }
+
+ override fun getOrCreateClass(ruleSet: CssRuleSet): String {
+ if (ruleSet.isEmpty()) return ""
+
+ val className = CssUtils.generateClassName(ruleSet)
+
+ return if (cssRules.containsKey(className)) {
+ className
+ } else {
+ val cssString = CssUtils.generateFullCssString(className, ruleSet)
+ cssRules[className] = cssString
+ updateStyles()
+ className
+ }
+ }
+
+ override fun updateStyles() {
+ val cssText = cssRules.values.joinToString("\n")
+ styleElement.textContent = cssText
+ }
+}
+
+actual fun createCssManager(): CssManager = JsCssManager()
\ No newline at end of file
diff --git a/renlin/src/jsMain/kotlin/net/kigawa/renlin/w3c/element/DomTagElement.kt b/renlin/src/jsMain/kotlin/net/kigawa/renlin/w3c/element/DomTagElement.kt
index 7b26946..d8b8304 100644
--- a/renlin/src/jsMain/kotlin/net/kigawa/renlin/w3c/element/DomTagElement.kt
+++ b/renlin/src/jsMain/kotlin/net/kigawa/renlin/w3c/element/DomTagElement.kt
@@ -7,6 +7,7 @@ import net.kigawa.renlin.tag.TextTag
import net.kigawa.renlin.w3c.event.RegisteredEvent
import net.kigawa.renlin.w3c.event.WebEvent
import net.kigawa.renlin.w3c.event.name.EventName
+import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.Text
@@ -59,4 +60,14 @@ class DomTagElement(
node.removeEventListener(registeredEvent.name.name, registeredEvent.listener)
}
+
+ 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/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..0a04aac
--- /dev/null
+++ b/renlin/src/jvmMain/kotlin/net/kigawa/renlin/css/JvmCssManager.kt
@@ -0,0 +1,78 @@
+package net.kigawa.renlin.css
+
+/**
+ * JVM版のCSSマネージャー実装(疑似クラス対応)
+ */
+class JvmCssManager : CssManager {
+ private val cssRules = mutableMapOf() // クラス名 -> 完全なCSS文字列
+
+ override fun getOrCreateClass(ruleSet: CssRuleSet): String {
+ if (ruleSet.isEmpty()) return ""
+
+ // React風のクラス名を生成
+ val className = CssUtils.generateClassName(ruleSet)
+
+ // 既に同じクラス名が存在する場合はそれを返す
+ return if (cssRules.containsKey(className)) {
+ className
+ } else {
+ val cssString = CssUtils.generateFullCssString(className, ruleSet)
+ cssRules[className] = cssString
+ updateStyles()
+ className
+ }
+ }
+
+ override fun updateStyles() {
+ // JVM版では何もしない(HTMLファイル生成時にまとめて出力)
+ }
+
+ /**
+ * スタイルタグを生成
+ */
+ fun generateStyleTag(): String {
+ return if (cssRules.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/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt
index 5bedbab..08ed7ad 100644
--- a/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt
+++ b/sample/src/commonMain/kotlin/net/kigawa/renlin/sample/Sub.kt
@@ -1,9 +1,8 @@
-@file:Suppress("unused")
-
package net.kigawa.renlin.sample
import net.kigawa.hakate.api.HakateInitializer
import net.kigawa.hakate.api.state.MutableState
+import net.kigawa.renlin.css.*
import net.kigawa.renlin.tag.div
import net.kigawa.renlin.tag.fragment
import net.kigawa.renlin.tag.p
@@ -14,11 +13,8 @@ import net.kigawa.renlin.w3c.category.native.PhrasingContent
import net.kigawa.renlin.w3c.category.t
-interface MarginValue
-
-
class Sub {
- val state: MutableState = HakateInitializer().newStateDispatcher().newState("state")
+ val state: MutableState = HakateInitializer().newStateDispatcher().newState("state 0")
val display = div.component {
t("display")
@@ -34,6 +30,13 @@ 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
+ hover {
+ cursor = Cursor.GRABBING
+ }
+ }
}
}
}
@@ -43,10 +46,28 @@ class Sub {
div("uuid aadaaaaaaa") {
t("display2-1")
+ css {
+ userSelect = UserSelect.NONE
+ hover {
+ cursor = Cursor.POINTER
+ backgroundColor = Color.rgba(0, 255, 255, 0.3)
+ }
+ active {
+ color = Color.RED
+ fontWeight = FontWeight.BOLD
+ }
+ }
}
}
div("uuid aawaaaaaaaa") {
t("display3")
+ css {
+ color = Color.YELLOW
+ backgroundColor = Color.BLUE
+ fontSize = 24.px
+ height = 100.px
+ padding = 1.percent
+ }
}
}