Bump version to 1.2.10, simplify attribute and event handling in ElementExtensions, and remove deprecated logic in HtmlBuilder.
Some checks failed
Gradle CI / build (push) Has been cancelled

This commit is contained in:
2025-10-18 16:24:52 +02:00
parent d3fef98203
commit 88cf5b8533
7 changed files with 119 additions and 177 deletions

View File

@@ -11,7 +11,7 @@ plugins {
} }
group = "nl.astraeus" group = "nl.astraeus"
version = "1.2.9" version = "1.2.10"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@@ -40,14 +40,6 @@ fun Element.printTree(indent: Int = 0): String {
} }
result.append(") {") result.append(") {")
result.append("\n") 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) { for (index in 0 until childNodes.length) {
childNodes[index]?.let { childNodes[index]?.let {
if (it is Element) { if (it is Element) {
@@ -65,55 +57,52 @@ fun Element.printTree(indent: Int = 0): String {
return result.toString() return result.toString()
} }
internal fun Element.setKompAttribute(attributeName: String, value: String?) { internal fun Element.setKompAttribute(attributeName: String, value: String) {
//val attributeName = name.lowercase() if (this is HTMLInputElement) {
if (value == null || value.isBlank()) { when (attributeName) {
if (this is HTMLInputElement) { "checked" -> {
when (attributeName) { checked = "checked" == value
"checked" -> {
checked = false
}
"class" -> {
className = ""
}
"value" -> {
this.value = ""
}
else -> {
removeAttribute(attributeName)
}
} }
} else {
removeAttribute(attributeName) "class" -> {
} className = value
} else { }
if (this is HTMLInputElement) {
when (attributeName) { "value" -> {
"checked" -> { this.value = value
checked = "checked" == value }
}
"class" -> { else -> {
className = value setAttribute(attributeName, value)
}
"value" -> {
this.value = value
}
else -> {
setAttribute(attributeName, value)
}
} }
} else if (this.getAttribute(attributeName) != value) {
setAttribute(attributeName, value)
} }
} else if (this.getAttribute(attributeName) != value) {
setAttribute(attributeName, value)
} }
} }
internal fun Element.clearKompEvents() { internal fun Element.clearKompAttribute(attributeName: String) {
val events = getKompEvents() if (this is HTMLInputElement) {
for ((name, event) in getKompEvents()) { when (attributeName) {
removeEventListener(name, event) "checked" -> {
checked = false
}
"class" -> {
className = ""
}
"value" -> {
this.value = ""
}
else -> {
removeAttribute(attributeName)
}
}
} else {
removeAttribute(attributeName)
} }
events.clear()
} }
internal fun Element.setKompEvent(name: String, event: (Event) -> Unit) { internal fun Element.setKompEvent(name: String, event: (Event) -> Unit) {
@@ -123,22 +112,9 @@ internal fun Element.setKompEvent(name: String, event: (Event) -> Unit) {
name name
} }
getKompEvents()[eventName] = event
this.addEventListener(eventName, event) this.addEventListener(eventName, event)
} }
internal fun Element.getKompEvents(): MutableMap<String, (Event) -> Unit> {
var map = this.asDynamic()["komp-events"] as? MutableMap<String, (Event) -> Unit>
if (map == null) {
map = mutableMapOf()
this.asDynamic()["komp-events"] = map
}
return map
}
internal fun Element.findElementIndex(): Int { internal fun Element.findElementIndex(): Int {
val childNodes = parentElement?.children val childNodes = parentElement?.children
if (childNodes != null) { if (childNodes != null) {

View File

@@ -6,7 +6,6 @@ import org.w3c.dom.get
data class ElementIndex( data class ElementIndex(
val parent: Node, val parent: Node,
var childIndex: Int, var childIndex: Int,
var setAttr: MutableSet<String> = mutableSetOf()
) { ) {
override fun toString(): String { override fun toString(): String {
return "${parent.nodeName}[$childIndex]" return "${parent.nodeName}[$childIndex]"
@@ -39,7 +38,6 @@ fun ArrayList<ElementIndex>.currentPosition(): ElementIndex? {
fun ArrayList<ElementIndex>.nextElement() { fun ArrayList<ElementIndex>.nextElement() {
this.lastOrNull()?.let { this.lastOrNull()?.let {
it.setAttr.clear()
it.childIndex++ it.childIndex++
} }
} }

View File

@@ -57,12 +57,6 @@ class HtmlBuilder(
} }
} else { } else {
// current element should become parent // current element should become parent
/*
val ce = komponent.element
if (ce != null) {
append(ce as Element)
}
*/
komponent.create( komponent.create(
currentPosition.last().parent as Element, currentPosition.last().parent as Element,
currentPosition.last().childIndex currentPosition.last().childIndex
@@ -100,39 +94,26 @@ class HtmlBuilder(
"onTagStart, [${tag.tagName}, ${tag.namespace ?: ""}], currentPosition: $currentPosition" "onTagStart, [${tag.tagName}, ${tag.namespace ?: ""}], currentPosition: $currentPosition"
} }
currentNode = currentPosition.currentElement() currentNode = if (tag.namespace != null) {
document.createElementNS(tag.namespace, tag.tagName)
} else {
document.createElement(tag.tagName)
}
if (currentNode == null) { if (currentNode == null) {
logReplace { "onTagStart, currentNode1: $currentNode" } logReplace { "onTagStart, currentNode1: $currentNode" }
currentNode = if (tag.namespace != null) {
document.createElementNS(tag.namespace, tag.tagName)
} else {
document.createElement(tag.tagName)
}
logReplace { "onTagStart, currentElement1.1: $currentNode" } logReplace { "onTagStart, currentElement1.1: $currentNode" }
currentPosition.currentParent().appendChild(currentNode!!) currentPosition.currentParent().appendChild(currentNode!!)
} else if ( } else {
!currentNode?.asElement()?.tagName.equals(tag.tagName, true) || logReplace {
( "onTagStart, currentElement, namespace: ${currentNode?.asElement()?.namespaceURI} -> ${tag.namespace}"
tag.namespace != null && }
!currentNode?.asElement()?.namespaceURI.equals(tag.namespace, true) logReplace {
) "onTagStart, currentElement, replace: ${currentNode?.asElement()?.tagName} -> ${tag.tagName}"
) { }
logReplace {
"onTagStart, currentElement, namespace: ${currentNode?.asElement()?.namespaceURI} -> ${tag.namespace}"
}
logReplace {
"onTagStart, currentElement, replace: ${currentNode?.asElement()?.tagName} -> ${tag.tagName}"
}
currentNode = if (tag.namespace != null) { currentPosition.replace(currentNode!!)
document.createElementNS(tag.namespace, tag.tagName)
} else {
document.createElement(tag.tagName)
}
currentPosition.replace(currentNode!!)
} }
currentElement = currentNode as? Element ?: currentElement currentElement = currentNode as? Element ?: currentElement
@@ -144,15 +125,8 @@ class HtmlBuilder(
firstTag = false firstTag = false
} }
currentElement?.clearKompEvents()
// if currentElement = checkbox make sure it's cleared
(currentElement as? HTMLInputElement)?.checked = false
currentPosition.lastOrNull()?.setAttr?.clear()
for (entry in tag.attributesEntries) { for (entry in tag.attributesEntries) {
currentElement!!.setKompAttribute(entry.key, entry.value) currentElement?.setKompAttribute(entry.key, entry.value)
currentPosition.lastOrNull()?.setAttr?.add(entry.key)
} }
} }
@@ -181,11 +155,10 @@ class HtmlBuilder(
checkTag("onTagAttributeChange", tag) checkTag("onTagAttributeChange", tag)
} }
currentElement?.setKompAttribute(attribute, value)
if (value == null || value.isEmpty()) { if (value == null || value.isEmpty()) {
currentPosition.currentPosition()?.setAttr?.remove(attribute) currentElement?.clearKompAttribute(attribute)
} else { } else {
currentPosition.currentPosition()?.setAttr?.add(attribute) currentElement?.setKompAttribute(attribute, value)
} }
} }
@@ -222,25 +195,6 @@ class HtmlBuilder(
checkTag("onTagEnd", tag) checkTag("onTagEnd", tag)
} }
if (currentElement != null) {
val setAttrs: Set<String> = currentPosition.currentPosition()?.setAttr ?: setOf()
// remove attributes that where not set
val element = currentElement
if (element?.hasAttributes() == true) {
for (index in 0 until element.attributes.length) {
val attribute = element.attributes[index]
if (attribute?.name != null) {
val attr = attribute.name
if (!setAttrs.contains(attr)) {
element.setKompAttribute(attr, null)
}
}
}
}
}
currentPosition.pop() currentPosition.pop()
currentNode = currentPosition.currentElement() currentNode = currentPosition.currentElement()

View File

@@ -38,7 +38,7 @@ class TestClassUpdate {
Komponent.create(div, classComponent) Komponent.create(div, classComponent)
// Verify initial state - should have the class // Verify initial state - should have the class
val contentDiv = div.querySelector("div") var contentDiv = div.querySelector("div")
println("[DEBUG_LOG] Initial DOM: ${div.printTree()}") println("[DEBUG_LOG] Initial DOM: ${div.printTree()}")
assertTrue(contentDiv?.classList?.contains("test-class") ?: false, "Div should have the class initially") assertTrue(contentDiv?.classList?.contains("test-class") ?: false, "Div should have the class initially")
@@ -47,6 +47,7 @@ class TestClassUpdate {
classComponent.requestImmediateUpdate() classComponent.requestImmediateUpdate()
// Verify the class was removed // Verify the class was removed
contentDiv = div.querySelector("div")
println("[DEBUG_LOG] After class removal: ${div.printTree()}") println("[DEBUG_LOG] After class removal: ${div.printTree()}")
assertFalse(contentDiv?.classList?.contains("test-class") ?: true, "Class should be removed after update") assertFalse(contentDiv?.classList?.contains("test-class") ?: true, "Class should be removed after update")
@@ -55,6 +56,7 @@ class TestClassUpdate {
classComponent.requestImmediateUpdate() classComponent.requestImmediateUpdate()
// Verify the class was added back // Verify the class was added back
contentDiv = div.querySelector("div")
println("[DEBUG_LOG] After class added back: ${div.printTree()}") println("[DEBUG_LOG] After class added back: ${div.printTree()}")
assertTrue(contentDiv?.classList?.contains("test-class") ?: false, "Class should be added back") assertTrue(contentDiv?.classList?.contains("test-class") ?: false, "Class should be added back")
@@ -63,6 +65,7 @@ class TestClassUpdate {
classComponent.requestImmediateUpdate() classComponent.requestImmediateUpdate()
// Verify the class was changed // Verify the class was changed
contentDiv = div.querySelector("div")
println("[DEBUG_LOG] After class name change: ${div.printTree()}") println("[DEBUG_LOG] After class name change: ${div.printTree()}")
assertFalse(contentDiv?.classList?.contains("test-class") ?: true, "Old class should be removed") assertFalse(contentDiv?.classList?.contains("test-class") ?: true, "Old class should be removed")
assertTrue(contentDiv?.classList?.contains("new-class") ?: false, "New class should be added") assertTrue(contentDiv?.classList?.contains("new-class") ?: false, "New class should be added")

View File

@@ -77,9 +77,9 @@ class TestInsert {
tableComponent.addRow("First Row") tableComponent.addRow("First Row")
// Verify the row was added // Verify the row was added
val rowsAfterFirstInsert = table.querySelectorAll("tr") val rowsAfterFirstInsert = div.querySelector("table")?.querySelectorAll("tr")
assertEquals(1, rowsAfterFirstInsert.length, "Table should have one row after insertion") assertEquals(1, rowsAfterFirstInsert?.length, "Table should have one row after insertion")
val firstRowCell = table.querySelector("tr td") val firstRowCell = div.querySelector("table")?.querySelector("tr td")
assertNotNull(firstRowCell, "First row cell should exist") assertNotNull(firstRowCell, "First row cell should exist")
assertEquals("First Row", firstRowCell.textContent, "Row content should match") assertEquals("First Row", firstRowCell.textContent, "Row content should match")
@@ -87,12 +87,12 @@ class TestInsert {
tableComponent.addRow("Second Row") tableComponent.addRow("Second Row")
// Verify both rows are present // Verify both rows are present
val rowsAfterSecondInsert = table.querySelectorAll("tr") val rowsAfterSecondInsert = div.querySelector("table")?.querySelectorAll("tr")
assertEquals(2, rowsAfterSecondInsert.length, "Table should have two rows after second insertion") assertEquals(2, rowsAfterSecondInsert?.length, "Table should have two rows after second insertion")
val allCells = table.querySelectorAll("tr td") val allCells = div.querySelector("table")?.querySelectorAll("tr td")
assertEquals(2, allCells.length, "Table should have two cells") assertEquals(2, allCells?.length, "Table should have two cells")
assertEquals("First Row", allCells.item(0)?.textContent, "First row content should match") assertEquals("First Row", allCells?.item(0)?.textContent, "First row content should match")
assertEquals("Second Row", allCells.item(1)?.textContent, "Second row content should match") assertEquals("Second Row", allCells?.item(1)?.textContent, "Second row content should match")
// Print the DOM tree for debugging // Print the DOM tree for debugging
println("Table DOM: ${div.printTree()}") println("Table DOM: ${div.printTree()}")

View File

@@ -12,57 +12,68 @@ import kotlin.test.assertNull
* Test class for verifying style attribute updates and removals * Test class for verifying style attribute updates and removals
*/ */
class StyleKomponent : Komponent() { class StyleKomponent : Komponent() {
var includeStyle = true var includeStyle = true
var styleValue = "color: red;" var styleValue = "color: red;"
override fun HtmlBuilder.render() { override fun HtmlBuilder.render() {
div { div {
if (includeStyle) { if (includeStyle) {
style = styleValue style = styleValue
} }
+"Content" +"Content"
}
} }
}
} }
class TestStyleUpdate { class TestStyleUpdate {
@Test @Test
fun testStyleRemoval() { fun testStyleRemoval() {
// Create a test component // Create a test component
val styleComponent = StyleKomponent() val styleComponent = StyleKomponent()
val div = document.createElement("div") as HTMLDivElement val div = document.createElement("div") as HTMLDivElement
// Render it // Render it
Komponent.create(div, styleComponent) Komponent.create(div, styleComponent)
// Verify initial state - should have the style // Verify initial state - should have the style
val contentDiv = div.querySelector("div") var contentDiv = div.querySelector("div")
println("[DEBUG_LOG] Initial DOM: ${div.printTree()}") println("[DEBUG_LOG] Initial DOM: ${div.printTree()}")
assertEquals("color: red;", contentDiv?.getAttribute("style"), "Div should have the style initially") assertEquals(
"color: red;",
contentDiv?.getAttribute("style"),
"Div should have the style initially"
)
// Update to remove the style // Update to remove the style
styleComponent.includeStyle = false styleComponent.includeStyle = false
styleComponent.requestImmediateUpdate() styleComponent.requestImmediateUpdate()
// Verify the style was removed // Verify the style was removed
println("[DEBUG_LOG] After style removal: ${div.printTree()}") contentDiv = div.querySelector("div")
assertNull(contentDiv?.getAttribute("style"), "Style should be removed after update") println("[DEBUG_LOG] After style removal: ${div.printTree()}")
assertNull(contentDiv?.getAttribute("style"), "Style should be removed after update")
// Add the style back // Add the style back
styleComponent.includeStyle = true styleComponent.includeStyle = true
styleComponent.requestImmediateUpdate() styleComponent.requestImmediateUpdate()
// Verify the style was added back // Verify the style was added back
println("[DEBUG_LOG] After style added back: ${div.printTree()}") contentDiv = div.querySelector("div")
assertEquals("color: red;", contentDiv?.getAttribute("style"), "Style should be added back") println("[DEBUG_LOG] After style added back: ${div.printTree()}")
assertEquals("color: red;", contentDiv?.getAttribute("style"), "Style should be added back")
// Change the style value // Change the style value
styleComponent.styleValue = "color: blue;" styleComponent.styleValue = "color: blue;"
styleComponent.requestImmediateUpdate() styleComponent.requestImmediateUpdate()
// Verify the style was changed // Verify the style was changed
println("[DEBUG_LOG] After style value change: ${div.printTree()}") contentDiv = div.querySelector("div")
assertEquals("color: blue;", contentDiv?.getAttribute("style"), "Style should be updated to new value") println("[DEBUG_LOG] After style value change: ${div.printTree()}")
} assertEquals(
"color: blue;",
contentDiv?.getAttribute("style"),
"Style should be updated to new value"
)
}
} }