From 0e1be722193537ca6e8072097886ffc6046bf292 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 11 Jan 2023 21:35:30 +0600 Subject: [PATCH 1/5] start 0.5.0 --- CHANGELOG.md | 2 ++ gradle.properties | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7139c03..fb013d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.5.0 + ## 0.4.3 * Add opportunity to now show dialog automatically diff --git a/gradle.properties b/gradle.properties index 2d59a31..9b1c18e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,4 @@ android.enableJetifier=true # Project data group=dev.inmo -version=0.4.3 +version=0.5.0 From 46a897e629c94bcdb1cb5245d7930d2e94e88006 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 11 Jan 2023 21:55:47 +0600 Subject: [PATCH 2/5] total rework of dialogs --- CHANGELOG.md | 2 + .../dev/inmo/jsuikit/elements/Dialog.kt | 200 +++++++++++------- 2 files changed, 121 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb013d8..f899ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 0.5.0 +* Fully rework `Dialog` elements + ## 0.4.3 * Add opportunity to now show dialog automatically diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt index d8f574f..72058ab 100644 --- a/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt +++ b/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt @@ -1,33 +1,30 @@ package dev.inmo.jsuikit.elements import androidx.compose.runtime.Composable +import androidx.compose.runtime.Composition +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffectResult +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.currentRecomposeScope +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import dev.inmo.jsuikit.modifiers.* import org.jetbrains.compose.web.dom.* +import org.jetbrains.compose.web.renderComposableInBody import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLElement -import org.w3c.dom.events.Event +import org.w3c.dom.MutationObserver +import org.w3c.dom.MutationObserverInit import kotlin.random.Random import kotlin.random.nextUInt -private class DialogDisposableEffectResult( - private val element: HTMLElement, - private val onDispose: (() -> Unit)?, - private val onDisposed: (() -> Unit)? -) : DisposableEffectResult { - override fun dispose() { - onDispose?.invoke() - UIKit.modal("#${element.id}") ?.hide() - onDisposed?.invoke() - } -} - @Composable fun Dialog( vararg modifiers: UIKitModifier, attributesCustomizer: AttrBuilderContext = {}, - onHide: (() -> Unit)? = null, - onHidden: (() -> Unit)? = null, + onHidden: ((HTMLDivElement) -> Unit)? = null, + onShown: ((HTMLDivElement) -> Unit)? = null, dialogAttrsBuilder: AttrBuilderContext? = null, headerAttrsBuilder: AttrBuilderContext? = null, headerBuilder: ContentBuilder? = null, @@ -37,73 +34,106 @@ fun Dialog( footerBuilder: ContentBuilder? = null, bodyAttrsBuilder: AttrBuilderContext? = null, autoShow: Boolean = true, + removeOnHide: Boolean = true, bodyBuilder: ContentBuilder ) { - Div( - { - if (modifiers.none { it is UIKitModal.WithCustomAttributes }) { - include(UIKitModal) - } - id("dialog${Random.nextUInt()}") - include(*modifiers) - attributesCustomizer() - } - ) { + val drawDiv = remember { mutableStateOf(true) } + val composition = renderComposableInBody { Div( { - include(UIKitModal.Dialog) - dialogAttrsBuilder ?.let { it() } ?: include(UIKitMargin.Auto.Vertical) + if (modifiers.none { it is UIKitModal.WithCustomAttributes }) { + include(UIKitModal) + } + id("dialog${Random.nextUInt()}") + include(*modifiers) + + ref { htmlElement -> + inline fun isShown() = htmlElement.classList.contains(UIKitUtility.Open.classes.first()) + + var latestStateIsShown = isShown() + + val observer = MutationObserver { _, _ -> + val currentStateIsShown = isShown() + + when (currentStateIsShown) { + latestStateIsShown -> return@MutationObserver + true -> onShown ?.invoke(htmlElement) + false -> onHidden ?.invoke(htmlElement) + } + + latestStateIsShown = currentStateIsShown + + if (removeOnHide && !currentStateIsShown) { + htmlElement.remove() + } + } + + observer.observe(htmlElement, MutationObserverInit(attributes = true, attributeFilter = arrayOf("class"))) + + if (autoShow) { + UIKit.modal("#${htmlElement.id}") ?.show() + } + + onDispose { + observer.disconnect() + drawDiv.value = false + } + } + + attributesCustomizer() } ) { - headerBuilder ?.let { - Div( - { - include(UIKitModal.Header) - headerAttrsBuilder ?.let { it() } - } - ) { - it() - } - } - afterHeaderBuilder ?.let { it() } Div( { - include(UIKitModal.Body) - bodyAttrsBuilder ?.let { it() } + include(UIKitModal.Dialog) + dialogAttrsBuilder ?.let { it() } ?: include(UIKitMargin.Auto.Vertical) } ) { - bodyBuilder() - } - beforeFooterBuilder ?.let { it() } - footerBuilder ?.let { + headerBuilder ?.let { + Div( + { + include(UIKitModal.Header) + headerAttrsBuilder ?.let { it() } + } + ) { + it() + } + } + afterHeaderBuilder ?.let { it() } Div( { - include(UIKitModal.Footer) - footerAttrsBuilder ?.let { it() } ?: include(UIKitText.Alignment.Horizontal.Right) + include(UIKitModal.Body) + bodyAttrsBuilder ?.let { it() } } ) { - it() + bodyBuilder() + } + beforeFooterBuilder ?.let { it() } + footerBuilder ?.let { + Div( + { + include(UIKitModal.Footer) + footerAttrsBuilder ?.let { it() } ?: include(UIKitText.Alignment.Horizontal.Right) + } + ) { + it() + } } } } + } - DisposableRefEffect { - DialogDisposableEffectResult(it, onHide, onHidden) - } - - DomSideEffect { htmlElement -> - var wrapper: (Event) -> Unit = {} - wrapper = { it: Event -> - htmlElement.removeEventListener("hidden", wrapper) - htmlElement.remove() - onHidden ?.invoke() + if (drawDiv.value) { + Div({ + hidden() + ref { + onDispose { + composition.dispose() + } } - htmlElement.addEventListener("hidden", wrapper) - - if (autoShow) { - UIKit.modal("#${htmlElement.id}") ?.show() - } - } + }) + } else { + runCatching { composition.dispose() } } } @@ -111,31 +141,39 @@ fun Dialog( fun Dialog( title: String, vararg modifiers: UIKitModifier, - hide: (() -> Unit)? = null, - hidden: (() -> Unit)? = null, - footerBuilder: (@Composable () -> Unit)? = null, attributesCustomizer: AttrBuilderContext = {}, + onHidden: ((HTMLDivElement) -> Unit)? = null, + onShown: ((HTMLDivElement) -> Unit)? = null, + dialogAttrsBuilder: AttrBuilderContext? = null, + headerAttrsBuilder: AttrBuilderContext? = null, + headerBuilder: ContentBuilder? = null, + afterHeaderBuilder: ContentBuilder? = null, + beforeFooterBuilder: ContentBuilder? = null, + footerAttrsBuilder: AttrBuilderContext? = null, + footerBuilder: ContentBuilder? = null, + bodyAttrsBuilder: AttrBuilderContext? = null, autoShow: Boolean = true, - bodyBuilder: @Composable () -> Unit + removeOnHide: Boolean = true, + bodyBuilder: ContentBuilder ) = Dialog( modifiers = modifiers, + attributesCustomizer, + onHidden, + onShown, + dialogAttrsBuilder, + headerAttrsBuilder, headerBuilder = { H2({ include(UIKitModal.Title) }) { Text(title) } + headerBuilder ?.invoke(this) }, - onHide = hide, - onHidden = hidden, - footerBuilder = footerBuilder ?.let { _ -> - { - footerBuilder() - } - }, - attributesCustomizer = { - attributesCustomizer() - }, - autoShow = autoShow, - bodyBuilder = { - bodyBuilder() - } + afterHeaderBuilder, + beforeFooterBuilder, + footerAttrsBuilder, + footerBuilder, + bodyAttrsBuilder, + autoShow, + removeOnHide, + bodyBuilder ) From 0b8017a1647fb3ed486614532e5e9bb2d2582177 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 11 Jan 2023 21:57:43 +0600 Subject: [PATCH 3/5] small improvement in dialogs --- src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt index 72058ab..4a01603 100644 --- a/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt +++ b/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt @@ -1,19 +1,12 @@ package dev.inmo.jsuikit.elements import androidx.compose.runtime.Composable -import androidx.compose.runtime.Composition -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.DisposableEffectResult -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.runtime.currentRecomposeScope import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import dev.inmo.jsuikit.modifiers.* import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.renderComposableInBody import org.w3c.dom.HTMLDivElement -import org.w3c.dom.HTMLElement import org.w3c.dom.MutationObserver import org.w3c.dom.MutationObserverInit import kotlin.random.Random @@ -35,7 +28,7 @@ fun Dialog( bodyAttrsBuilder: AttrBuilderContext? = null, autoShow: Boolean = true, removeOnHide: Boolean = true, - bodyBuilder: ContentBuilder + bodyBuilder: ContentBuilder = {} ) { val drawDiv = remember { mutableStateOf(true) } val composition = renderComposableInBody { @@ -154,7 +147,7 @@ fun Dialog( bodyAttrsBuilder: AttrBuilderContext? = null, autoShow: Boolean = true, removeOnHide: Boolean = true, - bodyBuilder: ContentBuilder + bodyBuilder: ContentBuilder = {} ) = Dialog( modifiers = modifiers, attributesCustomizer, From 71ccc74c943f4b64a36e0d194eba1d5dcb30b588 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 12 Jan 2023 14:31:38 +0600 Subject: [PATCH 4/5] fixes --- .../dev/inmo/jsuikit/elements/Dialog.kt | 145 +++++++++--------- .../kotlin/dev/inmo/jsuikit/types/UIKit.kt | 1 + .../dev/inmo/jsuikit/types/UIKitUtil.kt | 8 + .../inmo/jsuikit/types/UIKitUtilConstants.kt | 4 + .../utils/AttributeBuilderContextSums.kt | 9 ++ 5 files changed, 94 insertions(+), 73 deletions(-) create mode 100644 src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtil.kt create mode 100644 src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt create mode 100644 src/jsMain/kotlin/dev/inmo/jsuikit/utils/AttributeBuilderContextSums.kt diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt index 4a01603..0558762 100644 --- a/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt +++ b/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt @@ -1,6 +1,7 @@ package dev.inmo.jsuikit.elements import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import dev.inmo.jsuikit.modifiers.* @@ -31,85 +32,83 @@ fun Dialog( bodyBuilder: ContentBuilder = {} ) { val drawDiv = remember { mutableStateOf(true) } - val composition = renderComposableInBody { - Div( - { - if (modifiers.none { it is UIKitModal.WithCustomAttributes }) { - include(UIKitModal) - } - id("dialog${Random.nextUInt()}") - include(*modifiers) - - ref { htmlElement -> - inline fun isShown() = htmlElement.classList.contains(UIKitUtility.Open.classes.first()) - - var latestStateIsShown = isShown() - - val observer = MutationObserver { _, _ -> - val currentStateIsShown = isShown() - - when (currentStateIsShown) { - latestStateIsShown -> return@MutationObserver - true -> onShown ?.invoke(htmlElement) - false -> onHidden ?.invoke(htmlElement) - } - - latestStateIsShown = currentStateIsShown - - if (removeOnHide && !currentStateIsShown) { - htmlElement.remove() - } + val composition = remember { + renderComposableInBody { + Div( + { + if (modifiers.none { it is UIKitModal.WithCustomAttributes }) { + include(UIKitModal) } + id("dialog${Random.nextUInt()}") + include(*modifiers) - observer.observe(htmlElement, MutationObserverInit(attributes = true, attributeFilter = arrayOf("class"))) + attributesCustomizer() + } + ) { + DisposableEffect(true) { + val htmlElement = scopeElement if (autoShow) { - UIKit.modal("#${htmlElement.id}") ?.show() + UIKit.modal(htmlElement).show() } + if (onHidden != null || removeOnHide) { + htmlElement.addEventListener("hidden", { + onHidden ?.invoke(htmlElement) + + if (removeOnHide) { + htmlElement.remove() + } + }) + } + + onShown ?.let { + htmlElement.addEventListener("shown", { + onShown(htmlElement) + }) + } + + onDispose { - observer.disconnect() drawDiv.value = false } } - attributesCustomizer() - } - ) { - Div( - { - include(UIKitModal.Dialog) - dialogAttrsBuilder ?.let { it() } ?: include(UIKitMargin.Auto.Vertical) - } - ) { - headerBuilder ?.let { - Div( - { - include(UIKitModal.Header) - headerAttrsBuilder ?.let { it() } - } - ) { - it() - } - } - afterHeaderBuilder ?.let { it() } Div( { - include(UIKitModal.Body) - bodyAttrsBuilder ?.let { it() } + include(UIKitModal.Dialog) + dialogAttrsBuilder ?.let { it() } ?: include(UIKitMargin.Auto.Vertical) } ) { - bodyBuilder() - } - beforeFooterBuilder ?.let { it() } - footerBuilder ?.let { + headerBuilder ?.let { + Div( + { + include(UIKitModal.Header) + headerAttrsBuilder ?.let { it() } + } + ) { + it() + } + } + afterHeaderBuilder ?.let { it() } Div( { - include(UIKitModal.Footer) - footerAttrsBuilder ?.let { it() } ?: include(UIKitText.Alignment.Horizontal.Right) + include(UIKitModal.Body) + bodyAttrsBuilder ?.let { it() } } ) { - it() + bodyBuilder() + } + beforeFooterBuilder ?.let { it() } + footerBuilder ?.let { + Div( + { + include(UIKitModal.Footer) + footerAttrsBuilder ?.let { it() } ?: include(UIKitText.Alignment.Horizontal.Right) + } + ) { + it() + } } } } @@ -150,23 +149,23 @@ fun Dialog( bodyBuilder: ContentBuilder = {} ) = Dialog( modifiers = modifiers, - attributesCustomizer, - onHidden, - onShown, - dialogAttrsBuilder, - headerAttrsBuilder, + attributesCustomizer = attributesCustomizer, + onHidden = onHidden, + onShown = onShown, + dialogAttrsBuilder = dialogAttrsBuilder, + headerAttrsBuilder = headerAttrsBuilder, headerBuilder = { H2({ include(UIKitModal.Title) }) { Text(title) } headerBuilder ?.invoke(this) }, - afterHeaderBuilder, - beforeFooterBuilder, - footerAttrsBuilder, - footerBuilder, - bodyAttrsBuilder, - autoShow, - removeOnHide, - bodyBuilder + afterHeaderBuilder = afterHeaderBuilder, + beforeFooterBuilder = beforeFooterBuilder, + footerAttrsBuilder = footerAttrsBuilder, + footerBuilder = footerBuilder, + bodyAttrsBuilder = bodyAttrsBuilder, + autoShow = autoShow, + removeOnHide = removeOnHide, + bodyBuilder = bodyBuilder ) diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKit.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKit.kt index 8b5b718..155d9d4 100644 --- a/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKit.kt +++ b/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKit.kt @@ -6,6 +6,7 @@ import kotlin.js.Json external interface UIKit { val notification: UIKitNotifications val modal: UIKitDialogs + val util: UIKitUtil fun notification(message: String, parameters: Json) diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtil.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtil.kt new file mode 100644 index 0000000..cca85bc --- /dev/null +++ b/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtil.kt @@ -0,0 +1,8 @@ +package dev.inmo.jsuikit.types + +import org.w3c.dom.HTMLElement + +external interface UIKitUtil { + fun on(selector: String, event: String, callback: () -> Unit) + fun on(element: HTMLElement, event: String, callback: () -> Unit) +} diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt new file mode 100644 index 0000000..17f5780 --- /dev/null +++ b/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt @@ -0,0 +1,4 @@ +package dev.inmo.jsuikit.types + +object UIKitUtilConstants { +} diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/utils/AttributeBuilderContextSums.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/utils/AttributeBuilderContextSums.kt new file mode 100644 index 0000000..7c3d25e --- /dev/null +++ b/src/jsMain/kotlin/dev/inmo/jsuikit/utils/AttributeBuilderContextSums.kt @@ -0,0 +1,9 @@ +package dev.inmo.jsuikit.utils + +import org.jetbrains.compose.web.dom.AttrBuilderContext +import org.w3c.dom.Element + +operator fun AttrBuilderContext.plus(other: AttrBuilderContext): AttrBuilderContext = { + this@plus() + other() +} From 402af2445f28d46b4d2efb20bd3d535aed7d7a75 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 12 Jan 2023 14:50:23 +0600 Subject: [PATCH 5/5] temporarily remove UIKitConstants --- .../kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt deleted file mode 100644 index 17f5780..0000000 --- a/src/jsMain/kotlin/dev/inmo/jsuikit/types/UIKitUtilConstants.kt +++ /dev/null @@ -1,4 +0,0 @@ -package dev.inmo.jsuikit.types - -object UIKitUtilConstants { -}