diff --git a/CHANGELOG.md b/CHANGELOG.md index 7139c03..f899ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.5.0 + +* Fully rework `Dialog` elements + ## 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 diff --git a/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt b/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt index d8f574f..0558762 100644 --- a/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt +++ b/src/jsMain/kotlin/dev/inmo/jsuikit/elements/Dialog.kt @@ -1,33 +1,24 @@ package dev.inmo.jsuikit.elements import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffectResult +import androidx.compose.runtime.DisposableEffect +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 +28,104 @@ fun Dialog( footerBuilder: ContentBuilder? = null, bodyAttrsBuilder: AttrBuilderContext? = null, autoShow: Boolean = true, - bodyBuilder: ContentBuilder + removeOnHide: Boolean = true, + bodyBuilder: ContentBuilder = {} ) { - Div( - { - if (modifiers.none { it is UIKitModal.WithCustomAttributes }) { - include(UIKitModal) - } - id("dialog${Random.nextUInt()}") - include(*modifiers) - 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() } + val drawDiv = remember { mutableStateOf(true) } + val composition = remember { + renderComposableInBody { Div( { - include(UIKitModal.Body) - bodyAttrsBuilder ?.let { it() } + if (modifiers.none { it is UIKitModal.WithCustomAttributes }) { + include(UIKitModal) + } + id("dialog${Random.nextUInt()}") + include(*modifiers) + + attributesCustomizer() } ) { - bodyBuilder() - } - beforeFooterBuilder ?.let { it() } - footerBuilder ?.let { + DisposableEffect(true) { + val htmlElement = scopeElement + + if (autoShow) { + 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 { + drawDiv.value = false + } + } + Div( { - include(UIKitModal.Footer) - footerAttrsBuilder ?.let { it() } ?: include(UIKitText.Alignment.Horizontal.Right) + include(UIKitModal.Dialog) + dialogAttrsBuilder ?.let { it() } ?: include(UIKitMargin.Auto.Vertical) } ) { - it() + headerBuilder ?.let { + Div( + { + include(UIKitModal.Header) + headerAttrsBuilder ?.let { it() } + } + ) { + it() + } + } + afterHeaderBuilder ?.let { it() } + Div( + { + include(UIKitModal.Body) + bodyAttrsBuilder ?.let { 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 +133,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 = attributesCustomizer, + onHidden = onHidden, + onShown = onShown, + dialogAttrsBuilder = dialogAttrsBuilder, + headerAttrsBuilder = headerAttrsBuilder, headerBuilder = { H2({ include(UIKitModal.Title) }) { Text(title) } + headerBuilder ?.invoke(this) }, - onHide = hide, - onHidden = hidden, - footerBuilder = footerBuilder ?.let { _ -> - { - footerBuilder() - } - }, - attributesCustomizer = { - attributesCustomizer() - }, + afterHeaderBuilder = afterHeaderBuilder, + beforeFooterBuilder = beforeFooterBuilder, + footerAttrsBuilder = footerAttrsBuilder, + footerBuilder = footerBuilder, + bodyAttrsBuilder = bodyAttrsBuilder, autoShow = autoShow, - bodyBuilder = { - bodyBuilder() - } + 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/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() +}