VDom implementation

This commit is contained in:
2021-04-05 17:21:45 +02:00
parent 68fde339a8
commit d0442e785f
10 changed files with 459 additions and 380 deletions

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp:commonMain" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.2.5-SNAPSHOT" type="JAVA_MODULE" version="4">
<module external.linked.project.id="komp:commonMain" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="Common (experimental) " allPlatforms="JS []/JVM [1.6]/Native []/Native [general]" useProjectSettings="false" isTestModule="false" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin;/home/rnentjes/Development/komp/komp/src/commonMain/kotlin">
<configuration version="3" platform="Common (experimental) " allPlatforms="JS []/JVM [1.8]/Native []/Native [general]" useProjectSettings="false" isTestModule="false" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin">
<newMppModelJpsModuleKind>SOURCE_SET_HOLDER</newMppModelJpsModuleKind>
<compilerSettings />
<compilerArguments>
@@ -13,13 +13,13 @@
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.30/7d0085e5781847da84580d16239c1c4e0980bdb0/kotlin-scripting-jvm-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.30/c7896c5c6b504f44e7cf9f5719d4393e20680666/kotlin-scripting-common-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.32/49656d531bfab9d6e45d3f27bc4d03b542d6766/kotlin-scripting-jvm-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.32/6abda0fe69677f0e46e7539fd185e4bd093b7258/kotlin-scripting-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.8/f62be6d4cbf27781c2969867b4ed952f38378492/kotlinx-coroutines-core-1.3.8.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.30/d10d1e10f47006ee08162dde039e38ac487de4ac/kotlin-stdlib-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.30/bb9a3173350f55732416ee27956ea8f9b81f4dbb/kotlin-stdlib-common-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.30/b9c2a1fab9217623fc0fbadf6190e77eed6f054d/kotlin-script-runtime-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.32/bac50b0748be017dbc13fc1cb7231b1c9da0e106/kotlin-script-runtime-1.4.32.jar" />
</array>
</option>
<option name="multiPlatform" value="true" />
@@ -32,7 +32,7 @@
<content url="file://$MODULE_DIR$/src/commonMain" />
<orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.30" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" level="project" />
</component>
</module>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp:commonTest" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.2.5-SNAPSHOT" type="JAVA_MODULE" version="4">
<module external.linked.project.id="komp:commonTest" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="Common (experimental) " allPlatforms="JS []/JVM [1.6]/Native []/Native [general]" useProjectSettings="false" isTestModule="true" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin;/home/rnentjes/Development/komp/komp/src/commonMain/kotlin">
<configuration version="3" platform="Common (experimental) " allPlatforms="JS []/JVM [1.8]/Native []/Native [general]" useProjectSettings="false" isTestModule="true" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin">
<newMppModelJpsModuleKind>SOURCE_SET_HOLDER</newMppModelJpsModuleKind>
<externalSystemTestTasks>
<externalSystemTestTask>jsLegacyBrowserTest|komp:jsTest|jsLegacy</externalSystemTestTask>
@@ -16,13 +16,13 @@
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.30/7d0085e5781847da84580d16239c1c4e0980bdb0/kotlin-scripting-jvm-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.30/c7896c5c6b504f44e7cf9f5719d4393e20680666/kotlin-scripting-common-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.32/49656d531bfab9d6e45d3f27bc4d03b542d6766/kotlin-scripting-jvm-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.32/6abda0fe69677f0e46e7539fd185e4bd093b7258/kotlin-scripting-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.8/f62be6d4cbf27781c2969867b4ed952f38378492/kotlinx-coroutines-core-1.3.8.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.30/d10d1e10f47006ee08162dde039e38ac487de4ac/kotlin-stdlib-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.30/bb9a3173350f55732416ee27956ea8f9b81f4dbb/kotlin-stdlib-common-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.30/b9c2a1fab9217623fc0fbadf6190e77eed6f054d/kotlin-script-runtime-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.32/bac50b0748be017dbc13fc1cb7231b1c9da0e106/kotlin-script-runtime-1.4.32.jar" />
</array>
</option>
<option name="multiPlatform" value="true" />
@@ -36,9 +36,9 @@
<orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="komp.commonMain" scope="TEST" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.30" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.30" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" level="project" />
</component>
<component name="TestModuleProperties" production-module="komp.commonMain" />
</module>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.version="0.2.5-SNAPSHOT" type="JAVA_MODULE" version="4">
<module external.linked.project.id="komp" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">

View File

