From 46a897e629c94bcdb1cb5245d7930d2e94e88006 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 11 Jan 2023 21:55:47 +0600 Subject: [PATCH] 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 )