diff --git a/.github/workflows/deploy_maven_central.yml b/.github/workflows/deploy_maven_central.yml index e0cb4d0..25a12fe 100644 --- a/.github/workflows/deploy_maven_central.yml +++ b/.github/workflows/deploy_maven_central.yml @@ -1,8 +1,21 @@ name: deploy maven central +# This workflow handles deployment to Maven Central +# Deployment occurs automatically in two scenarios: +# 1. When code is merged to the main branch +# 2. When a PR is opened or updated to merge to the main branch +# For releases, use branch naming convention: release/vx.x.x (e.g., release/v1.0.0) +# The version number will be extracted from the branch name + on: push: + branches: + - main + pull_request: + types: [opened, synchronize] + branches: + - main workflow_dispatch: jobs: @@ -10,7 +23,8 @@ jobs: if: github.ref != 'refs/heads/main' uses: ./.github/workflows/test.yml cd: - if: github.ref == 'refs/heads/main' + # Run on main branch or when a PR is targeting the main branch + if: github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main') runs-on: ubuntu-latest steps: @@ -29,6 +43,11 @@ jobs: run: chmod +x ./gradlew - name: Validate Gradle wrapper uses: gradle/wrapper-validation-action@v1 + - name: Display version info + run: | + echo "Branch name from GITHUB_REF_NAME: $GITHUB_REF_NAME" + echo "Branch name from GITHUB_HEAD_REF: $GITHUB_HEAD_REF" + ./gradlew -q printVersion - name: Publish package uses: gradle/gradle-build-action@v2 with: 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/convention-plugins/build.gradle.kts b/convention-plugins/build.gradle.kts index 5e77211..ffffeff 100644 --- a/convention-plugins/build.gradle.kts +++ b/convention-plugins/build.gradle.kts @@ -1,7 +1,9 @@ plugins { `kotlin-dsl` } +fun pluginId(pluginName: String, version: String) = "$pluginName:$pluginName.gradle.plugin:$version" dependencies { implementation(libs.nexus.publish) + implementation(pluginId("org.jetbrains.kotlin.multiplatform", "2.1.0")) } \ No newline at end of file diff --git a/convention-plugins/src/main/kotlin/renlin.common.gradle.kts b/convention-plugins/src/main/kotlin/renlin.common.gradle.kts new file mode 100644 index 0000000..570f536 --- /dev/null +++ b/convention-plugins/src/main/kotlin/renlin.common.gradle.kts @@ -0,0 +1,45 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + +plugins { + kotlin("multiplatform") + +} + + +kotlin { + jvm { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + } + } + js { + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + } + sourceSets["commonMain"].dependencies { + implementation("net.kigawa:hakate:3.3.2") + } + sourceSets["commonTest"].dependencies { + + + } + sourceSets["jvmMain"].dependencies { + + } + sourceSets["jvmTest"].dependencies { + + } + + sourceSets["jsMain"].dependencies { + + } + sourceSets["jsTest"].dependencies { + } +} + diff --git a/convention-plugins/src/main/kotlin/root.publication.gradle.kts b/convention-plugins/src/main/kotlin/root.publication.gradle.kts index 82f7df2..df2b1bb 100644 --- a/convention-plugins/src/main/kotlin/root.publication.gradle.kts +++ b/convention-plugins/src/main/kotlin/root.publication.gradle.kts @@ -3,14 +3,67 @@ plugins { } object Conf { const val GROUP = "net.kigawa" - const val VERSION = "1.0.0" + // Base version - will be modified based on branch name if available + const val BASE_VERSION = "1.0.1" } +// Determine version based on branch name +// Branch naming convention for releases: release/vx.x.x (e.g., release/v1.0.0) +fun determineVersion(): String { + // Try to get branch name from different environment variables + // For pull requests, GITHUB_HEAD_REF contains the source branch name + // For direct pushes, GITHUB_REF_NAME contains the branch name + val branchName = System.getenv("GITHUB_HEAD_REF") + ?: System.getenv("GITHUB_REF_NAME") + ?: return Conf.BASE_VERSION + + // For main branch, use the base version + if (branchName == "main") { + return Conf.BASE_VERSION + } + + // For release branches in format 'release/vx.x.x', use the version from the branch name + val releasePattern = Regex("^release/v(\\d+\\.\\d+\\.\\d+)$") + val matchResult = releasePattern.find(branchName) + if (matchResult != null) { + // Extract version number from branch name + val versionFromBranch = matchResult.groupValues[1] + return versionFromBranch + } + + // For other branches, use format: baseVersion-branchName-SNAPSHOT + // Replace any non-alphanumeric characters with dashes for Maven compatibility + val sanitizedBranchName = branchName.replace(Regex("[^a-zA-Z0-9]"), "-") + return "${Conf.BASE_VERSION}-${sanitizedBranchName}-SNAPSHOT" +} + +val projectVersion = determineVersion() + group = Conf.GROUP -version = Conf.VERSION +version = projectVersion allprojects { + apply(plugin = "renlin.common") group = Conf.GROUP - version = Conf.VERSION + version = projectVersion +} + +// Add a task to print the version (useful for debugging) +tasks.register("printVersion") { + doLast { + val branchName = System.getenv("GITHUB_HEAD_REF") ?: System.getenv("GITHUB_REF_NAME") ?: "unknown" + println("Project version: $version") + println("Determined from branch: $branchName") + + // Check if it's a release branch + val releasePattern = Regex("^release/v(\\d+\\.\\d+\\.\\d+)$") + val isReleaseBranch = releasePattern.matches(branchName) + println("Is release branch format (release/vx.x.x): $isReleaseBranch") + + if (isReleaseBranch) { + val versionFromBranch = releasePattern.find(branchName)?.groupValues?.get(1) + println("Version extracted from branch name: $versionFromBranch") + } + } } nexusPublishing { diff --git a/generate/build.gradle.kts b/generate/build.gradle.kts new file mode 100644 index 0000000..90a7dc4 --- /dev/null +++ b/generate/build.gradle.kts @@ -0,0 +1,28 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + +plugins { + alias(libs.plugins.kotlinMultiplatform) +} + + +repositories { + mavenCentral() +} + +dependencies { +} + +kotlin { + jvm { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + } + } + js { + browser { + binaries.executable() + } + } + +} diff --git a/generate/src/jvmMain/kotlin/_Tag_generate.kt b/generate/src/jvmMain/kotlin/_Tag_generate.kt new file mode 100644 index 0000000..c9df43b --- /dev/null +++ b/generate/src/jvmMain/kotlin/_Tag_generate.kt @@ -0,0 +1,43 @@ +import generator.DslGenerator +import generator.IntegrationGenerator +import generator.NativeGenerator +import generator.TagGenerator +import generator.UnionGenerator +import java.io.File + +fun main() { + // 出力先ディレクトリ + val outputDir = "../renlin/src/commonMain/kotlin/net/kigawa/renlin/tag" + val categoryOutputDir = "../renlin/src/commonMain/kotlin/net/kigawa/renlin/w3c/category" + val categoryUnionOutputDir = "$categoryOutputDir/native" + val categoryIntegrationOutputDir = "$categoryOutputDir/integration" + val categoryDslOutputDir = "$categoryOutputDir/dsl" + val categoryNativeOutputDir = "$categoryOutputDir/native" + File(outputDir).mkdirs() + File(categoryOutputDir).mkdirs() + File(categoryUnionOutputDir).mkdirs() + File(categoryIntegrationOutputDir).mkdirs() + File(categoryDslOutputDir).mkdirs() + File(categoryNativeOutputDir).mkdirs() + + val tagGenerator = TagGenerator(outputDir).also { + it.generate() + } + val unionGenerator = UnionGenerator(categoryUnionOutputDir).also { + it.generate() + } + val integrationGenerator = IntegrationGenerator(categoryIntegrationOutputDir).also { + it.generate() + } + val dslGenerator = DslGenerator(categoryDslOutputDir).also { + it.generate() + } + val nativeGenerator = NativeGenerator(categoryNativeOutputDir).also { + it.generate(unionGenerator.nativeCategories) + } + + println("タグのコード生成が完了しました。") + println("生成されたUnionクラス: ${unionGenerator.processedUnions.size}") + println("生成されたIntegrationクラス: ${integrationGenerator.processedIntegrations.size}") + println("生成されたDSLクラス: ${dslGenerator.processedDsls.size}") +} \ No newline at end of file diff --git a/generate/src/jvmMain/kotlin/generator/DslGenerator.kt b/generate/src/jvmMain/kotlin/generator/DslGenerator.kt new file mode 100644 index 0000000..3cef961 --- /dev/null +++ b/generate/src/jvmMain/kotlin/generator/DslGenerator.kt @@ -0,0 +1,57 @@ +package generator + +import model.AllowedCategories +import tagCategories +import java.io.File + +class DslGenerator( + val categoryDslOutputDir: String, +) { + val processedDsls = mutableSetOf() + fun generate() { + // DSLクラスの生成 + (tagCategories.map { it.allowedCategories } + + tagCategories.flatMap { it.allowedCategories.categories }.map { AllowedCategories(it) }) + .filter { it.categories.isNotEmpty() } + .forEach { allowedCategories -> + val dslName = allowedCategories.connectedStr() + "Dsl" + if (!processedDsls.contains(dslName)) { + processedDsls.add(dslName) + val categories = allowedCategories.categories + + + val imports = mutableListOf() + + val fileContent = """ + package net.kigawa.renlin.w3c.category.dsl + + ${imports.distinct().joinToString("\n ")} + ${ + if (allowedCategories.categories.size > 1) + "import net.kigawa.renlin.w3c.category.integration.${ + allowedCategories.connectedStr("Integration") + }" + else "import net.kigawa.renlin.w3c.category.native.${ + allowedCategories.connectedStr("Integration") + }" + } + + + /** + * DSL for ${categories.joinToString(", ")} + */ + interface ${dslName}${ + if (categories.size <= 1) "" + else (categories.filter { it.trim() != dslName.trim() } + .joinToString(separator = ",", prefix = ":") + { "\n ${it}Dsl" }) + } + """.trimIndent() + + val file = File("$categoryDslOutputDir/${dslName}.kt") + file.writeText(fileContent) + println("Generated DSL: ${dslName}.kt") + } + } + } +} \ No newline at end of file diff --git a/generate/src/jvmMain/kotlin/generator/IntegrationGenerator.kt b/generate/src/jvmMain/kotlin/generator/IntegrationGenerator.kt new file mode 100644 index 0000000..2caaf0f --- /dev/null +++ b/generate/src/jvmMain/kotlin/generator/IntegrationGenerator.kt @@ -0,0 +1,42 @@ +package generator + +import tagCategories +import java.io.File + +class IntegrationGenerator( + val categoryIntegrationOutputDir: String, +) { + val processedIntegrations = mutableSetOf() + fun generate() { + // Integrationクラスの生成 + tagCategories + .map { it.allowedCategories } + .filter { it.categories.size > 1 } + .forEach { allowedCategories -> + val integrationName = allowedCategories.connectedStr("Integration") + if (!processedIntegrations.contains(integrationName)) { + processedIntegrations.add(integrationName) + val categories = allowedCategories.categories + + val fileContent = """ + package net.kigawa.renlin.w3c.category.integration + + import net.kigawa.renlin.w3c.category.ContentCategory + ${ + categories.joinToString("\n ") + { "import net.kigawa.renlin.w3c.category.native.$it" } + } + + /** + * Integration of ${categories.joinToString(", ")} + */ + interface $integrationName : ${(categories + "ContentCategory").joinToString(", ")} + """.trimIndent() + + val file = File("$categoryIntegrationOutputDir/${integrationName}.kt") + file.writeText(fileContent) + println("Generated integration: ${integrationName}.kt") + } + } + } +} \ No newline at end of file diff --git a/generate/src/jvmMain/kotlin/generator/NativeGenerator.kt b/generate/src/jvmMain/kotlin/generator/NativeGenerator.kt new file mode 100644 index 0000000..bcdfc23 --- /dev/null +++ b/generate/src/jvmMain/kotlin/generator/NativeGenerator.kt @@ -0,0 +1,47 @@ +package generator + +import java.io.File +import tagCategories +import categoryParents + +class NativeGenerator(val categoryNativeOutputDir: String) { + fun generate(categories: Map>) { + // 親カテゴリーの情報を収集 + val allParentCategories = categoryParents.toMutableMap() + tagCategories.forEach { tagInfo -> + allParentCategories.putAll(tagInfo.tagCategories.parentCategories) + allParentCategories.putAll(tagInfo.allowedCategories.parentCategories) + } + + // Unionクラスの生成 + categories.forEach { (name, deps) -> + val categoryName = name + + // 親カテゴリーを取得 + val parentCategory = allParentCategories[name] + + // 継承するインターフェースのリスト + val interfaces = if (parentCategory != null) { + (deps + "ContentCategory" + parentCategory).joinToString(", ") + } else { + (deps + "ContentCategory").joinToString(", ") + } + + val fileContent = """ + package net.kigawa.renlin.w3c.category.native + + import net.kigawa.renlin.w3c.category.ContentCategory + + /** + * Union to ${deps.joinToString(", ")} + * ${if (parentCategory != null) "Parent: $parentCategory" else ""} + */ + interface $categoryName : $interfaces + """.trimIndent() + + val file = File("$categoryNativeOutputDir/${categoryName}.kt") + file.writeText(fileContent) + println("Generated native: ${categoryName}.kt") + } + } +} diff --git a/generate/src/jvmMain/kotlin/generator/TagGenerator.kt b/generate/src/jvmMain/kotlin/generator/TagGenerator.kt new file mode 100644 index 0000000..24295df --- /dev/null +++ b/generate/src/jvmMain/kotlin/generator/TagGenerator.kt @@ -0,0 +1,73 @@ +package generator + +import tagCategories +import java.io.File + +class TagGenerator( + val outputDir: String, +) { + fun generate() { + // 各タグのクラスを生成 + tagCategories.forEach { tagInfo -> + + val fileContent = """ + package net.kigawa.renlin.tag + + import net.kigawa.renlin.w3c.category.native.${tagInfo.tagCategories.connectedStr("Union")} + ${ + if (tagInfo.allowedCategories.categories.size > 1) + "import net.kigawa.renlin.w3c.category.integration.${ + tagInfo.allowedCategories.connectedStr("Integration") + }" + else if ( + tagInfo.allowedCategories.categories.isNotEmpty() && + tagInfo.tagCategories.connectedStr("Union") != + tagInfo.allowedCategories.connectedStr("Integration") + ) "import net.kigawa.renlin.w3c.category.native.${ + tagInfo.allowedCategories.connectedStr("Integration") + }" + else "" + } + import net.kigawa.renlin.dsl.DslBase + import net.kigawa.renlin.dsl.StatedDsl + import net.kigawa.renlin.tag.component.TagComponent1 + import net.kigawa.renlin.w3c.element.TagNode + import net.kigawa.renlin.state.DslState + ${ + if (tagInfo.allowedCategories.categories.isEmpty()) + "import net.kigawa.renlin.w3c.category.ContentCategory" + else "import net.kigawa.renlin.w3c.category.dsl.${tagInfo.allowedCategories.connectedStr()}Dsl\n" + } + + /** + * HTML <${tagInfo.name}> element + * + * model.Categories: ${tagInfo.tagCategories.categories.joinToString(", ")} + */ + class ${tagInfo.className}Dsl(dslState: DslState): + DslBase<${tagInfo.allowedCategories.connectedStr("Integration")}>(dslState), + StatedDsl<${tagInfo.allowedCategories.connectedStr("Integration")}>${ + if (tagInfo.allowedCategories.categories.isEmpty()) "" + else ",\n ${tagInfo.allowedCategories.connectedStr()}" + + "Dsl<${tagInfo.allowedCategories.connectedStr("Integration")}>" + } { + override fun applyElement(element: TagNode): ()->Unit { + return {} + } + } + + val ${tagInfo.escapement} = TagComponent1<${tagInfo.className}, ${tagInfo.className}Dsl>(${tagInfo.className}, ::${tagInfo.className}Dsl) + + object ${tagInfo.className} : Tag<${tagInfo.tagCategories.connectedStr("Union")}> { + override val name: String + get() = "${tagInfo.name}" + } + """.trimIndent() + + // ファイルに書き込む + val file = File("$outputDir/${tagInfo.className}.kt") + file.writeText(fileContent) + println("Generated: ${tagInfo.className}.kt") + } + } +} \ No newline at end of file diff --git a/generate/src/jvmMain/kotlin/generator/UnionGenerator.kt b/generate/src/jvmMain/kotlin/generator/UnionGenerator.kt new file mode 100644 index 0000000..67d344f --- /dev/null +++ b/generate/src/jvmMain/kotlin/generator/UnionGenerator.kt @@ -0,0 +1,45 @@ +package generator + +import tagCategories +import java.io.File + +class UnionGenerator( + val categoryUnionOutputDir: String, +) { + val processedUnions = mutableSetOf() + val nativeCategories = mutableMapOf>() + fun generate() { + // Unionクラスの生成 + tagCategories + .map { it.tagCategories } + .filter { it.categories.size > 1 } + .toSet() + .forEach { tagCategories -> + val unionName = tagCategories.connectedStr("Union") + if (!processedUnions.contains(unionName)) { + processedUnions.add(unionName) + + tagCategories.categories.forEach { + nativeCategories.getOrPut(it) { mutableSetOf() }.add(unionName) + } + + val categories = tagCategories.categories + + val fileContent = """ + package net.kigawa.renlin.w3c.category.native + + import net.kigawa.renlin.w3c.category.ContentCategory + + /** + * Union of ${categories.joinToString(", ")} + */ + sealed interface $unionName: ContentCategory + """.trimIndent() + + val file = File("$categoryUnionOutputDir/${unionName}.kt") + file.writeText(fileContent) + println("Generated union: ${unionName}.kt") + } + } + } +} \ No newline at end of file diff --git a/generate/src/jvmMain/kotlin/model/AllowedCategories.kt b/generate/src/jvmMain/kotlin/model/AllowedCategories.kt new file mode 100644 index 0000000..d5e7215 --- /dev/null +++ b/generate/src/jvmMain/kotlin/model/AllowedCategories.kt @@ -0,0 +1,10 @@ +package model + +data class AllowedCategories( + override val categories: Set, + override val parentCategories: Map = emptyMap(), +) : Categories { + constructor(vararg categories: String) : this(categories.toSet()) + constructor(vararg categories: String, parentCategories: Map) : this(categories.toSet(), parentCategories) + constructor(categories: Set, vararg parentCategoryPairs: Pair) : this(categories, parentCategoryPairs.toMap()) +} diff --git a/generate/src/jvmMain/kotlin/model/Categories.kt b/generate/src/jvmMain/kotlin/model/Categories.kt new file mode 100644 index 0000000..d08bfdd --- /dev/null +++ b/generate/src/jvmMain/kotlin/model/Categories.kt @@ -0,0 +1,18 @@ +package model + +interface Categories { + val categories: Set + val parentCategories: Map + + fun connectedStr(pluralSuffix: String = ""): String { + // 複数のカテゴリーがある場合、交差インターフェース名を生成 + if (categories.size > 1) { + val sortedCategories = categories.sorted() + val interfaceName = sortedCategories.map { it.replace("Content", "") }.toSet() + return interfaceName.joinToString("") + pluralSuffix + } + + // 单一カテゴリーの場合 + return categories.firstOrNull() ?: "ContentCategory" + } +} diff --git a/generate/src/jvmMain/kotlin/model/TagCategories.kt b/generate/src/jvmMain/kotlin/model/TagCategories.kt new file mode 100644 index 0000000..1234616 --- /dev/null +++ b/generate/src/jvmMain/kotlin/model/TagCategories.kt @@ -0,0 +1,10 @@ +package model + +data class TagCategories( + override val categories: Set, + override val parentCategories: Map = emptyMap(), +) : Categories { + constructor(vararg categories: String) : this(categories.toSet()) + constructor(vararg categories: String, parentCategories: Map) : this(categories.toSet(), parentCategories) + constructor(categories: Set, vararg parentCategoryPairs: Pair) : this(categories, parentCategoryPairs.toMap()) +} diff --git a/generate/src/jvmMain/kotlin/model/TagInfo.kt b/generate/src/jvmMain/kotlin/model/TagInfo.kt new file mode 100644 index 0000000..ae74b27 --- /dev/null +++ b/generate/src/jvmMain/kotlin/model/TagInfo.kt @@ -0,0 +1,15 @@ +package model + +data class TagInfo( + val name: String, + val tagCategories: TagCategories, + val allowedCategories: AllowedCategories, +) { + val className get() = name.replaceFirstChar { it.uppercase() } + val escapement + get() = when (name) { + "object" -> "`object`" + "var" -> "`var`" + else -> name + } +} \ No newline at end of file diff --git a/generate/src/jvmMain/kotlin/tagCategories.kt b/generate/src/jvmMain/kotlin/tagCategories.kt new file mode 100644 index 0000000..ced2319 --- /dev/null +++ b/generate/src/jvmMain/kotlin/tagCategories.kt @@ -0,0 +1,338 @@ +import model.AllowedCategories +import model.TagCategories +import model.TagInfo + +// カテゴリーの親子関係を定義 +val categoryParents = mapOf( + "PhrasingContent" to "FlowContent", + "HeadingContent" to "FlowContent", + "SectioningContent" to "FlowContent", + "EmbeddedContent" to "PhrasingContent", + "InteractiveContent" to "PhrasingContent", + "FlowContent" to "EventTarget", + "MetaDataContent" to "FlowContent", + "PhrasingPhrasingContent" to "PhrasingContent", + "FlowPhrasingContent" to "FlowContent", + "MetaDataPhrasingContent" to "MetaDataContent", + "FlowMetaDataContent" to "FlowContent", + "HeadingPhrasingContent" to "HeadingContent", + "SectioningRoot" to "Any", + "SectioningHeadings" to "HeadingContent", + "FlowSectioningContent" to "FlowContent", +) + +// 元のコードの並び順を維持するためのタグリスト +val tagCategories = setOf( + // Main root + TagInfo("html", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + + // Document metadata + TagInfo("base", TagCategories("MetadataContent"), AllowedCategories("MetadataContent")), + TagInfo("head", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo( + "link", TagCategories("MetadataContent", "FlowContent", "PhrasingContent"), + AllowedCategories("MetadataContent", "FlowContent", "PhrasingContent") + ), + TagInfo( + "meta", TagCategories(setOf("MetadataContent", "FlowContent", "PhrasingContent")), + AllowedCategories(setOf("MetadataContent", "FlowContent", "PhrasingContent")) + ), + TagInfo("style", TagCategories("MetadataContent"), AllowedCategories("MetadataContent")), + TagInfo("title", TagCategories("MetadataContent"), AllowedCategories("MetadataContent")), + + //Sectioning root + TagInfo("body", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + + // Content sectioning + TagInfo( + "address", TagCategories(setOf("FlowContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PalpableContent")) + ), + TagInfo( + "article", TagCategories(setOf("FlowContent", "SectioningContent", "PalpableContent")), AllowedCategories() + ), + TagInfo( + "aside", TagCategories(setOf("FlowContent", "SectioningContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("footer", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + TagInfo("header", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + TagInfo("h1", TagCategories(setOf("FlowContent", "HeadingContent", "PalpableContent")), AllowedCategories()), + TagInfo("h2", TagCategories(setOf("FlowContent", "HeadingContent", "PalpableContent")), AllowedCategories()), + TagInfo("h3", TagCategories(setOf("FlowContent", "HeadingContent", "PalpableContent")), AllowedCategories()), + TagInfo("h4", TagCategories(setOf("FlowContent", "HeadingContent", "PalpableContent")), AllowedCategories()), + TagInfo("h5", TagCategories(setOf("FlowContent", "HeadingContent", "PalpableContent")), AllowedCategories()), + TagInfo("h6", TagCategories(setOf("FlowContent", "HeadingContent", "PalpableContent")), AllowedCategories()), + TagInfo("hgroup", TagCategories(setOf("FlowContent", "HeadingContent")), AllowedCategories()), + TagInfo("main", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + TagInfo( + "nav", TagCategories(setOf("FlowContent", "SectioningContent", "PalpableContent")), AllowedCategories() + ), + TagInfo( + "section", TagCategories(setOf("FlowContent", "SectioningContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("search", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + + // Text content + TagInfo("blockquote", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + TagInfo("dd", TagCategories("FlowContent"), AllowedCategories()), + TagInfo( + "div", TagCategories(setOf("FlowContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PalpableContent", "PhrasingContent")) + ), + TagInfo("dl", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + TagInfo("dt", TagCategories("FlowContent"), AllowedCategories()), + TagInfo("figcaption", TagCategories("FlowContent"), AllowedCategories()), + TagInfo("figure", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + TagInfo("hr", TagCategories("FlowContent"), AllowedCategories()), + TagInfo("li", TagCategories("FlowContent"), AllowedCategories()), + TagInfo("menu", TagCategories("FlowContent"), AllowedCategories()), + TagInfo("ol", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + TagInfo("p", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories("PhrasingContent")), + TagInfo("pre", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + TagInfo("ul", TagCategories(setOf("FlowContent", "PalpableContent")), AllowedCategories()), + + // Inline text semantics + TagInfo( + "a", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent", "InteractiveContent")), + AllowedCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent", "InteractiveContent")) + ), + TagInfo( + "abbr", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("b", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("bdi", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("bdo", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("br", TagCategories(setOf("FlowContent", "PhrasingContent")), AllowedCategories()), + TagInfo( + "cite", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo( + "code", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo( + "data", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("dfn", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("em", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("i", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("kbd", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo( + "mark", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("q", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("rp", TagCategories(setOf("FlowContent", "PhrasingContent")), AllowedCategories()), + TagInfo("rt", TagCategories(setOf("FlowContent", "PhrasingContent")), AllowedCategories()), + TagInfo( + "ruby", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("s", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo( + "samp", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo( + "small", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo( + "span", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo( + "strong", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("sub", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("sup", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo( + "time", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("u", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("var", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("wbr", TagCategories(setOf("FlowContent", "PhrasingContent")), AllowedCategories()), + + // Image and multimedia + TagInfo("area", TagCategories(setOf("FlowContent", "PhrasingContent")), AllowedCategories()), + TagInfo( + "audio", TagCategories( + setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent", "InteractiveContent") + ), AllowedCategories() + ), + TagInfo( + "img", TagCategories( + setOf( + "FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent", "FormAssociatedContent", + "InteractiveContent" + ) + ), AllowedCategories() + ), + TagInfo("map", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories()), + TagInfo("track", TagCategories("FlowContent"), AllowedCategories()), + TagInfo( + "video", TagCategories( + setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent", "InteractiveContent") + ), AllowedCategories() + ), + + // Embedded content + TagInfo( + "embed", TagCategories( + setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "InteractiveContent", "PalpableContent") + ), AllowedCategories() + ), + TagInfo("fencedframe", TagCategories("FlowContent"), AllowedCategories()), + TagInfo( + "iframe", TagCategories( + setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "InteractiveContent", "PalpableContent") + ), AllowedCategories() + ), + TagInfo( + "object", TagCategories( + setOf( + "FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent", "FormAssociatedContent", + "InteractiveContent" + ) + ), AllowedCategories() + ), + TagInfo( + "picture", TagCategories(setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent")) + ), + TagInfo("source", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + + // SVG and MathML + TagInfo( + "svg", TagCategories(setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent")) + ), + TagInfo( + "math", TagCategories(setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent")) + ), + + // Scripting + TagInfo( + "canvas", TagCategories(setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PhrasingContent", "EmbeddedContent", "PalpableContent")) + ), + TagInfo( + "noscript", TagCategories(setOf("MetadataContent", "FlowContent", "PhrasingContent")), + AllowedCategories(setOf("MetadataContent", "FlowContent", "PhrasingContent")) + ), + TagInfo( + "script", + TagCategories(setOf("MetadataContent", "FlowContent", "PhrasingContent", "ScriptSupportingContent")), + AllowedCategories(setOf("MetadataContent", "FlowContent", "PhrasingContent", "ScriptSupportingContent")) + ), + + // Demarcating edits + TagInfo( + "del", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")) + ), + TagInfo( + "ins", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")) + ), + + // Table content + TagInfo("caption", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo("col", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo("colgroup", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo( + "table", TagCategories(setOf("FlowContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PalpableContent")) + ), + TagInfo("tbody", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo("td", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo("tfoot", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo("th", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo("thead", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + TagInfo("tr", TagCategories("FlowContent"), AllowedCategories("FlowContent")), + + // Forms + TagInfo( + "button", TagCategories( + setOf( + "FlowContent", "PhrasingContent", "InteractiveContent", "PalpableContent", "FormAssociatedContent" + ) + ), AllowedCategories( + setOf( + "FlowContent", "PhrasingContent", "InteractiveContent", "PalpableContent", "FormAssociatedContent" + ) + ) + ), + TagInfo( + "datalist", TagCategories(setOf("FlowContent", "PhrasingContent")), + AllowedCategories(setOf("FlowContent", "PhrasingContent")) + ), + TagInfo( + "fieldset", TagCategories( + setOf("FlowContent", "PalpableContent", "FormAssociatedContent", "AutocapitalizeInheritingFormContent") + ), AllowedCategories( + setOf("FlowContent", "PalpableContent", "FormAssociatedContent", "AutocapitalizeInheritingFormContent") + ) + ), + TagInfo( + "form", TagCategories(setOf("FlowContent", "PalpableContent")), + AllowedCategories(setOf("FlowContent", "PalpableContent")) + ), + TagInfo( + "input", TagCategories( + setOf( + "FlowContent", "PhrasingContent", "PalpableContent", "InteractiveContent", "FormAssociatedContent" + ) + ), AllowedCategories( + setOf( + "FlowContent", "PhrasingContent", "PalpableContent", "InteractiveContent", "FormAssociatedContent" + ) + ) + ), + TagInfo( + "label", TagCategories(setOf("FlowContent", "PhrasingContent", "InteractiveContent", "PalpableContent")), + AllowedCategories() + ), + TagInfo("legend", TagCategories("FlowContent"), AllowedCategories()), + TagInfo( + "meter", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo("optgroup", TagCategories("FlowContent"), AllowedCategories()), + TagInfo("option", TagCategories("FlowContent"), AllowedCategories()), + TagInfo( + "output", + TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent", "FormAssociatedContent")), + AllowedCategories() + ), + TagInfo( + "progress", TagCategories(setOf("FlowContent", "PhrasingContent", "PalpableContent")), AllowedCategories() + ), + TagInfo( + "select", TagCategories( + setOf( + "FlowContent", "PhrasingContent", "InteractiveContent", "PalpableContent", "FormAssociatedContent" + ) + ), AllowedCategories() + ), + TagInfo("selectedcontent", TagCategories("FlowContent"), AllowedCategories()), + TagInfo( + "textarea", TagCategories( + setOf( + "FlowContent", "PhrasingContent", "InteractiveContent", "PalpableContent", "FormAssociatedContent" + ) + ), AllowedCategories() + ), + + // Interactive elements + TagInfo( + "details", TagCategories(setOf("FlowContent", "InteractiveContent", "PalpableContent")), + AllowedCategories() + ), + TagInfo("dialog", TagCategories("FlowContent"), AllowedCategories()), + TagInfo("summary", TagCategories("FlowContent"), AllowedCategories()), + + // Web Components + TagInfo("slot", TagCategories(setOf("FlowContent", "PhrasingContent")), AllowedCategories()), + TagInfo( + "template", + TagCategories( + setOf("MetadataContent", "FlowContent", "PhrasingContent", "ScriptSupportingContent") + ), + AllowedCategories() + ) +) diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/renlin/build.gradle.kts b/renlin/build.gradle.kts index a45e5b1..24bfd87 100644 --- a/renlin/build.gradle.kts +++ b/renlin/build.gradle.kts @@ -1,7 +1,6 @@ import com.vanniktech.maven.publish.JavadocJar import com.vanniktech.maven.publish.KotlinMultiplatform import com.vanniktech.maven.publish.SonatypeHost -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { alias(libs.plugins.kotlinMultiplatform) @@ -19,26 +18,9 @@ dependencies { } kotlin { - jvm { - @OptIn(ExperimentalKotlinGradlePluginApi::class) - compilerOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - } - } - js { - browser { - testTask { - useKarma { - useChromeHeadless() - } - } - binaries.executable() - } - } sourceSets["commonMain"].dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlin.stdlib) - implementation(libs.hakate) } sourceSets["commonTest"].dependencies { implementation(kotlin("test-common")) diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/Noting.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/Noting.kt index 1f0bd78..c41f3f4 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/Noting.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/Noting.kt @@ -1,5 +1,5 @@ package net.kigawa.renlin -import net.kigawa.renlin.category.ContentCategory +import net.kigawa.renlin.w3c.category.ContentCategory -interface NotingContent: ContentCategory \ No newline at end of file +interface NotingContent : ContentCategory \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/FlowContent.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/FlowContent.kt deleted file mode 100644 index addb686..0000000 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/FlowContent.kt +++ /dev/null @@ -1,25 +0,0 @@ -package net.kigawa.renlin.category - -import net.kigawa.renlin.Html -import net.kigawa.renlin.dsl.Dsl - - -interface FlowContent : ContentCategory { -// override fun newContentDsl(): FlowContentDsl { -// return FragmentDsl() -// } -} - -@Html -interface FlowContentDsl : - Dsl { - -// fun fragment(block: FlowContentDsl.() -> Unit) { -// object : FragmentComponent() { -// override fun newDsl(): FlowContentDsl { -// return FragmentDsl() -// } -// }.render(this, block) -// } -} - diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/FlowPhrasing.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/FlowPhrasing.kt deleted file mode 100644 index 5b13275..0000000 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/FlowPhrasing.kt +++ /dev/null @@ -1,5 +0,0 @@ -package net.kigawa.renlin.category - -interface FlowPhrasingIntersection : FlowContent, PhrasingContent {} -interface FlowPhrasingDsl : - FlowContentDsl \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/PhrasingContentDsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/PhrasingContentDsl.kt deleted file mode 100644 index 90df22b..0000000 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/category/PhrasingContentDsl.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.kigawa.renlin.category - -import net.kigawa.renlin.dsl.Dsl -import net.kigawa.renlin.tag.text - - -interface PhrasingContent : ContentCategory - - -fun Dsl.t(str: String, key: String? = null) { - text.render(this) { - this@render.key = key - this@render.text = str - } -} \ 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/Dsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/Dsl.kt index 8d0e022..b35e790 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/Dsl.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/Dsl.kt @@ -2,26 +2,13 @@ package net.kigawa.renlin.dsl import net.kigawa.hakate.api.state.State import net.kigawa.renlin.Html -import net.kigawa.renlin.category.ContentCategory -import net.kigawa.renlin.dsl.state.DslState -import net.kigawa.renlin.element.TagNode -import net.kigawa.renlin.tag.Fragment -import net.kigawa.renlin.tag.Tag -import net.kigawa.renlin.tag.component.SubComponent +import net.kigawa.renlin.state.DslStateData @Html -interface Dsl { - var key: String? - var dslState: DslState? - fun subDsl(registeredDslData: RegisteredDslData) - fun mountDslState(state: DslState, registeredDslData: RegisteredDslData) - fun applyElement(element: TagNode) +interface Dsl { + /** + * このDSLで使用されているすべての状態のセット。 + */ val states: Set> - operator fun , DSL : Dsl<*>> SubComponent.invoke(block: DSL.() -> Unit) = - this.render(this@Dsl, block) - - val fragment: SubComponent, out Dsl> - get() = Fragment.create() - - fun State.useValue(): T + val dslStateData: DslStateData? } \ 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..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,37 +1,44 @@ package net.kigawa.renlin.dsl import net.kigawa.hakate.api.state.State -import net.kigawa.renlin.category.ContentCategory -import net.kigawa.renlin.dsl.state.DslState -import kotlin.uuid.ExperimentalUuidApi -import kotlin.uuid.Uuid - -abstract class DslBase : Dsl { - override var dslState: DslState? = null - override var key: String? = null +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, 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() - override fun subDsl(registeredDslData: RegisteredDslData) { - @OptIn(ExperimentalUuidApi::class) - if (registeredDslData.dsl.key == null) registeredDslData.dsl.key = Uuid.random().toString() + override fun registerSubDsl(registeredDslData: RegisteredDslData) { - val i = subDsls.indexOfFirst { it.dsl.key == registeredDslData.dsl.key } + val i = subDsls.indexOfFirst { it.key == registeredDslData.key } if (i == -1) subDsls.add(registeredDslData) else subDsls[i] = registeredDslData - dslState?.let { - registeredDslData.dsl.mountDslState( - it.subDslState(registeredDslData.dsl.key!!, registeredDslData.component), registeredDslData + dslState.let { + registeredDslData.dsl.applyToDslState( + it.getOrCreateSubDslState(registeredDslData.key, registeredDslData.component), registeredDslData ) } } - override fun mountDslState(state: DslState, registeredDslData: RegisteredDslData) { - dslState = state + override fun applyToDslState(state: DslState, registeredDslData: RegisteredDslData) { + + // dslStateが設定されたタイミングでpendingCssPropertiesを処理 + processPendingCss() + subDsls.forEach { - it.dsl.mountDslState( - state.subDslState(it.dsl.key!!, it.component), it + it.dsl.applyToDslState( + state.getOrCreateSubDslState(it.key, it.component), it ) } state.setSubDsls(subDsls) @@ -42,6 +49,31 @@ abstract class DslBase : Dsl + 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/dsl/EmptyDsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/EmptyDsl.kt index c45d73f..bb5e3ed 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/EmptyDsl.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/EmptyDsl.kt @@ -1,12 +1,9 @@ package net.kigawa.renlin.dsl -import net.kigawa.renlin.NotingContent -import net.kigawa.renlin.element.TagNode +import net.kigawa.hakate.api.state.State +import net.kigawa.renlin.state.DslStateData -class EmptyDsl : DslBase(), Dsl { - override var key: String? = null - - override fun applyElement(element: TagNode) { - throw IllegalStateException("EmptyDsl applyElement") - } +class EmptyDsl : Dsl { + override val states = mutableSetOf>() + override val dslStateData: DslStateData = DslStateData("empty dsl") } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/EmptyStatedDsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/EmptyStatedDsl.kt new file mode 100644 index 0000000..4c4172c --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/EmptyStatedDsl.kt @@ -0,0 +1,11 @@ +package net.kigawa.renlin.dsl + +import net.kigawa.renlin.NotingContent +import net.kigawa.renlin.state.DslState +import net.kigawa.renlin.w3c.element.TagNode + +class EmptyStatedDsl(dslState: DslState) : DslBase(dslState), StatedDsl { + override fun applyElement(element: TagNode): () -> Unit { + throw IllegalStateException("EmptyDsl applyElement") + } +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/RegisteredDslData.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/RegisteredDslData.kt index d9821ca..8400b4b 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/RegisteredDslData.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/RegisteredDslData.kt @@ -1,10 +1,35 @@ package net.kigawa.renlin.dsl -import net.kigawa.renlin.tag.component.SubComponent +import net.kigawa.renlin.tag.component.Component +/** + * `RegisteredDslData` は、DSLの登録情報を保持するデータクラスです。 + * このクラスは、DSLインスタンス、関連するコンポーネント、再読み込み関数、および一意のキーを含みます。 + * + * 主な用途: + * - DSLインスタンスの追跡と管理 + * - コンポーネントとDSLの関連付け + * - DSLの再読み込み機能の提供 + */ data class RegisteredDslData( - val dsl: Dsl<*>, - val component: SubComponent<*, *>, + /** + * 登録されたDSLインスタンス + */ + val dsl: StatedDsl<*>, + + /** + * DSLに関連付けられたコンポーネント + */ + val component: Component, + + /** + * DSLを再読み込みするための関数 + */ val reload: () -> Unit, + + /** + * DSLの一意の識別キー + */ + val key: String, ) { -} \ No newline at end of file +} diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/StatedDsl.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/StatedDsl.kt new file mode 100644 index 0000000..bfd56d4 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/StatedDsl.kt @@ -0,0 +1,85 @@ +package net.kigawa.renlin.dsl + +import net.kigawa.hakate.api.state.State +import net.kigawa.renlin.state.DslState +import net.kigawa.renlin.state.DslStateData +import net.kigawa.renlin.tag.Fragment +import net.kigawa.renlin.tag.Tag +import net.kigawa.renlin.tag.component.Component1 +import net.kigawa.renlin.w3c.category.ContentCategory +import net.kigawa.renlin.w3c.element.TagNode + +/** + * `Dsl` インターフェースは、HTML構造を構築するためのDSL(ドメイン特化言語)の基本機能を定義します。 + * このインターフェースは、コンテンツカテゴリに基づいたHTML要素の構築と状態管理を提供します。 + * + * 主な機能: + * - HTML要素の構築と管理 + * - コンポーネントの描画と状態管理 + * - サブDSLの管理とマウント + * - 状態値の使用と追跡 + * + * 使用場所: + * - HTML構造を構築するDSLの実装 + * - コンポーネントベースのUI構築 + * - 状態管理を伴うHTML生成 + * + * @param CONTENT_CATEGORY このDSLが生成できるHTMLコンテンツのカテゴリ + */ +interface StatedDsl : Dsl { + /** + * 現在のDSLに関連付けられた状態。 + * この状態はDSLの動作と描画を制御します。 + */ + val dslState: DslState + + /** + * サブDSLを現在のDSLに登録します。 + * + * @param registeredDslData 登録するDSLのデータ + */ + fun registerSubDsl(registeredDslData: RegisteredDslData) + + /** + * 指定された状態をDSLにマウントします。 + * これにより、DSLの状態が更新され、関連するサブDSLも更新されます。 + * + * @param state マウントする状態 + * @param registeredDslData 関連するDSLデータ + */ + fun applyToDslState(state: DslState, registeredDslData: RegisteredDslData) + + /** + * 指定された要素をDSLに適用します。 + * + * @param element 適用するHTML要素 + * @return 要素の適用を取り消すための関数 + */ + fun applyElement(element: TagNode): () -> Unit + + + /** + * コンポーネントを呼び出し、レンダリングするための演算子オーバーロード。 + * + * @param key コンポーネントの一意のキー(オプション) + * @param block コンポーネントの内容を定義するブロック + * @return コンポーネントのレンダリング結果 + */ + operator fun , DSL : Dsl> Component1.invoke( + key: String? = null, block: DSL.() -> Unit, + ) = this.render(this@StatedDsl, block, key) + + /** + * フラグメントコンポーネント。 + * これは、複数の要素をグループ化するためのコンテナとして機能します。 + */ + val fragment: Component1, out StatedDsl> + get() = Fragment.create() + + /** + * 状態の現在の値を取得し、この状態をDSLの状態セットに追加します。 + * + * @return 状態の現在の値 + */ + fun State.useValue(): T +} 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 b1b446c..0000000 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/DslState.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.kigawa.renlin.dsl.state - -import net.kigawa.renlin.dsl.Dsl -import net.kigawa.renlin.dsl.RegisteredDslData -import net.kigawa.renlin.element.TagNode -import net.kigawa.renlin.tag.component.SubComponent - -interface DslState { - val ownElement: TagNode? - val latestRegisteredDslData: RegisteredDslData? - fun subDslState(key: String, second: SubComponent<*, *>): DslState - fun setSubDsls(dsls: List) - fun applyDsl(dsl: Dsl<*>, registeredDslData: RegisteredDslData) -} \ 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 e6b85d9..0000000 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/element/TagNode.kt +++ /dev/null @@ -1,12 +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) -} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/AdditionalDslStateData.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/AdditionalDslStateData.kt new file mode 100644 index 0000000..b1e4726 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/AdditionalDslStateData.kt @@ -0,0 +1,11 @@ +package net.kigawa.renlin.state + +import kotlin.reflect.KClass +import kotlin.reflect.KType + +data class AdditionalDslStateData( + val contextClass: KClass<*>, + val valueType: KType, + val key: String?, + val value: T, +) diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/BasicDslStateBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt similarity index 62% rename from renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/BasicDslStateBase.kt rename to renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt index f1b0395..7e4d313 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/BasicDslStateBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/BasicDslStateBase.kt @@ -1,21 +1,27 @@ -package net.kigawa.renlin.dsl.state +package net.kigawa.renlin.state import net.kigawa.hakate.api.state.StateContext -import net.kigawa.renlin.dsl.Dsl +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.element.TagNode +import net.kigawa.renlin.dsl.StatedDsl import net.kigawa.renlin.tag.Tag -import net.kigawa.renlin.tag.component.SubComponent +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 subDslState(key: String, component: SubComponent<*, *>): DslState { + override fun getOrCreateSubDslState(key: String, second: Component): DslState { return subStates.firstOrNull { it.key == key } ?: SubBasicDslState( - key, this, component, stateContext.newStateContext() + key, this, second, stateContext.newStateContext() ).also { subStates.add(it) } @@ -25,7 +31,7 @@ abstract class BasicDslStateBase( val newList = mutableListOf() dsls.forEach { registeredData -> - val newState = subStates.first { it.key == registeredData.dsl.key } + val newState = subStates.first { it.key == registeredData.key } subStates.remove(newState) newList.add(newState) } @@ -57,11 +63,19 @@ abstract class BasicDslStateBase( subStates.forEach { it.remove() } } - - override fun applyDsl(dsl: Dsl<*>, registeredDslData: RegisteredDslData) { + 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 new file mode 100644 index 0000000..b3ba2e1 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslState.kt @@ -0,0 +1,60 @@ +package net.kigawa.renlin.state + +import net.kigawa.renlin.css.CssManager +import net.kigawa.renlin.dsl.RegisteredDslData +import net.kigawa.renlin.dsl.StatedDsl +import net.kigawa.renlin.tag.component.Component +import net.kigawa.renlin.w3c.element.TagNode + +/** + * `DslState` インターフェースは、DSLの状態を管理するための機能を定義します。 + * このインターフェースは、DSLの要素、サブDSL、および状態の適用を管理します。 + * + * 主な機能: + * - DSLに関連付けられた要素の管理 + * - サブDSLの状態管理 + * - DSL状態の適用と更新 + * + * 使用場所: + * - DSLの状態管理システム + * - コンポーネントの再描画と更新メカニズム + */ +interface DslState { + /** + * このDSL状態に関連付けられた要素 + */ + val ownElement: TagNode? + + /** + * 最後に登録されたDSLデータ + */ + val latestRegisteredDslData: RegisteredDslData? + + /** + * 指定されたキーとコンポーネントに基づいてサブDSLの状態を取得または作成します + * + * @param key サブDSLの一意のキー + * @param second 関連するコンポーネント + * @return サブDSLの状態 + */ + fun getOrCreateSubDslState(key: String, second: Component): DslState + + /** + * このDSL状態に関連するサブDSLのリストを設定します + * + * @param dsls 設定するサブDSLのリスト + */ + fun setSubDsls(dsls: List) + + /** + * 指定されたDSLにこの状態を適用します + * + * @param dsl 状態を適用するDSL + * @param registeredDslData 関連するDSLデータ + */ + fun applyDsl(dsl: StatedDsl<*>, registeredDslData: RegisteredDslData) + fun dslStateData(): DslStateData? + + // CSS機能の追加 + val cssManager: CssManager? +} diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslStateData.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslStateData.kt new file mode 100644 index 0000000..dd0ea7f --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslStateData.kt @@ -0,0 +1,34 @@ +package net.kigawa.renlin.state + +import net.kigawa.renlin.w3c.event.WebPointerEvent +import kotlin.reflect.KClass +import kotlin.reflect.typeOf + +data class DslStateData( + val key: String, + var onClick: ((WebPointerEvent) -> Unit)? = null, + var additionalData: List> = listOf() +) { + inline fun setAdditionalData(contextClass: KClass<*>, value: T, key: String? = null) { + removeAdditionalData(contextClass) + additionalData = additionalData + AdditionalDslStateData( + contextClass, typeOf(), key, value + ) + } + + inline fun removeAdditionalData(contextClass: KClass<*>,key: String? = null) { + additionalData = additionalData.filter { + it.contextClass != contextClass || it.valueType != typeOf() || it.key != key + } + } + + inline fun getAdditionalData(contextClass: KClass<*>, key: String? = null): T? { + @Suppress("UNCHECKED_CAST") + return additionalData + .firstOrNull { + it.contextClass == contextClass && + it.valueType == typeOf() && + it.key == key + }?.value as? T? + } +} diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/RootDslStateBase.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/RootDslStateBase.kt similarity index 69% rename from renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/RootDslStateBase.kt rename to renlin/src/commonMain/kotlin/net/kigawa/renlin/state/RootDslStateBase.kt index 06a4701..ea9dc8e 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/RootDslStateBase.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/RootDslStateBase.kt @@ -1,14 +1,19 @@ -package net.kigawa.renlin.dsl.state +package net.kigawa.renlin.state import net.kigawa.hakate.api.state.StateContext import net.kigawa.renlin.dsl.RegisteredDslData -import net.kigawa.renlin.element.TagNode +import net.kigawa.renlin.w3c.element.TagNode import net.kigawa.renlin.tag.Tag class RootDslStateBase( override val ownElement: TagNode, stateContext: StateContext, ) : BasicDslStateBase(stateContext) { + init { + // ルートではCssManagerを初期化 + initializeCssManager() + } + override fun newElement(tag: Tag<*>): TagNode { return ownElement.newNode(tag) } @@ -21,4 +26,8 @@ class RootDslStateBase( override val latestRegisteredDslData: RegisteredDslData? get() = null + + override fun dslStateData(): DslStateData? { + return null + } } \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/SubBasicDslState.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt similarity index 69% rename from renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/SubBasicDslState.kt rename to renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt index 8ddaccf..07b923e 100644 --- a/renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/state/SubBasicDslState.kt +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/state/SubBasicDslState.kt @@ -1,32 +1,46 @@ -package net.kigawa.renlin.dsl.state +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.dsl.Dsl +import net.kigawa.renlin.css.CssCapable +import net.kigawa.renlin.css.CssManager import net.kigawa.renlin.dsl.RegisteredDslData -import net.kigawa.renlin.element.TagNode +import net.kigawa.renlin.dsl.StatedDsl import net.kigawa.renlin.tag.Tag -import net.kigawa.renlin.tag.component.SubComponent +import net.kigawa.renlin.tag.component.Component import net.kigawa.renlin.tag.component.TagComponent +import net.kigawa.renlin.w3c.element.TagNode class SubBasicDslState( val key: String, private val parent: BasicDslStateBase, - component: SubComponent<*, *>, + component: Component, stateContext: StateContext, ) : BasicDslStateBase(stateContext), DslState { override val ownElement = if (component is TagComponent<*>) { parent.newElement(component.tag) } else null 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: Dsl<*>, registeredDslData: RegisteredDslData) { + 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) + latestDslStateData = dsl.dslStateData parent.setElements(index, listOf(ownElement)) } else { parent.setElements(index, subStates.flatMap { it.getElements() }) @@ -53,6 +67,10 @@ class SubBasicDslState( } } + override fun dslStateData(): DslStateData? { + return latestDslStateData?.copy() + } + override fun setElements(index: Int, elements: List) { if (ownElement == null) { val ownIndex = parent.getIndex(this) diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/A.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/A.kt new file mode 100644 index 0000000..32deb5c --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/A.kt @@ -0,0 +1,32 @@ +package net.kigawa.renlin.tag + +import net.kigawa.renlin.w3c.category.native.FlowInteractivePalpablePhrasingUnion +import net.kigawa.renlin.w3c.category.integration.FlowInteractivePalpablePhrasingIntegration +import net.kigawa.renlin.dsl.DslBase +import net.kigawa.renlin.dsl.StatedDsl +import net.kigawa.renlin.tag.component.TagComponent1 +import net.kigawa.renlin.w3c.element.TagNode +import net.kigawa.renlin.state.DslState +import net.kigawa.renlin.w3c.category.dsl.FlowInteractivePalpablePhrasingDsl + + +/** + * HTML element + * + * model.Categories: FlowContent, PhrasingContent, PalpableContent, InteractiveContent + */ +class ADsl(dslState: DslState): + DslBase(dslState), + StatedDsl, + FlowInteractivePalpablePhrasingDsl { + override fun applyElement(element: TagNode): ()->Unit { + return {} + } +} + +val a = TagComponent1(A, ::ADsl) + +object A : Tag { + override val name: String + get() = "a" +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Abbr.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Abbr.kt new file mode 100644 index 0000000..2f00fe1 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Abbr.kt @@ -0,0 +1,30 @@ +package net.kigawa.renlin.tag + +import net.kigawa.renlin.w3c.category.native.FlowPalpablePhrasingUnion + +import net.kigawa.renlin.dsl.DslBase +import net.kigawa.renlin.dsl.StatedDsl +import net.kigawa.renlin.tag.component.TagComponent1 +import net.kigawa.renlin.w3c.element.TagNode +import net.kigawa.renlin.state.DslState +import net.kigawa.renlin.w3c.category.ContentCategory + +/** + * HTML element + * + * model.Categories: FlowContent, PhrasingContent, PalpableContent + */ +class AbbrDsl(dslState: DslState): + DslBase(dslState), + StatedDsl { + override fun applyElement(element: TagNode): ()->Unit { + return {} + } +} + +val abbr = TagComponent1(Abbr, ::AbbrDsl) + +object Abbr : Tag { + override val name: String + get() = "abbr" +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Address.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Address.kt new file mode 100644 index 0000000..774ca6d --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Address.kt @@ -0,0 +1,32 @@ +package net.kigawa.renlin.tag + +import net.kigawa.renlin.w3c.category.native.FlowPalpableUnion +import net.kigawa.renlin.w3c.category.integration.FlowPalpableIntegration +import net.kigawa.renlin.dsl.DslBase +import net.kigawa.renlin.dsl.StatedDsl +import net.kigawa.renlin.tag.component.TagComponent1 +import net.kigawa.renlin.w3c.element.TagNode +import net.kigawa.renlin.state.DslState +import net.kigawa.renlin.w3c.category.dsl.FlowPalpableDsl + + +/** + * HTML
element + * + * model.Categories: FlowContent, PalpableContent + */ +class AddressDsl(dslState: DslState): + DslBase(dslState), + StatedDsl, + FlowPalpableDsl { + override fun applyElement(element: TagNode): ()->Unit { + return {} + } +} + +val address = TagComponent1(Address, ::AddressDsl) + +object Address : Tag { + override val name: String + get() = "address" +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Area.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Area.kt new file mode 100644 index 0000000..2f07b1d --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Area.kt @@ -0,0 +1,30 @@ +package net.kigawa.renlin.tag + +import net.kigawa.renlin.w3c.category.native.FlowPhrasingUnion + +import net.kigawa.renlin.dsl.DslBase +import net.kigawa.renlin.dsl.StatedDsl +import net.kigawa.renlin.tag.component.TagComponent1 +import net.kigawa.renlin.w3c.element.TagNode +import net.kigawa.renlin.state.DslState +import net.kigawa.renlin.w3c.category.ContentCategory + +/** + * HTML element + * + * model.Categories: FlowContent, PhrasingContent + */ +class AreaDsl(dslState: DslState): + DslBase(dslState), + StatedDsl { + override fun applyElement(element: TagNode): ()->Unit { + return {} + } +} + +val area = TagComponent1(Area, ::AreaDsl) + +object Area : Tag { + override val name: String + get() = "area" +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Article.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Article.kt new file mode 100644 index 0000000..52a6d53 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Article.kt @@ -0,0 +1,30 @@ +package net.kigawa.renlin.tag + +import net.kigawa.renlin.w3c.category.native.FlowPalpableSectioningUnion + +import net.kigawa.renlin.dsl.DslBase +import net.kigawa.renlin.dsl.StatedDsl +import net.kigawa.renlin.tag.component.TagComponent1 +import net.kigawa.renlin.w3c.element.TagNode +import net.kigawa.renlin.state.DslState +import net.kigawa.renlin.w3c.category.ContentCategory + +/** + * HTML
element + * + * model.Categories: FlowContent, SectioningContent, PalpableContent + */ +class ArticleDsl(dslState: DslState): + DslBase(dslState), + StatedDsl { + override fun applyElement(element: TagNode): ()->Unit { + return {} + } +} + +val article = TagComponent1(Article, ::ArticleDsl) + +object Article : Tag { + override val name: String + get() = "article" +} \ No newline at end of file diff --git a/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Aside.kt b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Aside.kt new file mode 100644 index 0000000..0dce848 --- /dev/null +++ b/renlin/src/commonMain/kotlin/net/kigawa/renlin/tag/Aside.kt @@ -0,0 +1,30 @@ +package net.kigawa.renlin.tag + +import net.kigawa.renlin.w3c.category.native.FlowPalpableSectioningUnion + +import net.kigawa.renlin.dsl.DslBase +import net.kigawa.renlin.dsl.StatedDsl +import net.kigawa.renlin.tag.component.TagComponent1 +import net.kigawa.renlin.w3c.element.TagNode +import net.kigawa.renlin.state.DslState +import net.kigawa.renlin.w3c.category.ContentCategory + +/** + * HTML