From 18f8ce777dcb0fa7d6723ffa9f36c35342cd342d Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Tue, 7 May 2019 20:12:19 +0200
Subject: Upgrade to Kotlin 1.3.31

---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 3d8b64ac..ec093d09 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,6 +1,6 @@
 group=pl.treksoft
 version=0.0.35
-kotlinVersion=1.3.30
+kotlinVersion=1.3.31
 javaVersion=1.8
 coroutinesVersion=1.2.0
 serializationVersion=0.11.0
-- 
cgit 


From e5e523d4a9a5c90156f6dc20410be06bd151eee9 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Tue, 7 May 2019 20:12:45 +0200
Subject: Fix routing initialization.

---
 src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt b/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt
index 950dfe1c..4628d989 100644
--- a/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt
@@ -48,4 +48,4 @@ open class Routing : Navigo(null, true, "#!") {
 /**
  * Default JavaScript router.
  */
-var routing = Routing()
+var routing = Routing().also { it.resolve() }
-- 
cgit 


From 857afb0fa7e8bdf435e2bb7cb5d951ba29449362 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Tue, 7 May 2019 20:42:30 +0200
Subject: Closable tabs in the TabPanel container.

---
 .../src/main/resources/css/style.css               | 14 ++++
 .../kotlin/pl/treksoft/kvision/panel/StackPanel.kt | 15 ++++-
 .../kotlin/pl/treksoft/kvision/panel/TabPanel.kt   | 76 ++++++++++++++++++----
 .../kotlin/pl/treksoft/kvision/utils/Snabbdom.kt   |  2 +
 .../test/pl/treksoft/kvision/panel/TabPanelSpec.kt | 25 +++++++
 5 files changed, 117 insertions(+), 15 deletions(-)

diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
index a0314ced..ddbd5d28 100644
--- a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
+++ b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
@@ -163,3 +163,17 @@ ul.tabs-top > li {
     float:none;
     flex-shrink: 0;
 }
+
+.kv-tab-close {
+    margin-left: 10px;
+    color: #000;
+    text-shadow: 0 1px 0 #fff;
+    filter: alpha(opacity=20);
+    opacity: 0.2;
+}
+
+.kv-tab-close:hover, .kv-tab-close:focus {
+    cursor: pointer;
+    filter: alpha(opacity=50);
+    opacity: 0.5;
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt
index 3b045fa6..37dd449b 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt
@@ -55,6 +55,8 @@ open class StackPanel(
             activeIndex = children.indexOf(value)
         }
 
+    internal val childrenMap = mutableMapOf<Int, Component>()
+
     init {
         @Suppress("LeakingThis")
         init?.invoke(this)
@@ -76,8 +78,11 @@ open class StackPanel(
      */
     open fun add(panel: Component, route: String): StackPanel {
         add(panel)
-        val currentIndex = children.size - 1
-        routing.on(route, { _ -> activeIndex = currentIndex }).resolve()
+        val currentIndex = counter++
+        childrenMap[currentIndex] = panel
+        routing.on(route, { _ ->
+            activeChild = childrenMap[currentIndex]!!
+        }).resolve()
         return this
     }
 
@@ -97,17 +102,23 @@ open class StackPanel(
 
     override fun remove(child: Component): StackPanel {
         super.remove(child)
+        childrenMap.filter { it.value == child }.keys.firstOrNull()?.let {
+            childrenMap.remove(it)
+        }
         if (activeIndex > children.size - 1) activeIndex = children.size - 1
         return this
     }
 
     override fun removeAll(): StackPanel {
         super.removeAll()
+        childrenMap.clear()
         if (activeIndex > children.size - 1) activeIndex = children.size - 1
         return this
     }
 
     companion object {
+        internal var counter = 0
+
         /**
          * DSL builder extension function.
          *
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt
index 5a4fac0c..ae8360b5 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt
@@ -25,10 +25,13 @@ import pl.treksoft.kvision.core.Component
 import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.ResString
 import pl.treksoft.kvision.core.WidgetWrapper
-import pl.treksoft.kvision.html.Link
+import pl.treksoft.kvision.html.Icon
+import pl.treksoft.kvision.html.Link.Companion.link
 import pl.treksoft.kvision.html.TAG
 import pl.treksoft.kvision.html.Tag
 import pl.treksoft.kvision.routing.routing
+import pl.treksoft.kvision.utils.obj
+import pl.treksoft.kvision.html.Icon.Companion.icon as cicon
 
 /**
  * Tab position.
@@ -95,6 +98,8 @@ open class TabPanel(
     private var nav = Tag(TAG.UL, classes = navClasses)
     private var content = StackPanel(false)
 
+    internal val childrenMap = mutableMapOf<Int, Component>()
+
     init {
         when (tabPosition) {
             TabPosition.TOP -> {
@@ -135,23 +140,46 @@ open class TabPanel(
      * @param panel child component
      * @param icon icon of the tab
      * @param image image of the tab
+     * @param closable determines if this tab is closable
      * @param route JavaScript route to activate given child
      * @return current container
      */
     open fun addTab(
         title: String, panel: Component, icon: String? = null,
-        image: ResString? = null, route: String? = null
+        image: ResString? = null, closable: Boolean = false, route: String? = null
     ): TabPanel {
-        val tag = Tag(TAG.LI)
-        tag.role = "presentation"
-        tag.add(Link(title, "#", icon, image))
-        val index = nav.children.size
-        tag.setEventListener {
-            click = { e ->
-                activeIndex = index
-                e.preventDefault()
-                if (route != null) {
-                    routing.navigate(route)
+        val currentIndex = counter++
+        childrenMap[currentIndex] = panel
+        val tag = Tag(TAG.LI) {
+            role = "presentation"
+            link(title, "#", icon, image) {
+                if (closable) {
+                    cicon("remove") {
+                        addCssClass("kv-tab-close")
+                        setEventListener<Icon> {
+                            click = { e ->
+                                val actIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex])
+                                e.asDynamic().data = actIndex
+                                if (this@TabPanel.dispatchEvent(
+                                        "tabClosing",
+                                        obj { detail = e; cancelable = true }) != false
+                                ) {
+                                    this@TabPanel.removeTab(actIndex)
+                                    this@TabPanel.dispatchEvent("tabClosed", obj { detail = e })
+                                }
+                                e.stopPropagation()
+                            }
+                        }
+                    }
+                }
+            }
+            setEventListener {
+                click = { e ->
+                    activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex])
+                    e.preventDefault()
+                    if (route != null) {
+                        routing.navigate(route)
+                    }
                 }
             }
         }
@@ -162,7 +190,8 @@ open class TabPanel(
         }
         content.add(panel)
         if (route != null) {
-            routing.on(route, { _ -> activeIndex = index }).resolve()
+            routing.on(route, { _ -> activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) })
+                .resolve()
         }
         return this
     }
@@ -172,6 +201,9 @@ open class TabPanel(
      */
     open fun removeTab(index: Int): TabPanel {
         nav.remove(nav.children[index])
+        childrenMap.filter { it.value == content.children[index] }.keys.firstOrNull()?.let {
+            childrenMap.remove(it)
+        }
         content.remove(content.children[index])
         activeIndex = content.activeIndex
         return this
@@ -191,14 +223,32 @@ open class TabPanel(
         return removeTab(index)
     }
 
+    /**
+     * Returns child component by tab index.
+     * @param index tab index
+     */
+    open fun getChildComponent(index: Int): Component? {
+        return content.children[index]
+    }
+
+    /**
+     * Returns tab header component by tab index.
+     * @param index tab index
+     */
+    open fun getNavComponent(index: Int): Tag? {
+        return nav.children[index] as? Tag
+    }
+
     override fun removeAll(): TabPanel {
         content.removeAll()
         nav.removeAll()
+        childrenMap.clear()
         refresh()
         return this
     }
 
     companion object {
+        internal var counter = 0
         /**
          * DSL builder extension function.
          *
diff --git a/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt b/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
index d2177a97..cb48cfd1 100644
--- a/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
@@ -130,6 +130,8 @@ interface BtOn : On {
     var tabulatorDataLoading: ((KvEvent) -> Unit)?
     var tabulatorDataLoaded: ((KvEvent) -> Unit)?
     var tabulatorDataEdited: ((KvEvent) -> Unit)?
+    var tabClosing: ((KvEvent) -> Unit)?
+    var tabClosed: ((KvEvent) -> Unit)?
 }
 
 /**
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
index 900a7268..f1f3beeb 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
@@ -21,6 +21,7 @@
  */
 package test.pl.treksoft.kvision.panel
 
+import pl.treksoft.jquery.jQuery
 import pl.treksoft.kvision.html.Span
 import pl.treksoft.kvision.panel.Root
 import pl.treksoft.kvision.panel.TabPanel
@@ -89,4 +90,28 @@ class TabPanelSpec : DomSpec {
             )
         }
     }
+
+
+    @Test
+    fun tabClick() {
+        run {
+            val root = Root("test", true)
+            val tabs = TabPanel()
+            root.add(tabs)
+            val label1 = Span("abc")
+            val label2 = Span("def")
+            tabs.addTab("ABC", label1)
+            tabs.addTab("DEF", label2)
+            tabs.removeTab(0)
+            val label3 = Span("ghi")
+            tabs.addTab("GHI", label3)
+            jQuery("#test a")[0]?.click()
+            val element = document.getElementById("test")
+            assertEqualsHtml(
+                "<div><ul class=\"nav nav-tabs\"><li role=\"presentation\" class=\"\"><a href=\"#\">DEF</a></li><li role=\"presentation\"><a href=\"#\">GHI</a></li></ul><div><span>def</span></div></div>",
+                element?.innerHTML,
+                "Should select correct tab by clicking"
+            )
+        }
+    }
 }
\ No newline at end of file
-- 
cgit 


From 825445b7041e2b07622801b0bbbf779b70f03f23 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 11 May 2019 02:29:40 +0200
Subject: Refactor focus and blur methods in FormControl interface.

---
 .../main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt |  4 ++--
 .../src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt  |  4 ++--
 .../main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt | 10 +++++-----
 .../kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt    |  4 ++--
 .../main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt |  4 ++--
 src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt        | 10 ++++++++++
 src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt   |  4 ++--
 .../kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt   |  4 ++--
 .../kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt  |  4 ++--
 9 files changed, 29 insertions(+), 19 deletions(-)

diff --git a/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
index 7c1c8d71..5858d04b 100644
--- a/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
+++ b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
@@ -282,14 +282,14 @@ open class DateTimeInput(
     /**
      * Makes the input element focused.
      */
-    open fun focus() {
+    override fun focus() {
         getElementJQuery()?.focus()
     }
 
     /**
      * Makes the input element blur.
      */
-    open fun blur() {
+    override fun blur() {
         getElementJQuery()?.blur()
     }
 
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
index f19081e1..db8a5b3b 100644
--- a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
@@ -39,7 +39,7 @@ import pl.treksoft.kvision.utils.SnOn
  * [SelectOption] or [SelectOptGroup] components to the container.
  *
  * @constructor
- * @param options an optional list of options (label to value pairs) for the select control
+ * @param options an optional list of options (value to label pairs) for the select control
  * @param value selected value
  * @param name the name attribute of the generated HTML input element
  * @param multiple allows multiple value selection (multiple values are comma delimited)
@@ -55,7 +55,7 @@ open class Select(
 ) : SimplePanel(setOf("form-group")), StringFormControl {
 
     /**
-     * A list of options (label to value pairs) for the select control.
+     * A list of options (value to label pairs) for the select control.
      */
     var options
         get() = input.options
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
index 7ffdf2c2..9ae1a4e9 100644
--- a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
@@ -50,7 +50,7 @@ enum class SelectWidthType(internal val value: String) {
  * [SelectOption] or [SelectOptGroup] components to the container.
  *
  * @constructor
- * @param options an optional list of options (label to value pairs) for the select control
+ * @param options an optional list of options (value to label pairs) for the select control
  * @param value selected value
  * @param multiple allows multiple value selection (multiple values are comma delimited)
  * @param ajaxOptions additional options for remote (AJAX) data source
@@ -64,9 +64,9 @@ open class SelectInput(
 ) : SimplePanel(classes), FormInput {
 
     /**
-     * A list of options (label to value pairs) for the select control.
+     * A list of options (value to label pairs) for the select control.
      */
-    internal var options by refreshOnUpdate(options) { setChildrenFromOptions() }
+    var options by refreshOnUpdate(options) { setChildrenFromOptions() }
     /**
      * A value of the selected option.
      */
@@ -337,14 +337,14 @@ open class SelectInput(
     /**
      * Makes the input element focused.
      */
-    open fun focus() {
+    override fun focus() {
         getElementJQuery()?.focus()
     }
 
     /**
      * Makes the input element blur.
      */
-    open fun blur() {
+    override fun blur() {
         getElementJQuery()?.blur()
     }
 
diff --git a/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
index c60aab2f..66e98ab5 100644
--- a/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
+++ b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
@@ -293,14 +293,14 @@ open class SpinnerInput(
     /**
      * Makes the input element focused.
      */
-    open fun focus() {
+    override fun focus() {
         getElementJQuery()?.focus()
     }
 
     /**
      * Makes the input element blur.
      */
-    open fun blur() {
+    override fun blur() {
         getElementJQuery()?.blur()
     }
 
diff --git a/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
index a250df42..39c4c4c6 100644
--- a/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
+++ b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
@@ -278,14 +278,14 @@ open class UploadInput(uploadUrl: String? = null, multiple: Boolean = false, cla
     /**
      * Makes the input element focused.
      */
-    open fun focus() {
+    override fun focus() {
         getElementJQuery()?.focus()
     }
 
     /**
      * Makes the input element blur.
      */
-    open fun blur() {
+    override fun blur() {
         getElementJQuery()?.blur()
     }
 
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
index 57ce88eb..3019fb6f 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
@@ -47,6 +47,16 @@ interface FormInput : Component {
      * The name attribute of the generated HTML input element.
      */
     var name: String?
+
+    /**
+     * Makes the input element focused.
+     */
+    fun focus()
+
+    /**
+     * Makes the input element blur.
+     */
+    fun blur()
 }
 
 /**
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
index 6973efe9..07b86b4c 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
@@ -152,14 +152,14 @@ abstract class CheckInput(
     /**
      * Makes the input element focused.
      */
-    open fun focus() {
+    override fun focus() {
         getElementJQuery()?.focus()
     }
 
     /**
      * Makes the input element blur.
      */
-    open fun blur() {
+    override fun blur() {
         getElementJQuery()?.blur()
     }
 
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt
index 98839982..fca681f6 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt
@@ -150,11 +150,11 @@ open class RadioGroupInput(
         }
     }
 
-    fun focus() {
+    override fun focus() {
         getChildren().filterIsInstance<Radio>().firstOrNull()?.focus()
     }
 
-    fun blur() {
+    override fun blur() {
         getChildren().filterIsInstance<Radio>().firstOrNull()?.blur()
     }
 
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
index c9ea2dba..393ae63f 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
@@ -157,14 +157,14 @@ abstract class AbstractTextInput(
     /**
      * Makes the input element focused.
      */
-    open fun focus() {
+    override fun focus() {
         getElementJQuery()?.focus()
     }
 
     /**
      * Makes the input element blur.
      */
-    open fun blur() {
+    override fun blur() {
         getElementJQuery()?.blur()
     }
 
-- 
cgit 


From 0b91779a5baf5f78ae616db8f67dcf4d81e839cc Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 11 May 2019 02:33:48 +0200
Subject: Allow constructing Root containers from HTMLElement objects.

---
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../pl/treksoft/kvision/dropdown/DropDownSpec.kt    | 12 ++++++------
 .../test/pl/treksoft/kvision/modal/AlertSpec.kt     |  2 +-
 .../test/pl/treksoft/kvision/modal/CloseIconSpec.kt |  2 +-
 .../test/pl/treksoft/kvision/modal/ConfirmSpec.kt   |  2 +-
 .../test/pl/treksoft/kvision/modal/ModalSpec.kt     |  4 ++--
 .../test/pl/treksoft/kvision/window/WindowSpec.kt   |  2 +-
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../pl/treksoft/kvision/chart/ChartCanvasSpec.kt    |  4 ++--
 .../test/pl/treksoft/kvision/chart/ChartSpec.kt     |  4 ++--
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../pl/treksoft/kvision/data/DataContainerSpec.kt   |  2 +-
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../treksoft/kvision/form/time/DateTimeInputSpec.kt |  2 +-
 .../pl/treksoft/kvision/form/time/DateTimeSpec.kt   |  2 +-
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../pl/treksoft/kvision/redux/StateBindingSpec.kt   |  4 ++--
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../treksoft/kvision/form/text/RichTextInputSpec.kt |  2 +-
 .../pl/treksoft/kvision/form/text/RichTextSpec.kt   |  2 +-
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../treksoft/kvision/form/select/SelectInputSpec.kt |  2 +-
 .../kvision/form/select/SelectOptGroupSpec.kt       |  2 +-
 .../kvision/form/select/SelectOptionSpec.kt         |  2 +-
 .../pl/treksoft/kvision/form/select/SelectSpec.kt   |  2 +-
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../kvision/form/spinner/SpinnerInputSpec.kt        |  6 +++---
 .../pl/treksoft/kvision/form/spinner/SpinnerSpec.kt |  6 +++---
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../pl/treksoft/kvision/tabulator/TabulatorSpec.kt  |  2 +-
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../treksoft/kvision/form/upload/UploadInputSpec.kt |  2 +-
 .../pl/treksoft/kvision/form/upload/UploadSpec.kt   |  2 +-
 src/main/kotlin/pl/treksoft/kvision/KVManager.kt    |  5 +++++
 src/main/kotlin/pl/treksoft/kvision/panel/Root.kt   | 21 ++++++++++++++++++---
 .../kotlin/test/pl/treksoft/kvision/TestUtil.kt     |  2 +-
 .../test/pl/treksoft/kvision/core/ContainerSpec.kt  | 10 +++++-----
 .../test/pl/treksoft/kvision/core/StyleSpec.kt      |  6 +++---
 .../test/pl/treksoft/kvision/core/WidgetSpec.kt     |  2 +-
 .../pl/treksoft/kvision/core/WidgetWrapperSpec.kt   |  2 +-
 .../pl/treksoft/kvision/dropdown/ContextMenuSpec.kt |  4 ++--
 .../test/pl/treksoft/kvision/dropdown/HeaderSpec.kt |  2 +-
 .../pl/treksoft/kvision/dropdown/SeparatorSpec.kt   |  2 +-
 .../test/pl/treksoft/kvision/form/FieldLabelSpec.kt |  2 +-
 .../test/pl/treksoft/kvision/form/HelpBlockSpec.kt  |  2 +-
 .../kvision/form/check/CheckBoxInputSpec.kt         |  2 +-
 .../pl/treksoft/kvision/form/check/CheckBoxSpec.kt  |  2 +-
 .../kvision/form/check/RadioGroupInputSpec.kt       |  2 +-
 .../treksoft/kvision/form/check/RadioGroupSpec.kt   |  2 +-
 .../treksoft/kvision/form/check/RadioInputSpec.kt   |  2 +-
 .../pl/treksoft/kvision/form/check/RadioSpec.kt     |  2 +-
 .../pl/treksoft/kvision/form/text/PasswordSpec.kt   |  2 +-
 .../treksoft/kvision/form/text/TextAreaInputSpec.kt |  2 +-
 .../pl/treksoft/kvision/form/text/TextAreaSpec.kt   |  2 +-
 .../pl/treksoft/kvision/form/text/TextInputSpec.kt  |  2 +-
 .../test/pl/treksoft/kvision/form/text/TextSpec.kt  |  2 +-
 .../test/pl/treksoft/kvision/html/ButtonSpec.kt     |  2 +-
 .../test/pl/treksoft/kvision/html/CanvasSpec.kt     |  2 +-
 .../kotlin/test/pl/treksoft/kvision/html/DivSpec.kt |  2 +-
 .../test/pl/treksoft/kvision/html/FooterSpec.kt     |  2 +-
 .../kotlin/test/pl/treksoft/kvision/html/H1Spec.kt  |  2 +-
 .../kotlin/test/pl/treksoft/kvision/html/H2Spec.kt  |  2 +-
 .../kotlin/test/pl/treksoft/kvision/html/H3Spec.kt  |  2 +-
 .../kotlin/test/pl/treksoft/kvision/html/H4Spec.kt  |  2 +-
 .../kotlin/test/pl/treksoft/kvision/html/H5Spec.kt  |  2 +-
 .../kotlin/test/pl/treksoft/kvision/html/H6Spec.kt  |  2 +-
 .../test/pl/treksoft/kvision/html/HeaderSpec.kt     |  2 +-
 .../test/pl/treksoft/kvision/html/IconSpec.kt       |  2 +-
 .../test/pl/treksoft/kvision/html/IframeSpec.kt     |  2 +-
 .../test/pl/treksoft/kvision/html/ImageSpec.kt      |  2 +-
 .../test/pl/treksoft/kvision/html/LinkSpec.kt       |  2 +-
 .../test/pl/treksoft/kvision/html/ListSpec.kt       |  4 ++--
 .../kotlin/test/pl/treksoft/kvision/html/PSpec.kt   |  2 +-
 .../test/pl/treksoft/kvision/html/SectionSpec.kt    |  2 +-
 .../test/pl/treksoft/kvision/html/SpanSpec.kt       |  2 +-
 .../kotlin/test/pl/treksoft/kvision/html/TagSpec.kt |  8 ++++----
 .../test/pl/treksoft/kvision/navbar/NavFormSpec.kt  |  2 +-
 .../test/pl/treksoft/kvision/navbar/NavSpec.kt      |  2 +-
 .../test/pl/treksoft/kvision/navbar/NavbarSpec.kt   |  2 +-
 .../test/pl/treksoft/kvision/panel/DockPanelSpec.kt |  2 +-
 .../test/pl/treksoft/kvision/panel/FlexPanelSpec.kt |  2 +-
 .../test/pl/treksoft/kvision/panel/GridPanelSpec.kt |  2 +-
 .../test/pl/treksoft/kvision/panel/HPanelSpec.kt    |  2 +-
 .../kvision/panel/ResponsiveGridPanelSpec.kt        |  2 +-
 .../test/pl/treksoft/kvision/panel/RootSpec.kt      |  2 +-
 .../pl/treksoft/kvision/panel/SplitPanelSpec.kt     |  2 +-
 .../pl/treksoft/kvision/panel/StackPanelSpec.kt     |  8 ++++----
 .../test/pl/treksoft/kvision/panel/TabPanelSpec.kt  |  8 ++++----
 .../test/pl/treksoft/kvision/panel/VPanelSpec.kt    |  2 +-
 .../pl/treksoft/kvision/progress/ProgressBarSpec.kt |  2 +-
 .../kvision/progress/ProgressIndicatorSpec.kt       |  2 +-
 .../test/pl/treksoft/kvision/table/CellSpec.kt      |  2 +-
 .../pl/treksoft/kvision/table/HeaderCellSpec.kt     |  2 +-
 .../test/pl/treksoft/kvision/table/RowSpec.kt       |  2 +-
 .../test/pl/treksoft/kvision/table/TableSpec.kt     |  2 +-
 .../pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt  |  2 +-
 .../test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt |  2 +-
 97 files changed, 148 insertions(+), 128 deletions(-)

diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 2fc0320a..1ad97acc 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -88,7 +88,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt
index ff4daea7..af35fa51 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt
@@ -34,7 +34,7 @@ class DropDownSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag")
             root.add(dd)
             dd.toggle()
@@ -51,7 +51,7 @@ class DropDownSpec : DomSpec {
     @Test
     fun renderDropUp() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag").apply { dropup = true }
             root.add(dd)
             dd.toggle()
@@ -68,7 +68,7 @@ class DropDownSpec : DomSpec {
     @Test
     fun renderHeaderElement() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val dd = DropDown("Dropdown", listOf("abc" to DD.HEADER.option), "flag")
             root.add(dd)
             dd.toggle()
@@ -85,7 +85,7 @@ class DropDownSpec : DomSpec {
     @Test
     fun renderSeparatorElement() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val dd = DropDown("Dropdown", listOf("abc" to DD.SEPARATOR.option), "flag")
             root.add(dd)
             dd.toggle()
@@ -102,7 +102,7 @@ class DropDownSpec : DomSpec {
     @Test
     fun renderDisabledElement() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val dd = DropDown("Dropdown", listOf("abc" to DD.DISABLED.option), "flag")
             root.add(dd)
             dd.toggle()
@@ -119,7 +119,7 @@ class DropDownSpec : DomSpec {
     @Test
     fun toggle() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag")
             root.add(dd)
             val visible = dd.getElementJQuery()?.hasClass("open") ?: false
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt
index 807f837a..c3d17de9 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt
@@ -36,7 +36,7 @@ class AlertSpec : DomSpec {
     @Test
     fun render() {
         run {
-            Root("test", true)
+            Root("test", fixed = true)
             Alert.show("Alert caption", "Alert content")
             val alert = document.getElementById("test")?.let { jQuery(it).find(".modal")[0] }
             assertNotNull(alert, "Should show alert window")
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt
index 2e3ea3ef..1893ce81 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt
@@ -32,7 +32,7 @@ class CloseIconSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ci = CloseIcon()
             root.add(ci)
             val element = document.getElementById("test")
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt
index dc734e10..875bf9e2 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt
@@ -36,7 +36,7 @@ class ConfirmSpec : DomSpec {
     @Test
     fun render() {
         run {
-            Root("test", true)
+            Root("test", fixed = true)
             Confirm.show("Confirm caption", "Confirm content")
             val confirm = document.getElementById("test")?.let { jQuery(it).find(".modal")[0] }
             assertNotNull(confirm, "Should show confirm window")
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt
index 523abfd5..7149b163 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt
@@ -35,7 +35,7 @@ class ModalSpec : DomSpec {
     @Test
     fun render() {
         run {
-            Root("test", true)
+            Root("test", fixed = true)
             val modal = Modal("Modal")
             modal.show()
             val content = document.getElementById("test")?.let { jQuery(it).find(".modal-title").html() }
@@ -47,7 +47,7 @@ class ModalSpec : DomSpec {
     @Test
     fun toggle() {
         run {
-            Root("test", true)
+            Root("test", fixed = true)
             val modal = Modal("Modal")
             modal.toggle()
             val content = document.getElementById("test")?.let { jQuery(it).find(".modal-title").html() }
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt
index e87626ca..c79b9d32 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt
@@ -32,7 +32,7 @@ class WindowSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val window = Window("Window title", isResizable = false)
             root.add(window)
             val id = window.id
diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt
index 6fae54ad..da83b989 100644
--- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt
+++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt
@@ -36,7 +36,7 @@ class ChartCanvasSpec : DomSpec {
     @Test
     fun renderResponsive() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val chart = ChartCanvas(
                 configuration = Configuration(
                     ChartType.SCATTER,
@@ -56,7 +56,7 @@ class ChartCanvasSpec : DomSpec {
     @Test
     fun renderNotResponsive() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val chart = ChartCanvas(
                 300, 600,
                 configuration = Configuration(
diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt
index 7974ea78..1fe73608 100644
--- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt
+++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt
@@ -37,7 +37,7 @@ class ChartSpec : DomSpec {
     @Test
     fun renderResponsive() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val chart = Chart(
                 Configuration(
                     ChartType.SCATTER,
@@ -60,7 +60,7 @@ class ChartSpec : DomSpec {
     @Test
     fun renderNotResponsive() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val chart = Chart(
                 Configuration(
                     ChartType.SCATTER,
diff --git a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt
index 931294d5..556d3991 100644
--- a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt
+++ b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt
@@ -36,7 +36,7 @@ class DataContainerSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
 
             class Model(value: String) : BaseDataComponent() {
                 var value: String by obs(value)
diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt
index 69ab46ec..877cf650 100644
--- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt
@@ -34,7 +34,7 @@ class DateTimeInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val data = Date()
             val dti = DateTimeInput(value = data).apply {
                 placeholder = "place"
diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt
index 19cefd86..b5e393bb 100644
--- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt
@@ -34,7 +34,7 @@ class DateTimeSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val data = Date()
             val ti = DateTime(value = data, label = "Label").apply {
                 placeholder = "place"
diff --git a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt
index 0271a956..5204402d 100644
--- a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt
+++ b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt
@@ -55,7 +55,7 @@ class StateBindingSpec : DomSpec {
     @Test
     fun stateBinding() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val store = createReduxStore(::stateReducer, State(10))
 
             val container = SimplePanel()
@@ -81,7 +81,7 @@ class StateBindingSpec : DomSpec {
     @Test
     fun stateUpdate() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val store = createReduxStore(::stateReducer, State(10))
 
             val container = SimplePanel()
diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt
index 21b7dc39..4ebf7b45 100644
--- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt
@@ -34,7 +34,7 @@ class RichTextInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val hai = RichTextInput(value = "abc").apply {
                 placeholder = "place"
                 id = "idti"
diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt
index 844b7e94..1ea79790 100644
--- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt
@@ -34,7 +34,7 @@ class RichTextSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val hai = RichText(value = "abc", label = "Field").apply {
                 placeholder = "place"
                 id = "idti"
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt
index 30f42e9c..bfd93900 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt
@@ -34,7 +34,7 @@ class SelectInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val selectInput = SelectInput(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true).apply {
                 liveSearch = true
                 placeholder = "Choose ..."
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt
index bd88f560..33ccc843 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt
@@ -33,7 +33,7 @@ class SelectOptGroupSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val selectOptGroup = SelectOptGroup("Group", listOf("test1" to "Test 1", "test2" to "Test 2"), 2)
             root.add(selectOptGroup)
             val element = document.getElementById("test")
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt
index f7e07d42..33c36576 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt
@@ -32,7 +32,7 @@ class SelectOptionSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val selectOption = SelectOption("testValue", "testLabel")
             root.add(selectOption)
             val element = document.getElementById("test")
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt
index eaccd551..9eddff81 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt
@@ -34,7 +34,7 @@ class SelectSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val select = Select(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", null, true, null, "Label").apply {
                 liveSearch = true
                 placeholder = "Choose ..."
diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt
index a240bfd8..467e48db 100644
--- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt
@@ -32,7 +32,7 @@ class SpinnerInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val si = SpinnerInput(value = 13).apply {
                 placeholder = "place"
                 id = "idti"
@@ -46,7 +46,7 @@ class SpinnerInputSpec : DomSpec {
     @Test
     fun spinUp() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val si = SpinnerInput(value = 13).apply {
                 placeholder = "place"
                 id = "idti"
@@ -61,7 +61,7 @@ class SpinnerInputSpec : DomSpec {
     @Test
     fun spinDown() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val si = SpinnerInput(value = 13).apply {
                 placeholder = "place"
                 id = "idti"
diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt
index 30f12a93..928fe0b1 100644
--- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt
@@ -33,7 +33,7 @@ class SpinnerSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ti = Spinner(value = 13, label = "Label").apply {
                 placeholder = "place"
                 name = "name"
@@ -59,7 +59,7 @@ class SpinnerSpec : DomSpec {
     @Test
     fun spinUp() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val si = Spinner(value = 13)
             root.add(si)
             assertEquals(13, si.value, "Should return initial value before spinUp")
@@ -71,7 +71,7 @@ class SpinnerSpec : DomSpec {
     @Test
     fun spinDown() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val si = Spinner(value = 13)
             root.add(si)
             assertEquals(13, si.value, "Should return initial value before spinDown")
diff --git a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt
index d6b33a78..1f49ee93 100644
--- a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt
+++ b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt
@@ -34,7 +34,7 @@ class TabulatorSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tabulator = Tabulator<Any>(options = TabulatorOptions(data = arrayOf(obj {
                 id = 1
                 name = "Name"
diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt
index 626b70e4..de7a9315 100644
--- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt
@@ -33,7 +33,7 @@ class UploadInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val upi = UploadInput(multiple = true).apply {
                 id = "idti"
             }
diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt
index bea4ddee..92078153 100644
--- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt
@@ -33,7 +33,7 @@ class UploadSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val upi = Upload(multiple = true)
             val id = upi.input.id
             root.add(upi)
diff --git a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
index faa0b77c..d1a4a8be 100644
--- a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
@@ -29,6 +29,7 @@ import com.github.snabbdom.datasetModule
 import com.github.snabbdom.eventListenersModule
 import com.github.snabbdom.propsModule
 import com.github.snabbdom.styleModule
+import org.w3c.dom.HTMLElement
 import pl.treksoft.kvision.core.Component
 import pl.treksoft.kvision.utils.isIE11
 import kotlin.browser.document
@@ -72,6 +73,10 @@ internal object KVManager {
         return sdPatch(container, vnode)
     }
 
+    internal fun patch(element: HTMLElement, vnode: VNode): VNode {
+        return sdPatch(element, vnode)
+    }
+
     internal fun patch(oldVNode: VNode, newVNode: VNode): VNode {
         return sdPatch(oldVNode, newVNode)
     }
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
index c17ea1a4..dd3d39b6 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
@@ -23,6 +23,7 @@ package pl.treksoft.kvision.panel
 
 import com.github.snabbdom.VNode
 import com.github.snabbdom.h
+import org.w3c.dom.HTMLElement
 import pl.treksoft.kvision.KVManager
 import pl.treksoft.kvision.core.StringBoolPair
 import pl.treksoft.kvision.core.Style
@@ -45,7 +46,12 @@ import pl.treksoft.kvision.utils.snOpt
  * @param init an initializer extension function
  */
 @Suppress("TooManyFunctions")
-class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Unit)? = null) : SimplePanel() {
+class Root(
+    id: String? = null,
+    element: HTMLElement? = null,
+    private val fixed: Boolean = false,
+    init: (Root.() -> Unit)? = null
+) : SimplePanel() {
     private val contextMenus: MutableList<ContextMenu> = mutableListOf()
     private var rootVnode: VNode = renderVNode()
 
@@ -54,8 +60,15 @@ class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Uni
     val isFirstRoot = roots.isEmpty()
 
     init {
-        rootVnode = KVManager.patch(id, this.renderVNode())
-        this.id = id
+        if (id != null) {
+            rootVnode = KVManager.patch(id, this.renderVNode())
+            this.id = id
+        } else if (element != null) {
+            rootVnode = KVManager.patch(element, this.renderVNode())
+            this.id = "kv_root_${counter++}"
+        } else {
+            throw IllegalArgumentException("No root element specified!")
+        }
         roots.add(this)
         if (isFirstRoot) {
             Style.styles.forEach { it.parent = this }
@@ -144,6 +157,8 @@ class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Uni
     }
 
     companion object {
+        internal var counter = 0
+
         /**
          * @suppress internal function
          */
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
 
     fun runW(code: (widget: Widget, element: Element?) -> Unit) {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             widget.id = "test_id"
             root.add(widget)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt
index 3e16fff8..960a18b6 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt
@@ -34,7 +34,7 @@ class ContainerSpec : DomSpec {
     @Test
     fun add() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val container = SimplePanel()
             val child1 = Widget()
             child1.id = "child1"
@@ -52,7 +52,7 @@ class ContainerSpec : DomSpec {
     @Test
     fun addAll() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val container = SimplePanel()
             val child1 = Widget()
             child1.id = "child1"
@@ -69,7 +69,7 @@ class ContainerSpec : DomSpec {
     @Test
     fun remove() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val container = SimplePanel()
             val child1 = Widget()
             child1.id = "child1"
@@ -88,7 +88,7 @@ class ContainerSpec : DomSpec {
     @Test
     fun removeAll() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val container = SimplePanel()
             val child1 = Widget()
             child1.id = "child1"
@@ -107,7 +107,7 @@ class ContainerSpec : DomSpec {
     @Test
     fun getChildren() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val container = SimplePanel()
             val child1 = Widget()
             child1.id = "child1"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt
index bdb93744..4fbe3d59 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt
@@ -37,7 +37,7 @@ class StyleSpec : DomSpec {
     @Test
     fun render() {
         run {
-            Root("test", true) {
+            Root("test", fixed = true) {
                 widget {
                     style {
                         margin = 2.px
@@ -58,7 +58,7 @@ class StyleSpec : DomSpec {
     @Test
     fun renderCustomClass() {
         run {
-            Root("test", true) {
+            Root("test", fixed = true) {
                 widget {
                     style("customclass") {
                         margin = 2.px
@@ -79,7 +79,7 @@ class StyleSpec : DomSpec {
     @Test
     fun renderSubclass() {
         run {
-            Root("test", true) {
+            Root("test", fixed = true) {
                 widget {
                     style("customclass") {
                         margin = 2.px
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt
index fc1cc761..6b9be23d 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt
@@ -130,7 +130,7 @@ class WidgetSpec : WSpec {
     @Test
     fun getRoot() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             root.add(widget)
             val r = widget.getRoot()
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt
index aac92c50..0c28c327 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt
@@ -35,7 +35,7 @@ class WidgetWrapperSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val widget = Widget()
             val wrapper = WidgetWrapper(widget)
             wrapper.width = 100 to UNIT.em
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt
index 7d320e2e..35172267 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt
@@ -34,7 +34,7 @@ class ContextMenuSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val m = ContextMenu {
                 link("a", "b")
                 link("c", "d")
@@ -54,7 +54,7 @@ class ContextMenuSpec : DomSpec {
     @Test
     fun positionMenu() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val m = ContextMenu {
                 link("a", "b")
                 link("c", "d")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt
index 5a9a050c..e75baf9e 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt
@@ -32,7 +32,7 @@ class HeaderSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val h = Header("Test")
             root.add(h)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt
index 2f2d22bf..86607ec7 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt
@@ -32,7 +32,7 @@ class SeparatorSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val s = Separator()
             root.add(s)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt
index d13bc920..5319d4bc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt
@@ -32,7 +32,7 @@ class FieldLabelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val fl = FieldLabel("input", "Label")
             root.add(fl)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt
index c7d0b0da..c7c4ede5 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt
@@ -32,7 +32,7 @@ class HelpBlockSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val fl = HelpBlock("Form Error")
             root.add(fl)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt
index 8a9f86d5..677a2b8e 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt
@@ -32,7 +32,7 @@ class CheckBoxInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ci = CheckBoxInput(value = true).apply {
                 name = "name"
                 id = "idti"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt
index 9a178abb..16da0c70 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt
@@ -33,7 +33,7 @@ class CheckBoxSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ci = CheckBox(value = true, label = "Label").apply {
                 name = "name"
                 style = CheckBoxStyle.DANGER
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt
index f74a76f7..55788c84 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt
@@ -33,7 +33,7 @@ class RadioGroupInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ci = RadioGroupInput(options = listOf("a" to "A", "b" to "B"), value = "a").apply {
                 disabled = true
                 inline = true
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt
index e55e9913..2ed52b67 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt
@@ -33,7 +33,7 @@ class RadioGroupSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ci = RadioGroup(options = listOf("a" to "A", "b" to "B"), value = "a", label = "Label").apply {
                 disabled = true
                 inline = true
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt
index 55d4108a..5b4fe836 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt
@@ -32,7 +32,7 @@ class RadioInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ci = RadioInput(value = true).apply {
                 name = "name"
                 id = "idti"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt
index 09763cc7..a8fbbcc5 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt
@@ -33,7 +33,7 @@ class RadioSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ci = Radio(value = true, label = "Label", extraValue = "abc").apply {
                 name = "name"
                 style = RadioStyle.DANGER
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt
index dc46782b..5d6c2738 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt
@@ -32,7 +32,7 @@ class PasswordSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ti = Password(value = "abc", label = "Label").apply {
                 placeholder = "place"
                 name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt
index 0e8257ff..815f12e7 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt
@@ -32,7 +32,7 @@ class TextAreaInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ti = TextAreaInput(cols = 5, rows = 2, value = "abc").apply {
                 placeholder = "place"
                 name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt
index c948628b..31b4baf2 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt
@@ -32,7 +32,7 @@ class TextAreaSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ti = TextArea(cols = 5, rows = 2, value = "abc", label = "Label").apply {
                 placeholder = "place"
                 name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt
index bd8c9786..6d21ab14 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt
@@ -33,7 +33,7 @@ class TextInputSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ti = TextInput(type = TextInputType.PASSWORD, value = "abc").apply {
                 placeholder = "place"
                 name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt
index 071bf35b..7ce811fa 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt
@@ -32,7 +32,7 @@ class TextSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ti = Text(value = "abc", label = "Label").apply {
                 placeholder = "place"
                 name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt
index de2ea036..512c3bc2 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt
@@ -34,7 +34,7 @@ class ButtonSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val button = Button("Cancel", "fa-bars", ButtonStyle.PRIMARY)
             button.size = ButtonSize.LARGE
             button.block = true
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt
index a896d9e5..f30872ca 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt
@@ -32,7 +32,7 @@ class CanvasSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val canvas = Canvas(800, 600)
             root.add(canvas)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt
index 4fd64478..528fc0ae 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt
@@ -32,7 +32,7 @@ class DivSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val div = Div("This is a div")
             root.add(div)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt
index 6398d95c..69819203 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt
@@ -32,7 +32,7 @@ class FooterSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val footer = Footer("This is a footer")
             root.add(footer)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt
index 6f22b89f..bd0c69b6 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt
@@ -32,7 +32,7 @@ class H1Spec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val h1 = H1("This is h1 header")
             root.add(h1)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt
index 1ca74077..5ec2a666 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt
@@ -32,7 +32,7 @@ class H2Spec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val h2 = H2("This is h2 header")
             root.add(h2)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt
index a9f58473..f85ede34 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt
@@ -32,7 +32,7 @@ class H3Spec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val h3 = H3("This is h3 header")
             root.add(h3)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt
index 47550459..ce52282b 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt
@@ -32,7 +32,7 @@ class H4Spec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val h4 = H4("This is h4 header")
             root.add(h4)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt
index 6b3fc647..029a692f 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt
@@ -32,7 +32,7 @@ class H5Spec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val h5 = H5("This is h5 header")
             root.add(h5)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt
index e22bd401..06a851e0 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt
@@ -32,7 +32,7 @@ class H6Spec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val div = H1("This is h1 header")
             root.add(div)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt
index e5ea8679..1b33adcc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt
@@ -32,7 +32,7 @@ class HeaderSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val header = Header("This is a header")
             root.add(header)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt
index fde07413..ea3425c5 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt
@@ -32,7 +32,7 @@ class IconSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val icon = Icon("fa-check")
             root.add(icon)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt
index 60a380b7..69051f92 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt
@@ -33,7 +33,7 @@ class IframeSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val iframe = Iframe("https://www.google.com", null, "test", 800, 600, setOf(Sandbox.ALLOWSAMEORIGIN))
             root.add(iframe)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt
index fa32a24f..169575fc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt
@@ -34,7 +34,7 @@ class ImageSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val res = require("./img/placeholder.png")
             @Suppress("UnsafeCastFromDynamic")
             val image = Image(res, "Image", true, ImageShape.ROUNDED, true)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt
index 6a4434ae..88869cf4 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt
@@ -32,7 +32,7 @@ class LinkSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val link = Link("Google", "http://www.google.com")
             root.add(link)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt
index 5f0aac69..bf52f1dc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt
@@ -35,7 +35,7 @@ class ListSpec : DomSpec {
     @Test
     fun renderElements() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val list = ListTag(ListType.DL_HORIZ, listOf("a1", "a2", "b1", "b2"))
             root.add(list)
             val element = document.getElementById("test")
@@ -50,7 +50,7 @@ class ListSpec : DomSpec {
     @Test
     fun renderAsContainer() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val list = ListTag(ListType.UL)
             list.add(Tag(TAG.PRE, "pre"))
             list.add(Tag(TAG.DEL, "del"))
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt
index d5af81e3..0ab9c5bc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt
@@ -32,7 +32,7 @@ class PSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val par = P("This is a paragraph")
             root.add(par)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt
index 4fb7a490..fa987d05 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt
@@ -32,7 +32,7 @@ class SectionSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val section = Section("This is a section")
             root.add(section)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt
index b1f49b45..88604b5e 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt
@@ -32,7 +32,7 @@ class SpanSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val span = Span("This is a label")
             root.add(span)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt
index 29a5c079..b6017804 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt
@@ -35,7 +35,7 @@ class TagSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tag = Tag(TAG.H1, "This is <b>h1</b>", rich = false, align = Align.CENTER)
             root.add(tag)
             val element = document.getElementById("test")
@@ -50,7 +50,7 @@ class TagSpec : DomSpec {
     @Test
     fun renderRich() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tag = Tag(TAG.H1, "This is <b>h1</b>", rich = true, align = Align.RIGHT)
             root.add(tag)
             val element = document.getElementById("test")
@@ -65,7 +65,7 @@ class TagSpec : DomSpec {
     @Test
     fun renderAsContainer() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tag = Tag(TAG.P, align = Align.RIGHT)
             tag.add(Tag(TAG.DEL, "This is test"))
             tag.add(Link("abc", "/x"))
@@ -82,7 +82,7 @@ class TagSpec : DomSpec {
     @Test
     fun renderUnaryPlus() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tag = Tag(TAG.H1, rich = true) {
                 +"This is <b>h1</b>"
             }
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt
index aeea7a3b..40720bcb 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt
@@ -32,7 +32,7 @@ class NavFormSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val navf = NavForm()
             root.add(navf)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt
index 2ecad124..988a706d 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt
@@ -32,7 +32,7 @@ class NavSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val nav = Nav()
             root.add(nav)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt
index 90af4450..f38a05f9 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt
@@ -37,7 +37,7 @@ class NavbarSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val navbar = Navbar("TEST", NavbarType.FIXEDTOP)
             root.add(navbar)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt
index d387be56..5684120d 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt
@@ -34,7 +34,7 @@ class DockPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val dockPanel = DockPanel()
             root.add(dockPanel)
             dockPanel.add(Span("abc"), Side.UP)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt
index 5bcc68f6..b897fef2 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt
@@ -35,7 +35,7 @@ class FlexPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val flexPanel = FlexPanel(FlexDir.ROWREV, justify = FlexJustify.SPACEEVENLY)
             root.add(flexPanel)
             flexPanel.add(Span("abc"), 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt
index add3638f..d7d9beb7 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt
@@ -33,7 +33,7 @@ class GridPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val gridPanel = GridPanel()
             root.add(gridPanel)
             gridPanel.add(Span("abc"), 1, 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt
index a185420c..c53b2e57 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt
@@ -34,7 +34,7 @@ class HPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val hPanel = HPanel(justify = FlexJustify.SPACEBETWEEN)
             root.add(hPanel)
             hPanel.add(Span("abc"), 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt
index eb710cef..fcdf9860 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt
@@ -33,7 +33,7 @@ class ResponsiveGridPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val rgPanel = ResponsiveGridPanel()
             root.add(rgPanel)
             rgPanel.add(Span("abc"), 1, 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt
index 659dac9a..474b1d6a 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt
@@ -50,7 +50,7 @@ class RootSpec : DomSpec {
     @Test
     fun getRoot() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val r = root.getRoot()
             assertTrue("Should return self") { r == root }
         }
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt
index 09c397ce..94ed165a 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt
@@ -34,7 +34,7 @@ class SplitPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val splitPanel = SplitPanel(Direction.VERTICAL)
             root.add(splitPanel)
             val label1 = Span("abc")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt
index f52b6486..8a1ce795 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt
@@ -33,7 +33,7 @@ class StackPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val stackPanel = StackPanel()
             root.add(stackPanel)
             val label1 = Span("abc")
@@ -48,7 +48,7 @@ class StackPanelSpec : DomSpec {
     @Test
     fun renderNotActivateLast() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val stackPanel = StackPanel(activateLast = false)
             root.add(stackPanel)
             val label1 = Span("abc")
@@ -67,7 +67,7 @@ class StackPanelSpec : DomSpec {
     @Test
     fun remove() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val stackPanel = StackPanel(activateLast = false)
             root.add(stackPanel)
             val label1 = Span("abc")
@@ -83,7 +83,7 @@ class StackPanelSpec : DomSpec {
     @Test
     fun removeAll() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val stackPanel = StackPanel(activateLast = false)
             root.add(stackPanel)
             val label1 = Span("abc")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
index f1f3beeb..35620818 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
@@ -34,7 +34,7 @@ class TabPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tabs = TabPanel()
             root.add(tabs)
             val label1 = Span("abc")
@@ -53,7 +53,7 @@ class TabPanelSpec : DomSpec {
     @Test
     fun setActiveIndex() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tabs = TabPanel()
             root.add(tabs)
             val label1 = Span("abc")
@@ -73,7 +73,7 @@ class TabPanelSpec : DomSpec {
     @Test
     fun removeTab() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tabs = TabPanel()
             root.add(tabs)
             val label1 = Span("abc")
@@ -95,7 +95,7 @@ class TabPanelSpec : DomSpec {
     @Test
     fun tabClick() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val tabs = TabPanel()
             root.add(tabs)
             val label1 = Span("abc")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt
index 81ea720d..5bed89f1 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt
@@ -34,7 +34,7 @@ class VPanelSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val vPanel = VPanel(justify = FlexJustify.SPACEBETWEEN)
             root.add(vPanel)
             vPanel.add(Span("abc"), 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt
index d520f0b6..2f044987 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt
@@ -33,7 +33,7 @@ class ProgressBarSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val progressBar =
                 ProgressBar(50, style = ProgressBarStyle.SUCCESS, striped = true, content = "Processing ...")
             root.add(progressBar)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt
index 83892ed9..4aa14230 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt
@@ -33,7 +33,7 @@ class ProgressIndicatorSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val ind = ProgressIndicator(50, style = ProgressBarStyle.SUCCESS, striped = true)
             root.add(ind)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt
index 435e9b22..582212bd 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt
@@ -32,7 +32,7 @@ class CellSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val cell = Cell("This is a cell")
             root.add(cell)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt
index 8c210ae0..40f25a66 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt
@@ -32,7 +32,7 @@ class HeaderCellSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val cell = HeaderCell("This is a header cell")
             root.add(cell)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt
index 6c2f3c1c..d9a6fdfa 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt
@@ -33,7 +33,7 @@ class RowSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val row = Row {
                 cell("A")
             }
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt
index 637f8f28..997da597 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt
@@ -35,7 +35,7 @@ class TableSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val table = Table(listOf("a", "b")) {
                 row {
                     cell("A")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt
index 16b352d8..b324b8ab 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt
@@ -34,7 +34,7 @@ class ButtonGroupSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val group = ButtonGroup()
             root.add(group)
             val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt
index 4d296e03..d41ef05e 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt
@@ -32,7 +32,7 @@ class ToolbarSpec : DomSpec {
     @Test
     fun render() {
         run {
-            val root = Root("test", true)
+            val root = Root("test", fixed = true)
             val toolbar = Toolbar()
             root.add(toolbar)
             val element = document.getElementById("test")
-- 
cgit 


From 046e5586674c9debde9cdd9108e60c74265e8119 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 11 May 2019 02:37:06 +0200
Subject: Add support for additional attributes in the Tag component. Add some
 missing tag types.

---
 src/main/kotlin/pl/treksoft/kvision/html/Tag.kt | 56 +++++++++++++++++++++++--
 1 file changed, 52 insertions(+), 4 deletions(-)

diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
index 32119a90..d9d839ba 100644
--- a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
@@ -25,6 +25,8 @@ import com.github.snabbdom.VNode
 import pl.treksoft.kvision.KVManager
 import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.core.Widget
 import pl.treksoft.kvision.i18n.I18n
 import pl.treksoft.kvision.panel.SimplePanel
 
@@ -78,7 +80,10 @@ enum class TAG(internal val tagName: String) {
     TD("td"),
 
     FORM("form"),
-    INPUT("input")
+    INPUT("input"),
+    SELECT("select"),
+    OPTION("option"),
+    BUTTON("button")
 }
 
 /**
@@ -101,13 +106,17 @@ enum class Align(val className: String) {
  * @param rich determines if [content] can contain HTML code
  * @param align content align
  * @param classes a set of CSS class names
+ * @param attributes a map of additional attributes
  * @param init an initializer extension function
  */
 open class Tag(
     type: TAG, content: String? = null, rich: Boolean = false, align: Align? = null,
-    classes: Set<String> = setOf(), init: (Tag.() -> Unit)? = null
+    classes: Set<String> = setOf(), attributes: Map<String, String> = mapOf(),
+    init: (Tag.() -> Unit)? = null
 ) : SimplePanel(classes), Template {
 
+    protected val attributes = attributes.toMutableMap()
+
     /**
      * Tag type.
      */
@@ -172,6 +181,14 @@ open class Tag(
         return cl
     }
 
+    override fun getSnAttrs(): List<StringPair> {
+        return if (attributes.isEmpty()) {
+            super.getSnAttrs()
+        } else {
+            attributes.toList() + super.getSnAttrs()
+        }
+    }
+
     operator fun String.unaryPlus() {
         if (content == null)
             content = this
@@ -179,6 +196,36 @@ open class Tag(
             content += translate(this)
     }
 
+    /**
+     * Returns the value of an additional attribute.
+     * @param name the name of the attribute
+     * @return the value of the attribute
+     */
+    fun getAttribute(name: String): String? {
+        return this.attributes[name]
+    }
+
+    /**
+     * Sets the value of additional attribute.
+     * @param name the name of the attribute
+     * @param value the value of the attribute
+     */
+    fun setAttribute(name: String, value: String): Widget {
+        this.attributes[name] = value
+        refresh()
+        return this
+    }
+
+    /**
+     * Removes the value of additional attribute.
+     * @param name the name of the attribute
+     */
+    fun removeAttribute(name: String): Widget {
+        this.attributes.remove(name)
+        refresh()
+        return this
+    }
+
     companion object {
         /**
          * DSL builder extension function.
@@ -187,9 +234,10 @@ open class Tag(
          */
         fun Container.tag(
             type: TAG, content: String? = null, rich: Boolean = false, align: Align? = null,
-            classes: Set<String> = setOf(), init: (Tag.() -> Unit)? = null
+            classes: Set<String> = setOf(), attributes: Map<String, String> = mapOf(),
+            init: (Tag.() -> Unit)? = null
         ): Tag {
-            val tag = Tag(type, content, rich, align, classes, init)
+            val tag = Tag(type, content, rich, align, classes, attributes, init)
             this.add(tag)
             return tag
         }
-- 
cgit 


From a4ff696ce73bd1c4894a6ea444c5fbb048bc1e44 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 11 May 2019 02:38:53 +0200
Subject: Add new SimpleSelect form control.

---
 .../src/main/resources/css/style.css               |  29 +++
 .../treksoft/kvision/form/select/SimpleSelect.kt   | 209 ++++++++++++++++++++
 .../kvision/form/select/SimpleSelectInput.kt       | 213 +++++++++++++++++++++
 .../kvision/form/select/SimpleSelectInputSpec.kt   |  51 +++++
 .../kvision/form/select/SimpleSelectSpec.kt        |  53 +++++
 5 files changed, 555 insertions(+)
 create mode 100644 src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt
 create mode 100644 src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt
 create mode 100644 src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt
 create mode 100644 src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt

diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
index ddbd5d28..eac7ddb9 100644
--- a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
+++ b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
@@ -177,3 +177,32 @@ ul.tabs-top > li {
     filter: alpha(opacity=50);
     opacity: 0.5;
 }
+
+select.form-control, .tabulator-row .tabulator-cell.tabulator-editing select {
+    -webkit-appearance: none;
+    -moz-appearance: none;
+    appearance: none;
+    background: transparent none no-repeat;
+    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAKCAYAAABblxXYAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUKFyIn4IjqJgAAAENJREFUKM/l0LERACEQQlGsiTa2px1aokGugNNAx8wfMy8AeLoBALYjaTqoKkga2+gKPgF/2Q7JkEx359oftu+C7/UBCUIcVQz0PvcAAAAASUVORK5CYII=');
+    background-position: right center;
+    cursor: pointer;
+}
+
+select.form-control:hover {
+    background-color: #e6e6e6;
+}
+
+select.form-control option {
+    background-color: white;
+}
+
+.tabulator-row .tabulator-cell.tabulator-editing input, .tabulator-row .tabulator-cell.tabulator-editing select {
+    border: 1px solid #ccc;
+    border-radius: 4px;
+}
+
+.tabulator-row .tabulator-cell.tabulator-editing input:focus, .tabulator-row .tabulator-cell.tabulator-editing select:focus {
+    border-color: #66afe9;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt
new file mode 100644
index 00000000..4d278ad2
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.form.select
+
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FieldLabel
+import pl.treksoft.kvision.form.HelpBlock
+import pl.treksoft.kvision.form.StringFormControl
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.SnOn
+
+/**
+ * The form field component for SimpleSelect control.
+ *
+ * @constructor
+ * @param options an optional list of options (value to label pairs) for the select control
+ * @param value selected value
+ * @param emptyOption determines if an empty option is automatically generated
+ * @param name the name attribute of the generated HTML input element
+ * @param label label text bound to the input element
+ * @param rich determines if [label] can contain HTML code
+ */
+@Suppress("TooManyFunctions")
+open class SimpleSelect(
+    options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false,
+    name: String? = null, label: String? = null, rich: Boolean = false
+) : SimplePanel(setOf("form-group")), StringFormControl {
+
+    /**
+     * A list of options (value to label pairs) for the select control.
+     */
+    var options
+        get() = input.options
+        set(value) {
+            input.options = value
+        }
+    /**
+     * A value of the selected option.
+     */
+    override var value
+        get() = input.value
+        set(value) {
+            input.value = value
+        }
+    /**
+     * The value of the selected child option.
+     *
+     * This value is placed directly in the generated HTML code, while the [value] property is dynamically
+     * bound to the select component.
+     */
+    var startValue
+        get() = input.startValue
+        set(value) {
+            input.startValue = value
+        }
+    /**
+     * Determines if an empty option is automatically generated.
+     */
+    var emptyOption
+        get() = input.emptyOption
+        set(value) {
+            input.emptyOption = value
+        }
+    /**
+     * Determines if the select is automatically focused.
+     */
+    var autofocus
+        get() = input.autofocus
+        set(value) {
+            input.autofocus = value
+        }
+    /**
+     * The label text bound to the select element.
+     */
+    var label
+        get() = flabel.content
+        set(value) {
+            flabel.content = value
+        }
+    /**
+     * Determines if [label] can contain HTML code.
+     */
+    var rich
+        get() = flabel.rich
+        set(value) {
+            flabel.rich = value
+        }
+
+    private val idc = "kv_form_simpleselect_$counter"
+    final override val input: SimpleSelectInput = SimpleSelectInput(
+        options, value, emptyOption, setOf("form-control")
+    ).apply {
+        this.id = idc
+        this.name = name
+    }
+    final override val flabel: FieldLabel = FieldLabel(idc, label, rich)
+    final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false }
+
+    init {
+        @Suppress("LeakingThis")
+        input.eventTarget = this
+        this.addInternal(flabel)
+        this.addInternal(input)
+        this.addInternal(validationInfo)
+        counter++
+    }
+
+    override fun getSnClass(): List<StringBoolPair> {
+        val cl = super.getSnClass().toMutableList()
+        if (validatorError != null) {
+            cl.add("has-error" to true)
+        }
+        return cl
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget {
+        input.setEventListener(block)
+        return this
+    }
+
+    override fun setEventListener(block: SnOn<Widget>.() -> Unit): Widget {
+        input.setEventListener(block)
+        return this
+    }
+
+    override fun removeEventListeners(): Widget {
+        input.removeEventListeners()
+        return this
+    }
+
+    override fun add(child: Component): SimplePanel {
+        input.add(child)
+        return this
+    }
+
+    override fun addAll(children: List<Component>): SimplePanel {
+        input.addAll(children)
+        return this
+    }
+
+    override fun remove(child: Component): SimplePanel {
+        input.remove(child)
+        return this
+    }
+
+    override fun removeAll(): SimplePanel {
+        input.removeAll()
+        return this
+    }
+
+    override fun getChildren(): List<Component> {
+        return input.getChildren()
+    }
+
+    override fun focus() {
+        input.focus()
+    }
+
+    override fun blur() {
+        input.blur()
+    }
+
+    companion object {
+        internal var counter = 0
+
+        /**
+         * DSL builder extension function.
+         *
+         * It takes the same parameters as the constructor of the built component.
+         */
+        fun Container.simpleSelect(
+            options: List<StringPair>? = null,
+            value: String? = null,
+            emptyOption: Boolean = false,
+            name: String? = null,
+            label: String? = null,
+            rich: Boolean = false,
+            init: (SimpleSelect.() -> Unit)? = null
+        ): SimpleSelect {
+            val simpleSelect = SimpleSelect(options, value, emptyOption, name, label, rich).apply { init?.invoke(this) }
+            this.add(simpleSelect)
+            return simpleSelect
+        }
+    }
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt
new file mode 100644
index 00000000..df334c1c
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.form.select
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.form.FormInput
+import pl.treksoft.kvision.form.InputSize
+import pl.treksoft.kvision.html.TAG
+import pl.treksoft.kvision.html.Tag
+import pl.treksoft.kvision.panel.SimplePanel
+
+internal const val KVNULL = "#kvnull"
+
+/**
+ * Simple select component.
+ *
+ * @constructor
+ * @param options an optional list of options (value to label pairs) for the select control
+ * @param value text input value
+ * @param emptyOption determines if an empty option is automatically generated
+ * @param classes a set of CSS class names
+ */
+open class SimpleSelectInput(
+    options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false,
+    classes: Set<String> = setOf()
+) : SimplePanel(classes + "form-control"), FormInput {
+
+    /**
+     * A list of options (value to label pairs) for the select control.
+     */
+    var options by refreshOnUpdate(options) { setChildrenFromOptions() }
+
+    /**
+     * Text input value.
+     */
+    var value by refreshOnUpdate(value) { refreshState() }
+    /**
+     * The value of the selected child option.
+     *
+     * This value is placed directly in the generated HTML code, while the [value] property is dynamically
+     * bound to the select component.
+     */
+    var startValue by refreshOnUpdate(value) { this.value = it; selectOption() }
+    /**
+     * The name attribute of the generated HTML input element.
+     */
+    override var name: String? by refreshOnUpdate()
+    /**
+     * Determines if the field is disabled.
+     */
+    override var disabled by refreshOnUpdate(false)
+    /**
+     * Determines if the text input is automatically focused.
+     */
+    var autofocus: Boolean? by refreshOnUpdate()
+    /**
+     * Determines if an empty option is automatically generated.
+     */
+    var emptyOption by refreshOnUpdate(emptyOption) { setChildrenFromOptions() }
+    /**
+     * The size of the input.
+     */
+    override var size: InputSize? by refreshOnUpdate()
+
+    init {
+        this.vnkey = "kv_simpleselectinput_${counter++}"
+        setChildrenFromOptions()
+        this.setInternalEventListener<SimpleSelectInput> {
+            change = {
+                self.changeValue()
+            }
+        }
+    }
+
+    override fun render(): VNode {
+        return render("select", childrenVNodes())
+    }
+
+    private fun setChildrenFromOptions() {
+        super.removeAll()
+        if (emptyOption) {
+            super.add(Tag(TAG.OPTION, "", attributes = mapOf("value" to KVNULL)))
+        }
+        options?.let {
+            val c = it.map {
+                val attributes = if (it.first == value) {
+                    mapOf("value" to it.first, "selected" to "selected")
+                } else {
+                    mapOf("value" to it.first)
+                }
+                Tag(TAG.OPTION, it.second, attributes = attributes)
+            }
+            super.addAll(c)
+        }
+    }
+
+    private fun selectOption() {
+        children.forEach { child ->
+            if (child is Tag && child.type == TAG.OPTION) {
+                if (value != null && child.getAttribute("value") == value) {
+                    child.setAttribute("selected", "selected")
+                } else {
+                    child.removeAttribute("selected")
+                }
+            }
+        }
+    }
+
+    override fun getSnClass(): List<StringBoolPair> {
+        val cl = super.getSnClass().toMutableList()
+        size?.let {
+            cl.add(it.className to true)
+        }
+        return cl
+    }
+
+    override fun getSnAttrs(): List<StringPair> {
+        val sn = super.getSnAttrs().toMutableList()
+        name?.let {
+            sn.add("name" to it)
+        }
+        autofocus?.let {
+            if (it) {
+                sn.add("autofocus" to "autofocus")
+            }
+        }
+        if (disabled) {
+            sn.add("disabled" to "disabled")
+        }
+        return sn
+    }
+
+    override fun afterInsert(node: VNode) {
+        refreshState()
+    }
+
+    /**
+     * @suppress
+     * Internal function
+     */
+    protected open fun refreshState() {
+        value?.let {
+            getElementJQuery()?.`val`(it)
+        } ?: getElementJQueryD()?.`val`(null)
+    }
+
+    /**
+     * @suppress
+     * Internal function
+     */
+    protected open fun changeValue() {
+        val v = getElementJQuery()?.`val`() as String?
+        if (v != null && v.isNotEmpty() && v != KVNULL) {
+            this.value = v
+        } else {
+            this.value = null
+        }
+    }
+
+    /**
+     * Makes the input element focused.
+     */
+    override fun focus() {
+        getElementJQuery()?.focus()
+    }
+
+    /**
+     * Makes the input element blur.
+     */
+    override fun blur() {
+        getElementJQuery()?.blur()
+    }
+
+    companion object {
+        internal var counter = 0
+
+        /**
+         * DSL builder extension function.
+         *
+         * It takes the same parameters as the constructor of the built component.
+         */
+        fun Container.simpleSelectInput(
+            options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false,
+            classes: Set<String> = setOf(), init: (SimpleSelectInput.() -> Unit)? = null
+        ): SimpleSelectInput {
+            val simpleSelectInput = SimpleSelectInput(options, value, emptyOption, classes).apply { init?.invoke(this) }
+            this.add(simpleSelectInput)
+            return simpleSelectInput
+        }
+    }
+}
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt
new file mode 100644
index 00000000..b2c77d10
--- /dev/null
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package test.pl.treksoft.kvision.form.select
+
+import pl.treksoft.kvision.form.select.SimpleSelectInput
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class SimpleSelectInputSpec : DomSpec {
+
+    @Test
+    fun render() {
+        run {
+            val root = Root("test", fixed = true)
+            val si = SimpleSelectInput(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true).apply {
+                name = "name"
+                id = "idti"
+                disabled = true
+            }
+            root.add(si)
+            val element = document.getElementById("test")
+            assertEqualsHtml(
+                "<select class=\"form-control\" id=\"idti\" name=\"name\" disabled=\"disabled\"><option value=\"#kvnull\"></option><option value=\"test1\" selected=\"selected\">Test 1</option><option value=\"test2\">Test 2</option></select>",
+                element?.innerHTML,
+                "Should render correct simple select input control"
+            )
+        }
+    }
+
+}
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt
new file mode 100644
index 00000000..db1c36f0
--- /dev/null
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package test.pl.treksoft.kvision.form.select
+
+import pl.treksoft.kvision.form.select.SimpleSelect
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class SimpleSelectSpec : DomSpec {
+
+    @Test
+    fun render() {
+        run {
+            val root = Root("test", fixed = true)
+            val select =
+                SimpleSelect(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true, "select", "Label").apply {
+                    name = "name"
+                    id = "idti"
+                    disabled = true
+                }
+            root.add(select)
+            val element = document.getElementById("test")
+            val id = select.input.id
+            assertEqualsHtml(
+                "<div class=\"form-group\" id=\"idti\"><label class=\"control-label\" for=\"$id\">Label</label><select class=\"form-control\" id=\"$id\" name=\"name\" disabled=\"disabled\"><option value=\"#kvnull\"></option><option value=\"test1\" selected=\"selected\">Test 1</option><option value=\"test2\">Test 2</option></select></div>",
+                element?.innerHTML,
+                "Should render correct simple select form control"
+            )
+        }
+    }
+
+}
-- 
cgit 


From 7550bcabe74c6adfef702bc9dbb4ed5f0bb178d6 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 11 May 2019 03:15:12 +0200
Subject: Support for custom sorters, editors and validators in the Tabulator
 component. Support for KVision components in custom formatters and editors.

---
 .../pl/treksoft/kvision/tabulator/Options.kt       | 103 ++++++++++++++++++++-
 1 file changed, 98 insertions(+), 5 deletions(-)

diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
index dac29ab9..055cd88d 100644
--- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
+++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
@@ -22,8 +22,17 @@
 
 package pl.treksoft.kvision.tabulator
 
+import org.w3c.dom.HTMLElement
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.form.FormControl
+import pl.treksoft.kvision.form.FormInput
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.tabulator.EditorRoot.disposeTimer
+import pl.treksoft.kvision.tabulator.EditorRoot.root
 import pl.treksoft.kvision.tabulator.js.Tabulator
 import pl.treksoft.kvision.utils.obj
+import kotlin.browser.document
+import kotlin.browser.window
 import kotlin.js.Promise
 
 /**
@@ -265,6 +274,10 @@ data class ColumnDefinition(
     val rowHandle: Boolean? = null,
     val hideInHtml: Boolean? = null,
     val sorter: Sorter? = null,
+    val sorterFunction: ((
+        a: dynamic, b: dynamic, aRow: Tabulator.RowComponent, bRow: Tabulator.RowComponent,
+        column: Tabulator.ColumnComponent, dir: SortingDir, sorterParams: dynamic
+    ) -> Number)? = null,
     val sorterParams: dynamic = null,
     val formatter: Formatter? = null,
     val formatterFunction: ((
@@ -275,8 +288,14 @@ data class ColumnDefinition(
     val variableHeight: Boolean? = null,
     val editable: ((cell: Tabulator.CellComponent) -> Boolean)? = null,
     val editor: Editor? = null,
+    val editorFunction: ((
+        cell: Tabulator.CellComponent,
+        onRendered: (callback: () -> Unit) -> Unit,
+        success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, editorParams: dynamic
+    ) -> dynamic)? = null,
     val editorParams: dynamic = null,
     val validator: Validator? = null,
+    val validatorFunction: dynamic = null,
     val validatorParams: String? = null,
     val download: Boolean? = null,
     val downloadTitle: String? = null,
@@ -325,11 +344,77 @@ data class ColumnDefinition(
     val cellEditCancelled: ((cell: Tabulator.CellComponent) -> Unit)? = null
 )
 
+internal object EditorRoot {
+    internal var root: Root? = null
+    internal var disposeTimer: Int? = null
+}
+
 /**
  * An extension function to convert column definition class to JS object.
  */
-@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod")
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod", "MagicNumber")
 fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.ColumnDefinition {
+    val tmpEditorFunction = editorFunction?.let {
+        { cell: Tabulator.CellComponent,
+          onRendered: (callback: () -> Unit) -> Unit,
+          success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, editorParams: dynamic ->
+            var onRenderedCallback: (() -> Unit)? = null
+            val component = it(cell, { callback ->
+                onRenderedCallback = callback
+            }, { value ->
+                success(value)
+                disposeTimer = window.setTimeout({
+                    root?.dispose()
+                    disposeTimer = null
+                    root = null
+                }, 500)
+            }, cancel, editorParams)
+            if (component is Component) {
+                val rootElement = document.createElement("div") as HTMLElement
+                onRendered {
+                    if (root != null) {
+                        disposeTimer?.let { window.clearTimeout(it) }
+                        root?.dispose()
+                    }
+                    root = Root(element = rootElement)
+                    @Suppress("UnsafeCastFromDynamic")
+                    root?.add(component)
+                    (component as? FormControl)?.focus()
+                    (component as? FormInput)?.focus()
+                    cell.checkHeight()
+                    onRenderedCallback?.invoke()
+                }
+                rootElement
+            } else {
+                component
+            }
+        }
+    }
+
+    val tmpFormatterFunction = formatterFunction?.let {
+        { cell: Tabulator.CellComponent, formatterParams: dynamic,
+          onRendered: (callback: () -> Unit) -> Unit ->
+            var onRenderedCallback: (() -> Unit)? = null
+            val component = it(cell, formatterParams) { callback ->
+                onRenderedCallback = callback
+            }
+            if (component is Component) {
+                val rootElement = document.createElement("div") as HTMLElement
+                onRendered {
+                    val root = Root(element = rootElement)
+                    console.log("root created")
+                    @Suppress("UnsafeCastFromDynamic")
+                    root.add(component)
+                    cell.checkHeight()
+                    onRenderedCallback?.invoke()
+                }
+                rootElement
+            } else {
+                component
+            }
+        }
+    }
+
     return obj {
         this.title = i18nTranslator(title)
         if (field != null) this.field = field
@@ -346,17 +431,25 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum
         if (cssClass != null) this.cssClass = cssClass
         if (rowHandle != null) this.rowHandle = rowHandle
         if (hideInHtml != null) this.hideInHtml = hideInHtml
-        if (sorter != null) this.sorter = sorter.sorter
+        if (sorterFunction != null) {
+            this.sorter = sorterFunction
+        } else if (sorter != null) {
+            this.sorter = sorter.sorter
+        }
         if (sorterParams != null) this.sorterParams = sorterParams
-        if (formatterFunction != null) {
-            this.formatter = formatterFunction
+        if (tmpFormatterFunction != null) {
+            this.formatter = tmpFormatterFunction
         } else if (formatter != null) {
             this.formatter = formatter.formatter
         }
         if (formatterParams != null) this.formatterParams = formatterParams
         if (variableHeight != null) this.variableHeight = variableHeight
         if (editable != null) this.editable = editable
-        if (editor != null) this.editor = editor.editor
+        if (tmpEditorFunction != null) {
+            this.editor = tmpEditorFunction
+        } else if (editor != null) {
+            this.editor = editor.editor
+        }
         if (editorParams != null) this.editorParams = editorParams
         if (validator != null) this.validator = validator.validator
         if (validatorParams != null) this.validatorParams = validatorParams
-- 
cgit 


From 0f854be2619161f997b98aa9ad18459008b93afe Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 11 May 2019 03:21:21 +0200
Subject: Remove forgotten debug info from code.

---
 .../src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt             | 1 -
 1 file changed, 1 deletion(-)

diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
index 055cd88d..3c2675cc 100644
--- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
+++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
@@ -402,7 +402,6 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum
                 val rootElement = document.createElement("div") as HTMLElement
                 onRendered {
                     val root = Root(element = rootElement)
-                    console.log("root created")
                     @Suppress("UnsafeCastFromDynamic")
                     root.add(component)
                     cell.checkHeight()
-- 
cgit 


From f262805b2710f145ef16bb18fee0e53530ef9505 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 11 May 2019 22:01:36 +0200
Subject: Fixed styling of SimpleSelect control.

---
 kvision-modules/kvision-bootstrap/src/main/resources/css/style.css | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
index eac7ddb9..992a5a0b 100644
--- a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
+++ b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
@@ -196,6 +196,10 @@ select.form-control option {
     background-color: white;
 }
 
+select.input-sm {
+    line-height: inherit;
+}
+
 .tabulator-row .tabulator-cell.tabulator-editing input, .tabulator-row .tabulator-cell.tabulator-editing select {
     border: 1px solid #ccc;
     border-radius: 4px;
-- 
cgit 


From 5902d5c3f24e1cd6ca241e3b9589545480eea373 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 11 May 2019 22:03:02 +0200
Subject: Type safety for custom editors and formatters based on KVision
 components.

---
 .../pl/treksoft/kvision/tabulator/Options.kt       | 111 +++++++++++++--------
 .../pl/treksoft/kvision/tabulator/Tabulator.kt     |  18 ++--
 2 files changed, 77 insertions(+), 52 deletions(-)

diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
index 3c2675cc..dd3bff5f 100644
--- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
+++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
@@ -22,6 +22,7 @@
 
 package pl.treksoft.kvision.tabulator
 
+import kotlinx.serialization.KSerializer
 import org.w3c.dom.HTMLElement
 import pl.treksoft.kvision.core.Component
 import pl.treksoft.kvision.form.FormControl
@@ -30,6 +31,7 @@ import pl.treksoft.kvision.panel.Root
 import pl.treksoft.kvision.tabulator.EditorRoot.disposeTimer
 import pl.treksoft.kvision.tabulator.EditorRoot.root
 import pl.treksoft.kvision.tabulator.js.Tabulator
+import pl.treksoft.kvision.utils.JSON
 import pl.treksoft.kvision.utils.obj
 import kotlin.browser.document
 import kotlin.browser.window
@@ -257,7 +259,7 @@ fun DownloadConfig.toJs(): Tabulator.DownloadConfig {
 /**
  * Column definition options.
  */
-data class ColumnDefinition(
+data class ColumnDefinition<T : Any>(
     val title: String,
     val field: String? = null,
     val visible: Boolean? = null,
@@ -284,6 +286,9 @@ data class ColumnDefinition(
         cell: Tabulator.CellComponent, formatterParams: dynamic,
         onRendered: (callback: () -> Unit) -> Unit
     ) -> dynamic)? = null,
+    val formatterComponentFunction: ((
+        cell: Tabulator.CellComponent, onRendered: (callback: () -> Unit) -> Unit, data: T
+    ) -> Component)? = null,
     val formatterParams: dynamic = null,
     val variableHeight: Boolean? = null,
     val editable: ((cell: Tabulator.CellComponent) -> Boolean)? = null,
@@ -293,6 +298,11 @@ data class ColumnDefinition(
         onRendered: (callback: () -> Unit) -> Unit,
         success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, editorParams: dynamic
     ) -> dynamic)? = null,
+    val editorComponentFunction: ((
+        cell: Tabulator.CellComponent,
+        onRendered: (callback: () -> Unit) -> Unit,
+        success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, data: T
+    ) -> Component)? = null,
     val editorParams: dynamic = null,
     val validator: Validator? = null,
     val validatorFunction: dynamic = null,
@@ -353,12 +363,19 @@ internal object EditorRoot {
  * An extension function to convert column definition class to JS object.
  */
 @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod", "MagicNumber")
-fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.ColumnDefinition {
-    val tmpEditorFunction = editorFunction?.let {
+fun <T : Any> ColumnDefinition<T>.toJs(
+    i18nTranslator: (String) -> (String),
+    dataSerializer: KSerializer<T>? = null
+): Tabulator.ColumnDefinition {
+    val tmpEditorFunction = editorComponentFunction?.let {
         { cell: Tabulator.CellComponent,
           onRendered: (callback: () -> Unit) -> Unit,
-          success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, editorParams: dynamic ->
+          success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, _: dynamic ->
             var onRenderedCallback: (() -> Unit)? = null
+            val str = kotlin.js.JSON.stringify(cell.getData())
+            @Suppress("UNCHECKED_CAST") val data = dataSerializer?.let {
+                JSON.plain.parse(it, str)
+            } ?: cell.getData() as T
             val component = it(cell, { callback ->
                 onRenderedCallback = callback
             }, { value ->
@@ -368,49 +385,44 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum
                     disposeTimer = null
                     root = null
                 }, 500)
-            }, cancel, editorParams)
-            if (component is Component) {
-                val rootElement = document.createElement("div") as HTMLElement
-                onRendered {
-                    if (root != null) {
-                        disposeTimer?.let { window.clearTimeout(it) }
-                        root?.dispose()
-                    }
-                    root = Root(element = rootElement)
-                    @Suppress("UnsafeCastFromDynamic")
-                    root?.add(component)
-                    (component as? FormControl)?.focus()
-                    (component as? FormInput)?.focus()
-                    cell.checkHeight()
-                    onRenderedCallback?.invoke()
+            }, cancel, data)
+            val rootElement = document.createElement("div") as HTMLElement
+            onRendered {
+                if (root != null) {
+                    disposeTimer?.let { window.clearTimeout(it) }
+                    root?.dispose()
                 }
-                rootElement
-            } else {
-                component
+                root = Root(element = rootElement)
+                @Suppress("UnsafeCastFromDynamic")
+                root?.add(component)
+                (component as? FormControl)?.focus()
+                (component as? FormInput)?.focus()
+                cell.checkHeight()
+                onRenderedCallback?.invoke()
             }
+            rootElement
         }
     }
 
-    val tmpFormatterFunction = formatterFunction?.let {
-        { cell: Tabulator.CellComponent, formatterParams: dynamic,
+    val tmpFormatterFunction = formatterComponentFunction?.let {
+        { cell: Tabulator.CellComponent, _: dynamic,
           onRendered: (callback: () -> Unit) -> Unit ->
             var onRenderedCallback: (() -> Unit)? = null
-            val component = it(cell, formatterParams) { callback ->
+            val str = kotlin.js.JSON.stringify(cell.getData())
+            @Suppress("UNCHECKED_CAST") val data =
+                dataSerializer?.let { JSON.plain.parse(it, str) } ?: cell.getData() as T
+            val component = it(cell, { callback ->
                 onRenderedCallback = callback
+            }, data)
+            val rootElement = document.createElement("div") as HTMLElement
+            onRendered {
+                val root = Root(element = rootElement)
+                @Suppress("UnsafeCastFromDynamic")
+                root.add(component)
+                cell.checkHeight()
+                onRenderedCallback?.invoke()
             }
-            if (component is Component) {
-                val rootElement = document.createElement("div") as HTMLElement
-                onRendered {
-                    val root = Root(element = rootElement)
-                    @Suppress("UnsafeCastFromDynamic")
-                    root.add(component)
-                    cell.checkHeight()
-                    onRenderedCallback?.invoke()
-                }
-                rootElement
-            } else {
-                component
-            }
+            rootElement
         }
     }
 
@@ -438,6 +450,8 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum
         if (sorterParams != null) this.sorterParams = sorterParams
         if (tmpFormatterFunction != null) {
             this.formatter = tmpFormatterFunction
+        } else if (formatterFunction != null) {
+            this.formatter = formatterFunction
         } else if (formatter != null) {
             this.formatter = formatter.formatter
         }
@@ -446,6 +460,8 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum
         if (editable != null) this.editable = editable
         if (tmpEditorFunction != null) {
             this.editor = tmpEditorFunction
+        } else if (editorFunction != null) {
+            this.editor = editorFunction
         } else if (editor != null) {
             this.editor = editor.editor
         }
@@ -496,14 +512,20 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum
         if (cellMouseMove != null) this.cellMouseMove = cellMouseMove
         if (cellEditing != null) this.cellEditing = cellEditing
         if (cellEdited != null) this.cellEdited = cellEdited
-        if (cellEditCancelled != null) this.cellEditCancelled = cellEditCancelled
+        if (cellEditCancelled != null) {
+            this.cellEditCancelled = cellEditCancelled
+        } else if (tmpEditorFunction != null) {
+            this.cellEditCancelled = { cell: Tabulator.CellComponent ->
+                cell.checkHeight()
+            }
+        }
     } as Tabulator.ColumnDefinition
 }
 
 /**
  * Tabulator options.
  */
-data class TabulatorOptions(
+data class TabulatorOptions<T : Any>(
     val height: String? = null,
     val virtualDom: Boolean? = null,
     val virtualDomBuffer: Int? = null,
@@ -517,7 +539,7 @@ data class TabulatorOptions(
     val downloadConfig: DownloadConfig? = null,
     val reactiveData: Boolean? = null,
     val autoResize: Boolean? = null,
-    val columns: List<ColumnDefinition>? = null,
+    val columns: List<ColumnDefinition<T>>? = null,
     val autoColumns: Boolean? = null,
     val layout: Layout? = null,
     val layoutColumnsOnNewData: Boolean? = null,
@@ -664,7 +686,10 @@ data class TabulatorOptions(
  * An extension function to convert tabulator options class to JS object.
  */
 @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod")
-fun TabulatorOptions.toJs(i18nTranslator: (String) -> (String)): Tabulator.Options {
+fun <T : Any> TabulatorOptions<T>.toJs(
+    i18nTranslator: (String) -> (String),
+    dataSerializer: KSerializer<T>? = null
+): Tabulator.Options {
     return obj {
         if (height != null) this.height = height
         if (virtualDom != null) this.virtualDom = virtualDom
@@ -679,7 +704,7 @@ fun TabulatorOptions.toJs(i18nTranslator: (String) -> (String)): Tabulator.Optio
         if (downloadConfig != null) this.downloadConfig = downloadConfig.toJs()
         if (reactiveData != null) this.reactiveData = reactiveData
         if (autoResize != null) this.autoResize = autoResize
-        if (columns != null) this.columns = columns.map { it.toJs(i18nTranslator) }.toTypedArray()
+        if (columns != null) this.columns = columns.map { it.toJs(i18nTranslator, dataSerializer) }.toTypedArray()
         if (autoColumns != null) {
             this.autoColumns = autoColumns
         } else if (columns == null) {
diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
index 249c578f..6c7480fc 100644
--- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
+++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
@@ -54,7 +54,7 @@ import pl.treksoft.kvision.tabulator.js.Tabulator as JsTabulator
 @Suppress("LargeClass", "TooManyFunctions")
 open class Tabulator<T : Any>(
     protected val data: List<T>? = null,
-    val options: TabulatorOptions = TabulatorOptions(),
+    val options: TabulatorOptions<T> = TabulatorOptions(),
     types: Set<TableType> = setOf(),
     classes: Set<String> = setOf(),
     protected val dataSerializer: KSerializer<T>? = null
@@ -228,7 +228,7 @@ open class Tabulator<T : Any>(
         (this.getElement() as? HTMLElement)?.let {
             jsTabulator =
                 KVManagerTabulator.getConstructor()
-                    .createInstance(it, options.toJs(this::translate))
+                    .createInstance(it, options.toJs(this::translate, dataSerializer))
             if (currentPage != null) {
                 jsTabulator?.setPageSize(pageSize ?: 0)
                 jsTabulator?.setPage(currentPage)
@@ -561,7 +561,7 @@ open class Tabulator<T : Any>(
          */
         inline fun <reified T : Any> Container.tabulator(
             data: List<T>? = null,
-            options: TabulatorOptions = TabulatorOptions(),
+            options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             noinline init: (Tabulator<T>.() -> Unit)? = null
@@ -578,7 +578,7 @@ open class Tabulator<T : Any>(
         inline fun <reified T : Any, S : Any, A : RAction> Container.tabulator(
             store: ReduxStore<S, A>,
             noinline dataFactory: (S) -> List<T>,
-            options: TabulatorOptions = TabulatorOptions(),
+            options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             noinline init: (Tabulator<T>.() -> Unit)? = null
@@ -594,7 +594,7 @@ open class Tabulator<T : Any>(
          */
         inline fun <reified T : Any, A : RAction> Container.tabulator(
             store: ReduxStore<List<T>, A>,
-            options: TabulatorOptions = TabulatorOptions(),
+            options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             noinline init: (Tabulator<T>.() -> Unit)? = null
@@ -609,7 +609,7 @@ open class Tabulator<T : Any>(
          * DSL builder extension function for dynamic data (send within options parameter).
          */
         fun <T : Any> Container.tabulator(
-            options: TabulatorOptions = TabulatorOptions(),
+            options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             init: (Tabulator<T>.() -> Unit)? = null
@@ -625,7 +625,7 @@ open class Tabulator<T : Any>(
          */
         @UseExperimental(ImplicitReflectionSerializer::class)
         inline fun <reified T : Any> create(
-            data: List<T>? = null, options: TabulatorOptions = TabulatorOptions(),
+            data: List<T>? = null, options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             noinline init: (Tabulator<T>.() -> Unit)? = null
@@ -642,7 +642,7 @@ open class Tabulator<T : Any>(
         inline fun <reified T : Any, S : Any, A : RAction> create(
             store: ReduxStore<S, A>,
             noinline dataFactory: (S) -> List<T>,
-            options: TabulatorOptions = TabulatorOptions(),
+            options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             noinline init: (Tabulator<T>.() -> Unit)? = null
@@ -662,7 +662,7 @@ open class Tabulator<T : Any>(
         @UseExperimental(ImplicitReflectionSerializer::class)
         inline fun <reified T : Any, A : RAction> create(
             store: ReduxStore<List<T>, A>,
-            options: TabulatorOptions = TabulatorOptions(),
+            options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             noinline init: (Tabulator<T>.() -> Unit)? = null
-- 
cgit 


From d5ad6278fbc4184fc3d3a648e2419652a3c29e25 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Mon, 13 May 2019 15:24:50 +0200
Subject: Auto update of mutable data model after edit actions.

---
 .../pl/treksoft/kvision/tabulator/Options.kt       | 31 +++++++++++++---------
 .../pl/treksoft/kvision/tabulator/Tabulator.kt     | 22 ++++++++++-----
 2 files changed, 34 insertions(+), 19 deletions(-)

diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
index dd3bff5f..08f2603b 100644
--- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
+++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
@@ -448,22 +448,18 @@ fun <T : Any> ColumnDefinition<T>.toJs(
             this.sorter = sorter.sorter
         }
         if (sorterParams != null) this.sorterParams = sorterParams
-        if (tmpFormatterFunction != null) {
-            this.formatter = tmpFormatterFunction
-        } else if (formatterFunction != null) {
-            this.formatter = formatterFunction
-        } else if (formatter != null) {
-            this.formatter = formatter.formatter
+        when {
+            tmpFormatterFunction != null -> this.formatter = tmpFormatterFunction
+            formatterFunction != null -> this.formatter = formatterFunction
+            formatter != null -> this.formatter = formatter.formatter
         }
         if (formatterParams != null) this.formatterParams = formatterParams
         if (variableHeight != null) this.variableHeight = variableHeight
         if (editable != null) this.editable = editable
-        if (tmpEditorFunction != null) {
-            this.editor = tmpEditorFunction
-        } else if (editorFunction != null) {
-            this.editor = editorFunction
-        } else if (editor != null) {
-            this.editor = editor.editor
+        when {
+            tmpEditorFunction != null -> this.editor = tmpEditorFunction
+            editorFunction != null -> this.editor = editorFunction
+            editor != null -> this.editor = editor.editor
         }
         if (editorParams != null) this.editorParams = editorParams
         if (validator != null) this.validator = validator.validator
@@ -690,6 +686,13 @@ fun <T : Any> TabulatorOptions<T>.toJs(
     i18nTranslator: (String) -> (String),
     dataSerializer: KSerializer<T>? = null
 ): Tabulator.Options {
+    val tmpCellEditCancelled = this.columns?.find { it.editorComponentFunction != null }?.let {
+        { cell: Tabulator.CellComponent ->
+            cellEditCancelled?.invoke(cell)
+            cell.getTable().redraw(true)
+        }
+    } ?: cellEditCancelled
+
     return obj {
         if (height != null) this.height = height
         if (virtualDom != null) this.virtualDom = virtualDom
@@ -817,7 +820,9 @@ fun <T : Any> TabulatorOptions<T>.toJs(
         if (cellMouseMove != null) this.cellMouseMove = cellMouseMove
         if (cellEditing != null) this.cellEditing = cellEditing
         if (cellEdited != null) this.cellEdited = cellEdited
-        if (cellEditCancelled != null) this.cellEditCancelled = cellEditCancelled
+        if (tmpCellEditCancelled != null) {
+            this.cellEditCancelled = tmpCellEditCancelled
+        }
         if (columnMoved != null) this.columnMoved = columnMoved
         if (columnResized != null) this.columnResized = columnResized
         if (columnVisibilityChanged != null) this.columnVisibilityChanged = columnVisibilityChanged
diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
index 6c7480fc..72a2809a 100644
--- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
+++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
@@ -38,6 +38,7 @@ import pl.treksoft.kvision.table.TableType
 import pl.treksoft.kvision.utils.JSON
 import pl.treksoft.kvision.utils.createInstance
 import pl.treksoft.kvision.utils.obj
+import pl.treksoft.kvision.utils.syncWithList
 import redux.RAction
 import pl.treksoft.kvision.tabulator.js.Tabulator as JsTabulator
 
@@ -47,13 +48,16 @@ import pl.treksoft.kvision.tabulator.js.Tabulator as JsTabulator
  * @constructor
  * @param T serializable type
  * @param data a list of serializable objects
+ * @param dataUpdateOnEdit determines if the data model is automatically updated after tabulator edit action
  * @param options tabulator options
+ * @param types a set of table types
  * @param classes a set of CSS class names
  * @param dataSerializer a serializer for class T
  */
 @Suppress("LargeClass", "TooManyFunctions")
 open class Tabulator<T : Any>(
     protected val data: List<T>? = null,
+    protected val dataUpdateOnEdit: Boolean = true,
     val options: TabulatorOptions<T> = TabulatorOptions(),
     types: Set<TableType> = setOf(),
     classes: Set<String> = setOf(),
@@ -191,6 +195,9 @@ open class Tabulator<T : Any>(
                 val d = nativeToData(data, dataSerializer)
                 @Suppress("UnsafeCastFromDynamic")
                 this.dispatchEvent("tabulatorDataEdited", obj { detail = d })
+                if (dataUpdateOnEdit && this.data is MutableList<T>) {
+                    this.data.syncWithList(d)
+                }
             }
         }
         counter++
@@ -561,12 +568,13 @@ open class Tabulator<T : Any>(
          */
         inline fun <reified T : Any> Container.tabulator(
             data: List<T>? = null,
+            dataUpdateOnEdit: Boolean = true,
             options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             noinline init: (Tabulator<T>.() -> Unit)? = null
         ): Tabulator<T> {
-            val tabulator = create(data, options, types, classes)
+            val tabulator = create(data, dataUpdateOnEdit, options, types, classes)
             init?.invoke(tabulator)
             this.add(tabulator)
             return tabulator
@@ -614,7 +622,7 @@ open class Tabulator<T : Any>(
             classes: Set<String> = setOf(),
             init: (Tabulator<T>.() -> Unit)? = null
         ): Tabulator<T> {
-            val tabulator = Tabulator<T>(options = options, types = types, classes = classes)
+            val tabulator = Tabulator(dataUpdateOnEdit = false, options = options, types = types, classes = classes)
             init?.invoke(tabulator)
             this.add(tabulator)
             return tabulator
@@ -625,12 +633,14 @@ open class Tabulator<T : Any>(
          */
         @UseExperimental(ImplicitReflectionSerializer::class)
         inline fun <reified T : Any> create(
-            data: List<T>? = null, options: TabulatorOptions<T> = TabulatorOptions(),
+            data: List<T>? = null,
+            dataUpdateOnEdit: Boolean = true,
+            options: TabulatorOptions<T> = TabulatorOptions(),
             types: Set<TableType> = setOf(),
             classes: Set<String> = setOf(),
             noinline init: (Tabulator<T>.() -> Unit)? = null
         ): Tabulator<T> {
-            val tabulator = Tabulator(data, options, types, classes, T::class.serializer())
+            val tabulator = Tabulator(data, dataUpdateOnEdit, options, types, classes, T::class.serializer())
             init?.invoke(tabulator)
             return tabulator
         }
@@ -648,7 +658,7 @@ open class Tabulator<T : Any>(
             noinline init: (Tabulator<T>.() -> Unit)? = null
         ): Tabulator<T> {
             val data = dataFactory(store.getState())
-            val tabulator = Tabulator(data, options, types, classes, T::class.serializer())
+            val tabulator = Tabulator(data, false, options, types, classes, T::class.serializer())
             init?.invoke(tabulator)
             store.subscribe { s ->
                 tabulator.replaceData(dataFactory(s))
@@ -668,7 +678,7 @@ open class Tabulator<T : Any>(
             noinline init: (Tabulator<T>.() -> Unit)? = null
         ): Tabulator<T> {
             val data = store.getState()
-            val tabulator = Tabulator(data, options, types, classes, T::class.serializer())
+            val tabulator = Tabulator(data, false, options, types, classes, T::class.serializer())
             init?.invoke(tabulator)
             store.subscribe { s ->
                 tabulator.replaceData(s)
-- 
cgit 


From 6416ab05253ee96647541efa9521ff6ec84d8826 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Mon, 13 May 2019 15:25:26 +0200
Subject: Small styling fixes of Tabulator editable cells.

---
 kvision-modules/kvision-bootstrap/src/main/resources/css/style.css | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
index 992a5a0b..7633ed6c 100644
--- a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
+++ b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
@@ -210,3 +210,8 @@ select.input-sm {
     -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
     box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
 }
+
+.tabulator-row .tabulator-cell.tabulator-editing {
+    border-right: 1px solid #1d68cd !important;
+    padding: 2px !important;
+}
-- 
cgit 


From aa18fdc24378026cd797e61ae30f9f6a3c2ac29b Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Mon, 13 May 2019 15:57:01 +0200
Subject: Dependencies upgrade

---
 gradle.properties | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gradle.properties b/gradle.properties
index ec093d09..53c110fd 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,7 +2,7 @@ group=pl.treksoft
 version=0.0.35
 kotlinVersion=1.3.31
 javaVersion=1.8
-coroutinesVersion=1.2.0
+coroutinesVersion=1.2.1
 serializationVersion=0.11.0
 frontendPluginVersion=0.0.45
 dokkaVersion=0.9.18
@@ -10,7 +10,7 @@ detektVersion=1.0.0.RC9.2
 junitVersion=4.12
 joobyVersion=1.6.0
 springBootVersion=2.1.4.RELEASE
-ktorVersion=1.1.4
+ktorVersion=1.1.5
 guiceVersion=4.2.2
 pac4jVersion=3.5.0
 dependencyManagementPluginVersion=1.0.4.RELEASE
-- 
cgit 


From f27fff0539f46468a8629705bc6120e0b5362a33 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Mon, 13 May 2019 15:57:20 +0200
Subject: Version 0.0.36

---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 53c110fd..93354316 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
 group=pl.treksoft
-version=0.0.35
+version=0.0.36
 kotlinVersion=1.3.31
 javaVersion=1.8
 coroutinesVersion=1.2.1
-- 
cgit 


From 92bc1caf2f2230d7de125dd398ef49d92fe8fadb Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Mon, 13 May 2019 22:13:14 +0200
Subject: Revert dependency back to Ktor 1.1.4.

---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 93354316..e51a8eb4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,7 +10,7 @@ detektVersion=1.0.0.RC9.2
 junitVersion=4.12
 joobyVersion=1.6.0
 springBootVersion=2.1.4.RELEASE
-ktorVersion=1.1.5
+ktorVersion=1.1.4
 guiceVersion=4.2.2
 pac4jVersion=3.5.0
 dependencyManagementPluginVersion=1.0.4.RELEASE
-- 
cgit 


From 1b98de40aeda34381b35f41df89f2e32757c539f Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Thu, 16 May 2019 15:00:58 +0200
Subject: Updated README

---
 README.md | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 6390ac90..3721118d 100644
--- a/README.md
+++ b/README.md
@@ -49,13 +49,14 @@ allows to build full-stack, multiplatform applications with shared common code.
 - Innovative integration interface for [Ktor](https://ktor.io), [Jooby](https://jooby.org) and [Spring Boot](https://spring.io/projects/spring-boot) frameworks on the server side,
 including support for type-safe websockets connections.
 - Support for building cross-platform, desktop applications with [Electron](https://electronjs.org).
-- Ready to explore [KVision examples](https://github.com/rjaros/kvision-examples) are available,
-built with [Gradle](https://gradle.org/) and supporting Webpack's [Hot Module Replacement (HMR)](https://webpack.js.org/concepts/hot-module-replacement/) and
+- KVision applications are built with [Gradle](https://gradle.org/) with support for Webpack's [Hot Module Replacement (HMR)](https://webpack.js.org/concepts/hot-module-replacement/) and
 [Kotlin JavaScript DCE (dead code elimination)](https://kotlinlang.org/docs/reference/javascript-dce.html).
 - [Karma](https://karma-runner.github.io/) testing framework support.
 - IDE support (IntelliJ IDEA Community Edition).
 
-## Documentation
+## Examples and documentation
+
+Ready to explore, rich set of [KVision examples](https://github.com/rjaros/kvision-examples) is available in the separate project.
 
 The comprehensive [KVision guide](https://kvision.gitbook.io/kvision-guide/) is published on GitBook. 
 
-- 
cgit 


From 413a1ce79bb16a229b22970282a97c62ecadfb6a Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Thu, 23 May 2019 17:56:29 +0200
Subject: New module for Apache Cordova integration (experimental).

---
 build.gradle                                       |  1 +
 kvision-modules/kvision-cordova/build.gradle       |  5 ++
 .../kotlin/pl/treksoft/kvision/cordova/Battery.kt  | 54 +++++++++++++++++
 .../kotlin/pl/treksoft/kvision/cordova/Device.kt   | 69 ++++++++++++++++++++++
 settings.gradle                                    |  3 +-
 5 files changed, 131 insertions(+), 1 deletion(-)
 create mode 100644 kvision-modules/kvision-cordova/build.gradle
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt

diff --git a/build.gradle b/build.gradle
index cb32f60f..8e1ed991 100644
--- a/build.gradle
+++ b/build.gradle
@@ -178,6 +178,7 @@ dokka {
             'kvision-modules/kvision-server-jooby/src/main/kotlin',
             'kvision-modules/kvision-server-ktor/src/main/kotlin',
             'kvision-modules/kvision-server-spring-boot/src/main/kotlin',
+            'kvision-modules/kvision-cordova/src/main/kotlin',
             'kvision-modules/kvision-electron/src/main/kotlin')
     classpath = [new File("dokka/kvision-dokka-helper.jar")]
     outputFormat = 'html'
diff --git a/kvision-modules/kvision-cordova/build.gradle b/kvision-modules/kvision-cordova/build.gradle
new file mode 100644
index 00000000..4aaef76d
--- /dev/null
+++ b/kvision-modules/kvision-cordova/build.gradle
@@ -0,0 +1,5 @@
+apply from: "../shared.gradle"
+
+dependencies {
+    compile "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion"
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
new file mode 100644
index 00000000..c8c26475
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.browser.window
+
+/**
+ * Battery status event types.
+ */
+enum class BatteryEvent(internal var event: String) {
+    BATTERY_STATUS("batterystatus"),
+    BATTERY_LOW("batterylow"),
+    BATTERY_CRITICAL("batterycritical")
+}
+
+/**
+ * Battery status.
+ */
+external class BatteryStatus {
+    val level: Int = definedExternally
+    val isPlugged: Boolean = definedExternally
+}
+
+/**
+ * Add listeners for battery status Cordova events.
+ */
+fun addBatteryStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) {
+    addDeviceReadyListener {
+        window.addEventListener(event.event, { status ->
+            @Suppress("UnsafeCastFromDynamic")
+            listener(status.asDynamic())
+        }, false)
+    }
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
new file mode 100644
index 00000000..bf273ca7
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.browser.document
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Device information class.
+ */
+external class Device {
+    val cordova: String = definedExternally
+    val model: String = definedExternally
+    val platform: String = definedExternally
+    val uuid: String = definedExternally
+    val version: String = definedExternally
+    val manufacturer: String = definedExternally
+    val isVirtual: Boolean = definedExternally
+    val serial: String = definedExternally
+}
+
+/**
+ * External device information object.
+ */
+external val device: Device
+
+private var intDevice: Device? = null
+
+/**
+ * Add listeners for 'deviceready' Cordova event.
+ */
+fun addDeviceReadyListener(listener: (Device) -> Unit) {
+    document.addEventListener("deviceready", {
+        listener(device)
+    }, false)
+}
+
+/**
+ * Suspending function to return device information object.
+ */
+suspend fun getDevice(): Device {
+    return intDevice ?: suspendCoroutine { continuation ->
+        addDeviceReadyListener {
+            intDevice = device
+            continuation.resume(device)
+        }
+    }
+}
diff --git a/settings.gradle b/settings.gradle
index 995d49dd..767cdd7f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -22,4 +22,5 @@ include 'kvision-modules:kvision-base',
         'kvision-modules:kvision-server-jooby',
         'kvision-modules:kvision-server-ktor',
         'kvision-modules:kvision-server-spring-boot',
-        'kvision-modules:kvision-electron'
+        'kvision-modules:kvision-electron',
+        'kvision-modules:kvision-cordova'
-- 
cgit 


From d68eb4f4107576504887969eda53256b4d1c1e5c Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 25 May 2019 13:49:14 +0200
Subject: Implement Cordova API: lifecycle events, camera plugin.

---
 .../kotlin/pl/treksoft/kvision/cordova/Battery.kt  |   6 +-
 .../kotlin/pl/treksoft/kvision/cordova/Camera.kt   | 269 +++++++++++++++++++++
 .../kotlin/pl/treksoft/kvision/cordova/Device.kt   |  52 +++-
 .../kotlin/pl/treksoft/kvision/cordova/Result.kt   | 151 ++++++++++++
 4 files changed, 467 insertions(+), 11 deletions(-)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
index c8c26475..c6ac2a00 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
@@ -27,7 +27,7 @@ import kotlin.browser.window
 /**
  * Battery status event types.
  */
-enum class BatteryEvent(internal var event: String) {
+enum class BatteryEvent(internal val event: String) {
     BATTERY_STATUS("batterystatus"),
     BATTERY_LOW("batterylow"),
     BATTERY_CRITICAL("batterycritical")
@@ -37,8 +37,8 @@ enum class BatteryEvent(internal var event: String) {
  * Battery status.
  */
 external class BatteryStatus {
-    val level: Int = definedExternally
-    val isPlugged: Boolean = definedExternally
+    val level: Int
+    val isPlugged: Boolean
 }
 
 /**
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
new file mode 100644
index 00000000..885bfd08
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Exception class for camera errors.
+ */
+class CameraException(message: String) : Exception(message)
+
+/**
+ * Main object for Cordova camera.
+ */
+object Camera {
+
+    private const val CAMERA_ACTIVE_STORAGE_KEY = "kv_camera_active_storage_key"
+    private const val CAMERA_STATUS_OK = "OK"
+
+    /**
+     * Camera destination types.
+     */
+    enum class DestinationType {
+        DATA_URL,
+        FILE_URI,
+        NATIVE_URI
+    }
+
+    /**
+     * Picture encoding types.
+     */
+    enum class EncodingType {
+        JPEG,
+        PNG
+    }
+
+    /**
+     * Picture/video media types.
+     */
+    enum class MediaType {
+        PICTURE,
+        VIDEO,
+        ALLMEDIA
+    }
+
+    /**
+     * Camera picture/video sources.
+     */
+    enum class PictureSourceType {
+        PHOTOLIBRARY,
+        CAMERA,
+        SAVEDPHOTOALBUM
+    }
+
+    /**
+     * Camera facing types.
+     */
+    enum class Direction {
+        BACK,
+        FRONT
+    }
+
+    /**
+     * iOS popover arrow directions.
+     */
+    enum class PopoverArrowDirection {
+        ARROW_UP,
+        ARROW_DOWN,
+        ARROW_LEFT,
+        ARROW_RIGHT,
+        ARROW_ANY
+    }
+
+    /**
+     * iOS popover options.
+     */
+    data class CameraPopoverOptions(
+        val x: Int,
+        val y: Int,
+        val width: Int,
+        val height: Int,
+        val arrowDir: PopoverArrowDirection,
+        val popoverWidth: Int,
+        val popoverHeight: Int
+    )
+
+    /**
+     * Suspending function to get picture from the camera.
+     *
+     * Note: On Android platform you must also use [addCameraResultCallback] listener.
+     *
+     * @param options camera options
+     * @return a [Result] class containing the picture or the exception
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun getPicture(options: CameraOptions): Result<String, CameraException> {
+        return suspendCoroutine { continuation ->
+            getPicture(options) {
+                continuation.resume(it)
+            }
+        }
+    }
+
+    /**
+     * A function to get picture from the camera.
+     *
+     * Note: On Android platform you must also use [addCameraResultCallback] listener.
+     *
+     * @param options camera options
+     * @param resultCallback a callback function to get the [Result], containing the picture or the exception
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    fun getPicture(options: CameraOptions, resultCallback: (Result<String, CameraException>) -> Unit) {
+        window.localStorage.setItem(CAMERA_ACTIVE_STORAGE_KEY, "true")
+        addDeviceReadyListener {
+            window.navigator.asDynamic().camera.getPicture({ image ->
+                window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY)
+                resultCallback(Result.success(image))
+            }, { message ->
+                window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY)
+                resultCallback(Result.error(CameraException(message)))
+            }, options.toJs())
+        }
+    }
+
+    /**
+     * An Android specific function to get picture from the camera after resume when the application
+     * webview intent is killed.
+     *
+     * @param resultCallback a callback function to get the [Result], containing the picture or the exception
+     */
+    fun addCameraResultCallback(resultCallback: (Result<String, CameraException>) -> Unit) {
+        addResumeListener { resumeEvent ->
+            val isCameraActive = window.localStorage.getItem(CAMERA_ACTIVE_STORAGE_KEY) == "true"
+            if (isCameraActive && resumeEvent.pendingResult != null) {
+                window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY)
+                if (resumeEvent.pendingResult.pluginStatus == CAMERA_STATUS_OK) {
+                    resultCallback(Result.success(resumeEvent.pendingResult.result))
+                } else {
+                    resultCallback(Result.error(CameraException(resumeEvent.pendingResult.result)))
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes intermediate image files that are kept in the temporary storage after calling [getPicture].
+     *
+     * @param resultCallback an optional callback function to get the [Result] of the cleanup operation.
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    fun cleanup(resultCallback: ((Result<String, CameraException>) -> Unit)? = null) {
+        addDeviceReadyListener {
+            window.navigator.asDynamic().camera.cleanup({
+                resultCallback?.invoke(Result.success(CAMERA_STATUS_OK))
+            }, { message ->
+                resultCallback?.invoke(Result.error(CameraException(message)))
+            })
+        }
+    }
+}
+
+internal fun Camera.DestinationType.toJs(): dynamic = when (this) {
+    Camera.DestinationType.DATA_URL -> js("Camera.DestinationType.DATA_URL")
+    Camera.DestinationType.FILE_URI -> js("Camera.DestinationType.FILE_URI")
+    Camera.DestinationType.NATIVE_URI -> js("Camera.DestinationType.NATIVE_URI")
+}
+
+internal fun Camera.EncodingType.toJs(): dynamic = when (this) {
+    Camera.EncodingType.JPEG -> js("Camera.EncodingType.JPEG")
+    Camera.EncodingType.PNG -> js("Camera.EncodingType.PNG")
+}
+
+internal fun Camera.MediaType.toJs(): dynamic = when (this) {
+    Camera.MediaType.PICTURE -> js("Camera.MediaType.PICTURE")
+    Camera.MediaType.VIDEO -> js("Camera.MediaType.VIDEO")
+    Camera.MediaType.ALLMEDIA -> js("Camera.MediaType.ALLMEDIA")
+}
+
+internal fun Camera.PictureSourceType.toJs(): dynamic = when (this) {
+    Camera.PictureSourceType.PHOTOLIBRARY -> js("Camera.PictureSourceType.PHOTOLIBRARY")
+    Camera.PictureSourceType.CAMERA -> js("Camera.PictureSourceType.CAMERA")
+    Camera.PictureSourceType.SAVEDPHOTOALBUM -> js("Camera.PictureSourceType.SAVEDPHOTOALBUM")
+}
+
+internal fun Camera.PopoverArrowDirection.toJs(): dynamic = when (this) {
+    Camera.PopoverArrowDirection.ARROW_UP -> js("Camera.PopoverArrowDirection.ARROW_UP")
+    Camera.PopoverArrowDirection.ARROW_DOWN -> js("Camera.PopoverArrowDirection.ARROW_DOWN")
+    Camera.PopoverArrowDirection.ARROW_LEFT -> js("Camera.PopoverArrowDirection.ARROW_LEFT")
+    Camera.PopoverArrowDirection.ARROW_RIGHT -> js("Camera.PopoverArrowDirection.ARROW_RIGHT")
+    Camera.PopoverArrowDirection.ARROW_ANY -> js("Camera.PopoverArrowDirection.ARROW_ANY")
+}
+
+internal fun Camera.Direction.toJs(): dynamic = when (this) {
+    Camera.Direction.BACK -> js("Camera.Direction.BACK")
+    Camera.Direction.FRONT -> js("Camera.Direction.FRONT")
+}
+
+internal external class CameraPopoverOptions(
+    x: Int,
+    y: Int,
+    width: Int,
+    height: Int,
+    arrowDir: dynamic,
+    popoverWidth: Int,
+    popoverHeight: Int
+)
+
+internal fun Camera.CameraPopoverOptions.toJs(): dynamic {
+    return CameraPopoverOptions(x, y, width, height, arrowDir.toJs(), popoverWidth, popoverHeight)
+}
+
+/**
+ * Camera options.
+ */
+data class CameraOptions(
+    val quality: Int? = null,
+    val destinationType: Camera.DestinationType? = null,
+    val sourceType: Camera.PictureSourceType? = null,
+    val allowEdit: Boolean? = null,
+    val encodingType: Camera.EncodingType? = null,
+    val targetWidth: Int? = null,
+    val targetHeight: Int? = null,
+    val mediaType: Camera.MediaType? = null,
+    val correctOrientation: Boolean? = null,
+    val saveToPhotoAlbum: Boolean? = null,
+    val popoverOptions: Camera.CameraPopoverOptions? = null,
+    val cameraDirection: Camera.Direction? = null
+)
+
+internal fun CameraOptions.toJs(): dynamic {
+    return obj {
+        if (quality != null) this.quality = quality
+        if (destinationType != null) this.destinationType = destinationType.toJs()
+        if (sourceType != null) this.sourceType = sourceType.toJs()
+        if (allowEdit != null) this.allowEdit = allowEdit
+        if (encodingType != null) this.encodingType = encodingType.toJs()
+        if (targetWidth != null) this.targetWidth = targetWidth
+        if (targetHeight != null) this.targetHeight = targetHeight
+        if (mediaType != null) this.mediaType = mediaType.toJs()
+        if (correctOrientation != null) this.correctOrientation = correctOrientation
+        if (saveToPhotoAlbum != null) this.saveToPhotoAlbum = saveToPhotoAlbum
+        if (popoverOptions != null) this.popoverOptions = popoverOptions.toJs()
+        if (cameraDirection != null) this.cameraDirection = cameraDirection.toJs()
+    }
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
index bf273ca7..69e3defb 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
@@ -30,14 +30,31 @@ import kotlin.coroutines.suspendCoroutine
  * Device information class.
  */
 external class Device {
-    val cordova: String = definedExternally
-    val model: String = definedExternally
-    val platform: String = definedExternally
-    val uuid: String = definedExternally
-    val version: String = definedExternally
-    val manufacturer: String = definedExternally
-    val isVirtual: Boolean = definedExternally
-    val serial: String = definedExternally
+    val cordova: String
+    val model: String
+    val platform: String
+    val uuid: String
+    val version: String
+    val manufacturer: String
+    val isVirtual: Boolean
+    val serial: String
+}
+
+/**
+ * Pending result class.
+ */
+external class PendingResult {
+    val pluginServiceName: String
+    val pluginStatus: String
+    val result: dynamic
+}
+
+/**
+ * Resume event class.
+ */
+external class ResumeEvent {
+    val action: String?
+    val pendingResult: PendingResult?
 }
 
 /**
@@ -56,6 +73,25 @@ fun addDeviceReadyListener(listener: (Device) -> Unit) {
     }, false)
 }
 
+/**
+ * Add listeners for 'pause' Cordova event.
+ */
+fun addPauseListener(listener: () -> Unit) {
+    document.addEventListener("pause", {
+        listener()
+    }, false)
+}
+
+/**
+ * Add listeners for 'resume' Cordova event.
+ */
+fun addResumeListener(listener: (ResumeEvent) -> Unit) {
+    document.addEventListener("resume", { e ->
+        @Suppress("UnsafeCastFromDynamic")
+        listener(e.asDynamic())
+    }, false)
+}
+
 /**
  * Suspending function to return device information object.
  */
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt
new file mode 100644
index 00000000..bb125e24
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt
@@ -0,0 +1,151 @@
+/*
+ * Source: https://github.com/kittinunf/Result
+ *
+ * MIT License
+ *
+ * Copyright (c) 2017 Kittinun Vantasin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+inline fun <reified X> Result<*, *>.getAs() = when (this) {
+    is Result.Success -> value as? X
+    is Result.Failure -> error as? X
+}
+
+fun <V : Any> Result<V, *>.success(f: (V) -> Unit) = fold(f, {})
+
+fun <E : Exception> Result<*, E>.failure(f: (E) -> Unit) = fold({}, f)
+
+infix fun <V : Any, E : Exception> Result<V, E>.or(fallback: V) = when (this) {
+    is Result.Success -> this
+    else -> Result.Success(fallback)
+}
+
+infix fun <V : Any, E : Exception> Result<V, E>.getOrElse(fallback: V) = when (this) {
+    is Result.Success -> value
+    else -> fallback
+}
+
+inline fun <V : Any, U : Any, E : Exception> Result<V, E>.map(transform: (V) -> U): Result<U, E> = try {
+    when (this) {
+        is Result.Success -> Result.Success(transform(value))
+        is Result.Failure -> Result.Failure(error)
+    }
+} catch (ex: Exception) {
+    @Suppress("UNCHECKED_CAST")
+    Result.error(ex as E)
+}
+
+inline fun <V : Any, U : Any, E : Exception> Result<V, E>.flatMap(transform: (V) -> Result<U, E>): Result<U, E> = try {
+    when (this) {
+        is Result.Success -> transform(value)
+        is Result.Failure -> Result.Failure(error)
+    }
+} catch (ex: Exception) {
+    @Suppress("UNCHECKED_CAST")
+    Result.error(ex as E)
+}
+
+fun <V : Any, E : Exception, E2 : Exception> Result<V, E>.mapError(transform: (E) -> E2) = when (this) {
+    is Result.Success -> Result.Success(value)
+    is Result.Failure -> Result.Failure(transform(error))
+}
+
+fun <V : Any, E : Exception, E2 : Exception> Result<V, E>.flatMapError(transform: (E) -> Result<V, E2>) = when (this) {
+    is Result.Success -> Result.Success(value)
+    is Result.Failure -> transform(error)
+}
+
+fun <V : Any, E : Exception> Result<V, E>.any(predicate: (V) -> Boolean): Boolean = try {
+    when (this) {
+        is Result.Success -> predicate(value)
+        is Result.Failure -> false
+    }
+} catch (ex: Exception) {
+    false
+}
+
+fun <V : Any, U : Any> Result<V, *>.fanout(other: () -> Result<U, *>): Result<Pair<V, U>, *> =
+    flatMap { outer -> other().map { outer to it } }
+
+sealed class Result<out V : Any, out E : Exception> {
+
+    open operator fun component1(): V? = null
+    open operator fun component2(): E? = null
+
+    inline fun <X> fold(success: (V) -> X, failure: (E) -> X): X = when (this) {
+        is Success -> success(this.value)
+        is Failure -> failure(this.error)
+    }
+
+    abstract fun get(): V
+
+    class Success<out V : Any>(val value: V) : Result<V, Nothing>() {
+        override fun component1(): V? = value
+
+        override fun get(): V = value
+
+        override fun toString() = "[Success: $value]"
+
+        override fun hashCode(): Int = value.hashCode()
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            return other is Success<*> && value == other.value
+        }
+    }
+
+    class Failure<out E : Exception>(val error: E) : Result<Nothing, E>() {
+        override fun component2(): E? = error
+
+        override fun get() = throw error
+
+        fun getException(): E = error
+
+        override fun toString() = "[Failure: $error]"
+
+        override fun hashCode(): Int = error.hashCode()
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            return other is Failure<*> && error == other.error
+        }
+    }
+
+    companion object {
+        // Factory methods
+        fun <E : Exception> error(ex: E) = Failure(ex)
+
+        fun <V : Any> success(v: V) = Success(v)
+
+        fun <V : Any> of(value: V?, fail: (() -> Exception) = { Exception() }): Result<V, Exception> =
+            value?.let { success(it) } ?: error(fail())
+
+        fun <V : Any, E : Exception> of(f: () -> V): Result<V, E> = try {
+            success(f())
+        } catch (ex: Exception) {
+            @Suppress("UNCHECKED_CAST")
+            error(ex as E)
+        }
+    }
+
+}
-- 
cgit 


From 4577751d14166c3e4ae14e34bae71f5ce7e76d6d Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 25 May 2019 23:29:04 +0200
Subject: Support for Cordova core events.

---
 .../kotlin/pl/treksoft/kvision/cordova/Battery.kt  |  4 +-
 .../kotlin/pl/treksoft/kvision/cordova/Device.kt   | 60 +++++++++++++++++-----
 2 files changed, 49 insertions(+), 15 deletions(-)

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
index c6ac2a00..5d9b8e93 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
@@ -27,7 +27,7 @@ import kotlin.browser.window
 /**
  * Battery status event types.
  */
-enum class BatteryEvent(internal val event: String) {
+enum class BatteryEvent(internal val type: String) {
     BATTERY_STATUS("batterystatus"),
     BATTERY_LOW("batterylow"),
     BATTERY_CRITICAL("batterycritical")
@@ -46,7 +46,7 @@ external class BatteryStatus {
  */
 fun addBatteryStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) {
     addDeviceReadyListener {
-        window.addEventListener(event.event, { status ->
+        window.addEventListener(event.type, { status ->
             @Suppress("UnsafeCastFromDynamic")
             listener(status.asDynamic())
         }, false)
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
index 69e3defb..d83b79d4 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
@@ -22,6 +22,7 @@
 
 package pl.treksoft.kvision.cordova
 
+import org.w3c.dom.events.Event
 import kotlin.browser.document
 import kotlin.coroutines.resume
 import kotlin.coroutines.suspendCoroutine
@@ -58,17 +59,35 @@ external class ResumeEvent {
 }
 
 /**
- * External device information object.
+ * Cordova event types.
  */
-external val device: Device
+enum class CordovaEvent(internal val type: String) {
+    DEVICEREADY("deviceready"),
+    PAUSE("pause"),
+    RESUME("resume"),
+    BACKBUTTON("backbutton"),
+    MENUBUTTON("menubutton"),
+    SEARCHBUTTON("searchbutton"),
+    STARTCALLBUTTON("startcallbutton"),
+    ENDCALLBUTTON("endcallbutton"),
+    VOLUMEDOWNBUTTON("volumedownbutton"),
+    VOLUMEUPBUTTON("volumeupbutton"),
+    ACTIVATED("activated")
+}
+
+private external val device: Device
 
-private var intDevice: Device? = null
+/**
+ * Cordova device information object.
+ */
+var cordovaDevice: Device? = null
+    private set
 
 /**
  * Add listeners for 'deviceready' Cordova event.
  */
 fun addDeviceReadyListener(listener: (Device) -> Unit) {
-    document.addEventListener("deviceready", {
+    document.addEventListener(CordovaEvent.DEVICEREADY.type, {
         listener(device)
     }, false)
 }
@@ -77,28 +96,43 @@ fun addDeviceReadyListener(listener: (Device) -> Unit) {
  * Add listeners for 'pause' Cordova event.
  */
 fun addPauseListener(listener: () -> Unit) {
-    document.addEventListener("pause", {
-        listener()
-    }, false)
+    addDeviceReadyListener {
+        document.addEventListener(CordovaEvent.PAUSE.type, {
+            listener()
+        }, false)
+    }
 }
 
 /**
  * Add listeners for 'resume' Cordova event.
  */
 fun addResumeListener(listener: (ResumeEvent) -> Unit) {
-    document.addEventListener("resume", { e ->
-        @Suppress("UnsafeCastFromDynamic")
-        listener(e.asDynamic())
-    }, false)
+    addDeviceReadyListener {
+        document.addEventListener(CordovaEvent.RESUME.type, { e ->
+            @Suppress("UnsafeCastFromDynamic")
+            listener(e.asDynamic())
+        }, false)
+    }
+}
+
+/**
+ * Add listeners for a Cordova events.
+ */
+fun addCordovaEventListener(event: CordovaEvent, listener: (Event) -> Unit) {
+    addDeviceReadyListener {
+        document.addEventListener(event.type, { e ->
+            listener(e)
+        }, false)
+    }
 }
 
 /**
  * Suspending function to return device information object.
  */
 suspend fun getDevice(): Device {
-    return intDevice ?: suspendCoroutine { continuation ->
+    return cordovaDevice ?: suspendCoroutine { continuation ->
         addDeviceReadyListener {
-            intDevice = device
+            cordovaDevice = device
             continuation.resume(device)
         }
     }
-- 
cgit 


From ba0faac18b6641e90866d108f49bfc42148b4085 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sun, 26 May 2019 00:46:16 +0200
Subject: Support for Cordova notifications.

---
 .../pl/treksoft/kvision/cordova/Notification.kt    | 106 +++++++++++++++++++++
 1 file changed, 106 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt
new file mode 100644
index 00000000..5afddb41
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.browser.window
+
+/**
+ * A response object for prompt function callback.
+ */
+external class PromptResponse {
+    val buttonIndex: Int
+    val input1: String
+}
+
+/**
+ * Main object for Cordova notifications.
+ */
+object Notification {
+
+    /**
+     * Show alert dialog.
+     * @param message a message
+     * @param title an optional dialog title (defaults to Alert)
+     * @param buttonLabel an optional button label (defaults to OK)
+     * @param callback a callback function
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    fun alert(message: String, title: String? = null, buttonLabel: String? = null, callback: () -> Unit) {
+        addDeviceReadyListener {
+            window.navigator.asDynamic().notification.alert(message, callback, title, buttonLabel)
+        }
+    }
+
+    /**
+     * Show confirm dialog.
+     * @param message a message
+     * @param title an optional dialog title (defaults to Confirm)
+     * @param buttonLabels an optional list of button labels (defaults to [OK, Cancel])
+     * @param callback a callback function
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    fun confirm(message: String, title: String? = null, buttonLabels: List<String>? = null, callback: (Int) -> Unit) {
+        addDeviceReadyListener {
+            window.navigator.asDynamic().notification.confirm(message, callback, title, buttonLabels?.toTypedArray())
+        }
+    }
+
+    /**
+     * Show prompt dialog.
+     * @param message a message
+     * @param title an optional dialog title (defaults to Prompt)
+     * @param buttonLabels an optional list of button labels (defaults to [OK, Cancel])
+     * @param defaultText default input value (default to empty string)
+     * @param callback a callback function
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    fun prompt(
+        message: String,
+        title: String? = null,
+        buttonLabels: List<String>? = null,
+        defaultText: String? = null,
+        callback: (PromptResponse) -> Unit
+    ) {
+        addDeviceReadyListener {
+            window.navigator.asDynamic().notification.prompt(
+                message,
+                callback,
+                title,
+                buttonLabels?.toTypedArray(),
+                defaultText
+            )
+        }
+    }
+
+    /**
+     * Play a beep sound.
+     * @param times the number of times to repeat the beep (defaults to 1)
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    fun beep(times: Int = 1) {
+        addDeviceReadyListener {
+            window.navigator.asDynamic().notification.beep(times)
+        }
+    }
+
+}
-- 
cgit 


From 3101839b05347545a963c67df2b0d0506dc476d9 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Wed, 29 May 2019 12:53:47 +0200
Subject: Refactor battery api classes.

---
 .../kotlin/pl/treksoft/kvision/cordova/Battery.kt  | 38 +++++++++++++---------
 1 file changed, 22 insertions(+), 16 deletions(-)

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
index 5d9b8e93..84a7eb2e 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
@@ -24,15 +24,6 @@ package pl.treksoft.kvision.cordova
 
 import kotlin.browser.window
 
-/**
- * Battery status event types.
- */
-enum class BatteryEvent(internal val type: String) {
-    BATTERY_STATUS("batterystatus"),
-    BATTERY_LOW("batterylow"),
-    BATTERY_CRITICAL("batterycritical")
-}
-
 /**
  * Battery status.
  */
@@ -42,13 +33,28 @@ external class BatteryStatus {
 }
 
 /**
- * Add listeners for battery status Cordova events.
+ * Main object for Cordova battery.
  */
-fun addBatteryStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) {
-    addDeviceReadyListener {
-        window.addEventListener(event.type, { status ->
-            @Suppress("UnsafeCastFromDynamic")
-            listener(status.asDynamic())
-        }, false)
+object Battery {
+
+    /**
+     * Battery status event types.
+     */
+    enum class BatteryEvent(internal val type: String) {
+        BATTERY_STATUS("batterystatus"),
+        BATTERY_LOW("batterylow"),
+        BATTERY_CRITICAL("batterycritical")
+    }
+
+    /**
+     * Add listeners for battery status Cordova events.
+     */
+    fun addStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) {
+        addDeviceReadyListener {
+            window.addEventListener(event.type, { status ->
+                @Suppress("UnsafeCastFromDynamic")
+                listener(status.asDynamic())
+            }, false)
+        }
     }
 }
-- 
cgit 


From cb495fb29c997f76a420a9e653f4fa087b8b2cf8 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Wed, 29 May 2019 12:53:56 +0200
Subject: Support for Cordova network api.

---
 .../kotlin/pl/treksoft/kvision/cordova/Network.kt  | 89 ++++++++++++++++++++++
 1 file changed, 89 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt
new file mode 100644
index 00000000..eb98f8a4
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.browser.document
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Main object for Cordova network.
+ */
+object Network {
+
+    /**
+     * Connection types
+     */
+    enum class ConnectionType {
+        UNKNOWN,
+        ETHERNET,
+        WIFI,
+        CELL_2G,
+        CELL_3G,
+        CELL_4G,
+        CELL,
+        NONE
+    }
+
+    /**
+     * Network status events
+     */
+    enum class NetworkEvent(internal val type: String) {
+        OFFLINE("offline"),
+        ONLINE("online")
+    }
+
+    /**
+     * Get network connection type.
+     */
+    suspend fun getConnectionType(): ConnectionType {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                val connectionType = when (window.navigator.asDynamic().connection.type) {
+                    js("Connection.ETHERNET") -> ConnectionType.ETHERNET
+                    js("Connection.WIFI") -> ConnectionType.WIFI
+                    js("Connection.CELL_2G") -> ConnectionType.CELL_2G
+                    js("Connection.CELL_3G") -> ConnectionType.CELL_3G
+                    js("Connection.CELL_4G") -> ConnectionType.CELL_4G
+                    js("Connection.CELL") -> ConnectionType.CELL
+                    js("Connection.NONE") -> ConnectionType.NONE
+                    else -> ConnectionType.UNKNOWN
+                }
+                continuation.resume(connectionType)
+            }
+        }
+    }
+
+    /**
+     * Add listeners for network status Cordova events.
+     */
+    fun addStatusListener(event: NetworkEvent, listener: () -> Unit) {
+        addDeviceReadyListener {
+            document.addEventListener(event.type, {
+                listener()
+            }, false)
+        }
+    }
+
+}
-- 
cgit 


From 27447d5c0bb61a00511cbb03180e141b367e708d Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Wed, 29 May 2019 14:44:35 +0200
Subject: Support for Cordova screen orientation api.

---
 .../kotlin/pl/treksoft/kvision/cordova/Screen.kt   | 76 ++++++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt
new file mode 100644
index 00000000..6765b44e
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.browser.window
+
+/**
+ * Main object for Cordova screen.
+ */
+object Screen {
+
+    /**
+     * Screen orientations
+     */
+    enum class Orientation(internal val type: String) {
+        PORTRAIT_PRIMARY("portrait-primary"),
+        PORTRAIT_SECONDARY("portrait-secondary"),
+        LANDSCAPE_PRIMARY("landscape-primary"),
+        LANDSCAPE_SECONDARY("landscape-secondary"),
+        PORTRAIT("portrait"),
+        LANDSCAPE("landscape"),
+        ANY("any")
+    }
+
+    /**
+     * Lock screen orientation.
+     * @param orientation selected orientation
+     */
+    fun lock(orientation: Orientation) {
+        window.screen.asDynamic().orientation.lock(orientation.type)
+    }
+
+    /**
+     * Unlock screen orientation.
+     */
+    fun unlock() {
+        window.screen.asDynamic().orientation.unlock()
+    }
+
+    /**
+     * Get screen orientation.
+     */
+    fun getOrientation(): Orientation {
+        val type = window.screen.asDynamic().orientation.type
+        return Orientation.values().find { it.type == type } ?: Screen.Orientation.ANY
+    }
+
+    /**
+     * Add listeners for screen orientation change Cordova events.
+     */
+    fun addOrientationChangeListener(listener: (Orientation) -> Unit) {
+        window.addEventListener("orientationchange", {
+            listener(getOrientation())
+        }, false)
+    }
+}
-- 
cgit 


From 1490d855f926b5ae0c9b41d7ef904754e17d0204 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Wed, 29 May 2019 14:57:28 +0200
Subject: Support for Cordova splashscreen api.

---
 .../pl/treksoft/kvision/cordova/Splashscreen.kt    | 50 ++++++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt
new file mode 100644
index 00000000..03157534
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.browser.window
+
+/**
+ * Main object for Cordova splashscreen.
+ */
+object Splashscreen {
+
+    /**
+     * Show splashscreen.
+     */
+    fun show() {
+        addDeviceReadyListener {
+            window.navigator.asDynamic().splashscreen.show()
+        }
+    }
+
+    /**
+     * Hide splashscreen.
+     */
+    fun hide() {
+        addDeviceReadyListener {
+            window.navigator.asDynamic().splashscreen.hide()
+        }
+    }
+
+}
-- 
cgit 


From f969c5ccf7f1187dedda6c46fec5b0047bc28ae5 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Wed, 29 May 2019 16:05:12 +0200
Subject: Support for Cordova status bar api.

---
 .../pl/treksoft/kvision/cordova/StatusBar.kt       | 152 +++++++++++++++++++++
 1 file changed, 152 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt
new file mode 100644
index 00000000..5617bf9b
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import pl.treksoft.kvision.utils.toHexString
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+@JsName("StatusBar")
+internal external val StatusBarJs: dynamic
+
+/**
+ * Main object for Cordova status bar.
+ */
+object StatusBar {
+
+    /**
+     * Show status bar.
+     */
+    fun show() {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.show()
+        }
+    }
+
+    /**
+     * Hide status bar.
+     */
+    fun hide() {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.hide()
+        }
+    }
+
+    /**
+     * Set overlay.
+     * @param overlays determines if the status bar overlays the web view
+     */
+    fun overlaysWebView(overlays: Boolean) {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.overlaysWebView(overlays)
+        }
+    }
+
+    /**
+     * Use the default status bar (dark text, for light backgrounds).
+     */
+    fun styleDefault() {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.styleDefault()
+        }
+    }
+
+    /**
+     * Use the lightContent status bar (light text, for dark backgrounds).
+     */
+    fun styleLightContent() {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.styleLightContent()
+        }
+    }
+
+    /**
+     * Use the blackTranslucent status bar (light text, for dark backgrounds).
+     */
+    fun styleBlackTranslucent() {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.styleBlackTranslucent()
+        }
+    }
+
+    /**
+     * Use the blackOpaque status bar (light text, for dark backgrounds).
+     */
+    fun styleBlackOpaque() {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.styleBlackOpaque()
+        }
+    }
+
+    /**
+     * Sets the background color of the status bar by a hexadecimal Int.
+     */
+    fun setBackgroundColor(color: Int) {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.backgroundColorByHexString("#" + color.toHexString())
+        }
+    }
+
+    /**
+     * Sets the background color of the status bar by a hex string.
+     */
+    fun setBackgroundColorByHexString(color: String) {
+        addDeviceReadyListener {
+            @Suppress("UnsafeCastFromDynamic")
+            StatusBarJs.backgroundColorByHexString(color)
+        }
+    }
+
+    /**
+     * Returns if the status bar is visible.
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun isVisible(): Boolean {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                continuation.resume(StatusBarJs.isVisible)
+            }
+        }
+    }
+
+    /**
+     * Add listeners for the status bar tap event.
+     */
+    fun addTapListener(listener: () -> Unit) {
+        addDeviceReadyListener {
+            window.addEventListener("statusTap", {
+                listener()
+            })
+        }
+    }
+
+}
-- 
cgit 


From fc7f3588472423ce915113c8d2089e4df3793de0 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Thu, 30 May 2019 12:45:53 +0200
Subject: Support for Cordova vibration api.

---
 .../pl/treksoft/kvision/cordova/Vibration.kt       | 43 ++++++++++++++++++++++
 1 file changed, 43 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt
new file mode 100644
index 00000000..6d973bbc
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.browser.window
+
+/**
+ * Main object for Cordova vibration.
+ */
+object Vibration {
+
+    /**
+     * Generate a vibration with a given pattern.
+     * @param pattern a pattern - sequence of durations (in milliseconds) for which to turn on or off the vibrator.
+     */
+    fun vibrate(vararg pattern: Int) {
+        val patternArray = if (pattern.isEmpty()) arrayOf(0) else pattern.toTypedArray()
+        addDeviceReadyListener {
+            window.navigator.vibrate(patternArray)
+        }
+    }
+
+}
-- 
cgit 


From f9398d1ae7da496d3a290d7b225225d70a4bb174 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Mon, 3 Jun 2019 18:31:49 +0200
Subject: Refactor support for additional attributes from Tag up to the Widget
 class.

---
 .../kotlin/pl/treksoft/kvision/core/Component.kt   | 20 ++++++
 src/main/kotlin/pl/treksoft/kvision/core/Style.kt  | 78 +---------------------
 .../pl/treksoft/kvision/core/StyledComponent.kt    |  2 +-
 src/main/kotlin/pl/treksoft/kvision/core/Widget.kt | 22 +++++-
 src/main/kotlin/pl/treksoft/kvision/html/Tag.kt    | 43 +-----------
 .../kotlin/pl/treksoft/kvision/panel/FlexPanel.kt  |  6 +-
 src/main/kotlin/pl/treksoft/kvision/panel/Root.kt  |  6 +-
 7 files changed, 50 insertions(+), 127 deletions(-)

diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Component.kt b/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
index 411eae8d..fe5569d4 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
@@ -96,6 +96,26 @@ interface Component {
      */
     fun removeSurroundingCssClass(css: Style): Component
 
+    /**
+     * Returns the value of an additional attribute.
+     * @param name the name of the attribute
+     * @return the value of the attribute
+     */
+    fun getAttribute(name: String): String?
+
+    /**
+     * Sets the value of additional attribute.
+     * @param name the name of the attribute
+     * @param value the value of the attribute
+     */
+    fun setAttribute(name: String, value: String): Component
+
+    /**
+     * Removes the value of additional attribute.
+     * @param name the name of the attribute
+     */
+    fun removeAttribute(name: String): Component
+
     /**
      * @suppress
      * Internal function
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Style.kt b/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
index 282d2e7e..ff91c429 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
@@ -21,11 +21,6 @@
  */
 package pl.treksoft.kvision.core
 
-import com.github.snabbdom.VNode
-import com.github.snabbdom.h
-import org.w3c.dom.Node
-import pl.treksoft.jquery.JQuery
-import pl.treksoft.kvision.panel.Root
 import kotlin.reflect.KProperty
 
 /**
@@ -41,8 +36,6 @@ open class Style(className: String? = null, parentStyle: Style? = null, init: (S
     StyledComponent() {
     private val propertyValues: MutableMap<String, Any?> = mutableMapOf()
 
-    override var parent: Container? = Root.getFirstRoot()
-
     private val newClassName: String = if (parentStyle == null) {
         className ?: "kv_styleclass_${counter++}"
     } else {
@@ -55,86 +48,19 @@ open class Style(className: String? = null, parentStyle: Style? = null, init: (S
     var className: String by refreshOnUpdate(newClassName)
 
     init {
+        @Suppress("LeakingThis")
         styles.add(this)
         @Suppress("LeakingThis")
         init?.invoke(this)
     }
 
-    override var visible: Boolean = true
-        set(value) {
-            val oldField = field
-            field = value
-            if (oldField != field) refresh()
-        }
-
-    override fun addCssClass(css: String): Component {
-        return this
-    }
-
-    override fun removeCssClass(css: String): Component {
-        return this
-    }
-
-    override fun addSurroundingCssClass(css: String): Component {
-        return this
-    }
-
-    override fun removeSurroundingCssClass(css: String): Component {
-        return this
-    }
-
-    override fun addCssClass(css: Style): Component {
-        return this
-    }
-
-    override fun removeCssClass(css: Style): Component {
-        return this
-    }
-
-    override fun addSurroundingCssClass(css: Style): Component {
-        return this
-    }
-
-    override fun removeSurroundingCssClass(css: Style): Component {
-        return this
-    }
-
-    override fun renderVNode(): VNode {
-        return h("style", arrayOf(generateStyle()))
-    }
-
     internal fun generateStyle(): String {
-        val styles = getSnStyle()
+        val styles = getSnStyleInternal()
         return ".$className {\n" + styles.joinToString("\n") {
             "${it.first}: ${it.second};"
         } + "\n}"
     }
 
-    override fun getElement(): Node? {
-        return null
-    }
-
-    override fun getElementJQuery(): JQuery? {
-        return null
-    }
-
-    override fun getElementJQueryD(): dynamic {
-        return null
-    }
-
-    override fun clearParent(): Component {
-        this.parent = null
-        return this
-    }
-
-    override fun getRoot(): Root? {
-        return this.parent?.getRoot()
-    }
-
-    override fun dispose() {
-        styles.remove(this)
-    }
-
     protected fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }) =
         RefreshDelegateProvider<T>(null, refreshFunction)
 
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
index aa3f26bb..d9fa63fc 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
@@ -29,7 +29,7 @@ import kotlin.reflect.KProperty
  * Base class for components supporting CSS styling.
  */
 @Suppress("LargeClass")
-abstract class StyledComponent : Component {
+abstract class StyledComponent {
     private val propertyValues: MutableMap<String, Any?> = mutableMapOf()
 
     /**
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
index 3388a011..fbcd89da 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
@@ -53,11 +53,12 @@ import kotlin.reflect.KProperty
  * @param classes Set of CSS class names
  */
 @Suppress("TooManyFunctions", "LargeClass")
-open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
+open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component {
     private val propertyValues: MutableMap<String, Any?> = mutableMapOf()
 
     internal val classes = classes.toMutableSet()
     internal val surroundingClasses: MutableSet<String> = mutableSetOf()
+    internal val attributes: MutableMap<String, String> = mutableMapOf()
     internal val internalListeners = mutableListOf<SnOn<Widget>.() -> Unit>()
     internal val listeners = mutableListOf<SnOn<Widget>.() -> Unit>()
 
@@ -243,6 +244,9 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
         if (draggable == true) {
             snattrs.add("draggable" to "true")
         }
+        if (attributes.isNotEmpty()) {
+            snattrs += attributes.toList()
+        }
         return snattrs
     }
 
@@ -565,6 +569,22 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
         return removeSurroundingCssClass(css.className)
     }
 
+    override fun getAttribute(name: String): String? {
+        return this.attributes[name]
+    }
+
+    override fun setAttribute(name: String, value: String): Widget {
+        this.attributes[name] = value
+        refresh()
+        return this
+    }
+
+    override fun removeAttribute(name: String): Widget {
+        this.attributes.remove(name)
+        refresh()
+        return this
+    }
+
     override fun getElement(): Node? {
         return this.vnode?.elm
     }
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
index d9d839ba..81d6848b 100644
--- a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
@@ -25,8 +25,6 @@ import com.github.snabbdom.VNode
 import pl.treksoft.kvision.KVManager
 import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringBoolPair
-import pl.treksoft.kvision.core.StringPair
-import pl.treksoft.kvision.core.Widget
 import pl.treksoft.kvision.i18n.I18n
 import pl.treksoft.kvision.panel.SimplePanel
 
@@ -115,8 +113,6 @@ open class Tag(
     init: (Tag.() -> Unit)? = null
 ) : SimplePanel(classes), Template {
 
-    protected val attributes = attributes.toMutableMap()
-
     /**
      * Tag type.
      */
@@ -148,6 +144,7 @@ open class Tag(
     override var templates: Map<String, (Any?) -> String> by refreshOnUpdate(mapOf())
 
     init {
+        this.attributes += attributes
         @Suppress("LeakingThis")
         init?.invoke(this)
     }
@@ -181,14 +178,6 @@ open class Tag(
         return cl
     }
 
-    override fun getSnAttrs(): List<StringPair> {
-        return if (attributes.isEmpty()) {
-            super.getSnAttrs()
-        } else {
-            attributes.toList() + super.getSnAttrs()
-        }
-    }
-
     operator fun String.unaryPlus() {
         if (content == null)
             content = this
@@ -196,36 +185,6 @@ open class Tag(
             content += translate(this)
     }
 
-    /**
-     * Returns the value of an additional attribute.
-     * @param name the name of the attribute
-     * @return the value of the attribute
-     */
-    fun getAttribute(name: String): String? {
-        return this.attributes[name]
-    }
-
-    /**
-     * Sets the value of additional attribute.
-     * @param name the name of the attribute
-     * @param value the value of the attribute
-     */
-    fun setAttribute(name: String, value: String): Widget {
-        this.attributes[name] = value
-        refresh()
-        return this
-    }
-
-    /**
-     * Removes the value of additional attribute.
-     * @param name the name of the attribute
-     */
-    fun removeAttribute(name: String): Widget {
-        this.attributes.remove(name)
-        refresh()
-        return this
-    }
-
     companion object {
         /**
          * DSL builder extension function.
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt
index 310d4d49..010f7cba 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt
@@ -24,7 +24,7 @@ package pl.treksoft.kvision.panel
 import pl.treksoft.kvision.core.Component
 import pl.treksoft.kvision.core.Container
 import pl.treksoft.kvision.core.StringPair
-import pl.treksoft.kvision.core.StyledComponent
+import pl.treksoft.kvision.core.Widget
 import pl.treksoft.kvision.core.WidgetWrapper
 import pl.treksoft.kvision.utils.px
 
@@ -152,10 +152,10 @@ open class FlexPanel(
     }
 
     private fun refreshSpacing() {
-        getChildren().filterIsInstance<StyledComponent>().map { applySpacing(it) }
+        getChildren().filterIsInstance<Widget>().map { applySpacing(it) }
     }
 
-    private fun applySpacing(wrapper: StyledComponent): StyledComponent {
+    private fun applySpacing(wrapper: Widget): Widget {
         wrapper.marginTop = null
         wrapper.marginRight = null
         wrapper.marginBottom = null
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
index dd3d39b6..2d9dcc46 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
@@ -71,7 +71,6 @@ class Root(
         }
         roots.add(this)
         if (isFirstRoot) {
-            Style.styles.forEach { it.parent = this }
             Modal.modals.forEach { it.parent = this }
         }
         @Suppress("LeakingThis")
@@ -102,9 +101,8 @@ class Root(
 
     private fun stylesVNodes(): Array<VNode> {
         return if (isFirstRoot) {
-            val visibleStyles = Style.styles.filter { it.visible }
-            if (visibleStyles.isNotEmpty()) {
-                val stylesDesc = visibleStyles.joinToString("\n") { it.generateStyle() }
+            if (Style.styles.isNotEmpty()) {
+                val stylesDesc = Style.styles.joinToString("\n") { it.generateStyle() }
                 arrayOf(h("style", arrayOf(stylesDesc)))
             } else {
                 arrayOf()
-- 
cgit 


From d40abae23a4700447b000c55a177bfdb703fef44 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Fri, 7 Jun 2019 01:42:47 +0200
Subject: Support for Cordova file api.

---
 .../kotlin/pl/treksoft/kvision/cordova/File.kt     | 542 +++++++++++++++++++++
 .../pl/treksoft/kvision/cordova/FileSystem.kt      | 221 +++++++++
 .../pl/treksoft/kvision/cordova/FileWriter.kt      |  47 ++
 .../kotlin/pl/treksoft/kvision/cordova/Result.kt   |   7 +-
 4 files changed, 815 insertions(+), 2 deletions(-)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt
new file mode 100644
index 00000000..df176e84
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt
@@ -0,0 +1,542 @@
+@file:Suppress(
+    "TooManyFunctions", "TooGenericExceptionCaught"
+)
+
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import org.khronos.webgl.ArrayBuffer
+import org.khronos.webgl.Uint8Array
+import org.khronos.webgl.set
+import org.w3c.files.Blob
+import org.w3c.files.BlobPropertyBag
+import org.w3c.files.FileReader
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Exception class for file errors.
+ */
+class FileException(code: Int) : Exception("Error: $code")
+
+/**
+ * System directories class.
+ */
+external class SystemDirs {
+    val applicationDirectory: String
+    val applicationStorageDirectory: String
+    val dataDirectory: String
+    val cacheDirectory: String
+    val externalApplicationStorageDirectory: String?
+    val externalDataDirectory: String?
+    val externalCacheDirectory: String?
+    val externalRootDirectory: String?
+    val tempDirectory: String?
+    val syncedDataDirectory: String?
+    val documentsDirectory: String?
+    val sharedDirectory: String?
+}
+
+/**
+ * Main object for Cordova file.
+ */
+object File {
+
+    const val NOT_FOUND_ERR = 1
+    const val SECURITY_ERR = 2
+    const val ABORT_ERR = 3
+    const val NOT_READABLE_ERR = 4
+    const val ENCODING_ERR = 5
+    const val NO_MODIFICATION_ALLOWED_ERR = 6
+    const val INVALID_STATE_ERR = 7
+    const val SYNTAX_ERR = 8
+    const val INVALID_MODIFICATION_ERR = 9
+    const val QUOTA_EXCEEDED_ERR = 10
+    const val TYPE_MISMATCH_ERR = 11
+    const val PATH_EXISTS_ERR = 12
+
+    /**
+     * File system types.
+     */
+    enum class FileSystemType {
+        TEMPORARY,
+        PERSISTENT
+    }
+
+    /**
+     * Get system directories.
+     */
+    suspend fun getSystemDirectories(): SystemDirs {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                continuation.resume(window.asDynamic().cordova.file)
+            }
+        }
+    }
+
+    /**
+     * Resolve given path to a file or directory entry.
+     * @param url directory path
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun resolveLocalFileSystemURL(url: String): Result<Entry, FileException> {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                window.asDynamic().resolveLocalFileSystemURL(url, { entry ->
+                    continuation.resume(Result.Success(entry))
+                }, { error ->
+                    continuation.resume(Result.error(FileException(error.code)))
+                })
+            }
+        }
+    }
+
+    /**
+     * Resolve given path to a directory entry.
+     * @param url directory path
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun resolveLocalFileSystemURLForDir(url: String): Result<DirectoryEntry, FileException> {
+        return when (val result = resolveLocalFileSystemURL(url)) {
+            is Result.Success -> {
+                if (result.value.isDirectory) {
+                    result.map {
+                        @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
+                        it as DirectoryEntry
+                    }
+                } else {
+                    Result.error(FileException(TYPE_MISMATCH_ERR))
+                }
+            }
+            is Result.Failure -> result
+        }
+    }
+
+    /**
+     * Resolve given path to a file entry.
+     * @param url file path
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun resolveLocalFileSystemURLForFile(url: String): Result<FileEntry, FileException> {
+        return when (val result = resolveLocalFileSystemURL(url)) {
+            is Result.Success -> {
+                if (result.value.isFile) {
+                    result.map {
+                        @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
+                        it as FileEntry
+                    }
+                } else {
+                    Result.error(FileException(TYPE_MISMATCH_ERR))
+                }
+            }
+            is Result.Failure -> result
+        }
+    }
+
+    /**
+     * Request a file system of a given type.
+     * @param fileSystemType file system type (temporary or persistent)
+     * @param size file system size (quota)
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun requestFileSystem(
+        fileSystemType: File.FileSystemType,
+        size: Long = 0
+    ): Result<FileSystem, FileException> {
+        return suspendCoroutine { continuation ->
+            val type = when (fileSystemType) {
+                File.FileSystemType.TEMPORARY -> LocalFileSystem.TEMPORARY
+                File.FileSystemType.PERSISTENT -> LocalFileSystem.PERSISTENT
+            }
+            addDeviceReadyListener {
+                window.asDynamic().requestFileSystem(type, size, { fs ->
+                    continuation.resume(Result.Success(fs))
+                }, { error ->
+                    continuation.resume(Result.error(FileException(error.code)))
+                })
+            }
+        }
+    }
+
+    internal fun dataURLtoBlob(dataURI: String): Blob {
+        val byteString = window.atob(dataURI.split(',')[1])
+        val mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
+        val ab = ArrayBuffer(byteString.length)
+        val ia = Uint8Array(ab)
+        for (i in 0 until byteString.length) {
+            ia[i] = byteString[i].toByte()
+        }
+        return Blob(arrayOf(ab), BlobPropertyBag(type = mimeString))
+    }
+
+}
+
+/**
+ * Extension function to convert String to a directory entry.
+ */
+suspend fun String.toDirectoryEntry(): Result<DirectoryEntry, FileException> {
+    return File.resolveLocalFileSystemURLForDir(this)
+}
+
+/**
+ * Extension function to convert String to a file entry.
+ */
+suspend fun String.toFileEntry(): Result<FileEntry, FileException> {
+    return File.resolveLocalFileSystemURLForFile(this)
+}
+
+/**
+ * Get file or directory metadata.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.getMetadata(): Result<Metadata, FileException> {
+    return suspendCoroutine { continuation ->
+        this.getMetadata({ metadata: Metadata ->
+            continuation.resume(Result.success(metadata))
+        } as MetadataCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Get file or directory parent directory entry.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.getParent(): Result<DirectoryEntry, FileException> {
+    return suspendCoroutine { continuation ->
+        this.getParent({ directoryEntry: DirectoryEntry ->
+            continuation.resume(Result.success(directoryEntry))
+        } as DirectoryEntryCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Remove given file or directory.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.remove(): Result<Entry, FileException> {
+    return suspendCoroutine { continuation ->
+        this.remove({
+            continuation.resume(Result.success(this))
+        } as VoidCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Move given file or directory to a new location.
+ * @param parent new location parent directory entry
+ * @param newName new location name
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.moveTo(parent: DirectoryEntry, newName: String? = null): Result<Entry, FileException> {
+    return suspendCoroutine { continuation ->
+        this.moveTo(parent, newName, { entry: Entry ->
+            continuation.resume(Result.success(entry))
+        } as EntryCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Copy given file or directory to a new location.
+ * @param parent new location parent directory entry
+ * @param newName new location name
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.copyTo(parent: DirectoryEntry, newName: String? = null): Result<Entry, FileException> {
+    return suspendCoroutine { continuation ->
+        this.copyTo(parent, newName, { entry: Entry ->
+            continuation.resume(Result.success(entry))
+        } as EntryCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Create a FileWriter object for a given file entry.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun FileEntry.createWriter(): Result<FileWriter, FileException> {
+    return suspendCoroutine { continuation ->
+        this.createWriter({ fileWriter: FileWriter ->
+            continuation.resume(Result.success(fileWriter))
+        } as FileWriterCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Get a File object for a given file entry.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun FileEntry.file(): Result<org.w3c.files.File, FileException> {
+    return suspendCoroutine { continuation ->
+        this.file({ file: org.w3c.files.File ->
+            continuation.resume(Result.success(file))
+        } as FileCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Get or create a file in a given parent directory.
+ * @param path target file path
+ * @param create Used to indicate that the user wants to create a file if it was not previously there.
+ * @param exclusive Fail if the target path file already exists.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun DirectoryEntry.getFile(
+    path: String,
+    create: Boolean = true,
+    exclusive: Boolean = false
+): Result<FileEntry, FileException> {
+    return suspendCoroutine { continuation ->
+        this.getFile(path, obj {
+            this.create = create
+            this.exclusive = exclusive
+        }, { fileEntry: FileEntry ->
+            continuation.resume(Result.success(fileEntry))
+        } as FileEntryCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Get or create a directory in a given parent directory.
+ * @param path target directory path
+ * @param create Used to indicate that the user wants to create a directory if it was not previously there.
+ * @param exclusive Fail if the target path directory already exists.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun DirectoryEntry.getDirectory(
+    path: String,
+    create: Boolean = true,
+    exclusive: Boolean = false
+): Result<DirectoryEntry, FileException> {
+    return suspendCoroutine { continuation ->
+        this.getDirectory(path, obj {
+            this.create = create
+            this.exclusive = exclusive
+        }, { directoryEntry: DirectoryEntry ->
+            continuation.resume(Result.success(directoryEntry))
+        } as DirectoryEntryCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * Remove given directory recursively.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun DirectoryEntry.removeRecursively(): Result<DirectoryEntry, FileException> {
+    return suspendCoroutine { continuation ->
+        this.removeRecursively({
+            continuation.resume(Result.success(this))
+        } as VoidCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * List directory entries for a given DirectoryReader.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun DirectoryReader.readEntries(): Result<List<Entry>, FileException> {
+    return suspendCoroutine { continuation ->
+        this.readEntries({ entries: Array<Entry> ->
+            continuation.resume(Result.success(entries.toList()))
+        } as EntriesCallback, { error: dynamic ->
+            continuation.resume(Result.error(FileException(error.code)))
+        } as ErrorCallback)
+    }
+}
+
+/**
+ * List directory entries for a given parent directory entry.
+ */
+suspend fun DirectoryEntry.readEntries(): Result<List<Entry>, FileException> {
+    return this.createReader().readEntries()
+}
+
+/**
+ * Read file content as a plain string.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.readAsText(): Result<String, FileException> {
+    return this.file().flatMap { file ->
+        suspendCoroutine<Result<String, FileException>> { continuation ->
+            val reader = FileReader()
+            reader.onloadend = { e ->
+                continuation.resume(Result.success(e.target.asDynamic().result))
+            }
+            reader.onerror = { error: dynamic ->
+                continuation.resume(Result.error(FileException(error.code)))
+            }
+            reader.readAsText(file)
+        }
+    }
+}
+
+/**
+ * Read file content as a data url.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.readAsDataURL(): Result<String, FileException> {
+    return this.file().flatMap { file ->
+        suspendCoroutine<Result<String, FileException>> { continuation ->
+            val reader = FileReader()
+            reader.onloadend = { e ->
+                continuation.resume(Result.success(e.target.asDynamic().result))
+            }
+            reader.onerror = { error: dynamic ->
+                continuation.resume(Result.error(FileException(error.code)))
+            }
+            reader.readAsDataURL(file)
+        }
+    }
+}
+
+/**
+ * Read file content as an array buffer.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.readAsArrayBuffer(): Result<ArrayBuffer, FileException> {
+    return this.file().flatMap { file ->
+        suspendCoroutine<Result<ArrayBuffer, FileException>> { continuation ->
+            val reader = FileReader()
+            reader.onloadend = { e ->
+                continuation.resume(Result.success(e.target.asDynamic().result))
+            }
+            reader.onerror = { error: dynamic ->
+                continuation.resume(Result.error(FileException(error.code)))
+            }
+            reader.onabort = { _ ->
+                continuation.resume(Result.error(FileException(File.ABORT_ERR)))
+            }
+            reader.readAsArrayBuffer(file)
+        }
+    }
+}
+
+/**
+ * Write file content from a Blob.
+ * @param data a data Blob to be written.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.write(data: Blob): Result<FileEntry, FileException> {
+    return this.createWriter().flatMap { writer ->
+        suspendCoroutine<Result<FileEntry, FileException>> { continuation ->
+            writer.onwriteend = {
+                continuation.resume(Result.success(this))
+            }
+            writer.onerror = { error: dynamic ->
+                continuation.resume(Result.error(FileException(error.code)))
+            }
+            writer.onabort = { _ ->
+                continuation.resume(Result.error(FileException(File.ABORT_ERR)))
+            }
+            writer.write(data)
+        }
+    }
+}
+
+/**
+ * Write file content from a plain string.
+ * @param data a data string to be written.
+ */
+suspend fun FileEntry.write(data: String): Result<FileEntry, FileException> {
+    return this.write(Blob(arrayOf(data)))
+}
+
+/**
+ * Write file content from a data url.
+ * @param dataUrl a data url to be written.
+ */
+suspend fun FileEntry.writeDataUrL(dataUrl: String): Result<FileEntry, FileException> {
+    return this.write(File.dataURLtoBlob(dataUrl))
+}
+
+/**
+ * Write file content from an array buffer.
+ * @param data an array buffer to be written.
+ */
+suspend fun FileEntry.write(data: ArrayBuffer): Result<FileEntry, FileException> {
+    return this.write(data.toBlob())
+}
+
+/**
+ * Append file content from a Blob.
+ * @param data a data Blob to be appended.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.append(data: Blob): Result<FileEntry, FileException> {
+    return this.createWriter().flatMap { writer ->
+        try {
+            writer.seek(writer.length)
+        } catch (e: Exception) {
+            console.log("File doesn't exist!")
+        }
+        suspendCoroutine<Result<FileEntry, FileException>> { continuation ->
+            writer.onwriteend = {
+                continuation.resume(Result.success(this))
+            }
+            writer.onerror = { error: dynamic ->
+                continuation.resume(Result.error(FileException(error.code)))
+            }
+            writer.onabort = { _ ->
+                continuation.resume(Result.error(FileException(File.ABORT_ERR)))
+            }
+            writer.write(data)
+        }
+    }
+}
+
+/**
+ * Append file content from a plain string.
+ * @param data a string to be appended.
+ */
+suspend fun FileEntry.append(data: String): Result<FileEntry, FileException> {
+    return this.append(Blob(arrayOf(data)))
+}
+
+/**
+ * Convert array buffer to a blob.
+ */
+fun ArrayBuffer.toBlob(): Blob {
+    return Blob(arrayOf(Uint8Array(this)))
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt
new file mode 100644
index 00000000..48f4f3d2
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt
@@ -0,0 +1,221 @@
+@file:Suppress(
+    "INTERFACE_WITH_SUPERCLASS",
+    "OVERRIDING_FINAL_MEMBER",
+    "RETURN_TYPE_MISMATCH_ON_OVERRIDE",
+    "CONFLICTING_OVERLOADS",
+    "EXTERNAL_DELEGATION",
+    "NESTED_CLASS_IN_EXTERNAL_INTERFACE",
+    "unused", "PropertyName", "TooManyFunctions", "VariableNaming", "MaxLineLength"
+)
+
+package pl.treksoft.kvision.cordova
+
+import org.w3c.files.File
+import kotlin.js.Date
+
+external object LocalFileSystem {
+    var TEMPORARY: Number
+    var PERSISTENT: Number
+    fun requestFileSystem(
+        type: Number,
+        size: Number,
+        successCallback: FileSystemCallback,
+        errorCallback: ErrorCallback? = definedExternally /* null */
+    )
+
+    fun resolveLocalFileSystemURL(
+        url: String,
+        successCallback: EntryCallback,
+        errorCallback: ErrorCallback? = definedExternally /* null */
+    )
+
+    fun webkitRequestFileSystem(
+        type: Number,
+        size: Number,
+        successCallback: FileSystemCallback,
+        errorCallback: ErrorCallback? = definedExternally /* null */
+    )
+}
+
+external object LocalFileSystemSync {
+    var TEMPORARY: Number
+    var PERSISTENT: Number
+    fun requestFileSystemSync(type: Number, size: Number): FileSystemSync
+    fun resolveLocalFileSystemSyncURL(url: String): EntrySync
+    fun webkitRequestFileSystemSync(type: Number, size: Number): FileSystemSync
+}
+
+external interface Metadata {
+    var modificationTime: Date
+    var size: Number
+}
+
+external interface Flags {
+    var create: Boolean? get() = definedExternally; set(value) = definedExternally
+    var exclusive: Boolean? get() = definedExternally; set(value) = definedExternally
+}
+
+external interface FileSystem {
+    var name: String
+    var root: DirectoryEntry
+}
+
+external interface Entry {
+    var isFile: Boolean
+    var isDirectory: Boolean
+    fun getMetadata(successCallback: MetadataCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+    var name: String
+    var fullPath: String
+    var filesystem: FileSystem
+    fun moveTo(
+        parent: DirectoryEntry,
+        newName: String? = definedExternally /* null */,
+        successCallback: EntryCallback? = definedExternally /* null */,
+        errorCallback: ErrorCallback? = definedExternally /* null */
+    )
+
+    fun copyTo(
+        parent: DirectoryEntry,
+        newName: String? = definedExternally /* null */,
+        successCallback: EntryCallback? = definedExternally /* null */,
+        errorCallback: ErrorCallback? = definedExternally /* null */
+    )
+
+    fun toURL(): String
+    fun toInternalURL(): String
+    fun remove(successCallback: VoidCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+    fun getParent(successCallback: DirectoryEntryCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+}
+
+external interface DirectoryEntry : Entry {
+    fun createReader(): DirectoryReader
+    fun getFile(
+        path: String,
+        options: Flags? = definedExternally /* null */,
+        successCallback: FileEntryCallback?,
+        errorCallback: ErrorCallback? = definedExternally /* null */
+    )
+
+    fun getDirectory(
+        path: String,
+        options: Flags? = definedExternally /* null */,
+        successCallback: DirectoryEntryCallback?,
+        errorCallback: ErrorCallback? = definedExternally /* null */
+    )
+
+    fun removeRecursively(successCallback: VoidCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+}
+
+external interface DirectoryReader {
+    fun readEntries(successCallback: EntriesCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+}
+
+external interface FileEntry : Entry {
+    fun createWriter(successCallback: FileWriterCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+    fun file(successCallback: FileCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+}
+
+external interface FileSystemCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun FileSystemCallback.invoke(filesystem: FileSystem) {
+    asDynamic()(filesystem)
+}
+
+external interface EntryCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun EntryCallback.invoke(entry: Entry) {
+    asDynamic()(entry)
+}
+
+external interface FileEntryCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun FileEntryCallback.invoke(entry: FileEntry) {
+    asDynamic()(entry)
+}
+
+external interface DirectoryEntryCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun DirectoryEntryCallback.invoke(entry: DirectoryEntry) {
+    asDynamic()(entry)
+}
+
+external interface EntriesCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun EntriesCallback.invoke(entries: Array<Entry>) {
+    asDynamic()(entries)
+}
+
+external interface MetadataCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun MetadataCallback.invoke(metadata: Metadata) {
+    asDynamic()(metadata)
+}
+
+external interface FileWriterCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun FileWriterCallback.invoke(fileWriter: FileWriter) {
+    asDynamic()(fileWriter)
+}
+
+external interface FileCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun FileCallback.invoke(file: File) {
+    asDynamic()(file)
+}
+
+external interface VoidCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun VoidCallback.invoke() {
+    asDynamic()()
+}
+
+external interface ErrorCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun ErrorCallback.invoke(err: Error) {
+    asDynamic()(err)
+}
+
+external interface FileSystemSync {
+    var name: String
+    var root: DirectoryEntrySync
+}
+
+external interface EntrySync {
+    var isFile: Boolean
+    var isDirectory: Boolean
+    fun getMetadata(): Metadata
+    var name: String
+    var fullPath: String
+    var filesystem: FileSystemSync
+    fun moveTo(parent: DirectoryEntrySync, newName: String? = definedExternally /* null */): EntrySync
+    fun copyTo(parent: DirectoryEntrySync, newName: String? = definedExternally /* null */): EntrySync
+    fun toURL(): String
+    fun remove()
+    fun getParent(): DirectoryEntrySync
+}
+
+external interface DirectoryEntrySync : EntrySync {
+    fun createReader(): DirectoryReaderSync
+    fun getFile(path: String, options: Flags? = definedExternally /* null */): FileEntrySync
+    fun getDirectory(path: String, options: Flags? = definedExternally /* null */): DirectoryEntrySync
+    fun removeRecursively()
+}
+
+external interface DirectoryReaderSync {
+    fun readEntries(): Array<EntrySync>
+}
+
+external interface FileEntrySync : EntrySync {
+    fun createWriter(): FileWriterSync
+    fun file(): File
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt
new file mode 100644
index 00000000..26215aa8
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt
@@ -0,0 +1,47 @@
+@file:Suppress(
+    "INTERFACE_WITH_SUPERCLASS",
+    "OVERRIDING_FINAL_MEMBER",
+    "RETURN_TYPE_MISMATCH_ON_OVERRIDE",
+    "CONFLICTING_OVERLOADS",
+    "EXTERNAL_DELEGATION",
+    "NESTED_CLASS_IN_EXTERNAL_INTERFACE",
+    "unused", "PropertyName", "TooManyFunctions", "VariableNaming", "MaxLineLength"
+)
+
+package pl.treksoft.kvision.cordova
+
+import org.w3c.dom.events.EventTarget
+import org.w3c.files.Blob
+import org.w3c.xhr.ProgressEvent
+
+external interface FileSaver : EventTarget {
+    fun abort()
+    var INIT: Number
+    var WRITING: Number
+    var DONE: Number
+    var readyState: Number
+    var error: Error
+    var onwritestart: (event: ProgressEvent) -> Unit
+    var onprogress: (event: ProgressEvent) -> Unit
+    var onwrite: (event: ProgressEvent) -> Unit
+    var onabort: (event: ProgressEvent) -> Unit
+    var onerror: (event: ProgressEvent) -> Unit
+    var onwriteend: (event: ProgressEvent) -> Unit
+
+}
+
+external interface FileWriter : FileSaver {
+    var position: Number
+    var length: Number
+    fun write(data: Blob)
+    fun seek(offset: Number)
+    fun truncate(size: Number)
+}
+
+external interface FileWriterSync {
+    var position: Number
+    var length: Number
+    fun write(data: Blob)
+    fun seek(offset: Number)
+    fun truncate(size: Number)
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt
index bb125e24..83a1987e 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt
@@ -1,3 +1,6 @@
+@file:Suppress(
+    "TooManyFunctions", "TooGenericExceptionCaught"
+)
 /*
  * Source: https://github.com/kittinunf/Result
  *
@@ -31,9 +34,9 @@ inline fun <reified X> Result<*, *>.getAs() = when (this) {
     is Result.Failure -> error as? X
 }
 
-fun <V : Any> Result<V, *>.success(f: (V) -> Unit) = fold(f, {})
+inline fun <V : Any> Result<V, *>.success(f: (V) -> Unit) = fold(f, {})
 
-fun <E : Exception> Result<*, E>.failure(f: (E) -> Unit) = fold({}, f)
+inline fun <E : Exception> Result<*, E>.failure(f: (E) -> Unit) = fold({}, f)
 
 infix fun <V : Any, E : Exception> Result<V, E>.or(fallback: V) = when (this) {
     is Result.Success -> this
-- 
cgit 


From c73905e037608c76b08e4a3327fc53fe72a96622 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Fri, 7 Jun 2019 01:43:13 +0200
Subject: Code style fixes.

---
 .../src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt           | 1 +
 .../src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt        | 2 ++
 .../src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt            | 6 ++++--
 3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
index 885bfd08..40c731c7 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
@@ -251,6 +251,7 @@ data class CameraOptions(
     val cameraDirection: Camera.Direction? = null
 )
 
+@Suppress("ComplexMethod")
 internal fun CameraOptions.toJs(): dynamic {
     return obj {
         if (quality != null) this.quality = quality
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt
index 5617bf9b..e3de6af2 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt
@@ -27,12 +27,14 @@ import kotlin.browser.window
 import kotlin.coroutines.resume
 import kotlin.coroutines.suspendCoroutine
 
+@Suppress("TopLevelPropertyNaming")
 @JsName("StatusBar")
 internal external val StatusBarJs: dynamic
 
 /**
  * Main object for Cordova status bar.
  */
+@Suppress("TooManyFunctions")
 object StatusBar {
 
     /**
diff --git a/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt b/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt
index 9eb9d9a7..39e7c2dc 100644
--- a/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt
+++ b/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt
@@ -23,7 +23,7 @@ package pl.treksoft.kvision.moment
 
 import kotlin.js.Date
 
-@Suppress("unused")
+@Suppress("unused", "TooManyFunctions")
 @JsNonModule
 @JsModule("moment")
 open external class Moment {
@@ -79,7 +79,9 @@ open external class Moment {
     open fun isSameOrAfter(moment: dynamic): Boolean = definedExternally
     open fun isSameOrAfter(moment: dynamic, key: String): Boolean = definedExternally
     open fun isBetween(firstMoment: dynamic, secondMoment: dynamic): Boolean = definedExternally
-    open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String, inclusivity: String): Boolean = definedExternally
+    open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String, inclusivity: String): Boolean =
+        definedExternally
+
     open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String): Boolean = definedExternally
     open fun isDST(): Boolean = definedExternally
     open fun isDSTShifted(): Boolean = definedExternally
-- 
cgit 


From 5f1681474cdfb7a140adc8e93414e488b335d29e Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Fri, 7 Jun 2019 20:03:50 +0200
Subject: New getStatus() function for cordova battery api.

---
 .../src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt  | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
index 84a7eb2e..656a519a 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
@@ -23,6 +23,8 @@
 package pl.treksoft.kvision.cordova
 
 import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
 
 /**
  * Battery status.
@@ -57,4 +59,15 @@ object Battery {
             }, false)
         }
     }
+
+    /**
+     * Get battery status.
+     */
+    suspend fun getStatus(): BatteryStatus {
+        return suspendCoroutine { continuation ->
+            addStatusListener(Battery.BatteryEvent.BATTERY_STATUS) { status ->
+                continuation.resume(status)
+            }
+        }
+    }
 }
-- 
cgit 


From 7b9fb94d618fcf619de410cdeb9c000f21991b71 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 8 Jun 2019 00:18:11 +0200
Subject: Support for Cordova geolocation api.

---
 .../pl/treksoft/kvision/cordova/Geolocation.kt     | 263 +++++++++++++++++++++
 1 file changed, 263 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
new file mode 100644
index 00000000..9f4d9cfe
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Geolocation coordinates values.
+ */
+external class Coordinates {
+    val latitude: Number
+    val longitude: Number
+    val altitude: Number
+    val accuracy: Number
+    val altitudeAccuracy: Number?
+    val heading: Number?
+    val speed: Number
+}
+
+/**
+ * Geolocation position value.
+ */
+external class Position {
+    val coords: Coordinates
+    val timestamp: Number
+}
+
+/**
+ * Geolocaton error codes.
+ */
+enum class PositionError {
+    PERMISSION_DENIED,
+    POSITION_UNAVAILABLE,
+    TIMEOUT
+}
+
+private fun codeToEnum(code: Int): PositionError {
+    return when (code) {
+        1 -> PositionError.PERMISSION_DENIED
+        2 -> PositionError.POSITION_UNAVAILABLE
+        else -> PositionError.TIMEOUT
+    }
+}
+
+/**
+ * Exception class for geolocation errors.
+ */
+class GeolocationException(val code: PositionError, message: String) : Exception(message)
+
+/**
+ * Main geolocation object based on webview api.
+ */
+object Geolocation {
+
+    /**
+     * Get current location.
+     * @param enableHighAccuracy provides a hint that the application needs the best possible results
+     * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result
+     * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun getCurrentPosition(
+        enableHighAccuracy: Boolean = true,
+        timeout: Number? = null,
+        maximumAge: Number? = null
+    ): Result<Position, GeolocationException> {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                window.navigator.asDynamic().geolocation.getCurrentPosition({ position: Position ->
+                    continuation.resume(Result.Success(position))
+                }, { error ->
+                    continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
+                }, obj {
+                    this.enableHighAccuracy = enableHighAccuracy
+                    if (timeout != null) this.timeout = timeout
+                    if (maximumAge != null) this.maximumAge = maximumAge
+                })
+            }
+        }
+    }
+
+    /**
+     * Watch location changes.
+     * @param enableHighAccuracy provides a hint that the application needs the best possible results
+     * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result
+     * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds
+     * @param resultCallback a callback function receiving the result
+     * @return watch identifier (can be removed with a [clearWatch] function)
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun watchPosition(
+        enableHighAccuracy: Boolean = true,
+        timeout: Number? = null,
+        maximumAge: Number? = null,
+        resultCallback: (Result<Position, GeolocationException>) -> Unit
+    ): String? {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                val watchId = window.navigator.asDynamic().geolocation.watchPosition({ position: Position ->
+                    resultCallback(Result.Success(position))
+                }, { error ->
+                    resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
+                }, obj {
+                    this.enableHighAccuracy = enableHighAccuracy
+                    if (timeout != null) this.timeout = timeout
+                    if (maximumAge != null) this.maximumAge = maximumAge
+                })
+                continuation.resume(watchId)
+            }
+        }
+    }
+
+    /**
+     * Clear the given watch.
+     * @param watchId watch identifier returned from [watchPosition] function
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    fun clearWatch(watchId: String) {
+        if (window.navigator.asDynamic().geolocation != null) {
+            window.navigator.asDynamic().geolocation.clearWatch(watchId)
+        }
+    }
+
+}
+
+/**
+ * Main geolocation object based on Google location services api.
+ */
+object Locationservices {
+
+    /**
+     * Location services priority.
+     */
+    enum class Priority {
+        PRIORITY_HIGH_ACCURACY,
+        PRIORITY_BALANCED_POWER_ACCURACY,
+        PRIORITY_LOW_POWER,
+        PRIORITY_NO_POWER
+    }
+
+    /**
+     * Get current location.
+     * @param enableHighAccuracy provides a hint that the application needs the best possible results
+     * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result
+     * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds
+     * @param priority the priority of the request is a strong hint for which location sources to use
+     * @param interval set the desired interval for active location updates, in milliseconds
+     * @param fastInterval explicitly set the fastest interval for location updates, in milliseconds
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun getCurrentPosition(
+        enableHighAccuracy: Boolean = true,
+        timeout: Number? = null,
+        maximumAge: Number? = null,
+        priority: Priority? = null,
+        interval: Number? = null,
+        fastInterval: Number? = null
+    ): Result<Position, GeolocationException> {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                window.asDynamic()
+                    .cordova.plugins.locationServices.geolocation.getCurrentPosition({ position: Position ->
+                    continuation.resume(Result.Success(position))
+                }, { error ->
+                    continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
+                }, obj {
+                    this.enableHighAccuracy = enableHighAccuracy
+                    if (timeout != null) this.timeout = timeout
+                    if (maximumAge != null) this.maximumAge = maximumAge
+                    if (priority != null) this.priority = getJsPriority(priority)
+                    if (interval != null) this.interval = interval
+                    if (fastInterval != null) this.fastInterval = fastInterval
+                })
+            }
+        }
+    }
+
+    /**
+     * Watch location changes.
+     * @param enableHighAccuracy provides a hint that the application needs the best possible results
+     * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result
+     * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds
+     * @param priority the priority of the request is a strong hint for which location sources to use
+     * @param interval set the desired interval for active location updates, in milliseconds
+     * @param fastInterval explicitly set the fastest interval for location updates, in milliseconds
+     * @param resultCallback a callback function receiving the result
+     * @return watch identifier (can be removed with a [clearWatch] function)
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun watchPosition(
+        enableHighAccuracy: Boolean = true,
+        timeout: Number? = null,
+        maximumAge: Number? = null,
+        priority: Priority? = null,
+        interval: Number? = null,
+        fastInterval: Number? = null,
+        resultCallback: (Result<Position, GeolocationException>) -> Unit
+    ): String? {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                val watchId =
+                    window.asDynamic()
+                        .cordova.plugins.locationServices.geolocation.watchPosition({ position: Position ->
+                        resultCallback(Result.Success(position))
+                    }, { error ->
+                        resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
+                    }, obj {
+                        this.enableHighAccuracy = enableHighAccuracy
+                        if (timeout != null) this.timeout = timeout
+                        if (maximumAge != null) this.maximumAge = maximumAge
+                        if (priority != null) this.priority = getJsPriority(priority)
+                        if (interval != null) this.interval = interval
+                        if (fastInterval != null) this.fastInterval = fastInterval
+                    })
+                continuation.resume(watchId)
+            }
+        }
+    }
+
+    /**
+     * Clear the given watch.
+     * @param watchId watch identifier returned from [watchPosition] function
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    fun clearWatch(watchId: String) {
+        if (window.asDynamic().cordova.plugins.locationServices.geolocation != null) {
+            window.asDynamic().cordova.plugins.locationServices.geolocation.clearWatch(watchId)
+        }
+    }
+
+    private fun getJsPriority(priority: Priority): dynamic {
+        return when (priority) {
+            Priority.PRIORITY_HIGH_ACCURACY -> 100
+            Priority.PRIORITY_BALANCED_POWER_ACCURACY -> 102
+            Priority.PRIORITY_LOW_POWER -> 104
+            Priority.PRIORITY_NO_POWER -> 105
+        }
+    }
+
+}
-- 
cgit 


From bb065993c3155a48e8c4095bb39970879c414547 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 8 Jun 2019 00:56:29 +0200
Subject: Support for Cordova InAppBrowser api.

---
 .../pl/treksoft/kvision/cordova/InAppBrowser.kt    | 74 ++++++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt
new file mode 100644
index 00000000..c0c8f0b4
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * InAppBrowser event type.
+ */
+external class InAppBrowserEvent {
+    val type: String
+    val url: String
+    val code: Number?
+    val message: String?
+    val data: String?
+}
+
+/**
+ * InAppBrowser reference object.
+ */
+external class Browser {
+    fun addEventListener(eventname: String, callback: (InAppBrowserEvent) -> Unit)
+    fun removeEventListener(eventname: String, callback: (InAppBrowserEvent) -> Unit)
+    fun close()
+    fun show()
+    fun hide()
+    fun executeScript(details: dynamic, callback: ((dynamic) -> Unit) = definedExternally)
+    fun insertCSS(details: dynamic, callback: (() -> Unit) = definedExternally)
+}
+
+/**
+ * Main object for Cordova InAppBrowser api.
+ */
+object InAppBrowser {
+
+    /**
+     * Open new browser window.
+     * @param url an URL address
+     * @param target a target window
+     * @param options a string with window options
+     * @return a browser window reference
+     */
+    suspend fun open(url: String, target: String = "_blank", options: String? = null): Browser {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                val ref = window.asDynamic().cordova.InAppBrowser.open(url, target, options)
+                continuation.resume(ref)
+            }
+        }
+    }
+
+}
-- 
cgit 


From 0f0f0e0cfb1238f9b5325fba89874a19d8b10c4a Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Sat, 8 Jun 2019 13:33:14 +0200
Subject: Fix test compilation error with karma-webpack 4.

---
 build.gradle                                       | 1 +
 kvision-modules/kvision-bootstrap/build.gradle     | 1 +
 kvision-modules/kvision-chart/build.gradle         | 1 +
 kvision-modules/kvision-datacontainer/build.gradle | 1 +
 kvision-modules/kvision-datetime/build.gradle      | 1 +
 kvision-modules/kvision-dialog/build.gradle        | 1 +
 kvision-modules/kvision-handlebars/build.gradle    | 1 +
 kvision-modules/kvision-i18n/build.gradle          | 1 +
 kvision-modules/kvision-redux/build.gradle         | 1 +
 kvision-modules/kvision-richtext/build.gradle      | 1 +
 kvision-modules/kvision-select/build.gradle        | 1 +
 kvision-modules/kvision-spinner/build.gradle       | 1 +
 kvision-modules/kvision-tabulator/build.gradle     | 1 +
 kvision-modules/kvision-upload/build.gradle        | 1 +
 14 files changed, 14 insertions(+)

diff --git a/build.gradle b/build.gradle
index 8e1ed991..1c77ff6e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -138,6 +138,7 @@ if (!project.gradle.startParameter.taskNames.contains("dokka")) {
             dependency("navigo", "7.0.0")
             devDependency("karma", "3.1.4")
             devDependency("karma-chrome-launcher", "2.2.0")
+            devDependency("karma-webpack", "3.0.5")
             devDependency("qunit", "2.8.0")
         }
 
diff --git a/kvision-modules/kvision-bootstrap/build.gradle b/kvision-modules/kvision-bootstrap/build.gradle
index 5f5d0c49..6c504c17 100644
--- a/kvision-modules/kvision-bootstrap/build.gradle
+++ b/kvision-modules/kvision-bootstrap/build.gradle
@@ -11,6 +11,7 @@ kotlinFrontend {
         dependency("bootstrap-vertical-tabs", "1.2.2")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-chart/build.gradle b/kvision-modules/kvision-chart/build.gradle
index 4d4ba4df..d90de6ce 100644
--- a/kvision-modules/kvision-chart/build.gradle
+++ b/kvision-modules/kvision-chart/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
         dependency("chart.js", "2.7.3")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-datacontainer/build.gradle b/kvision-modules/kvision-datacontainer/build.gradle
index 7ad09e67..9337698c 100644
--- a/kvision-modules/kvision-datacontainer/build.gradle
+++ b/kvision-modules/kvision-datacontainer/build.gradle
@@ -9,6 +9,7 @@ kotlinFrontend {
     npm {
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-datetime/build.gradle b/kvision-modules/kvision-datetime/build.gradle
index 610b1d9c..bbd4bb79 100644
--- a/kvision-modules/kvision-datetime/build.gradle
+++ b/kvision-modules/kvision-datetime/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
         dependency("bootstrap-datetime-picker", "2.4.4")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-dialog/build.gradle b/kvision-modules/kvision-dialog/build.gradle
index 16c2720a..292a228f 100644
--- a/kvision-modules/kvision-dialog/build.gradle
+++ b/kvision-modules/kvision-dialog/build.gradle
@@ -9,6 +9,7 @@ kotlinFrontend {
     npm {
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-handlebars/build.gradle b/kvision-modules/kvision-handlebars/build.gradle
index de48816a..a971427e 100644
--- a/kvision-modules/kvision-handlebars/build.gradle
+++ b/kvision-modules/kvision-handlebars/build.gradle
@@ -7,6 +7,7 @@ kotlinFrontend {
         dependency("handlebars-loader", "1.7.1")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-i18n/build.gradle b/kvision-modules/kvision-i18n/build.gradle
index 88147bbe..72870169 100644
--- a/kvision-modules/kvision-i18n/build.gradle
+++ b/kvision-modules/kvision-i18n/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
         dependency("jed", "1.1.1")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-redux/build.gradle b/kvision-modules/kvision-redux/build.gradle
index db0610bd..3394095f 100644
--- a/kvision-modules/kvision-redux/build.gradle
+++ b/kvision-modules/kvision-redux/build.gradle
@@ -14,6 +14,7 @@ kotlinFrontend {
         dependency("core-js", "3.0.0")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-richtext/build.gradle b/kvision-modules/kvision-richtext/build.gradle
index 86be8e7f..fde9ff17 100644
--- a/kvision-modules/kvision-richtext/build.gradle
+++ b/kvision-modules/kvision-richtext/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
         dependency("trix", "1.1.0")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-select/build.gradle b/kvision-modules/kvision-select/build.gradle
index 65b8a83c..7525f037 100644
--- a/kvision-modules/kvision-select/build.gradle
+++ b/kvision-modules/kvision-select/build.gradle
@@ -7,6 +7,7 @@ kotlinFrontend {
         dependency("ajax-bootstrap-select", "1.4.3")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-spinner/build.gradle b/kvision-modules/kvision-spinner/build.gradle
index e7600e5a..4569b8bc 100644
--- a/kvision-modules/kvision-spinner/build.gradle
+++ b/kvision-modules/kvision-spinner/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
         dependency("bootstrap-touchspin", "4.2.5")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-tabulator/build.gradle b/kvision-modules/kvision-tabulator/build.gradle
index d461e542..5ea97e50 100644
--- a/kvision-modules/kvision-tabulator/build.gradle
+++ b/kvision-modules/kvision-tabulator/build.gradle
@@ -11,6 +11,7 @@ kotlinFrontend {
         dependency("tabulator-tables", "4.2.5")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
diff --git a/kvision-modules/kvision-upload/build.gradle b/kvision-modules/kvision-upload/build.gradle
index 21ac7eab..2ac20c32 100644
--- a/kvision-modules/kvision-upload/build.gradle
+++ b/kvision-modules/kvision-upload/build.gradle
@@ -10,6 +10,7 @@ kotlinFrontend {
         dependency("bootstrap-fileinput", "4.5.2")
         devDependency("karma", "3.1.4")
         devDependency("karma-chrome-launcher", "2.2.0")
+        devDependency("karma-webpack", "3.0.5")
         devDependency("qunit", "2.8.0")
     }
 
-- 
cgit 


From 6b32017e39015f6c4179018dd7a78542c0d79b10 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Thu, 20 Jun 2019 13:32:42 +0200
Subject: Support for Cordova media api.

---
 .../kotlin/pl/treksoft/kvision/cordova/Media.kt    | 104 +++++++++++++++++++++
 1 file changed, 104 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt
new file mode 100644
index 00000000..6ea43d76
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Media error class.
+ */
+external class MediaError {
+    val code: Int
+    val message: String
+
+    companion object {
+        val MEDIA_ERR_ABORTED: Int
+        val MEDIA_ERR_NETWORK: Int
+        val MEDIA_ERR_DECODE: Int
+        val MEDIA_ERR_NONE_SUPPORTED: Int
+    }
+}
+
+/**
+ * Cordova media class.
+ */
+external class Media(
+    src: String,
+    mediaSuccess: (() -> Unit)? = definedExternally,
+    mediaError: ((MediaError) -> Unit)? = definedExternally,
+    mediaStatus: ((Int) -> Unit)? = definedExternally
+) {
+
+    fun play()
+    fun pause()
+    fun stop()
+    fun startRecord()
+    fun pauseRecord()
+    fun resumeRecord()
+    fun stopRecord()
+    fun seekTo(millis: Number)
+    fun setRate(rate: Number)
+    fun setVolume(volume: Number)
+    fun getDuration(): Number
+    fun getCurrentAmplitude(mediaSuccess: (Number) -> Unit, mediaError: ((Int) -> Unit)? = definedExternally)
+    fun getCurrentPosition(mediaSuccess: (Number) -> Unit, mediaError: ((Int) -> Unit)? = definedExternally)
+    fun release()
+
+    val position: Number
+    val duration: Number
+
+    companion object {
+        val MEDIA_NONE: Int
+        val MEDIA_STARTING: Int
+        val MEDIA_RUNNING: Int
+        val MEDIA_PAUSED: Int
+        val MEDIA_STOPPED: Int
+    }
+}
+
+/**
+ * Returns the current amplitude within an audio file.
+ */
+suspend fun Media.getCurrentAmplitude(): Number {
+    return suspendCoroutine { continuation ->
+        this.getCurrentAmplitude({ amp ->
+            continuation.resume(amp)
+        }, {
+            continuation.resume(-1)
+        })
+    }
+}
+
+/**
+ * Returns the current position within an audio file.
+ */
+suspend fun Media.getCurrentPosition(): Number {
+    return suspendCoroutine { continuation ->
+        this.getCurrentPosition({ pos ->
+            continuation.resume(pos)
+        }, {
+            continuation.resume(-1)
+        })
+    }
+}
-- 
cgit 


From 35b3961a8280db4d8974a9c547cbabc06f02e364 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Thu, 20 Jun 2019 22:20:03 +0200
Subject: Support for Cordova media capture api.

---
 .../pl/treksoft/kvision/cordova/MediaCapture.kt    | 196 +++++++++++++++++++++
 1 file changed, 196 insertions(+)
 create mode 100644 kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt
new file mode 100644
index 00000000..c7b427dc
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.cordova
+
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.document
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Media error class.
+ */
+internal external class CaptureError {
+    val code: Int
+
+    companion object {
+        val CAPTURE_INTERNAL_ERR: Int
+        val CAPTURE_APPLICATION_BUSY: Int
+        val CAPTURE_INVALID_ARGUMENT: Int
+        val CAPTURE_NO_MEDIA_FILES: Int
+        val CAPTURE_PERMISSION_DENIED: Int
+        val CAPTURE_NOT_SUPPORTED: Int
+    }
+}
+
+/**
+ * Media file details class.
+ */
+external class MediaFileData {
+    val codecs: String?
+    val bitrate: Number
+    val height: Number
+    val width: Number
+    val duration: Number
+}
+
+/**
+ * Media file information class.
+ */
+external class MediaFile {
+    val name: String
+    val fullPath: String
+    val type: String
+    val lastModifiedDate: Number
+    val size: Number
+
+    fun getFormatData(successCallback: (MediaFileData) -> Unit, errorCallback: (() -> Unit)? = definedExternally)
+}
+
+/**
+ * Get details for the given file.
+ */
+suspend fun MediaFile.getFormatData(): MediaFileData? {
+    return suspendCoroutine { continuation ->
+        this.getFormatData({
+            continuation.resume(it)
+        }, {
+            continuation.resume(null)
+        })
+    }
+}
+
+/**
+ * Exception class for media capture errors.
+ */
+class CaptureException(val code: MediaCapture.CaptureErrorCode) : Exception("Capture exception: $code")
+
+/**
+ * Main media capture object.
+ */
+object MediaCapture {
+
+    enum class CaptureErrorCode {
+        CAPTURE_INTERNAL_ERR,
+        CAPTURE_APPLICATION_BUSY,
+        CAPTURE_INVALID_ARGUMENT,
+        CAPTURE_NO_MEDIA_FILES,
+        CAPTURE_PERMISSION_DENIED,
+        CAPTURE_NOT_SUPPORTED
+    }
+
+    private fun codeToEnum(code: Int): CaptureErrorCode {
+        return when (code) {
+            0 -> CaptureErrorCode.CAPTURE_INTERNAL_ERR
+            1 -> CaptureErrorCode.CAPTURE_APPLICATION_BUSY
+            2 -> CaptureErrorCode.CAPTURE_INVALID_ARGUMENT
+            3 -> CaptureErrorCode.CAPTURE_NO_MEDIA_FILES
+            4 -> CaptureErrorCode.CAPTURE_PERMISSION_DENIED
+            else -> CaptureErrorCode.CAPTURE_NOT_SUPPORTED
+        }
+    }
+
+    /**
+     * Capture audio file(s).
+     * @param limit The maximum number of audio clips the user can capture in a single capture operation.
+     * @param duration The maximum duration of an audio clip, in seconds.
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun captureAudio(limit: Number = 1, duration: Number? = null): Result<List<MediaFile>, CaptureException> {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                window.navigator.asDynamic().device.capture.captureAudio({ result: Array<MediaFile> ->
+                    continuation.resume(Result.success(result.asList()))
+                }, { err: CaptureError ->
+                    continuation.resume(Result.error(CaptureException(codeToEnum(err.code))))
+                }, obj {
+                    this.limit = limit
+                    if (duration != null) this.duration = duration
+                })
+            }
+        }
+    }
+
+    /**
+     * Capture image file(s).
+     * @param limit The maximum number of image files the user can capture in a single capture operation.
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun captureImage(limit: Number = 1): Result<List<MediaFile>, CaptureException> {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                window.navigator.asDynamic().device.capture.captureImage({ result: Array<MediaFile> ->
+                    continuation.resume(Result.success(result.asList()))
+                }, { err: CaptureError ->
+                    continuation.resume(Result.error(CaptureException(codeToEnum(err.code))))
+                }, obj {
+                    this.limit = limit
+                })
+            }
+        }
+    }
+
+    /**
+     * Capture video file(s).
+     * @param limit The maximum number of video clips the user can capture in a single capture operation.
+     * @param duration The maximum duration of an video clip, in seconds.
+     * @param lowQuality Capture video with a lower quality (Android only)
+     */
+    @Suppress("UnsafeCastFromDynamic")
+    suspend fun captureVideo(
+        limit: Number = 1,
+        duration: Number? = null,
+        lowQuality: Boolean = false
+    ): Result<List<MediaFile>, CaptureException> {
+        return suspendCoroutine { continuation ->
+            addDeviceReadyListener {
+                window.navigator.asDynamic().device.capture.captureVideo({ result: Array<MediaFile> ->
+                    continuation.resume(Result.success(result.asList()))
+                }, { err: CaptureError ->
+                    continuation.resume(Result.error(CaptureException(codeToEnum(err.code))))
+                }, obj {
+                    this.limit = limit
+                    if (duration != null) this.duration = duration
+                    if (lowQuality) this.quality = 0
+                })
+            }
+        }
+    }
+
+    /**
+     * Add listeners for pending capture Cordova events.
+     */
+    fun addPendingCaptureListener(listener: (Result<List<MediaFile>, CaptureException>) -> Unit) {
+        addDeviceReadyListener {
+            document.addEventListener("pendingcaptureresult", { result ->
+                @Suppress("CAST_NEVER_SUCCEEDS")
+                listener(Result.success((result as? Array<MediaFile>)?.asList() ?: listOf()))
+            }, false)
+            document.addEventListener("pendingcaptureerror", { error ->
+                @Suppress("CAST_NEVER_SUCCEEDS")
+                listener(Result.error(CaptureException(codeToEnum((error as? CaptureError)?.code ?: 0))))
+            }, false)
+        }
+    }
+}
-- 
cgit 


From 5082d9ec1507ebeca74b30e4f46a034ed688b46d Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Thu, 20 Jun 2019 22:22:19 +0200
Subject: Change result builder.

---
 .../src/main/kotlin/pl/treksoft/kvision/cordova/File.kt           | 4 ++--
 .../src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt    | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt
index df176e84..6e7ba732 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt
@@ -106,7 +106,7 @@ object File {
         return suspendCoroutine { continuation ->
             addDeviceReadyListener {
                 window.asDynamic().resolveLocalFileSystemURL(url, { entry ->
-                    continuation.resume(Result.Success(entry))
+                    continuation.resume(Result.success(entry))
                 }, { error ->
                     continuation.resume(Result.error(FileException(error.code)))
                 })
@@ -173,7 +173,7 @@ object File {
             }
             addDeviceReadyListener {
                 window.asDynamic().requestFileSystem(type, size, { fs ->
-                    continuation.resume(Result.Success(fs))
+                    continuation.resume(Result.success(fs))
                 }, { error ->
                     continuation.resume(Result.error(FileException(error.code)))
                 })
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
index 9f4d9cfe..4087a4f2 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
@@ -90,7 +90,7 @@ object Geolocation {
         return suspendCoroutine { continuation ->
             addDeviceReadyListener {
                 window.navigator.asDynamic().geolocation.getCurrentPosition({ position: Position ->
-                    continuation.resume(Result.Success(position))
+                    continuation.resume(Result.success(position))
                 }, { error ->
                     continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
                 }, obj {
@@ -120,7 +120,7 @@ object Geolocation {
         return suspendCoroutine { continuation ->
             addDeviceReadyListener {
                 val watchId = window.navigator.asDynamic().geolocation.watchPosition({ position: Position ->
-                    resultCallback(Result.Success(position))
+                    resultCallback(Result.success(position))
                 }, { error ->
                     resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
                 }, obj {
@@ -183,7 +183,7 @@ object Locationservices {
             addDeviceReadyListener {
                 window.asDynamic()
                     .cordova.plugins.locationServices.geolocation.getCurrentPosition({ position: Position ->
-                    continuation.resume(Result.Success(position))
+                    continuation.resume(Result.success(position))
                 }, { error ->
                     continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
                 }, obj {
@@ -224,7 +224,7 @@ object Locationservices {
                 val watchId =
                     window.asDynamic()
                         .cordova.plugins.locationServices.geolocation.watchPosition({ position: Position ->
-                        resultCallback(Result.Success(position))
+                        resultCallback(Result.success(position))
                     }, { error ->
                         resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
                     }, obj {
-- 
cgit 


From 0fe56689dbc57777b29881ff9439fb543f79b9ec Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Thu, 20 Jun 2019 22:28:40 +0200
Subject: Missing TAG.HR in enum.

---
 src/main/kotlin/pl/treksoft/kvision/html/Tag.kt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
index 81d6848b..68e43da0 100644
--- a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
@@ -69,6 +69,7 @@ enum class TAG(internal val tagName: String) {
     SAMP("samp"),
     SPAN("span"),
     LI("li"),
+    HR("hr"),
 
     CAPTION("caption"),
     THEAD("thead"),
-- 
cgit 


From d9f1a90c772719d14540eb2bf7bc3b8384fa7a72 Mon Sep 17 00:00:00 2001
From: Robert Jaros <rjaros@finn.pl>
Date: Thu, 20 Jun 2019 22:33:54 +0200
Subject: Suppress some style warnings.

---
 .../src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt           | 1 +
 .../kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt | 1 +
 .../src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt          | 1 +
 3 files changed, 3 insertions(+)

diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
index 4087a4f2..c4ccd354 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
@@ -251,6 +251,7 @@ object Locationservices {
         }
     }
 
+    @Suppress("MagicNumber")
     private fun getJsPriority(priority: Priority): dynamic {
         return when (priority) {
             Priority.PRIORITY_HIGH_ACCURACY -> 100
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt
index 6ea43d76..a126941b 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt
@@ -43,6 +43,7 @@ external class MediaError {
 /**
  * Cordova media class.
  */
+@Suppress("TooManyFunctions")
 external class Media(
     src: String,
     mediaSuccess: (() -> Unit)? = definedExternally,
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt
index c7b427dc..fda81da7 100644
--- a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt
@@ -100,6 +100,7 @@ object MediaCapture {
         CAPTURE_NOT_SUPPORTED
     }
 
+    @Suppress("MagicNumber")
     private fun codeToEnum(code: Int): CaptureErrorCode {
         return when (code) {
             0 -> CaptureErrorCode.CAPTURE_INTERNAL_ERR
-- 
cgit