@@ -55,6 +55,12 @@
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
<artifact type="jar" name="komp-jslegacy-0.3.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-jslegacy-0.3.0-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
</component>
<component name="CheckStyle-IDEA">
<option name="configuration">
@@ -429,49 +435,49 @@
</option>
</component>
<component name="libraryTable">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.30" type="kotlin.common">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.30/bb9a3173350f55732416ee27956ea8f9b81f4dbb/kotlin-stdlib-common-1.4.30.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.30/9427471650876e86fdb0dd878f147ff15c57eac0/kotlin-stdlib-common-1.4.30-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/f3e47c26a177728549b3dd2a624fec333aa17b36/kotlin-stdlib-common-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.30" type="kotlin.js">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.32" type="kotlin.js">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.4.30/6c66d1db9c724b087c3a6f37aecffd24d0b14f26/kotlin-stdlib-js-1.4.30.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.4.32/8dca0e318b64909eecfb2d00a9d045562f3e66f8/kotlin-stdlib-js-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.4.30/5729944dc91bf28689461fe44b9e401fd41a84a7/kotlin-stdlib-js-1.4.30-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.4.32/5729944dc91bf28689461fe44b9e401fd41a84a7/kotlin-stdlib-js-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.30" type="kotlin.common">
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.32" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.4.30/e77a76b23fc3007baf57a9d3354fdd318fe1fa88/kotlin-test-annotations-common-1.4.30.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.4.32/f242cb223feb6d44e79588c26ca66bf57b0bc3a8/kotlin-test-annotations-common-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.4.30/57512e29ee20defe37d54ce19507d599b0f2113/kotlin-test-annotations-common-1.4.30-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.4.32/57512e29ee20defe37d54ce19507d599b0f2113/kotlin-test-annotations-common-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-common:1.4.30" type="kotlin.common">
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-common:1.4.32" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.4.30/823ed87441680d1334a1f0d23781c0089e16c0a6/kotlin-test-common-1.4.30.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.4.32/a10168dd408319a4af03763ad11f12e0fc1fc5ba/kotlin-test-common-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.4.30/d1818d1bdc457b51289f006884a260b56a55cb77/kotlin-test-common-1.4.30-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.4.32/d1818d1bdc457b51289f006884a260b56a55cb77/kotlin-test-common-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-js:1.4.30" type="kotlin.js">
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-js:1.4.32" type="kotlin.js">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-js/1.4.30/5b1cb1ec31a344f6aa1047d6cae3a268825cb724/kotlin-test-js-1.4.30.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-js/1.4.32/f4b4bf663acffeff2a664677b9e780f206f6842f/kotlin-test-js-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-js/1.4.30/8e70170db077d3f421c3fb6d24468b3938990f4a/kotlin-test-js-1.4.30-sources.jar!/" />
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-js/1.4.32/8e70170db077d3f421c3fb6d24468b3938990f4a/kotlin-test-js-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" type="kotlin.common">

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp:jsMain" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.2.5-SNAPSHOT" type="JAVA_MODULE" version="4">
<module external.linked.project.id="komp:jsMain" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JavaScript " allPlatforms="JS []" useProjectSettings="false" isTestModule="false" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin;/home/rnentjes/Development/komp/komp/src/commonMain/kotlin">
<configuration version="3" platform="JavaScript " allPlatforms="JS []" useProjectSettings="false" isTestModule="false" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin">
<dependsOnModuleNames>komp:commonMain</dependsOnModuleNames>
<sourceSets>
<sourceSet>komp.commonMain</sourceSet>
@@ -24,11 +24,11 @@
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.30/b9c2a1fab9217623fc0fbadf6190e77eed6f054d/kotlin-script-runtime-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.30/c7896c5c6b504f44e7cf9f5719d4393e20680666/kotlin-scripting-common-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.30/7d0085e5781847da84580d16239c1c4e0980bdb0/kotlin-scripting-jvm-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.30/bb9a3173350f55732416ee27956ea8f9b81f4dbb/kotlin-stdlib-common-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.30/d10d1e10f47006ee08162dde039e38ac487de4ac/kotlin-stdlib-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.32/bac50b0748be017dbc13fc1cb7231b1c9da0e106/kotlin-script-runtime-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.32/6abda0fe69677f0e46e7539fd185e4bd093b7258/kotlin-scripting-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.32/49656d531bfab9d6e45d3f27bc4d03b542d6766/kotlin-scripting-jvm-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.8/f62be6d4cbf27781c2969867b4ed952f38378492/kotlinx-coroutines-core-1.3.8.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" />
</array>
@@ -53,7 +53,7 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="komp.commonMain" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.2" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.30" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.30" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.32" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" level="project" />
</component>
</module>

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp:jsTest" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.2.5-SNAPSHOT" type="JAVA_MODULE" version="4">
<module external.linked.project.id="komp:jsTest" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JavaScript " allPlatforms="JS []" useProjectSettings="false" isTestModule="true" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin;/home/rnentjes/Development/komp/komp/src/commonMain/kotlin">
<configuration version="3" platform="JavaScript " allPlatforms="JS []" useProjectSettings="false" isTestModule="true" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin">
<dependsOnModuleNames>komp:commonTest</dependsOnModuleNames>
<sourceSets>
<sourceSet>komp.jsMain</sourceSet>
<sourceSet>komp.commonTest</sourceSet>
<sourceSet>komp.jsMain</sourceSet>
<sourceSet>komp.commonMain</sourceSet>
</sourceSets>
<newMppModelJpsModuleKind>COMPILATION_AND_SOURCE_SET_HOLDER</newMppModelJpsModuleKind>
@@ -29,11 +29,11 @@
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.30/b9c2a1fab9217623fc0fbadf6190e77eed6f054d/kotlin-script-runtime-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.30/c7896c5c6b504f44e7cf9f5719d4393e20680666/kotlin-scripting-common-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.30/7d0085e5781847da84580d16239c1c4e0980bdb0/kotlin-scripting-jvm-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.30/bb9a3173350f55732416ee27956ea8f9b81f4dbb/kotlin-stdlib-common-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.30/d10d1e10f47006ee08162dde039e38ac487de4ac/kotlin-stdlib-1.4.30.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.32/bac50b0748be017dbc13fc1cb7231b1c9da0e106/kotlin-script-runtime-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.32/6abda0fe69677f0e46e7539fd185e4bd093b7258/kotlin-scripting-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.32/49656d531bfab9d6e45d3f27bc4d03b542d6766/kotlin-scripting-jvm-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.8/f62be6d4cbf27781c2969867b4ed952f38378492/kotlinx-coroutines-core-1.3.8.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" />
</array>
@@ -59,12 +59,12 @@
<orderEntry type="module" module-name="komp.jsMain" scope="TEST" />
<orderEntry type="module" module-name="komp.jsMain" scope="RUNTIME" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-js:1.4.30" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.30" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-js:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.30" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-common:1.4.30" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.30" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-common:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.32" level="project" />
</component>
<component name="TestModuleProperties" production-module="komp.jsMain" />
</module>

