Skip to content

Commit d290a9b

Browse files
authored
Merge pull request #17 from Code-Sakura/release/v1.3.1
Release/v1.3.1
2 parents 6251516 + fa23b20 commit d290a9b

File tree

349 files changed

+2631
-950
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

349 files changed

+2631
-950
lines changed

CLAUDE.md

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ RenlinはHTML UIを型安全なDSLアプローチで構築するためのKotlin
2020
- **ステート管理**: `StateDispatcher`を介したリアクティブステートハンドリングのためのHakateライブラリとの統合
2121
- **CSS管理**: 自動クラス生成と疑似クラスサポートを持つプラットフォーム固有のCSSマネージャー
2222
- **コンテンツカテゴリ**: W3C準拠のコンテンツモデル強制(FlowContent、PhrasingContentなど)
23+
- **属性システム**: `DslStateData`を通じた型安全な HTML 属性管理(href、onClick など)
2324

2425
### プラットフォームターゲット
25-
- **JavaScript**: DOM操作によるブラウザベースレンダリング
26+
- **JavaScript**: DOM操作によるブラウザベースレンダリング`DomTagElement` 経由)
2627
- **JVM**: サーバーサイドHTML生成機能
2728

2829
## 開発コマンド
@@ -73,14 +74,47 @@ JSアプリケーションは`Entrypoint(domElement).render(component, dispatche
7374
### コンテンツ型安全性
7475
DSLはW3Cコンテンツカテゴリをコンパイル時に強制します - FlowContentはPhrasingContentを含むことができますが、その逆はできません。
7576

77+
### 属性とイベント管理
78+
- **DslStateData パターン**: 属性(href など)とイベントハンドラー(onClick など)は`DslStateData`を通じて管理されます
79+
- **型安全な属性**: `Href`クラスなどのvalue objectsを使用して属性値を型安全に扱います
80+
- **自動DOM同期**: `TagNodeCommon.setDslStateData`が属性とイベントの DOM への同期を自動的に行います
81+
82+
## アーキテクチャの理解
83+
84+
### レイヤー構造
85+
1. **Component レイヤー**: `Component<TAG>` - 最上位のコンポーネント抽象化
86+
2. **DSL レイヤー**: `DslBase` - HTML構造構築とライフサイクル管理
87+
3. **State レイヤー**: `DslState` / `DslStateData` - 状態管理と属性/イベント管理
88+
4. **Platform レイヤー**: `TagNode` implementations - プラットフォーム固有のレンダリング
89+
90+
### W3C カテゴリシステム
91+
- `w3c/category/native/` - W3C HTML仕様に基づくコンテンツカテゴリ型定義
92+
- `w3c/category/dsl/` - 各カテゴリ用のDSLインターフェース
93+
- `w3c/category/integration/` - カテゴリ間の統合型定義
94+
- コンパイル時にHTMLコンテンツモデルの制約を強制
95+
7696
## 理解するための重要ファイル
7797

7898
- `renlin/src/commonMain/kotlin/net/kigawa/renlin/component/Component.kt` - コアコンポーネントインターフェース
79-
- `renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/Dsl.kt` - DSL基盤
80-
- `renlin/src/commonMain/kotlin/net/kigawa/renlin/css/CssManager.kt` - CSS管理システム
99+
- `renlin/src/commonMain/kotlin/net/kigawa/renlin/dsl/DslBase.kt` - DSL基底クラスと核心機能
100+
- `renlin/src/commonMain/kotlin/net/kigawa/renlin/state/DslStateData.kt` - 状態データと属性管理
101+
- `renlin/src/commonMain/kotlin/net/kigawa/renlin/w3c/element/TagNodeCommon.kt` - プラットフォーム間共通のDOM抽象化
102+
- `renlin/src/jsMain/kotlin/net/kigawa/renlin/w3c/element/DomTagElement.kt` - ブラウザ用DOM実装
81103
- `renlin/src/jsMain/kotlin/net/kigawa/renlin/Entrypoint.kt` - ブラウザエントリーポイント
82104
- `sample/src/jsMain/kotlin/net/kigawa/renlin/sample/Main.kt` - 使用例
83105

84106
## ステート管理統合
85107

86-
ライブラリはステート管理にHakateが必要です。コンポーネントは`MutableState<T>`を通じてリアクティブステートにアクセスし、`useValue()`を介して再レンダリングをトリガーします。ステートの変更は自動的にコンポーネントツリー全体に伝播されます。
108+
ライブラリはステート管理にHakateが必要です。コンポーネントは`MutableState<T>`を通じてリアクティブステートにアクセスし、`useValue()`を介して再レンダリングをトリガーします。ステートの変更は自動的にコンポーネントツリー全体に伝播されます。
109+
110+
## 拡張とカスタマイズ
111+
112+
### 新しい属性の追加
113+
1. `DslStateData`にプロパティを追加
114+
2. `TagNodeCommon.setDslStateData`で属性をDOMに適用するロジックを追加
115+
3. 対象DSLクラス用の拡張プロパティを`w3c/attribute/`に作成
116+
117+
### 新しいHTMLタグの追加
118+
1. `generate/`モジュールのコード生成を使用するか、手動でタグクラスを作成
119+
2. 適切なW3Cコンテンツカテゴリに従ってDSLクラスを実装
120+
3. プラットフォーム固有の実装が必要な場合は、各プラットフォームモジュールで対応

generate/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@ kotlin {
2626
}
2727

2828
}
29+
30+
tasks.register<JavaExec>("runGenerator") {
31+
group = "application"
32+
description = "Run the code generator"
33+
dependsOn("jvmMainClasses")
34+
classpath = kotlin.targets["jvm"].compilations["main"].output.allOutputs + kotlin.targets["jvm"].compilations["main"].runtimeDependencyFiles
35+
mainClass.set("_Tag_generateKt")
36+
}

generate/src/jvmMain/kotlin/_Tag_generate.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ fun main() {
2727
it.generate()
2828
}
2929
val nativeGenerator = NativeGenerator(categoryNativeOutputDir).also {
30-
it.generate(integrationGenerator.nativeCategories)
30+
it.generate()
3131
}
3232

3333
println("タグのコード生成が完了しました。")

generate/src/jvmMain/kotlin/generator/DslGenerator.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ class DslGenerator(
2929
${
3030
if (allowedCategories.categories.size > 1)
3131
"import net.kigawa.renlin.w3c.category.integration.${
32-
allowedCategories.connectedStr("Integration")
32+
allowedCategories.connectedStr()
3333
}"
3434
else "import net.kigawa.renlin.w3c.category.native.${
35-
allowedCategories.connectedStr("Integration")
35+
allowedCategories.connectedStr()
3636
}"
3737
}
3838
3939
4040
/**
4141
* DSL for ${categories.joinToString(", ")}
4242
*/
43-
interface ${dslName}<CATEGORY_DSL : ${allowedCategories.connectedStr("Integration")}>${
43+
interface ${dslName}<CATEGORY_DSL : ${allowedCategories.connectedStr()}>${
4444
if (categories.size <= 1) ""
4545
else (categories.filter { it.trim() != dslName.trim() }
4646
.joinToString(separator = ",", prefix = ":")

generate/src/jvmMain/kotlin/generator/IntegrationGenerator.kt

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class IntegrationGenerator(
1717
.filter { it.categories.size > 1 }
1818
.toSet()
1919
.forEach { tagCategories ->
20-
val integrationName = tagCategories.connectedStr("Integration")
20+
val integrationName = tagCategories.connectedStr()
2121
if (!processedIntegrations.contains(integrationName)) {
2222
processedIntegrations.add(integrationName)
2323
allIntegrations[integrationName] = tagCategories.categories.toSet()
@@ -28,8 +28,20 @@ class IntegrationGenerator(
2828
}
2929
}
3030

31-
// Generate each integration with inheritance from subset integrations
31+
// タグのCategoryインターフェースを生成対象に含める
32+
val tagCategoryMap = mutableMapOf<String, Set<String>>()
33+
tagCategories.forEach { tagInfo ->
34+
val tagCategoryName = "${tagInfo.className}Category"
35+
tagCategoryMap[tagCategoryName] = setOf(tagInfo.tagCategories.connectedStr())
36+
}
37+
38+
// Generate each integration with inheritance from tag categories
3239
allIntegrations.forEach { (integrationName, categories) ->
40+
// Find tag categories that match this integration
41+
val matchingTagCategories = tagCategoryMap.filter { (_, integrationSet) ->
42+
integrationSet.contains(integrationName)
43+
}.keys
44+
3345
// Find other integrations that are subsets of this integration
3446
val subsetIntegrations = allIntegrations.filter { (otherName, otherCategories) ->
3547
otherName != integrationName &&
@@ -38,7 +50,7 @@ class IntegrationGenerator(
3850
otherCategories.size < categories.size
3951
}.keys
4052

41-
// Generate imports for categories and subset integrations
53+
// Generate imports for categories, subset integrations, and tag categories
4254
val categoryImports = categories.map { category ->
4355
"import net.kigawa.renlin.w3c.category.native.$category"
4456
}
@@ -47,10 +59,14 @@ class IntegrationGenerator(
4759
"import net.kigawa.renlin.w3c.category.integration.$subsetIntegration"
4860
}
4961

50-
val allImports = (categoryImports + integrationImports).joinToString("\n ")
62+
val tagCategoryImports = matchingTagCategories.map { tagCategory ->
63+
"import net.kigawa.renlin.w3c.category.native.$tagCategory"
64+
}
65+
66+
val allImports = (categoryImports + integrationImports + tagCategoryImports).joinToString("\n ")
5167

52-
// Generate inheritance list including categories and subset integrations
53-
val inheritance = (categories + subsetIntegrations + "ContentCategory").joinToString(", ")
68+
// Generate inheritance list including categories, subset integrations, and tag categories
69+
val inheritance = (categories + subsetIntegrations + matchingTagCategories + "ContentCategory").joinToString(", ")
5470

5571
val fileContent = """
5672
package net.kigawa.renlin.w3c.category.integration

generate/src/jvmMain/kotlin/generator/NativeGenerator.kt

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,114 @@ import tagCategories
55
import categoryParents
66

77
class NativeGenerator(val categoryNativeOutputDir: String) {
8-
fun generate(categories: Map<String, Set<String>>) {
8+
fun generate() {
9+
// 使用されているすべてのネイティブカテゴリを収集
10+
val usedNativeCategories = mutableSetOf<String>()
11+
tagCategories.forEach { tagInfo ->
12+
usedNativeCategories.addAll(tagInfo.tagCategories.categories)
13+
usedNativeCategories.addAll(tagInfo.allowedCategories.categories)
14+
// 各タグ専用のCategoryインターフェースも追加
15+
usedNativeCategories.add("${tagInfo.className}Category")
16+
}
17+
18+
// categoryParentsで定義されているがタグで使用されていないカテゴリも追加(EventTargetなど)
19+
categoryParents.keys.forEach { categoryName ->
20+
usedNativeCategories.add(categoryName)
21+
}
22+
923
// 親カテゴリーの情報を収集
1024
val allParentCategories = categoryParents.toMutableMap()
1125
tagCategories.forEach { tagInfo ->
1226
allParentCategories.putAll(tagInfo.tagCategories.parentCategories)
1327
allParentCategories.putAll(tagInfo.allowedCategories.parentCategories)
28+
// 各タグ専用のCategoryの親カテゴリーを設定(ContentCategoryのみ継承)
29+
allParentCategories["${tagInfo.className}Category"] = ""
30+
}
31+
32+
// FlowContent, PhrasingContentなどが継承すべきタグカテゴリを設定
33+
val categoryToTagCategories = mutableMapOf<String, MutableSet<String>>()
34+
tagCategories.forEach { tagInfo ->
35+
tagInfo.tagCategories.categories.forEach { category ->
36+
categoryToTagCategories.getOrPut(category) { mutableSetOf() }.add("${tagInfo.className}Category")
37+
}
1438
}
1539

16-
// Integrationクラスの生成
17-
categories.forEach { (name, deps) ->
18-
val categoryName = name
19-
40+
// ネイティブカテゴリの生成
41+
usedNativeCategories.forEach { categoryName ->
2042
// 親カテゴリーを取得
21-
val parentCategory = allParentCategories[name]
43+
val parentCategory = allParentCategories[categoryName]
2244

2345
// 継承するインターフェースのリスト
24-
val interfaces = if (parentCategory != null) {
25-
(deps + "ContentCategory" + parentCategory).joinToString(", ")
26-
} else {
27-
(deps + "ContentCategory").joinToString(", ")
46+
val interfaces = when {
47+
parentCategory == null -> "ContentCategory"
48+
parentCategory.isEmpty() -> "" // P, Divの場合は継承なし
49+
else -> "ContentCategory, $parentCategory"
2850
}
2951

30-
val fileContent = """
31-
package net.kigawa.renlin.w3c.category.native
52+
val fileContent = when {
53+
parentCategory?.isEmpty() == true && !categoryName.endsWith("Category") -> {
54+
// P, Div, EventTargetの場合
55+
if (categoryName == "EventTarget") {
56+
// EventTargetはContentCategoryを継承
57+
"""
58+
package net.kigawa.renlin.w3c.category.native
59+
60+
import net.kigawa.renlin.w3c.category.ContentCategory
61+
62+
interface $categoryName : ContentCategory
63+
""".trimIndent()
64+
} else {
65+
// P, Divの場合は基本インターフェースのみ
66+
"""
67+
package net.kigawa.renlin.w3c.category.native
68+
69+
interface $categoryName
70+
""".trimIndent()
71+
}
72+
}
73+
categoryName.endsWith("Category") -> {
74+
// タグ専用カテゴリはContentCategoryのみ継承
75+
"""
76+
package net.kigawa.renlin.w3c.category.native
3277
33-
import net.kigawa.renlin.w3c.category.ContentCategory
78+
import net.kigawa.renlin.w3c.category.ContentCategory
3479
35-
/**
36-
* Integration to ${deps.joinToString(", ")}
37-
* ${if (parentCategory != null) "Parent: $parentCategory" else ""}
38-
*/
39-
interface $categoryName : $interfaces
40-
""".trimIndent()
80+
/**
81+
* ${categoryName} represents elements that are part of the ${categoryName.replace("Category", "").lowercase()} content category.
82+
*/
83+
interface $categoryName : ContentCategory
84+
""".trimIndent()
85+
}
86+
else -> {
87+
// 標準カテゴリ(FlowContent、PhrasingContentなど)の場合、関連するタグカテゴリを継承
88+
val tagCategoriesToInherit = categoryToTagCategories[categoryName] ?: emptySet()
89+
val tagCategoryImports = tagCategoriesToInherit.map { "import net.kigawa.renlin.w3c.category.native.$it" }
90+
val allTagCategoriesInheritance = if (tagCategoriesToInherit.isNotEmpty()) {
91+
tagCategoriesToInherit.joinToString(", ")
92+
} else {
93+
""
94+
}
95+
96+
val finalInterfaces = if (allTagCategoriesInheritance.isNotEmpty()) {
97+
if (interfaces.isNotEmpty()) "$interfaces, $allTagCategoriesInheritance" else allTagCategoriesInheritance
98+
} else {
99+
interfaces
100+
}
101+
102+
"""
103+
package net.kigawa.renlin.w3c.category.native
104+
105+
import net.kigawa.renlin.w3c.category.ContentCategory
106+
${tagCategoryImports.joinToString("\n ")}
107+
108+
/**
109+
* ${categoryName} represents elements that are part of the ${categoryName.replace("Content", "").lowercase()} content category.
110+
* ${if (parentCategory != null && parentCategory.isNotEmpty()) "Parent: $parentCategory" else ""}
111+
*/
112+
interface $categoryName : $finalInterfaces
113+
""".trimIndent()
114+
}
115+
}
41116

42117
val file = File("$categoryNativeOutputDir/${categoryName}.kt")
43118
file.writeText(fileContent)

generate/src/jvmMain/kotlin/generator/TagGenerator.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,20 @@ class TagGenerator(
1515
if (tagInfo.tagCategories.categories.size > 1)
1616
imports.add(
1717
"import net.kigawa.renlin.w3c.category.integration.${
18-
tagInfo.tagCategories.connectedStr(
19-
"Integration"
20-
)
18+
tagInfo.tagCategories.connectedStr()
2119
}"
2220
)
2321
else imports.add("import net.kigawa.renlin.w3c.category.native.${tagInfo.tagCategories.connectedStr()}")
2422
if (tagInfo.allowedCategories.categories.size > 1)
2523
imports.add(
2624
"import net.kigawa.renlin.w3c.category.integration.${
27-
tagInfo.allowedCategories.connectedStr("Integration")
25+
tagInfo.allowedCategories.connectedStr()
2826
}"
2927
)
3028
else if (
3129
tagInfo.allowedCategories.categories.isNotEmpty() &&
32-
tagInfo.tagCategories.connectedStr("Integration") !=
33-
tagInfo.allowedCategories.connectedStr("Integration")
30+
tagInfo.tagCategories.connectedStr() !=
31+
tagInfo.allowedCategories.connectedStr()
3432
) imports.add(
3533
"import net.kigawa.renlin.w3c.category.native.${
3634
tagInfo.allowedCategories.connectedStr()
@@ -43,8 +41,10 @@ class TagGenerator(
4341
import net.kigawa.renlin.dsl.DslBase
4442
import net.kigawa.renlin.dsl.StatedDsl
4543
import net.kigawa.renlin.component.TagComponent1
44+
import net.kigawa.renlin.component.Component
4645
import net.kigawa.renlin.w3c.element.TagNode
4746
import net.kigawa.renlin.state.DslState
47+
import net.kigawa.renlin.w3c.category.native.${tagInfo.className}Category
4848
${
4949
if (tagInfo.allowedCategories.categories.isEmpty())
5050
"import net.kigawa.renlin.w3c.category.ContentCategory"
@@ -57,11 +57,11 @@ class TagGenerator(
5757
* model.Categories: ${tagInfo.tagCategories.categories.joinToString(", ")}
5858
*/
5959
class ${tagInfo.className}Dsl(dslState: DslState):
60-
DslBase<${tagInfo.allowedCategories.connectedStr("Integration")}>(dslState),
61-
StatedDsl<${tagInfo.allowedCategories.connectedStr("Integration")}>${
60+
DslBase<${tagInfo.allowedCategories.connectedStr()}>(dslState),
61+
StatedDsl<${tagInfo.allowedCategories.connectedStr()}>${
6262
if (tagInfo.allowedCategories.categories.isEmpty()) ""
6363
else ",\n ${tagInfo.allowedCategories.connectedStr()}" +
64-
"Dsl<${tagInfo.allowedCategories.connectedStr("Integration")}>"
64+
"Dsl<${tagInfo.allowedCategories.connectedStr()}>"
6565
} {
6666
override fun applyElement(element: TagNode): ()->Unit {
6767
return {}
@@ -70,7 +70,7 @@ class TagGenerator(
7070
7171
val ${tagInfo.escapement} = TagComponent1(${tagInfo.className}, ::${tagInfo.className}Dsl)
7272
73-
object ${tagInfo.className} : Tag<${tagInfo.tagCategories.connectedStr("Integration")}> {
73+
object ${tagInfo.className} : Tag<${tagInfo.className}Category> {
7474
override val name: String
7575
get() = "${tagInfo.name}"
7676
}

0 commit comments

Comments
 (0)