diff --git a/build.gradle.kts b/build.gradle.kts
index 99db642..be0cea5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,10 +1,11 @@
+
plugins {
- kotlin("multiplatform") version "1.3.71"
+ kotlin("multiplatform") version "1.4.30"
`maven-publish`
}
group = "nl.astraeus"
-version = "0.1.21-SNAPSHOT"
+version = "0.2.5-SNAPSHOT"
repositories {
mavenCentral()
@@ -15,7 +16,7 @@ kotlin {
/* Targets configuration omitted.
* To find out how to configure the targets, please follow the link:
* https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets */
- js {
+ js(BOTH) {
browser {
//produceKotlinLibrary()
testTask {
@@ -31,14 +32,12 @@ kotlin {
dependencies {
implementation(kotlin("stdlib-common"))
- //implementation("org.jetbrains.kotlinx:kotlinx-html:0.7.2-build-1711")
+ api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2")
}
}
val jsMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
-
- api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1")
}
}
val jsTest by getting {
diff --git a/komp.commonMain.iml b/komp.commonMain.iml
new file mode 100644
index 0000000..72350c6
--- /dev/null
+++ b/komp.commonMain.iml
@@ -0,0 +1,38 @@
+
+
+
+
+
+ SOURCE_SET_HOLDER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/komp.commonTest.iml b/komp.commonTest.iml
new file mode 100644
index 0000000..719f2ec
--- /dev/null
+++ b/komp.commonTest.iml
@@ -0,0 +1,44 @@
+
+
+
+
+
+ SOURCE_SET_HOLDER
+
+ jsLegacyBrowserTest|komp:jsTest|jsLegacy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/komp.iml b/komp.iml
index b4be323..6f0c251 100644
--- a/komp.iml
+++ b/komp.iml
@@ -1,5 +1,5 @@
-
+
diff --git a/komp.ipr b/komp.ipr
index 46cb162..fbae6c3 100644
--- a/komp.ipr
+++ b/komp.ipr
@@ -13,6 +13,48 @@
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
@@ -75,8 +117,6 @@
-
-
@@ -216,7 +256,7 @@
-
+
@@ -254,6 +294,11 @@
+
+
+
+
+
diff --git a/komp.jsMain.iml b/komp.jsMain.iml
new file mode 100644
index 0000000..59aae30
--- /dev/null
+++ b/komp.jsMain.iml
@@ -0,0 +1,59 @@
+
+
+
+
+
+ komp:commonMain
+
+ komp.commonMain
+
+ COMPILATION_AND_SOURCE_SET_HOLDER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/komp.jsTest.iml b/komp.jsTest.iml
new file mode 100644
index 0000000..e8b019c
--- /dev/null
+++ b/komp.jsTest.iml
@@ -0,0 +1,70 @@
+
+
+
+
+
+ komp:commonTest
+
+ komp.commonTest
+ komp.jsMain
+ komp.commonMain
+
+ COMPILATION_AND_SOURCE_SET_HOLDER
+
+ jsLegacyBrowserTest|komp:jsTest|jsLegacy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/komp_main.iml b/komp_main.iml
deleted file mode 100644
index c3b60db..0000000
--- a/komp_main.iml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
- COMPILATION_AND_SOURCE_SET_HOLDER
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/komp_test.iml b/komp_test.iml
deleted file mode 100644
index ccc6890..0000000
--- a/komp_test.iml
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
- komp_main
-
- COMPILATION_AND_SOURCE_SET_HOLDER
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt
index 8d1f17d..a331b87 100644
--- a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt
+++ b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt
@@ -1,11 +1,7 @@
package nl.astraeus.komp
-import org.w3c.dom.HTMLElement
-import org.w3c.dom.HTMLInputElement
-import org.w3c.dom.Node
-import org.w3c.dom.NodeList
+import org.w3c.dom.*
import org.w3c.dom.events.Event
-import org.w3c.dom.get
const val HASH_VALUE = "komp-hash-value"
@@ -34,10 +30,20 @@ object DiffPatch {
fun hashesMatch(oldNode: Node, newNode: Node): Boolean {
return (
oldNode is HTMLElement &&
- newNode is HTMLElement &&
- oldNode.nodeName == newNode.nodeName &&
- oldNode.getKompHash() == newNode.getKompHash()
- )
+ newNode is HTMLElement &&
+ oldNode.nodeName == newNode.nodeName &&
+ oldNode.getKompHash() == newNode.getKompHash()
+ )
+ }
+
+ private fun updateKomponentOnNode(oldNode: Node, newNode: Node) {
+ val komponent = newNode.asDynamic()[KOMP_KOMPONENT] as? Komponent
+ if (komponent != null) {
+ if (Komponent.logReplaceEvent) {
+ console.log("Keeping oldNode, set oldNode element on Komponent", oldNode, komponent)
+ }
+ komponent.element = oldNode
+ }
}
fun updateNode(oldNode: Node, newNode: Node): Node {
@@ -45,6 +51,8 @@ object DiffPatch {
if (Komponent.logReplaceEvent) {
console.log("Hashes match", oldNode, newNode, oldNode.getKompHash(), newNode.getKompHash())
}
+
+ updateKomponentOnNode(oldNode, newNode)
return oldNode
}
@@ -55,6 +63,8 @@ object DiffPatch {
}
oldNode.textContent = newNode.textContent
}
+
+ updateKomponentOnNode(oldNode, newNode)
return oldNode
}
@@ -73,6 +83,8 @@ object DiffPatch {
}
updateChildren(oldNode, newNode)
oldNode.setKompHash(newNode.getKompHash())
+
+ updateKomponentOnNode(oldNode, newNode)
return oldNode
}
}
diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt
index d9ce14b..d4f44b4 100644
--- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt
+++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt
@@ -1,13 +1,13 @@
package nl.astraeus.komp
+import kotlinx.browser.document
import kotlinx.html.*
import org.w3c.dom.*
import org.w3c.dom.css.CSSStyleDeclaration
import org.w3c.dom.events.Event
-import kotlin.browser.document
@Suppress("NOTHING_TO_INLINE")
-inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit {
+inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit) {
val eventName = if (name.startsWith("on")) {
name.substring(2)
} else {
@@ -185,7 +185,7 @@ class HtmlBuilder(
throw IllegalStateException("No current DOM node")
}
- // stupid hack as browsers doesn't support createEntityReference
+ // stupid hack as browsers don't support createEntityReference
val s = document.createElement("span") as HTMLElement
s.innerHTML = entity.text
path.last().appendChild(s.childNodes.asList().first { it.nodeType == Node.TEXT_NODE })
diff --git a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt
index d9baf82..65d4a44 100644
--- a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt
+++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt
@@ -1,14 +1,38 @@
package nl.astraeus.komp
+import kotlinx.browser.document
+import kotlinx.browser.window
import kotlinx.html.div
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.Node
import org.w3c.dom.css.CSSStyleDeclaration
-import kotlin.browser.document
+import kotlin.reflect.KProperty
+
+const val KOMP_KOMPONENT = "komp-komponent"
typealias CssStyle = CSSStyleDeclaration.() -> Unit
+class StateDelegate(
+ val komponent: Komponent,
+ initialValue: T
+) {
+ var value: T = initialValue
+
+ operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
+ return value
+ }
+
+ operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+ if (this.value?.equals(value) != true) {
+ this.value = value
+ komponent.requestUpdate()
+ }
+ }
+}
+
+inline fun Komponent.state(initialValue: T): StateDelegate = StateDelegate(this, initialValue)
+
fun HtmlConsumer.include(component: Komponent) {
if (Komponent.updateStrategy == UpdateStrategy.REPLACE) {
if (component.element != null) {
@@ -39,6 +63,9 @@ enum class UpdateStrategy {
}
abstract class Komponent {
+ private var createIndex = getNextCreateIndex()
+ private var dirty: Boolean = true
+
var element: Node? = null
val declaredStyles: MutableMap = HashMap()
@@ -47,13 +74,25 @@ abstract class Komponent {
consumer.render()
val result = consumer.finalize()
+ if (result.id.isBlank()) {
+ result.id = "komp_${createIndex}"
+ }
+
element = result
+ element.asDynamic()[KOMP_KOMPONENT] = this
+
+ dirty = false
return result
}
abstract fun HtmlBuilder.render()
+ fun requestUpdate() {
+ dirty = true
+ scheduleForUpdate(this)
+ }
+
open fun style(className: String, vararg imports: CssStyle, block: CssStyle = {}) {
val style = (document.createElement("div") as HTMLDivElement).style
for (imp in imports) {
@@ -63,33 +102,41 @@ abstract class Komponent {
declaredStyles[className] = style
}
- open fun update() = refresh()
+ open fun update() {
+ refresh()
+ }
- open fun refresh() {
+ internal fun refresh() {
val oldElement = element
+
if (logRenderEvent) {
console.log("Rendering", this)
}
val newElement = create()
if (oldElement != null) {
- if (updateStrategy == UpdateStrategy.REPLACE) {
+ element = if (updateStrategy == UpdateStrategy.REPLACE) {
if (logReplaceEvent) {
console.log("Replacing", oldElement, newElement)
}
oldElement.parentNode?.replaceChild(newElement, oldElement)
- element = newElement
+ newElement
} else {
if (logReplaceEvent) {
console.log("DomDiffing", oldElement, newElement)
}
- element = DiffPatch.updateNode(oldElement, newElement)
+ DiffPatch.updateNode(oldElement, newElement)
}
}
+
+ dirty = false
}
@JsName("remove")
fun remove() {
+ check(updateStrategy == UpdateStrategy.REPLACE) {
+ "remote only works with UpdateStrategy.REPLACE"
+ }
element?.let {
val parent = it.parentElement ?: throw IllegalArgumentException("Element has no parent!?")
@@ -102,6 +149,10 @@ abstract class Komponent {
}
companion object {
+ private var nextCreateIndex: Int = 1
+ private var updateCallback: Int? = null
+ private var scheduledForUpdate = mutableSetOf()
+
var logRenderEvent = false
var logReplaceEvent = false
var updateStrategy = UpdateStrategy.DOM_DIFF
@@ -115,6 +166,49 @@ abstract class Komponent {
parent.appendChild(element)
}
}
+
+ private fun getNextCreateIndex() = nextCreateIndex++
+
+ private fun scheduleForUpdate(komponent: Komponent) {
+ scheduledForUpdate.add(komponent)
+
+ if (updateCallback == null) {
+ window.setTimeout({
+ runUpdate()
+ }, 0)
+ }
+ }
+
+ private fun runUpdate() {
+ val todo = scheduledForUpdate.sortedBy { komponent -> komponent.createIndex }
+
+ if (logRenderEvent) {
+ console.log("runUpdate")
+ }
+
+ todo.forEach { next ->
+ val element = next.element
+ console.log("update element", element)
+ if (element is HTMLElement) {
+ console.log("by id", document.getElementById(element.id))
+ if (document.getElementById(element.id) != null) {
+ if (next.dirty) {
+ if (logRenderEvent) {
+ console.log("Update dirty ${next.createIndex}")
+ }
+ next.update()
+ } else {
+ if (logRenderEvent) {
+ console.log("Skip ${next.createIndex}")
+ }
+ }
+ }
+ }
+ }
+
+ scheduledForUpdate.clear()
+ updateCallback = null
+ }
}
}