Clean up, release v. 1.0.0
This commit is contained in:
@@ -68,6 +68,7 @@ val javadocJar by tasks.registering(Jar::class) {
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "releases"
|
||||
// change to point to your repo, e.g. http://my.org/repo
|
||||
|
||||
154
src/jsMain/kotlin/nl/astraeus/komp/ElementExtentions.kt
Normal file
154
src/jsMain/kotlin/nl/astraeus/komp/ElementExtentions.kt
Normal file
@@ -0,0 +1,154 @@
|
||||
package nl.astraeus.komp
|
||||
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.get
|
||||
|
||||
private fun Int.asSpaces(): String {
|
||||
val result = StringBuilder()
|
||||
|
||||
repeat(this) {
|
||||
result.append(" ")
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
fun Element.printTree(indent: Int = 0): String {
|
||||
val result = StringBuilder()
|
||||
|
||||
result.append(indent.asSpaces())
|
||||
result.append(tagName)
|
||||
if (this.namespaceURI != "http://www.w3.org/1999/xhtml") {
|
||||
result.append(" [")
|
||||
result.append(namespaceURI)
|
||||
result.append("]")
|
||||
}
|
||||
result.append(" (")
|
||||
var first = true
|
||||
if (hasAttributes()) {
|
||||
for (index in 0 until attributes.length) {
|
||||
if (!first) {
|
||||
result.append(", ")
|
||||
} else {
|
||||
first = false
|
||||
}
|
||||
result.append(attributes[index]?.localName)
|
||||
result.append("=")
|
||||
result.append(attributes[index]?.value)
|
||||
}
|
||||
}
|
||||
result.append(") {")
|
||||
result.append("\n")
|
||||
for ((name, event) in getKompEvents()) {
|
||||
result.append(indent.asSpaces())
|
||||
result.append("on")
|
||||
result.append(name)
|
||||
result.append(" -> ")
|
||||
result.append(event)
|
||||
result.append("\n")
|
||||
}
|
||||
for (index in 0 until childNodes.length) {
|
||||
childNodes[index]?.let {
|
||||
if (it is Element) {
|
||||
result.append(it.printTree(indent + 2))
|
||||
} else {
|
||||
result.append((indent + 2).asSpaces())
|
||||
result.append(it.textContent)
|
||||
result.append("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
result.append(indent.asSpaces())
|
||||
result.append("}\n")
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
internal fun Element.clearKompAttributes() {
|
||||
val attributes = this.asDynamic()["komp-attributes"] as MutableSet<String>?
|
||||
|
||||
if (attributes == null) {
|
||||
this.asDynamic()["komp-attributes"] = mutableSetOf<String>()
|
||||
} else {
|
||||
attributes.clear()
|
||||
}
|
||||
|
||||
if (this is HTMLInputElement) {
|
||||
this.checked = false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Element.getKompAttributes(): MutableSet<String> {
|
||||
var result: MutableSet<String>? = this.asDynamic()["komp-attributes"] as MutableSet<String>?
|
||||
|
||||
if (result == null) {
|
||||
result = mutableSetOf()
|
||||
|
||||
this.asDynamic()["komp-attributes"] = result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun Element.setKompAttribute(name: String, value: String) {
|
||||
val setAttrs: MutableSet<String> = getKompAttributes()
|
||||
setAttrs.add(name)
|
||||
|
||||
if (this is HTMLInputElement) {
|
||||
when (name) {
|
||||
"checked" -> {
|
||||
this.checked = value == "checked"
|
||||
}
|
||||
"value" -> {
|
||||
this.value = value
|
||||
|
||||
}
|
||||
else -> {
|
||||
setAttribute(name, value)
|
||||
}
|
||||
}
|
||||
} else if (this.getAttribute(name) != value) {
|
||||
setAttribute(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Element.clearKompEvents() {
|
||||
for ((name, event) in getKompEvents()) {
|
||||
removeEventListener(name, event)
|
||||
}
|
||||
|
||||
val events = this.asDynamic()["komp-events"] as MutableMap<String, (Event) -> Unit>?
|
||||
|
||||
if (events == null) {
|
||||
this.asDynamic()["komp-events"] = mutableMapOf<String, (Event) -> Unit>()
|
||||
} else {
|
||||
events.clear()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Element.setKompEvent(name: String, event: (Event) -> Unit) {
|
||||
val eventName: String = if (name.startsWith("on")) {
|
||||
name.substring(2)
|
||||
} else {
|
||||
name
|
||||
}
|
||||
|
||||
val events: MutableMap<String, (Event) -> Unit> = getKompEvents()
|
||||
|
||||
events[eventName]?.let {
|
||||
println("Warn event '$eventName' already defined!")
|
||||
removeEventListener(eventName, it)
|
||||
}
|
||||
|
||||
events[eventName] = event
|
||||
|
||||
this.asDynamic()["komp-events"] = events
|
||||
|
||||
this.addEventListener(eventName, event)
|
||||
}
|
||||
|
||||
internal fun Element.getKompEvents(): MutableMap<String, (Event) -> Unit> {
|
||||
return this.asDynamic()["komp-events"] ?: mutableMapOf()
|
||||
}
|
||||
|
||||
@@ -24,155 +24,9 @@ interface HtmlConsumer : TagConsumer<Element> {
|
||||
fun debug(block: HtmlConsumer.() -> Unit)
|
||||
}
|
||||
|
||||
fun Int.asSpaces(): String {
|
||||
val result = StringBuilder()
|
||||
repeat(this) {
|
||||
result.append(" ")
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
fun FlowOrMetaDataOrPhrasingContent.currentElement(): Element =
|
||||
currentElement ?: error("No current element defined!")
|
||||
|
||||
fun Element.printTree(indent: Int = 0): String {
|
||||
val result = StringBuilder()
|
||||
|
||||
result.append(indent.asSpaces())
|
||||
result.append(tagName)
|
||||
if (this.namespaceURI != "http://www.w3.org/1999/xhtml") {
|
||||
result.append(" [")
|
||||
result.append(namespaceURI)
|
||||
result.append("]")
|
||||
}
|
||||
result.append(" (")
|
||||
var first = true
|
||||
if (hasAttributes()) {
|
||||
for (index in 0 until attributes.length) {
|
||||
if (!first) {
|
||||
result.append(", ")
|
||||
} else {
|
||||
first = false
|
||||
}
|
||||
result.append(attributes[index]?.localName)
|
||||
result.append("=")
|
||||
result.append(attributes[index]?.value)
|
||||
}
|
||||
}
|
||||
result.append(") {")
|
||||
result.append("\n")
|
||||
for ((name, event) in getKompEvents()) {
|
||||
result.append(indent.asSpaces())
|
||||
result.append("on")
|
||||
result.append(name)
|
||||
result.append(" -> ")
|
||||
result.append(event)
|
||||
result.append("\n")
|
||||
}
|
||||
for (index in 0 until childNodes.length) {
|
||||
childNodes[index]?.let {
|
||||
if (it is Element) {
|
||||
result.append(it.printTree(indent + 2))
|
||||
} else {
|
||||
result.append((indent + 2).asSpaces())
|
||||
result.append(it.textContent)
|
||||
result.append("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
result.append(indent.asSpaces())
|
||||
result.append("}\n")
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
private fun Element.clearKompAttributes() {
|
||||
val attributes = this.asDynamic()["komp-attributes"] as MutableSet<String>?
|
||||
|
||||
if (attributes == null) {
|
||||
this.asDynamic()["komp-attributes"] = mutableSetOf<String>()
|
||||
} else {
|
||||
attributes.clear()
|
||||
}
|
||||
|
||||
if (this is HTMLInputElement) {
|
||||
this.checked = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.getKompAttributes(): MutableSet<String> {
|
||||
var result: MutableSet<String>? = this.asDynamic()["komp-attributes"] as MutableSet<String>?
|
||||
|
||||
if (result == null) {
|
||||
result = mutableSetOf()
|
||||
|
||||
this.asDynamic()["komp-attributes"] = result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun Element.setKompAttribute(name: String, value: String) {
|
||||
val setAttrs: MutableSet<String> = getKompAttributes()
|
||||
setAttrs.add(name)
|
||||
|
||||
if (this is HTMLInputElement) {
|
||||
when (name) {
|
||||
"checked" -> {
|
||||
this.checked = value == "checked"
|
||||
}
|
||||
"value" -> {
|
||||
this.value = value
|
||||
|
||||
}
|
||||
else -> {
|
||||
setAttribute(name, value)
|
||||
}
|
||||
}
|
||||
} else if (this.getAttribute(name) != value) {
|
||||
setAttribute(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.clearKompEvents() {
|
||||
for ((name, event) in getKompEvents()) {
|
||||
currentElement?.removeEventListener(name, event)
|
||||
}
|
||||
|
||||
val events = this.asDynamic()["komp-events"] as MutableMap<String, (Event) -> Unit>?
|
||||
|
||||
if (events == null) {
|
||||
this.asDynamic()["komp-events"] = mutableMapOf<String, (Event) -> Unit>()
|
||||
} else {
|
||||
events.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.setKompEvent(name: String, event: (Event) -> Unit) {
|
||||
val eventName: String = if (name.startsWith("on")) {
|
||||
name.substring(2)
|
||||
} else {
|
||||
name
|
||||
}
|
||||
|
||||
val events: MutableMap<String, (Event) -> Unit> = getKompEvents()
|
||||
|
||||
events[eventName]?.let {
|
||||
println("Warn event already defined!")
|
||||
currentElement?.removeEventListener(eventName, it)
|
||||
}
|
||||
|
||||
events[eventName] = event
|
||||
|
||||
this.asDynamic()["komp-events"] = events
|
||||
|
||||
this.addEventListener(eventName, event)
|
||||
}
|
||||
|
||||
private fun Element.getKompEvents(): MutableMap<String, (Event) -> Unit> {
|
||||
return this.asDynamic()["komp-events"] ?: mutableMapOf()
|
||||
}
|
||||
|
||||
private data class ElementIndex(
|
||||
val parent: Node,
|
||||
var childIndex: Int
|
||||
@@ -219,14 +73,13 @@ private fun ArrayList<ElementIndex>.replace(new: Node) {
|
||||
private fun Node.asElement() = this as? HTMLElement
|
||||
|
||||
class HtmlBuilder(
|
||||
val parent: Element,
|
||||
var childIndex: Int = 0
|
||||
parent: Element,
|
||||
childIndex: Int = 0
|
||||
) : HtmlConsumer {
|
||||
private var currentPosition = arrayListOf<ElementIndex>()
|
||||
private var inDebug = false
|
||||
var currentNode: Node? = null
|
||||
var root: Element? = null
|
||||
val currentAttributes: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
init {
|
||||
currentPosition.add(ElementIndex(parent, childIndex))
|
||||
@@ -256,16 +109,19 @@ class HtmlBuilder(
|
||||
}
|
||||
|
||||
override fun debug(block: HtmlConsumer.() -> Unit) {
|
||||
val enableAssertions = Komponent.enableAssertions
|
||||
Komponent.enableAssertions = true
|
||||
inDebug = true
|
||||
|
||||
try {
|
||||
block.invoke(this)
|
||||
} finally {
|
||||
inDebug = false
|
||||
Komponent.enableAssertions = enableAssertions
|
||||
}
|
||||
}
|
||||
|
||||
fun logReplace(msg: String) {
|
||||
private fun logReplace(msg: String) {
|
||||
if (Komponent.logReplaceEvent && inDebug) {
|
||||
console.log(msg)
|
||||
}
|
||||
@@ -347,7 +203,9 @@ class HtmlBuilder(
|
||||
override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) {
|
||||
logReplace("onTagAttributeChange, ${tag.tagName} [$attribute, $value]")
|
||||
|
||||
if (Komponent.enableAssertions) {
|
||||
checkTag(tag)
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
currentElement?.removeAttribute(attribute.lowercase())
|
||||
@@ -357,9 +215,11 @@ class HtmlBuilder(
|
||||
}
|
||||
|
||||
override fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit) {
|
||||
//logReplace"onTagEvent, ${tag.tagName} [$event, $value]")
|
||||
logReplace("onTagEvent, ${tag.tagName} [$event, $value]")
|
||||
|
||||
if (Komponent.enableAssertions) {
|
||||
checkTag(tag)
|
||||
}
|
||||
|
||||
currentElement?.setKompEvent(event.lowercase(), value)
|
||||
}
|
||||
@@ -371,7 +231,9 @@ class HtmlBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
if (Komponent.enableAssertions) {
|
||||
checkTag(tag)
|
||||
}
|
||||
|
||||
currentPosition.pop()
|
||||
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
package nl.astraeus.komp
|
||||
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.html.FlowOrMetaDataOrPhrasingContent
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.get
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
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)
|
||||
private var currentKomponent: Komponent? = null
|
||||
fun FlowOrMetaDataOrPhrasingContent.currentKomponent(): Komponent =
|
||||
currentKomponent ?: error("No current komponent defined! Only call from render code!")
|
||||
|
||||
enum class UnsafeMode {
|
||||
UNSAFE_ALLOWED,
|
||||
@@ -33,12 +17,23 @@ enum class UnsafeMode {
|
||||
UNSAFE_SVG_ONLY
|
||||
}
|
||||
|
||||
var Element.memoizeHash: String?
|
||||
get() {
|
||||
return getAttribute("memoize-hash")
|
||||
}
|
||||
set(value) {
|
||||
if (value != null) {
|
||||
setAttribute("memoize-hash", value.toString())
|
||||
} else {
|
||||
removeAttribute("memoize-hash")
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Komponent {
|
||||
val createIndex = getNextCreateIndex()
|
||||
private var dirty: Boolean = true
|
||||
private var lastMemoizeHash: Int? = null
|
||||
|
||||
var element: Node? = null
|
||||
var element: Element? = null
|
||||
|
||||
open fun create(parent: Element, childIndex: Int? = null) {
|
||||
onBeforeUpdate()
|
||||
@@ -47,13 +42,30 @@ abstract class Komponent {
|
||||
childIndex ?: parent.childNodes.length
|
||||
)
|
||||
|
||||
currentKomponent = this
|
||||
builder.render()
|
||||
currentKomponent = null
|
||||
|
||||
element = builder.root
|
||||
lastMemoizeHash = generateMemoizeHash()
|
||||
updateMemoizeHash()
|
||||
onAfterUpdate()
|
||||
}
|
||||
|
||||
fun memoizeChanged() = lastMemoizeHash == null || lastMemoizeHash != generateMemoizeHash()
|
||||
fun memoizeChanged() = element?.memoizeHash == null || element?.memoizeHash != fullMemoizeHash()
|
||||
|
||||
fun updateMemoizeHash() {
|
||||
element?.memoizeHash = fullMemoizeHash()
|
||||
}
|
||||
|
||||
private fun fullMemoizeHash(): String? {
|
||||
val generated = generateMemoizeHash()
|
||||
|
||||
return if (generated != null) {
|
||||
"${this::class.simpleName}:${generateMemoizeHash()}"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun HtmlBuilder.render()
|
||||
|
||||
@@ -121,7 +133,11 @@ abstract class Komponent {
|
||||
}
|
||||
val consumer = HtmlBuilder(parent, childIndex)
|
||||
consumer.root = null
|
||||
|
||||
currentKomponent = this
|
||||
consumer.render()
|
||||
currentKomponent = null
|
||||
|
||||
element = consumer.root
|
||||
dirty = false
|
||||
}
|
||||
@@ -138,6 +154,7 @@ abstract class Komponent {
|
||||
|
||||
var logRenderEvent = false
|
||||
var logReplaceEvent = false
|
||||
var enableAssertions = false
|
||||
var unsafeMode = UnsafeMode.UNSAFE_DISABLED
|
||||
|
||||
fun create(parent: HTMLElement, component: Komponent) {
|
||||
@@ -183,10 +200,10 @@ abstract class Komponent {
|
||||
}
|
||||
val memoizeHash = next.generateMemoizeHash()
|
||||
|
||||
if (memoizeHash == null || next.lastMemoizeHash != memoizeHash) {
|
||||
if (next.memoizeChanged()) {
|
||||
next.onBeforeUpdate()
|
||||
next.update()
|
||||
next.lastMemoizeHash = memoizeHash
|
||||
next.updateMemoizeHash()
|
||||
next.onAfterUpdate()
|
||||
} else if (logRenderEvent) {
|
||||
console.log("Skipped render, memoizeHash is equal $next-[$memoizeHash]")
|
||||
|
||||
35
src/jsMain/kotlin/nl/astraeus/komp/State.kt
Normal file
35
src/jsMain/kotlin/nl/astraeus/komp/State.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package nl.astraeus.komp
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
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
|
||||
)
|
||||
Reference in New Issue
Block a user