diff --git a/build.gradle.kts b/build.gradle.kts index 817e3de..26df9e3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,15 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { - kotlin("multiplatform") version "1.9.0" - `maven-publish` - signing + kotlin("multiplatform") version "1.9.22" + id("maven-publish") + id("signing") id("org.jetbrains.dokka") version "1.5.31" } group = "nl.astraeus" -version = "1.1.1" +version = "1.2.1" repositories { mavenCentral() @@ -22,17 +25,35 @@ } } } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + //moduleName = project.name + browser() + + mavenPublication { + groupId = group as String + pom { name = "${project.name}-wasm-js" } + } + } + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate { + common { + group("jsCommon") { + withJs() + // TODO: switch to `withWasmJs()` after upgrade to Kotlin 2.0 + withWasm() + } + } + } sourceSets { val commonMain by getting { dependencies { - api("org.jetbrains.kotlinx:kotlinx-html:0.9.1") + api("org.jetbrains.kotlinx:kotlinx-html:0.11.0") } } - val jsMain by getting { - dependencies { - } - } + val jsMain by getting val jsTest by getting { dependencies { implementation(kotlin("test-js")) @@ -150,3 +171,14 @@ dependsOn(tasks.named("signJsPublication")) } +tasks.named("publishKotlinMultiplatformPublicationToMavenLocal") { + dependsOn(tasks.named("signWasmJsPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signKotlinMultiplatformPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJsPublication")) +} diff --git a/build.gradle.kts b/build.gradle.kts index 817e3de..26df9e3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,15 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { - kotlin("multiplatform") version "1.9.0" - `maven-publish` - signing + kotlin("multiplatform") version "1.9.22" + id("maven-publish") + id("signing") id("org.jetbrains.dokka") version "1.5.31" } group = "nl.astraeus" -version = "1.1.1" +version = "1.2.1" repositories { mavenCentral() @@ -22,17 +25,35 @@ } } } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + //moduleName = project.name + browser() + + mavenPublication { + groupId = group as String + pom { name = "${project.name}-wasm-js" } + } + } + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate { + common { + group("jsCommon") { + withJs() + // TODO: switch to `withWasmJs()` after upgrade to Kotlin 2.0 + withWasm() + } + } + } sourceSets { val commonMain by getting { dependencies { - api("org.jetbrains.kotlinx:kotlinx-html:0.9.1") + api("org.jetbrains.kotlinx:kotlinx-html:0.11.0") } } - val jsMain by getting { - dependencies { - } - } + val jsMain by getting val jsTest by getting { dependencies { implementation(kotlin("test-js")) @@ -150,3 +171,14 @@ dependsOn(tasks.named("signJsPublication")) } +tasks.named("publishKotlinMultiplatformPublicationToMavenLocal") { + dependsOn(tasks.named("signWasmJsPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signKotlinMultiplatformPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJsPublication")) +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 554856b..a5d4f22 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Wed Mar 04 13:29:12 CET 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/build.gradle.kts b/build.gradle.kts index 817e3de..26df9e3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,15 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { - kotlin("multiplatform") version "1.9.0" - `maven-publish` - signing + kotlin("multiplatform") version "1.9.22" + id("maven-publish") + id("signing") id("org.jetbrains.dokka") version "1.5.31" } group = "nl.astraeus" -version = "1.1.1" +version = "1.2.1" repositories { mavenCentral() @@ -22,17 +25,35 @@ } } } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + //moduleName = project.name + browser() + + mavenPublication { + groupId = group as String + pom { name = "${project.name}-wasm-js" } + } + } + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate { + common { + group("jsCommon") { + withJs() + // TODO: switch to `withWasmJs()` after upgrade to Kotlin 2.0 + withWasm() + } + } + } sourceSets { val commonMain by getting { dependencies { - api("org.jetbrains.kotlinx:kotlinx-html:0.9.1") + api("org.jetbrains.kotlinx:kotlinx-html:0.11.0") } } - val jsMain by getting { - dependencies { - } - } + val jsMain by getting val jsTest by getting { dependencies { implementation(kotlin("test-js")) @@ -150,3 +171,14 @@ dependsOn(tasks.named("signJsPublication")) } +tasks.named("publishKotlinMultiplatformPublicationToMavenLocal") { + dependsOn(tasks.named("signWasmJsPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signKotlinMultiplatformPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJsPublication")) +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 554856b..a5d4f22 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Wed Mar 04 13:29:12 CET 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt b/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt new file mode 100644 index 0000000..ea34fc8 --- /dev/null +++ b/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt @@ -0,0 +1,64 @@ +package nl.astraeus.komp + +import org.w3c.dom.Node +import org.w3c.dom.get + +data class ElementIndex( + val parent: Node, + var childIndex: Int, + var setAttr: MutableSet = mutableSetOf() +) { + override fun toString(): String { + return "${parent.nodeName}[$childIndex]" + } +} + +fun ArrayList.currentParent(): Node { + this.lastOrNull()?.let { + return it.parent + } + + throw IllegalStateException("currentParent should never be null!") +} + +fun ArrayList.currentElement(): Node? { + this.lastOrNull()?.let { + return it.parent.childNodes[it.childIndex] + } + + return null +} + +fun ArrayList.currentPosition(): ElementIndex? { + return if (this.size < 2) { + null + } else { + this[this.size - 2] + } +} + +fun ArrayList.nextElement() { + this.lastOrNull()?.let { + it.setAttr.clear() + it.childIndex++ + } +} + +fun ArrayList.pop() { + this.removeLast() +} + +fun ArrayList.push(element: Node) { + this.add(ElementIndex(element, 0)) +} + +fun ArrayList.replace(new: Node) { + if (this.currentElement() != null) { + this.currentElement()?.parentElement?.replaceChild( + new, + this.currentElement()!! + ) + } else { + this.last().parent.appendChild(new) + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 817e3de..26df9e3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,15 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { - kotlin("multiplatform") version "1.9.0" - `maven-publish` - signing + kotlin("multiplatform") version "1.9.22" + id("maven-publish") + id("signing") id("org.jetbrains.dokka") version "1.5.31" } group = "nl.astraeus" -version = "1.1.1" +version = "1.2.1" repositories { mavenCentral() @@ -22,17 +25,35 @@ } } } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + //moduleName = project.name + browser() + + mavenPublication { + groupId = group as String + pom { name = "${project.name}-wasm-js" } + } + } + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate { + common { + group("jsCommon") { + withJs() + // TODO: switch to `withWasmJs()` after upgrade to Kotlin 2.0 + withWasm() + } + } + } sourceSets { val commonMain by getting { dependencies { - api("org.jetbrains.kotlinx:kotlinx-html:0.9.1") + api("org.jetbrains.kotlinx:kotlinx-html:0.11.0") } } - val jsMain by getting { - dependencies { - } - } + val jsMain by getting val jsTest by getting { dependencies { implementation(kotlin("test-js")) @@ -150,3 +171,14 @@ dependsOn(tasks.named("signJsPublication")) } +tasks.named("publishKotlinMultiplatformPublicationToMavenLocal") { + dependsOn(tasks.named("signWasmJsPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signKotlinMultiplatformPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJsPublication")) +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 554856b..a5d4f22 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Wed Mar 04 13:29:12 CET 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt b/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt new file mode 100644 index 0000000..ea34fc8 --- /dev/null +++ b/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt @@ -0,0 +1,64 @@ +package nl.astraeus.komp + +import org.w3c.dom.Node +import org.w3c.dom.get + +data class ElementIndex( + val parent: Node, + var childIndex: Int, + var setAttr: MutableSet = mutableSetOf() +) { + override fun toString(): String { + return "${parent.nodeName}[$childIndex]" + } +} + +fun ArrayList.currentParent(): Node { + this.lastOrNull()?.let { + return it.parent + } + + throw IllegalStateException("currentParent should never be null!") +} + +fun ArrayList.currentElement(): Node? { + this.lastOrNull()?.let { + return it.parent.childNodes[it.childIndex] + } + + return null +} + +fun ArrayList.currentPosition(): ElementIndex? { + return if (this.size < 2) { + null + } else { + this[this.size - 2] + } +} + +fun ArrayList.nextElement() { + this.lastOrNull()?.let { + it.setAttr.clear() + it.childIndex++ + } +} + +fun ArrayList.pop() { + this.removeLast() +} + +fun ArrayList.push(element: Node) { + this.add(ElementIndex(element, 0)) +} + +fun ArrayList.replace(new: Node) { + if (this.currentElement() != null) { + this.currentElement()?.parentElement?.replaceChild( + new, + this.currentElement()!! + ) + } else { + this.last().parent.appendChild(new) + } +} diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index 0bbec8c..6f2c640 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -7,7 +7,6 @@ import kotlinx.html.Tag import kotlinx.html.TagConsumer import kotlinx.html.Unsafe -import org.w3c.dom.events.Event import org.w3c.dom.Element import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLInputElement @@ -27,62 +26,6 @@ fun FlowOrMetaDataOrPhrasingContent.currentElement(): Element = currentElement ?: error("No current element defined!") -private data class ElementIndex( - val parent: Node, - var childIndex: Int, - var setAttr: MutableSet = mutableSetOf() -) - -private fun ArrayList.currentParent(): Node { - this.lastOrNull()?.let { - return it.parent - } - - throw IllegalStateException("currentParent should never be null!") -} - -private fun ArrayList.currentElement(): Node? { - this.lastOrNull()?.let { - return it.parent.childNodes[it.childIndex] - } - - return null -} - -private fun ArrayList.currentPosition(): ElementIndex? { - return if (this.size < 2) { - null - } else { - this[this.size - 2] - } -} - -private fun ArrayList.nextElement() { - this.lastOrNull()?.let { - it.setAttr.clear() - it.childIndex++ - } -} - -private fun ArrayList.pop() { - this.removeLast() -} - -private fun ArrayList.push(element: Node) { - this.add(ElementIndex(element, 0)) -} - -private fun ArrayList.replace(new: Node) { - if (this.currentElement() != null) { - this.currentElement()?.parentElement?.replaceChild( - new, - this.currentElement()!! - ) - } else { - this.last().parent.appendChild(new) - } -} - private fun Node.asElement() = this as? HTMLElement class HtmlBuilder( @@ -94,6 +37,7 @@ private var inDebug = false private var exceptionThrown = false private var currentNode: Node? = null + private var firstTag: Boolean = true var root: Element? = null init { @@ -112,6 +56,13 @@ ) } } else { + // current element should become parent +/* + val ce = komponent.element + if (ce != null) { + append(ce as Element) + } +*/ komponent.create( currentPosition.last().parent as Element, currentPosition.last().childIndex @@ -146,7 +97,7 @@ override fun onTagStart(tag: Tag) { logReplace { - "onTagStart, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition" + "onTagStart, [${tag.tagName}, ${tag.namespace ?: ""}], currentPosition: $currentPosition" } currentNode = currentPosition.currentElement() @@ -159,7 +110,7 @@ document.createElement(tag.tagName) } - //logReplace"onTagStart, currentElement1.1: $currentNode") + logReplace { "onTagStart, currentElement1.1: $currentNode" } currentPosition.currentParent().appendChild(currentNode!!) } else if ( !currentNode?.asElement()?.tagName.equals(tag.tagName, true) || @@ -187,17 +138,18 @@ currentElement = currentNode as? Element ?: currentElement if (currentNode is Element) { - if (root == null) { - //logReplace"Setting root: $currentNode") + if (firstTag) { + logReplace { "Setting root: $currentNode" } root = currentNode as Element + firstTag = false } currentElement?.clearKompEvents() + + // if currentElement = checkbox make sure it's cleared (currentElement as? HTMLInputElement)?.checked = false currentPosition.lastOrNull()?.setAttr?.clear() - - // if currentElement = checkbox make sure it's cleared for (entry in tag.attributesEntries) { currentElement!!.setKompAttribute(entry.key, entry.value) currentPosition.lastOrNull()?.setAttr?.add(entry.key) @@ -207,14 +159,14 @@ currentPosition.push(currentNode!!) } - private fun checkTag(tag: Tag) { + private fun checkTag(source: String, tag: Tag) { check(currentElement != null) { - js("debugger") - "No current tag" + js("debugger;") + "No current tag ($source)" } check(currentElement?.tagName.equals(tag.tagName, ignoreCase = true)) { - js("debugger") - "Wrong current tag" + js("debugger;") + "Wrong current tag ($source), got: ${tag.tagName} expected ${currentElement?.tagName}" } } @@ -226,7 +178,7 @@ logReplace { "onTagAttributeChange, ${tag.tagName} [$attribute, $value]" } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagAttributeChange", tag) } currentElement?.setKompAttribute(attribute, value) @@ -245,13 +197,17 @@ logReplace { "onTagEvent, ${tag.tagName} [$event, $value]" } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagEvent", tag) } currentElement?.setKompEvent(event.lowercase(), value.asDynamic()) } override fun onTagEnd(tag: Tag) { + logReplace { + "onTagEnd, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition" + } + if (exceptionThrown) { return } @@ -263,7 +219,7 @@ } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagEnd", tag) } if (currentElement != null) { diff --git a/build.gradle.kts b/build.gradle.kts index 817e3de..26df9e3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,15 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { - kotlin("multiplatform") version "1.9.0" - `maven-publish` - signing + kotlin("multiplatform") version "1.9.22" + id("maven-publish") + id("signing") id("org.jetbrains.dokka") version "1.5.31" } group = "nl.astraeus" -version = "1.1.1" +version = "1.2.1" repositories { mavenCentral() @@ -22,17 +25,35 @@ } } } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + //moduleName = project.name + browser() + + mavenPublication { + groupId = group as String + pom { name = "${project.name}-wasm-js" } + } + } + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate { + common { + group("jsCommon") { + withJs() + // TODO: switch to `withWasmJs()` after upgrade to Kotlin 2.0 + withWasm() + } + } + } sourceSets { val commonMain by getting { dependencies { - api("org.jetbrains.kotlinx:kotlinx-html:0.9.1") + api("org.jetbrains.kotlinx:kotlinx-html:0.11.0") } } - val jsMain by getting { - dependencies { - } - } + val jsMain by getting val jsTest by getting { dependencies { implementation(kotlin("test-js")) @@ -150,3 +171,14 @@ dependsOn(tasks.named("signJsPublication")) } +tasks.named("publishKotlinMultiplatformPublicationToMavenLocal") { + dependsOn(tasks.named("signWasmJsPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signKotlinMultiplatformPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJsPublication")) +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 554856b..a5d4f22 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Wed Mar 04 13:29:12 CET 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt b/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt new file mode 100644 index 0000000..ea34fc8 --- /dev/null +++ b/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt @@ -0,0 +1,64 @@ +package nl.astraeus.komp + +import org.w3c.dom.Node +import org.w3c.dom.get + +data class ElementIndex( + val parent: Node, + var childIndex: Int, + var setAttr: MutableSet = mutableSetOf() +) { + override fun toString(): String { + return "${parent.nodeName}[$childIndex]" + } +} + +fun ArrayList.currentParent(): Node { + this.lastOrNull()?.let { + return it.parent + } + + throw IllegalStateException("currentParent should never be null!") +} + +fun ArrayList.currentElement(): Node? { + this.lastOrNull()?.let { + return it.parent.childNodes[it.childIndex] + } + + return null +} + +fun ArrayList.currentPosition(): ElementIndex? { + return if (this.size < 2) { + null + } else { + this[this.size - 2] + } +} + +fun ArrayList.nextElement() { + this.lastOrNull()?.let { + it.setAttr.clear() + it.childIndex++ + } +} + +fun ArrayList.pop() { + this.removeLast() +} + +fun ArrayList.push(element: Node) { + this.add(ElementIndex(element, 0)) +} + +fun ArrayList.replace(new: Node) { + if (this.currentElement() != null) { + this.currentElement()?.parentElement?.replaceChild( + new, + this.currentElement()!! + ) + } else { + this.last().parent.appendChild(new) + } +} diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index 0bbec8c..6f2c640 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -7,7 +7,6 @@ import kotlinx.html.Tag import kotlinx.html.TagConsumer import kotlinx.html.Unsafe -import org.w3c.dom.events.Event import org.w3c.dom.Element import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLInputElement @@ -27,62 +26,6 @@ fun FlowOrMetaDataOrPhrasingContent.currentElement(): Element = currentElement ?: error("No current element defined!") -private data class ElementIndex( - val parent: Node, - var childIndex: Int, - var setAttr: MutableSet = mutableSetOf() -) - -private fun ArrayList.currentParent(): Node { - this.lastOrNull()?.let { - return it.parent - } - - throw IllegalStateException("currentParent should never be null!") -} - -private fun ArrayList.currentElement(): Node? { - this.lastOrNull()?.let { - return it.parent.childNodes[it.childIndex] - } - - return null -} - -private fun ArrayList.currentPosition(): ElementIndex? { - return if (this.size < 2) { - null - } else { - this[this.size - 2] - } -} - -private fun ArrayList.nextElement() { - this.lastOrNull()?.let { - it.setAttr.clear() - it.childIndex++ - } -} - -private fun ArrayList.pop() { - this.removeLast() -} - -private fun ArrayList.push(element: Node) { - this.add(ElementIndex(element, 0)) -} - -private fun ArrayList.replace(new: Node) { - if (this.currentElement() != null) { - this.currentElement()?.parentElement?.replaceChild( - new, - this.currentElement()!! - ) - } else { - this.last().parent.appendChild(new) - } -} - private fun Node.asElement() = this as? HTMLElement class HtmlBuilder( @@ -94,6 +37,7 @@ private var inDebug = false private var exceptionThrown = false private var currentNode: Node? = null + private var firstTag: Boolean = true var root: Element? = null init { @@ -112,6 +56,13 @@ ) } } else { + // current element should become parent +/* + val ce = komponent.element + if (ce != null) { + append(ce as Element) + } +*/ komponent.create( currentPosition.last().parent as Element, currentPosition.last().childIndex @@ -146,7 +97,7 @@ override fun onTagStart(tag: Tag) { logReplace { - "onTagStart, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition" + "onTagStart, [${tag.tagName}, ${tag.namespace ?: ""}], currentPosition: $currentPosition" } currentNode = currentPosition.currentElement() @@ -159,7 +110,7 @@ document.createElement(tag.tagName) } - //logReplace"onTagStart, currentElement1.1: $currentNode") + logReplace { "onTagStart, currentElement1.1: $currentNode" } currentPosition.currentParent().appendChild(currentNode!!) } else if ( !currentNode?.asElement()?.tagName.equals(tag.tagName, true) || @@ -187,17 +138,18 @@ currentElement = currentNode as? Element ?: currentElement if (currentNode is Element) { - if (root == null) { - //logReplace"Setting root: $currentNode") + if (firstTag) { + logReplace { "Setting root: $currentNode" } root = currentNode as Element + firstTag = false } currentElement?.clearKompEvents() + + // if currentElement = checkbox make sure it's cleared (currentElement as? HTMLInputElement)?.checked = false currentPosition.lastOrNull()?.setAttr?.clear() - - // if currentElement = checkbox make sure it's cleared for (entry in tag.attributesEntries) { currentElement!!.setKompAttribute(entry.key, entry.value) currentPosition.lastOrNull()?.setAttr?.add(entry.key) @@ -207,14 +159,14 @@ currentPosition.push(currentNode!!) } - private fun checkTag(tag: Tag) { + private fun checkTag(source: String, tag: Tag) { check(currentElement != null) { - js("debugger") - "No current tag" + js("debugger;") + "No current tag ($source)" } check(currentElement?.tagName.equals(tag.tagName, ignoreCase = true)) { - js("debugger") - "Wrong current tag" + js("debugger;") + "Wrong current tag ($source), got: ${tag.tagName} expected ${currentElement?.tagName}" } } @@ -226,7 +178,7 @@ logReplace { "onTagAttributeChange, ${tag.tagName} [$attribute, $value]" } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagAttributeChange", tag) } currentElement?.setKompAttribute(attribute, value) @@ -245,13 +197,17 @@ logReplace { "onTagEvent, ${tag.tagName} [$event, $value]" } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagEvent", tag) } currentElement?.setKompEvent(event.lowercase(), value.asDynamic()) } override fun onTagEnd(tag: Tag) { + logReplace { + "onTagEnd, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition" + } + if (exceptionThrown) { return } @@ -263,7 +219,7 @@ } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagEnd", tag) } if (currentElement != null) { diff --git a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt index 56f1e43..c4cffda 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt @@ -111,8 +111,25 @@ * * HTMLBuilder.render() is called 1st time the component is rendered, after that this * method will be called + * + * @deprecated */ - open fun update() { + @Deprecated( + "Deprecated to avoid confusing with requestUpdate, use renderUpdate instead", + ReplaceWith("renderUpdate"), + level = DeprecationLevel.WARNING + ) + protected fun update() { + refresh() + } + + /** + * This function can be overwritten if you know how to update the Komponent yourself + * + * HTMLBuilder.render() is called 1st time the component is rendered, after that this + * method will be called + */ + protected fun renderUpdate() { refresh() } @@ -228,7 +245,7 @@ if (next.memoizeChanged()) { next.onBeforeUpdate() - next.update() + next.renderUpdate() next.updateMemoizeHash() next.onAfterUpdate() } else if (logRenderEvent) { diff --git a/build.gradle.kts b/build.gradle.kts index 817e3de..26df9e3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,15 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { - kotlin("multiplatform") version "1.9.0" - `maven-publish` - signing + kotlin("multiplatform") version "1.9.22" + id("maven-publish") + id("signing") id("org.jetbrains.dokka") version "1.5.31" } group = "nl.astraeus" -version = "1.1.1" +version = "1.2.1" repositories { mavenCentral() @@ -22,17 +25,35 @@ } } } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + //moduleName = project.name + browser() + + mavenPublication { + groupId = group as String + pom { name = "${project.name}-wasm-js" } + } + } + + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate { + common { + group("jsCommon") { + withJs() + // TODO: switch to `withWasmJs()` after upgrade to Kotlin 2.0 + withWasm() + } + } + } sourceSets { val commonMain by getting { dependencies { - api("org.jetbrains.kotlinx:kotlinx-html:0.9.1") + api("org.jetbrains.kotlinx:kotlinx-html:0.11.0") } } - val jsMain by getting { - dependencies { - } - } + val jsMain by getting val jsTest by getting { dependencies { implementation(kotlin("test-js")) @@ -150,3 +171,14 @@ dependsOn(tasks.named("signJsPublication")) } +tasks.named("publishKotlinMultiplatformPublicationToMavenLocal") { + dependsOn(tasks.named("signWasmJsPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signKotlinMultiplatformPublication")) +} + +tasks.named("publishWasmJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJsPublication")) +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 554856b..a5d4f22 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Wed Mar 04 13:29:12 CET 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt b/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt new file mode 100644 index 0000000..ea34fc8 --- /dev/null +++ b/src/jsMain/kotlin/nl/astraeus/komp/ElementIndex.kt @@ -0,0 +1,64 @@ +package nl.astraeus.komp + +import org.w3c.dom.Node +import org.w3c.dom.get + +data class ElementIndex( + val parent: Node, + var childIndex: Int, + var setAttr: MutableSet = mutableSetOf() +) { + override fun toString(): String { + return "${parent.nodeName}[$childIndex]" + } +} + +fun ArrayList.currentParent(): Node { + this.lastOrNull()?.let { + return it.parent + } + + throw IllegalStateException("currentParent should never be null!") +} + +fun ArrayList.currentElement(): Node? { + this.lastOrNull()?.let { + return it.parent.childNodes[it.childIndex] + } + + return null +} + +fun ArrayList.currentPosition(): ElementIndex? { + return if (this.size < 2) { + null + } else { + this[this.size - 2] + } +} + +fun ArrayList.nextElement() { + this.lastOrNull()?.let { + it.setAttr.clear() + it.childIndex++ + } +} + +fun ArrayList.pop() { + this.removeLast() +} + +fun ArrayList.push(element: Node) { + this.add(ElementIndex(element, 0)) +} + +fun ArrayList.replace(new: Node) { + if (this.currentElement() != null) { + this.currentElement()?.parentElement?.replaceChild( + new, + this.currentElement()!! + ) + } else { + this.last().parent.appendChild(new) + } +} diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index 0bbec8c..6f2c640 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -7,7 +7,6 @@ import kotlinx.html.Tag import kotlinx.html.TagConsumer import kotlinx.html.Unsafe -import org.w3c.dom.events.Event import org.w3c.dom.Element import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLInputElement @@ -27,62 +26,6 @@ fun FlowOrMetaDataOrPhrasingContent.currentElement(): Element = currentElement ?: error("No current element defined!") -private data class ElementIndex( - val parent: Node, - var childIndex: Int, - var setAttr: MutableSet = mutableSetOf() -) - -private fun ArrayList.currentParent(): Node { - this.lastOrNull()?.let { - return it.parent - } - - throw IllegalStateException("currentParent should never be null!") -} - -private fun ArrayList.currentElement(): Node? { - this.lastOrNull()?.let { - return it.parent.childNodes[it.childIndex] - } - - return null -} - -private fun ArrayList.currentPosition(): ElementIndex? { - return if (this.size < 2) { - null - } else { - this[this.size - 2] - } -} - -private fun ArrayList.nextElement() { - this.lastOrNull()?.let { - it.setAttr.clear() - it.childIndex++ - } -} - -private fun ArrayList.pop() { - this.removeLast() -} - -private fun ArrayList.push(element: Node) { - this.add(ElementIndex(element, 0)) -} - -private fun ArrayList.replace(new: Node) { - if (this.currentElement() != null) { - this.currentElement()?.parentElement?.replaceChild( - new, - this.currentElement()!! - ) - } else { - this.last().parent.appendChild(new) - } -} - private fun Node.asElement() = this as? HTMLElement class HtmlBuilder( @@ -94,6 +37,7 @@ private var inDebug = false private var exceptionThrown = false private var currentNode: Node? = null + private var firstTag: Boolean = true var root: Element? = null init { @@ -112,6 +56,13 @@ ) } } else { + // current element should become parent +/* + val ce = komponent.element + if (ce != null) { + append(ce as Element) + } +*/ komponent.create( currentPosition.last().parent as Element, currentPosition.last().childIndex @@ -146,7 +97,7 @@ override fun onTagStart(tag: Tag) { logReplace { - "onTagStart, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition" + "onTagStart, [${tag.tagName}, ${tag.namespace ?: ""}], currentPosition: $currentPosition" } currentNode = currentPosition.currentElement() @@ -159,7 +110,7 @@ document.createElement(tag.tagName) } - //logReplace"onTagStart, currentElement1.1: $currentNode") + logReplace { "onTagStart, currentElement1.1: $currentNode" } currentPosition.currentParent().appendChild(currentNode!!) } else if ( !currentNode?.asElement()?.tagName.equals(tag.tagName, true) || @@ -187,17 +138,18 @@ currentElement = currentNode as? Element ?: currentElement if (currentNode is Element) { - if (root == null) { - //logReplace"Setting root: $currentNode") + if (firstTag) { + logReplace { "Setting root: $currentNode" } root = currentNode as Element + firstTag = false } currentElement?.clearKompEvents() + + // if currentElement = checkbox make sure it's cleared (currentElement as? HTMLInputElement)?.checked = false currentPosition.lastOrNull()?.setAttr?.clear() - - // if currentElement = checkbox make sure it's cleared for (entry in tag.attributesEntries) { currentElement!!.setKompAttribute(entry.key, entry.value) currentPosition.lastOrNull()?.setAttr?.add(entry.key) @@ -207,14 +159,14 @@ currentPosition.push(currentNode!!) } - private fun checkTag(tag: Tag) { + private fun checkTag(source: String, tag: Tag) { check(currentElement != null) { - js("debugger") - "No current tag" + js("debugger;") + "No current tag ($source)" } check(currentElement?.tagName.equals(tag.tagName, ignoreCase = true)) { - js("debugger") - "Wrong current tag" + js("debugger;") + "Wrong current tag ($source), got: ${tag.tagName} expected ${currentElement?.tagName}" } } @@ -226,7 +178,7 @@ logReplace { "onTagAttributeChange, ${tag.tagName} [$attribute, $value]" } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagAttributeChange", tag) } currentElement?.setKompAttribute(attribute, value) @@ -245,13 +197,17 @@ logReplace { "onTagEvent, ${tag.tagName} [$event, $value]" } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagEvent", tag) } currentElement?.setKompEvent(event.lowercase(), value.asDynamic()) } override fun onTagEnd(tag: Tag) { + logReplace { + "onTagEnd, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition" + } + if (exceptionThrown) { return } @@ -263,7 +219,7 @@ } if (Komponent.enableAssertions) { - checkTag(tag) + checkTag("onTagEnd", tag) } if (currentElement != null) { diff --git a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt index 56f1e43..c4cffda 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt @@ -111,8 +111,25 @@ * * HTMLBuilder.render() is called 1st time the component is rendered, after that this * method will be called + * + * @deprecated */ - open fun update() { + @Deprecated( + "Deprecated to avoid confusing with requestUpdate, use renderUpdate instead", + ReplaceWith("renderUpdate"), + level = DeprecationLevel.WARNING + ) + protected fun update() { + refresh() + } + + /** + * This function can be overwritten if you know how to update the Komponent yourself + * + * HTMLBuilder.render() is called 1st time the component is rendered, after that this + * method will be called + */ + protected fun renderUpdate() { refresh() } @@ -228,7 +245,7 @@ if (next.memoizeChanged()) { next.onBeforeUpdate() - next.update() + next.renderUpdate() next.updateMemoizeHash() next.onAfterUpdate() } else if (logRenderEvent) { diff --git a/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt b/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt index 2e13289..9820b0a 100644 --- a/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt +++ b/src/jsTest/kotlin/nl/astraeus/komp/TestUpdate.kt @@ -1,6 +1,7 @@ package nl.astraeus.komp import kotlinx.browser.document +import kotlinx.html.DIV import kotlinx.html.InputType import kotlinx.html.classes import kotlinx.html.div @@ -125,7 +126,7 @@ } class ReplaceKomponent : Komponent() { - val includeKomponent = IncludeKomponent() + val includeKomponent = IncludeKomponent("Other text") var includeSpan = true override fun generateMemoizeHash(): Int = includeSpan.hashCode() * 7 + includeKomponent.generateMemoizeHash() @@ -136,20 +137,8 @@ div { if (includeSpan) { - span { - i("fas fa-eye") { - +"span1" - } - } - span { - i("fas fa-eye") { - +"span2" - } - } - span { - i("fas fa-eye") { - +"span3" - } + for (index in 0 ..< 3) { + extracted(index) } } @@ -157,6 +146,14 @@ } } } + + private fun HtmlBuilder.extracted(index: Int) { + span { + i("fas fa-eye") { + + ("span" + (index+1)) + } + } + } } class TestUpdate {