Merge remember branch, Update to 0.2.5-SNAPSHOT

This commit is contained in:
2021-02-10 13:20:07 +01:00
parent 59d812613e
commit 66b3fb3c22
12 changed files with 389 additions and 143 deletions

View File

@@ -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
}
}

View File

@@ -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 })

View File

@@ -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<T>(
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 <reified T> Komponent.state(initialValue: T): StateDelegate<T> = 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<String, CSSStyleDeclaration> = 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<Komponent>()
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
}
}
}