View File

@@ -1,297 +1,205 @@
package nl.astraeus.komp
import org.w3c.dom.*
import org.w3c.dom.events.Event
const val HASH_VALUE = "komp-hash-value"
//const val HASH_ATTRIBUTE = "data-komp-hash"
const val EVENT_PROPERTY = "komp-events"
fun Node.getKompHash(): Int = this.asDynamic()[HASH_VALUE] as? Int? ?: -1
fun Node.setKompHash(hash: Int) {
this.asDynamic()[HASH_VALUE] = hash
}
private fun NodeList.findNodeHashIndex(hash: Int): Int {
for (index in 0..this.length) {
val node = this[index]
if (node is HTMLElement && node.getKompHash() == hash) {
return index
}
}
return -1
}
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.Node
import org.w3c.dom.get
object DiffPatch {
fun hashesMatch(oldNode: Node, newNode: Node): Boolean {
return (
oldNode is HTMLElement &&
newNode is HTMLElement &&
oldNode.nodeName == newNode.nodeName &&
oldNode.getKompHash() != -1 &&
newNode.getKompHash() != -1 &&
oldNode.getKompHash() == newNode.getKompHash()
)
}
private fun updateKomponentOnNode(oldNode: Node, newNode: Node) {
val komponent = newNode.asDynamic()[KOMP_KOMPONENT] as? Komponent
private fun updateKomponentOnNode(element: Node, newVdom: VDOMElement) {
val komponent = newVdom.komponent
if (komponent != null) {
if (Komponent.logReplaceEvent) {
console.log("Keeping oldNode, set oldNode element on Komponent", oldNode, komponent)
console.log("Keeping oldNode, set oldNode element on Komponent", element, komponent)
}
komponent.element = oldNode
komponent.element = element
}
}
fun updateNode(oldNode: Node, newNode: Node): Node {
if (hashesMatch(oldNode, newNode)) {
fun updateNode(element: Node, oldVdom: VDOMElement, newVdom: VDOMElement): Node {
if (oldVdom.hash == newVdom.hash) {
if (Komponent.logReplaceEvent) {
console.log("Hashes match", oldNode, newNode, oldNode.getKompHash(), newNode.getKompHash())
console.log("Hashes match", oldVdom, newVdom, oldVdom.hash, newVdom.hash)
}
updateKomponentOnNode(oldNode, newNode)
return oldNode
// no change
return element
}
if (oldNode.nodeType == newNode.nodeType && oldNode.nodeType == 3.toShort()) {
if (oldNode.textContent != newNode.textContent) {
if (oldVdom.type == newVdom.type && oldVdom.type == VDOMElementType.TEXT) {
if (oldVdom.content != newVdom.content) {
if (Komponent.logReplaceEvent) {
console.log("Updating text content", oldNode, newNode)
console.log("Updating text content", oldVdom, newVdom)
}
oldNode.textContent = newNode.textContent
element.textContent = newVdom.content
}
updateKomponentOnNode(oldNode, newNode)
return oldNode
return element
}
if (oldNode is HTMLElement && newNode is HTMLElement) {
if (oldNode.nodeName == newNode.nodeName) {
if (oldVdom.type == newVdom.type && oldVdom.type == VDOMElementType.TAG) {
if (oldVdom.content == newVdom.content) {
if (Komponent.logReplaceEvent) {
console.log("Update attributes", oldNode.nodeName, newNode.nodeName)
console.log("Update attributes", oldVdom.content, newVdom.content)
}
updateAttributes(oldNode, newNode);
updateAttributes(element as HTMLElement, oldVdom, newVdom)
if (Komponent.logReplaceEvent) {
console.log("Update events", oldNode.nodeName, newNode.nodeName)
console.log("Update events", oldVdom.content, newVdom.content)
}
updateEvents(oldNode, newNode)
updateEvents(element as HTMLElement, oldVdom, newVdom)
if (Komponent.logReplaceEvent) {
console.log("Update children", oldNode.nodeName, newNode.nodeName)
console.log("Update children", oldVdom.content, newVdom.content)
}
updateKomponentOnNode(oldNode, newNode)
updateChildren(oldNode, newNode)
oldNode.setKompHash(newNode.getKompHash())
updateKomponentOnNode(element, newVdom)
updateChildren(element, oldVdom, newVdom)
return oldNode
return element
}
}
if (Komponent.logReplaceEvent) {
console.log("Replace node (type)", oldNode.nodeType, oldNode, newNode)
console.log("Replace node (type)", newVdom.type, oldVdom, newVdom)
}
oldNode.parentNode?.replaceChild(newNode, oldNode)
val newNode = newVdom.createElement()
updateKomponentOnNode(newNode, newVdom)
element.parentNode?.replaceChild(newNode, element)
return newNode
}
private fun updateAttributes(oldNode: HTMLElement, newNode: HTMLElement) {
private fun updateAttributes(element: HTMLElement, oldVdom: VDOMElement, newVdom: VDOMElement) {
// removed attributes
for (name in oldNode.getAttributeNames()) {
val attr = oldNode.attributes[name]
if (attr != null && newNode.getAttribute(name) == null) {
oldNode.removeAttribute(name)
for ((name, attr) in oldVdom.attributes) {
if (newVdom.attributes[name] == null) {
element.removeAttribute(name)
}
}
for (name in newNode.getAttributeNames()) {
val value = newNode.getAttribute(name)
val oldValue = oldNode.getAttribute(name)
for ((name, value) in newVdom.attributes) {
val oldValue = oldVdom.attributes[name]
if (value != oldValue) {
if (value != null) {
oldNode.setAttribute(name, value)
}else {
oldNode.removeAttribute(name)
}
element.setAttribute(name, value)
}
}
if (newNode is HTMLInputElement && oldNode is HTMLInputElement) {
oldNode.value = newNode.value
oldNode.checked = newNode.checked
if (newVdom.content == "input" && oldVdom.content == "input") {
if (element is HTMLInputElement) {
element.value = newVdom.attributes["value"] ?: ""
element.checked = newVdom.attributes["checked"] == "true"
}
}
}
private fun updateChildren(oldNode: HTMLElement, newNode: HTMLElement) {
private fun updateChildren(
element: HTMLElement,
oldVdom: VDOMElement,
newVdom: VDOMElement
) {
var oldIndex = 0
var newIndex = 0
if (Komponent.logReplaceEvent) {
console.log(
"updateChildren HTML old(${oldNode.childNodes.length})",
oldNode.innerHTML
"updateChildren HTML old(${oldVdom.childNodes.size})",
oldVdom.toString()
)
console.log(
"updateChildren HTML new(${newNode.childNodes.length})",
newNode.innerHTML
"updateChildren HTML new(${newVdom.childNodes.size})",
newVdom.toString()
)
}
while (newIndex < newNode.childNodes.length) {
while (oldIndex < oldVdom.childNodes.size && newIndex < newVdom.childNodes.size) {
if (Komponent.logReplaceEvent) {
console.log("Update Old/new", oldIndex, newIndex)
}
val newChildNode = newNode.childNodes[newIndex]
val oldChildNode = oldVdom.childNodes[oldIndex]
val newChildNode = newVdom.childNodes[newIndex]
if (oldIndex < oldNode.childNodes.length) {
val oldChildNode = oldNode.childNodes[oldIndex]
if (oldChildNode != null && newChildNode != null) {
/*
if (Komponent.logReplaceEvent) {
console.log(">>> updateChildren old/new", oldChildNode, newChildNode)
}
*/
if (Komponent.logReplaceEvent) {
console.log("Update node Old/new", oldChildNode, newChildNode)
}
if (!hashesMatch(oldChildNode, newChildNode) && newChildNode is HTMLElement && oldChildNode is HTMLElement) {
if (Komponent.logReplaceEvent) {
console.log("Hashes don't match")
}
val oldHash = oldChildNode.getKompHash()
val newHash = newChildNode.getKompHash()
if (newHash >= 0) {
val oldNodeWithNewHashIndex = oldNode.childNodes.findNodeHashIndex(newHash)
if (Komponent.logReplaceEvent) {
console.log("oldNodeWithNewHashIndex", newHash, oldNodeWithNewHashIndex)
}
if (oldNodeWithNewHashIndex > oldIndex) {
if (oldHash >= 0) {
val newNodeWithOldHashIndex = newNode.childNodes.findNodeHashIndex(oldHash)
// remove i.o. swap
if (newNodeWithOldHashIndex == -1) {
if (Komponent.logReplaceEvent) {
console.log("Old node missing in new tree, remove node", oldChildNode)
}
oldNode.removeChild(oldChildNode)
continue
}
}
val nodeWithHash = oldNode.childNodes[oldNodeWithNewHashIndex]
if (Komponent.logReplaceEvent) {
console.log("nodeWithHash", nodeWithHash)
}
if (nodeWithHash != null) {
if (Komponent.logReplaceEvent) {
console.log(">-> swap nodes", oldNode)
}
oldNode.insertBefore(nodeWithHash, oldNode.childNodes[oldIndex])
if (Komponent.logReplaceEvent) {
console.log(">-> swapped nodes", oldNode)
}
newIndex++
oldIndex++
continue
}
} else if (oldHash >= 0 && newNode.childNodes.findNodeHashIndex(oldHash) > newIndex) {
if (Komponent.logReplaceEvent) {
console.log("newNodeWithOldHashIndex", oldHash, newNode.childNodes.findNodeHashIndex(oldHash))
}
oldNode.insertBefore(newChildNode, oldChildNode)
oldIndex++
continue
}
}
}
val updatedNode = updateNode(oldChildNode, newChildNode)
if (updatedNode == newChildNode) {
if (oldChildNode is HTMLElement && newChildNode is HTMLElement) {
updateEvents(oldChildNode, newChildNode)
}
oldIndex++
continue
}
} else {
if (Komponent.logReplaceEvent) {
console.log("Null node", oldChildNode, newChildNode)
}
}
oldIndex++
newIndex++
} else {
if (Komponent.logReplaceEvent) {
console.log("Append Old/new/node", oldIndex, newIndex, newChildNode)
}
oldNode.append(newChildNode)
oldIndex++
if (Komponent.logReplaceEvent) {
console.log("Update node Old/new", oldChildNode, newChildNode)
}
/*
if (Komponent.logReplaceEvent) {
console.log("<<< Updated Old/new", oldNode.innerHTML, newNode.innerHTML)
}
*/
// scenarios:
// - hashes match, next
// - hashes don't match:
// -- old hash is down in new list
// --- delta == 1, insert new node
// -- new hash is down in old list
// --- delta == 1, remove current else swap?
// else: replace current node with new
if (oldVdom.hash != newVdom.hash &&
newChildNode.type == VDOMElementType.TAG &&
oldChildNode.type == VDOMElementType.TAG
) {
if (Komponent.logReplaceEvent) {
console.log("Hashes don't match")
}
val oldHash = oldChildNode.hash.hashCode()
val newHash = newChildNode.hash.hashCode()
val newHashIndexInOld = oldVdom.findNodeHashIndex(newHash)
val oldHashIndexInNew = newVdom.findNodeHashIndex(oldHash)
if (newHashIndexInOld == oldIndex + 1 && oldHashIndexInNew == newIndex) {
// remove
element.removeChild(element.childNodes[oldIndex]!!)
oldIndex++
continue
} else if (newHashIndexInOld == oldIndex && oldHashIndexInNew == newIndex + 1) {
// insert
element.insertBefore(newChildNode.createElement(), element.childNodes[oldIndex]!!)
newIndex++
oldIndex++
continue
}
}
// update
updateNode(element.childNodes[oldIndex]!!, oldChildNode, newChildNode)
oldIndex++
newIndex++
}
while (oldIndex < oldNode.childNodes.length) {
oldNode.childNodes[oldIndex]?.also {
while (element.childNodes.length > newVdom.childNodes.size) {
element.childNodes[element.childNodes.length - 1]?.also {
if (Komponent.logReplaceEvent) {
console.log("Remove old node", it)
}
oldNode.removeChild(it)
element.removeChild(it)
}
}
while (newIndex < newVdom.childNodes.size) {
newVdom.childNodes[newIndex].also {
element.appendChild(it.createElement())
}
newIndex++
}
}
private fun updateEvents(oldNode: HTMLElement, newNode: HTMLElement) {
val oldEvents = (oldNode.asDynamic()[EVENT_PROPERTY] as? MutableList<String>) ?: mutableListOf()
val newEvents = (newNode.asDynamic()[EVENT_PROPERTY] as? MutableList<String>) ?: mutableListOf()
private fun updateEvents(element: HTMLElement, oldVdom: VDOMElement, newVdom: VDOMElement) {
val oldEvents = oldVdom.events
val newEvents = newVdom.events
if (Komponent.logReplaceEvent) {
console.log("Update events", oldNode.getAttribute(EVENT_PROPERTY), newNode.getAttribute(EVENT_PROPERTY))
console.log("Update events", oldEvents, newEvents)
}
for (event in oldEvents) {
oldNode.removeKompEvent(event)
for ((name, event) in oldEvents) {
element.removeEventListener(name, event)
}
for (event in newEvents) {
val newNodeEvent = newNode.asDynamic()["event-$event"]
if (newNodeEvent != null) {
if (Komponent.logReplaceEvent) {
console.log("Set event $event on", oldNode)
}
oldNode.setKompEvent(event, newNodeEvent as ((Event) -> Unit))
for ((name, event) in newEvents) {
if (Komponent.logReplaceEvent) {
console.log("Set event $event on", element)
}
}
if (newEvents.isEmpty()) {
oldNode.asDynamic()[EVENT_PROPERTY] = null
} else {
oldNode.asDynamic()[EVENT_PROPERTY] = newNode.asDynamic()[EVENT_PROPERTY]
element.addEventListener(name, event)
}
}

View File

@@ -2,57 +2,223 @@ package nl.astraeus.komp
import kotlinx.browser.document
import kotlinx.html.*
import org.w3c.dom.*
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.events.Event
import kotlin.collections.MutableList
import kotlin.collections.MutableMap
import kotlin.collections.arrayListOf
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.isNotEmpty
import kotlin.collections.iterator
import kotlin.collections.last
import kotlin.collections.lastIndex
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.set
import kotlin.collections.withIndex
@Suppress("NOTHING_TO_INLINE")
inline fun HTMLElement.setKompEvent(name: String, noinline callback: (Event) -> Unit) {
val eventName = if (name.startsWith("on")) {
name.substring(2)
} else {
name
}
addEventListener(eventName, callback, null)
private fun attributeHash(key: String, value: String): Int =
3 * key.hashCode() +
5 * value.hashCode()
val events: MutableList<String> = (asDynamic()[EVENT_PROPERTY] as? MutableList<String>) ?: mutableListOf()
private fun MutableMap<String, String>.kompHash(): Int {
var result = 0
events.add(eventName)
asDynamic()[EVENT_PROPERTY] = events
asDynamic()["event-$eventName"] = callback
}
@Suppress("NOTHING_TO_INLINE")
inline fun HTMLElement.removeKompEvent(name: String) {
val eventName = if (name.startsWith("on")) {
name.substring(2)
} else {
name
for ((name, value) in this) {
result += attributeHash(name, value)
}
removeEventListener(eventName, asDynamic()["event-$eventName"] as ((Event) -> Unit), null)
val events: MutableList<String> = (asDynamic()[EVENT_PROPERTY] as? MutableList<String>) ?: mutableListOf()
events.remove(eventName)
asDynamic()["event-$eventName"] = null
return result
}
interface HtmlConsumer : TagConsumer<HTMLElement> {
fun append(node: Node)
private fun MutableMap<String, (Event) -> Unit>.kompHash(): Int {
var result = 0
for ((name, event) in this) {
result += attributeHash(name, event.toString())
}
return result
}
private fun MutableList<VDOMElement>.kompHash(): Int {
var result = 0
for (vdom in this) {
result += 3 * vdom.hash.hashCode()
}
return result
}
enum class VDOMElementType {
TAG,
TEXT,
ENTITY,
UNSAFE,
COMMENT
}
class VDOMElementHash(
var baseHash: Int,
var contentHash: Int,
var typeHash: Int,
var namespaceHash: Int = 0,
var attributesHash: Int = 0,
var eventsHash: Int = 0,
var childNodesHash: Int = 0
) {
override fun hashCode(): Int = baseHash +
3 * contentHash +
5 * typeHash +
7 * namespaceHash +
11 * attributesHash +
13 * eventsHash +
15 * childNodesHash
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is VDOMElementHash) return false
return other.hashCode() == this.hashCode()
}
}
class VDOMElement(
val baseHash: Int,
var content: String,
var namespace: String? = null,
var type: VDOMElementType = VDOMElementType.TAG,
) {
val attributes: MutableMap<String, String> = mutableMapOf()
val events: MutableMap<String, (Event) -> Unit> = mutableMapOf()
val childNodes: MutableList<VDOMElement> = mutableListOf()
val hash = VDOMElementHash(
baseHash,
content.hashCode(),
type.hashCode()
)
var id: String = ""
set(value) {
field = value
attributes["id"] = value
}
var komponent: Komponent? = null
fun setKompEvent(event: String, value: (Event) -> Unit) {
val eventName = if (event.startsWith("on")) {
event.substring(2)
} else {
event
}
val recalculate = events.containsKey(eventName)
events[eventName] = value
if (recalculate) {
hash.eventsHash = events.kompHash()
} else {
hash.eventsHash += attributeHash(eventName, value.toString())
}
}
fun appendChild(element: VDOMElement) {
childNodes.add(element)
//hash.childNodesHash += element.hash.hashCode()
}
fun updateChildHash() {
hash.childNodesHash = childNodes.kompHash()
}
fun removeAttribute(attr: String) {
if (attributes.containsKey(attr)) {
hash.attributesHash -= attributeHash(attr, attributes[attr] ?: "")
}
attributes.remove(attr)
}
fun setAttribute(attr: String, value: String) {
if (attributes.containsKey(attr)) {
hash.attributesHash -= attributeHash(attr, attributes[attr] ?: "")
}
if (attr.toLowerCase() == "id") {
id = value
}
attributes[attr] = value
hash.attributesHash += attributeHash(attr, value)
}
fun findNodeHashIndex(hash: Int): Int {
for ((index, node) in this.childNodes.withIndex()) {
if (node.type == VDOMElementType.TAG && node.hash.hashCode() == hash) {
return index
}
}
return -2
}
fun createElement(): Node {
val result = when (type) {
VDOMElementType.TAG -> {
val result: Element = if (namespace != null) {
document.createElementNS(namespace, content)
} else {
document.createElement(content)
}
for ((name, value) in attributes) {
result.setAttribute(name, value)
}
for ((name, value) in events) {
result.addEventListener(name, value)
}
for (child in childNodes) {
result.appendChild(child.createElement())
}
result
}
VDOMElementType.ENTITY,
VDOMElementType.UNSAFE,
VDOMElementType.TEXT -> {
document.createTextNode(content)
}
VDOMElementType.COMMENT -> {
document.createComment(content)
}
}
komponent?.also {
it.element = result
}
return result
}
}
interface HtmlConsumer : TagConsumer<VDOMElement> {
fun append(node: VDOMElement)
}
class HtmlBuilder(
val komponent: Komponent,
val document: Document,
val baseHash: Int
) : HtmlConsumer {
private val path = arrayListOf<HTMLElement>()
private var lastLeaved: HTMLElement? = null
private val path = arrayListOf<VDOMElement>()
private var lastLeaved: VDOMElement? = null
override fun onTagStart(tag: Tag) {
val element: HTMLElement = when {
tag.namespace != null -> document.createElementNS(tag.namespace!!, tag.tagName).asDynamic()
else -> document.createElement(tag.tagName) as HTMLElement
val element = VDOMElement(baseHash, tag.tagName, tag.namespace)
for (entry in tag.attributesEntries) {
element.setAttribute(entry.key, entry.value)
}
if (path.isNotEmpty()) {
@@ -64,9 +230,9 @@ class HtmlBuilder(
override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) {
when {
path.isEmpty() -> throw IllegalStateException("No current tag")
path.last().tagName.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag")
else -> path.last().let { node ->
path.isEmpty() -> throw IllegalStateException("No current tag")
path.last().content.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag")
else -> path.last().let { node ->
if (value == null) {
node.removeAttribute(attribute)
} else {
@@ -78,38 +244,19 @@ class HtmlBuilder(
override fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit) {
when {
path.isEmpty() -> throw IllegalStateException("No current tag")
path.last().tagName.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag")
path.isEmpty() -> throw IllegalStateException("No current tag")
path.last().content.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag")
else -> path.last().setKompEvent(event, value)
}
}
override fun onTagEnd(tag: Tag) {
var hash = 0
if (path.isEmpty() || path.last().tagName.toLowerCase() != tag.tagName.toLowerCase()) {
if (path.isEmpty() || path.last().content.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.getKompHash()
} else {
hash = hash * 37 + (child?.textContent?.hashCode() ?: 0)
}
}
for ((key, value) in tag.attributesEntries) {
element.setAttribute(key, value)
val key_value = "${key}-${value}"
hash = hash * 37 + key_value.hashCode()
}
element.setKompHash(baseHash * 53 + hash)
lastLeaved = path.removeAt(path.lastIndex)
lastLeaved?.updateChildHash()
}
override fun onTagContent(content: CharSequence) {
@@ -117,7 +264,7 @@ class HtmlBuilder(
throw IllegalStateException("No current DOM node")
}
path.last().appendChild(document.createTextNode(content.toString()))
path.last().appendChild(VDOMElement(baseHash, content.toString(), type = VDOMElementType.TEXT))
}
override fun onTagContentEntity(entity: Entities) {
@@ -126,15 +273,10 @@ class HtmlBuilder(
}
// 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 })
// other solution would be
// pathLast().innerHTML += entity.text
path.last().appendChild(VDOMElement(baseHash, entity.text, type = VDOMElementType.ENTITY))
}
override fun append(node: Node) {
override fun append(node: VDOMElement) {
path.last().appendChild(node)
}
@@ -142,7 +284,7 @@ class HtmlBuilder(
with(DefaultUnsafe()) {
block()
path.last().innerHTML += toString()
path.last().appendChild(VDOMElement(baseHash, toString(), type = VDOMElementType.UNSAFE))
}
}
@@ -151,18 +293,17 @@ class HtmlBuilder(
throw IllegalStateException("No current DOM node")
}
path.last().appendChild(document.createComment(content.toString()))
path.last().appendChild(VDOMElement(baseHash, content.toString(), type = VDOMElementType.COMMENT))
}
override fun finalize(): HTMLElement = lastLeaved?.asR() ?: throw IllegalStateException("We can't finalize as there was no tags")
@Suppress("UNCHECKED_CAST")
private fun HTMLElement.asR(): HTMLElement = this.asDynamic()
override fun finalize(): VDOMElement {
return lastLeaved ?: throw IllegalStateException("We can't finalize as there was no tags")
}
companion object {
fun create(content: HtmlBuilder.() -> Unit): HTMLElement {
fun create(content: HtmlBuilder.() -> Unit): VDOMElement {
val komponent = DummyKomponent()
val consumer = HtmlBuilder(komponent, document, komponent.hashCode())
val consumer = HtmlBuilder(komponent.hashCode())
content.invoke(consumer)
return consumer.finalize()
}

View File

@@ -9,8 +9,6 @@ import org.w3c.dom.Node
import org.w3c.dom.css.CSSStyleDeclaration
import kotlin.reflect.KProperty
const val KOMP_KOMPONENT = "komp-komponent"
typealias CssStyle = CSSStyleDeclaration.() -> Unit
class StateDelegate<T>(
@@ -49,11 +47,12 @@ abstract class Komponent {
private var createIndex = getNextCreateIndex()
private var dirty: Boolean = true
var vdom: VDOMElement? = null
var element: Node? = null
val declaredStyles: MutableMap<String, CSSStyleDeclaration> = HashMap()
open fun create(): HTMLElement {
val consumer = HtmlBuilder(this, document, this.createIndex)
open fun create(): VDOMElement {
val consumer = HtmlBuilder(this.createIndex)
try {
consumer.render()
} catch (e: Throwable) {
@@ -67,8 +66,9 @@ abstract class Komponent {
result.id = "komp_${createIndex}"
}
element = result
element.asDynamic()[KOMP_KOMPONENT] = this
result.komponent = this
vdom = result
dirty = false
@@ -96,20 +96,26 @@ abstract class Komponent {
}
internal fun refresh() {
val oldElement = element
val oldElement = vdom
if (logRenderEvent) {
console.log("Rendering", this)
}
val newElement = create()
if (oldElement != null) {
element = if (oldElement != null && element != null) {
if (logReplaceEvent) {
console.log("DomDiffing", oldElement, newElement)
}
element = DiffPatch.updateNode(oldElement, newElement)
DiffPatch.updateNode(element!!, oldElement, newElement)
} else {
if (logReplaceEvent) {
console.log("Create", newElement)
}
newElement.createElement()
}
vdom = newElement
dirty = false
}
@@ -122,7 +128,9 @@ abstract class Komponent {
var logReplaceEvent = false
fun create(parent: HTMLElement, component: Komponent, insertAsFirst: Boolean = false) {
val element = component.create()
val vdomElement = component.create()
val element = vdomElement.createElement()
component.element = element
if (insertAsFirst && parent.childElementCount > 0) {
parent.insertBefore(element, parent.firstChild)
@@ -165,7 +173,11 @@ abstract class Komponent {
console.log("Skip ${next.createIndex}")
}
}
} else {
console.log("Komponent element has no id, ", next, element)
}
} else {
console.log("Komponent element is null", next)
}
}

View File

@@ -2,46 +2,49 @@ package nl.astraeus.komp
import kotlinx.html.*
import kotlinx.html.js.onClickFunction
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import org.w3c.dom.Node
import org.w3c.dom.get
import kotlin.test.Test
import kotlin.test.assertTrue
fun nodesEqual(node1: Node, node2: Node): Boolean {
if (node1.childNodes.length != node1.childNodes.length) {
fun nodesEqual(element: Element, vdom: VDOMElement): Boolean {
if (element.childNodes.length != vdom.childNodes.size) {
return false
}
if (node1 is HTMLElement && node2 is HTMLElement) {
if (node1.attributes.length != node2.attributes.length) {
if (element.attributes.length != vdom.attributes.size) {
return false
}
for ((name, value) in vdom.attributes) {
if (value != element.getAttribute(name)) {
return false
}
for (index in 0 until node1.attributes.length) {
node1.attributes[index]?.also { attr1 ->
val attr2 = node2.getAttribute(attr1.name)
}
for ((index, child) in vdom.childNodes.withIndex()) {
if (index < element.childNodes.length) {
val elementChild = element.childNodes[index]
if (attr1.value != attr2) {
if (child.type == VDOMElementType.TAG) {
if (!nodesEqual(elementChild as HTMLElement, child)) {
return false
}
} else if (child.type == VDOMElementType.TEXT) {
if (child.content != element.textContent) {
return false
}
}
}
for (index in 0 until node1.childNodes.length) {
node1.childNodes[index]?.also { child1 ->
node2.childNodes[index]?.also { child2 ->
if (!nodesEqual(child1, child2)) {
return false
}
}
}
} else {
return false
}
}
return true
}
class TestUpdate {
@Test
fun testCompare1() {
fun testCompare() {
val dom1 = HtmlBuilder.create {
div {
div(classes = "bla") {
@@ -72,9 +75,13 @@ class TestUpdate {
}
}
DiffPatch.updateNode(dom1, dom2)
var element = dom1.createElement()
assertTrue(nodesEqual(dom1, dom2), "Updated dom not equal to original")
assertTrue(nodesEqual(element as Element, dom1), "Created dom not equal to original")
element = DiffPatch.updateNode(element, dom1, dom2) as HTMLElement
assertTrue(nodesEqual(element, dom2), "Updated dom not equal to original")
}
@Test
@@ -88,7 +95,7 @@ class TestUpdate {
table {
tr {
th {
+ "Header"
+"Header"
}
}
tr {
@@ -105,7 +112,7 @@ class TestUpdate {
div {
div {
span {
+ "Other text"
+"Other text"
}
}
span {
@@ -123,10 +130,15 @@ class TestUpdate {
}
}
Komponent.logReplaceEvent = true
DiffPatch.updateNode(dom1, dom2)
Komponent.logRenderEvent = true
assertTrue(nodesEqual(dom1, dom2), "Updated dom not equal to original")
var element = dom1.createElement()
assertTrue(nodesEqual(element as Element, dom1), "Created dom not equal to original")
element = DiffPatch.updateNode(element, dom1, dom2) as HTMLElement
assertTrue(nodesEqual(element, dom2), "Updated dom not equal to original")
}
}