diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index eb5b122..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,82 +0,0 @@
-buildscript {
- ext.kotlin_version = '1.3.70'
- repositories {
- maven {
- url "http://nexus.astraeus.nl/nexus/content/groups/public"
- }
- mavenCentral()
- }
- dependencies {
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
-}
-
-plugins {
- id("org.jetbrains.kotlin.js") version "1.3.70"
-}
-
-apply plugin: 'idea'
-apply plugin: 'maven'
-apply plugin: 'maven-publish'
-
-group 'nl.astraeus'
-version '0.1.17-SNAPSHOT'
-/*
-
-kotlin {
- target {
- browser {
- webpackTask {
- output.libraryTarget = "umd"
- }
- }
- }
-}
-
-compileKotlinJs.kotlinOptions.moduleKind = "umd"
-*/
-
-idea {
- module {
- name = "komp"
- }
-}
-
-repositories {
- maven {
- url "http://nexus.astraeus.nl/nexus/content/groups/public"
- }
- mavenCentral()
-}
-
-ext {
- kotlin_version = '1.3.70'
-}
-
-dependencies {
- compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
- compile 'org.jetbrains.kotlinx:kotlinx-html-js:0.7.1'
-}
-
-uploadArchives {
- //println 'user: ' + nexusUsername
- repositories {
- mavenDeployer {
- repository(url: "http://nexus.astraeus.nl/nexus/content/repositories/releases") {
- authentication(userName: nexusUsername, password: nexusPassword)
- }
- snapshotRepository(url: "http://nexus.astraeus.nl/nexus/content/repositories/snapshots") {
- authentication(userName: nexusUsername, password: nexusPassword)
- }
- }
- }
-}
-
-/*
-compileKotlin2Js {
- kotlinOptions.sourceMap = true
- kotlinOptions.sourceMapEmbedSources = "always"
-
- // remaining configuration options
-}
-*/
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..0483079
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,77 @@
+plugins {
+ kotlin("multiplatform") version "1.4-M2-eap-68"
+ `maven-publish`
+}
+
+group = "nl.astraeus"
+version = "0.1.20-SNAPSHOT"
+
+repositories {
+ maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
+ mavenCentral()
+ maven {
+ url = uri("https://dl.bintray.com/kotlin/kotlin-dev")
+ }
+}
+
+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 {
+ browser {
+ //produceKotlinLibrary()
+ }
+ }
+
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ implementation(kotlin("stdlib-common"))
+
+ //implementation("org.jetbrains.kotlinx:kotlinx-html:0.7.2-build-1711")
+ }
+ }
+ val jsMain by getting {
+ dependencies {
+ implementation(kotlin("stdlib-js"))
+
+ api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2-build-1716")
+ }
+ }
+ }
+}
+
+publishing {
+ repositories {
+ maven {
+ name = "releases"
+ // change to point to your repo, e.g. http://my.org/repo
+ url = uri("http://nexus.astraeus.nl/nexus/content/repositories/releases")
+ credentials {
+ val nexusUsername: String by project
+ val nexusPassword: String by project
+
+ username = nexusUsername
+ password = nexusPassword
+ }
+ }
+ maven {
+ name = "snapshots"
+ // change to point to your repo, e.g. http://my.org/repo
+ url = uri("http://nexus.astraeus.nl/nexus/content/repositories/snapshots")
+ credentials {
+ val nexusUsername: String by project
+ val nexusPassword: String by project
+
+ username = nexusUsername
+ password = nexusPassword
+ }
+ }
+ }
+ publications {
+ val kotlinMultiplatform by getting {
+ //artifactId = "kotlin-css-generator"
+ }
+ }
+}
diff --git a/komp.iml b/komp.iml
index 22881c0..910794e 100644
--- a/komp.iml
+++ b/komp.iml
@@ -1,41 +1,12 @@
-
-
-
-
- $MODULE_DIR$/build/classes/kotlin/test/komp_test.js
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
\ No newline at end of file
diff --git a/komp.ipr b/komp.ipr
index 503d6d5..46cb162 100644
--- a/komp.ipr
+++ b/komp.ipr
@@ -27,6 +27,9 @@
+
+
+
@@ -218,7 +221,7 @@
-
+
diff --git a/komp_test.iml b/komp_test.iml
index 65742cb..ccc6890 100644
--- a/komp_test.iml
+++ b/komp_test.iml
@@ -39,6 +39,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -48,45 +59,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index fadcfdf..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,3 +0,0 @@
-rootProject.name = 'komp'
-
-enableFeaturePreview('GRADLE_METADATA')
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..812c733
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+
+ maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
+
+ maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
+
+ mavenCentral()
+
+ maven { setUrl("https://plugins.gradle.org/m2/") }
+ }
+}
+
+rootProject.name = "komp"
+
+enableFeaturePreview("GRADLE_METADATA")
diff --git a/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt
new file mode 100644
index 0000000..5e934dd
--- /dev/null
+++ b/src/jsMain/kotlin/nl/astraeus/komp/DiffPatch.kt
@@ -0,0 +1,195 @@
+package nl.astraeus.komp
+
+import org.w3c.dom.HTMLElement
+import org.w3c.dom.Node
+import org.w3c.dom.events.Event
+import org.w3c.dom.get
+
+object DiffPatch {
+
+ fun updateNode(oldNode: Node, newNode: Node): Node {
+ if (oldNode is HTMLElement && newNode is HTMLElement) {
+ if (oldNode.nodeName == newNode.nodeName) {
+ if (oldNode.getAttribute("data-komp-hash") != null &&
+ oldNode.getAttribute("data-komp-hash") == newNode.getAttribute("data-komp-hash")) {
+
+ if (Komponent.logReplaceEvent) {
+ console.log("Skip node, hash equals", oldNode, newNode)
+ }
+
+ return oldNode
+ } else {
+ if (Komponent.logReplaceEvent) {
+ console.log("Update attributes", oldNode.innerHTML, newNode.innerHTML)
+ }
+ updateAttributes(oldNode, newNode);
+ if (Komponent.logReplaceEvent) {
+ console.log("Update children", oldNode.innerHTML, newNode.innerHTML)
+ }
+ updateChildren(oldNode, newNode)
+ updateEvents(oldNode, newNode)
+ return oldNode
+ }
+ } else {
+ if (Komponent.logReplaceEvent) {
+ console.log("Replace node ee", oldNode.innerHTML, newNode.innerHTML)
+ }
+ replaceNode(oldNode, newNode)
+ return newNode
+ }
+ } else {
+ if (oldNode.nodeType == newNode.nodeType && oldNode.nodeType == 3.toShort()) {
+ if (oldNode.textContent != newNode.textContent) {
+ if (Komponent.logReplaceEvent) {
+ console.log("Updating text content", oldNode, newNode)
+ }
+ oldNode.textContent = newNode.textContent
+ return oldNode
+ }
+ }
+
+ if (Komponent.logReplaceEvent) {
+ console.log("Replace node", oldNode, newNode)
+ }
+ replaceNode(oldNode, newNode)
+ return newNode
+
+ }
+ }
+
+ private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) {
+ // removed attributes
+ for (index in 0 until oldNode.attributes.length) {
+ val attr = oldNode.attributes[index]
+
+ if (attr != null && newNode.attributes[attr.name] == null) {
+ oldNode.removeAttribute(attr.name)
+ }
+ }
+
+ for (index in 0 until newNode.attributes.length) {
+ val attr = newNode.attributes[index]
+
+ if (attr != null) {
+ val oldAttr = oldNode.attributes[attr.name]
+
+ if (oldAttr == null || oldAttr.value != attr.value) {
+ oldNode.setAttribute(attr.name, attr.value)
+ }
+ }
+ }
+ }
+
+ private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) {
+ // todo: add 1 look ahead/back
+ var oldIndex = 0
+ var newIndex = 0
+
+ if (Komponent.logReplaceEvent) {
+ console.log("updateChildren old/new count", oldNode.childNodes.length, newNode.childNodes.length)
+ }
+
+ while(newIndex < newNode.childNodes.length) {
+ if (Komponent.logReplaceEvent) {
+ console.log(">>> updateChildren old/new count", oldNode.childNodes, newNode.childNodes)
+ console.log("Update Old/new", oldIndex, newIndex)
+ }
+ val newChildNode = newNode.childNodes[newIndex]
+
+ if (oldIndex < oldNode.childNodes.length) {
+ val oldChildNode = oldNode.childNodes[oldIndex]
+
+ if (oldChildNode != null && newChildNode != null) {
+ if (Komponent.logReplaceEvent) {
+ console.log("Update node Old/new", oldChildNode, newChildNode)
+ }
+
+ updateNode(oldChildNode, newChildNode)
+
+ if (Komponent.logReplaceEvent) {
+ console.log("--- Updated Old/new", oldNode.children, newNode.children)
+ }
+ } else {
+ if (Komponent.logReplaceEvent) {
+ console.log("Null node", oldChildNode, newChildNode)
+ }
+ }
+ } else {
+ if (Komponent.logReplaceEvent) {
+ console.log("Append Old/new/node", oldIndex, newIndex, newChildNode)
+ }
+ oldNode.append(newChildNode)
+ }
+
+ if (Komponent.logReplaceEvent) {
+ console.log("<<< Updated Old/new", oldNode.children, newNode.children)
+ }
+
+ oldIndex++
+ newIndex++
+ }
+
+ while(oldIndex < oldNode.childNodes.length) {
+ oldNode.childNodes[oldIndex]?.also {
+ if (Komponent.logReplaceEvent) {
+ console.log("Remove old node", it)
+ }
+
+ oldNode.removeChild(it)
+ }
+ oldIndex++
+ }
+ }
+
+ private fun updateEvents(oldNode: HTMLElement, newNode: HTMLElement) {
+ val oldEvents = mutableListOf()
+ oldEvents.addAll((oldNode.getAttribute("data-komp-events") ?: "").split(","))
+
+ val newEvents = (newNode.getAttribute("data-komp-events") ?: "").split(",")
+
+ for (event in newEvents) {
+ if (event.isNotBlank()) {
+ val oldNodeEvent = oldNode.asDynamic()["event-$event"]
+ val newNodeEvent = newNode.asDynamic()["event-$event"]
+ if (oldNodeEvent != null) {
+ oldNode.removeEventListener(event, oldNodeEvent as ((Event) -> Unit), null)
+ }
+ if (newNodeEvent != null) {
+ oldNode.addEventListener(event, newNodeEvent as ((Event) -> Unit), null)
+ oldNode.asDynamic()["event-$event"] = newNodeEvent
+ }
+ oldEvents.remove(event)
+ }
+ }
+
+ for (event in oldEvents) {
+ if (event.isNotBlank()) {
+ val oldNodeEvent = oldNode.asDynamic()["event-$event"]
+ if (oldNodeEvent != null) {
+ oldNode.removeEventListener(event, oldNodeEvent as ((Event) -> Unit), null)
+ }
+ }
+ }
+
+ newNode.getAttribute("data-komp-events")?.also {
+ oldNode.setAttribute("data-komp-events", it)
+ }
+ }
+
+ private fun replaceNode(oldNode: Node, newNode: Node) {
+ oldNode.parentNode?.also { parent ->
+ val clone = newNode.cloneNode(true)
+ if (newNode is HTMLElement) {
+ val events = (newNode.getAttribute("data-komp-events") ?: "").split(",")
+ for (event in events) {
+ val foundEvent = newNode.asDynamic()["event-$event"]
+ if (foundEvent != null) {
+ clone.addEventListener(event, foundEvent as ((Event) -> Unit), null)
+ }
+ }
+ }
+ parent.replaceChild(clone, oldNode)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt
similarity index 85%
rename from src/main/kotlin/nl/astraeus/komp/HtmlBuilder.kt
rename to src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt
index b6415df..3e685f2 100644
--- a/src/main/kotlin/nl/astraeus/komp/HtmlBuilder.kt
+++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt
@@ -1,21 +1,20 @@
package nl.astraeus.komp
-import kotlinx.html.DefaultUnsafe
-import kotlinx.html.Entities
-import kotlinx.html.Tag
-import kotlinx.html.TagConsumer
-import kotlinx.html.Unsafe
-import org.w3c.dom.Document
-import org.w3c.dom.HTMLElement
-import org.w3c.dom.Node
-import org.w3c.dom.asList
+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")
private inline fun HTMLElement.setEvent(name: String, noinline callback : (Event) -> Unit) : Unit {
- asDynamic()[name] = callback
+ val eventName = if (name.startsWith("on")) { name.substring(2) } else { name }
+ addEventListener(eventName, callback, null)
+ //asDynamic()[name] = callback
+ val events = getAttribute("data-komp-events") ?: ""
+
+ setAttribute("data-komp-events", if (events.isBlank()) { eventName } else { "$events,$eventName" })
+ asDynamic()["event-$eventName"] = callback
}
interface HtmlConsumer : TagConsumer {
@@ -73,13 +72,27 @@ class HtmlBuilder(
}
override fun onTagEnd(tag: Tag) {
+ var hash = 0
if (path.isEmpty() || path.last().tagName.toLowerCase() != tag.tagName.toLowerCase()) {
throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave")
}
val element = path.last()
+ for (index in 0 until element.childNodes.length) {
+ val child = element.childNodes[index]
+ if (child is HTMLElement) {
+
+ hash = hash * 37 + (child.getAttribute("data-komp-hash")?.toInt() ?: 0)
+ } else {
+ hash = hash * 37 + (child?.textContent?.hashCode() ?: 0)
+ }
+ }
+
tag.attributesEntries.forEach {
+ hash = hash * 37 + it.key.hashCode()
+ hash = hash * 37 + it.value.hashCode()
+
if (it.key == "class") {
val classes = it.value.split(Regex("\\s+"))
val classNames = StringBuilder()
@@ -121,6 +134,8 @@ class HtmlBuilder(
}
}
+ element.setAttribute("data-komp-hash", hash.toString())
+
lastLeaved = path.removeAt(path.lastIndex)
}
diff --git a/src/main/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt
similarity index 86%
rename from src/main/kotlin/nl/astraeus/komp/Komponent.kt
rename to src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt
index 3521be4..22cf766 100644
--- a/src/main/kotlin/nl/astraeus/komp/Komponent.kt
+++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt
@@ -33,6 +33,11 @@ class DummyKomponent: Komponent() {
}
}
+enum class UpdateStrategy {
+ REPLACE,
+ DOM_DIFF
+}
+
abstract class Komponent {
var element: Node? = null
val declaredStyles: MutableMap = HashMap()
@@ -68,10 +73,18 @@ abstract class Komponent {
val newElement = create()
if (oldElement != null) {
+ if (updateStrategy == UpdateStrategy.REPLACE) {
if (logReplaceEvent) {
console.log("Replacing", oldElement, newElement)
}
oldElement.parentNode?.replaceChild(newElement, oldElement)
+ element = newElement
+ } else {
+ if (logReplaceEvent) {
+ console.log("DomDiffing", oldElement, newElement)
+ }
+ element = DiffPatch.updateNode(oldElement, newElement)
+ }
}
}
@@ -91,6 +104,7 @@ abstract class Komponent {
companion object {
var logRenderEvent = false
var logReplaceEvent = false
+ var updateStrategy = UpdateStrategy.DOM_DIFF
fun create(parent: HTMLElement, component: Komponent, insertAsFirst: Boolean = false) {
val element = component.create()
@@ -102,4 +116,5 @@ abstract class Komponent {
}
}
}
+
}
diff --git a/src/main/resources/index.html b/src/main/resources/index.html
deleted file mode 100644
index 1477ac3..0000000
--- a/src/main/resources/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- Bla
-
-
-
-
-
-
-
-
\ No newline at end of file