David Herman
12/22/2022, 12:50 AMDavid Herman
12/22/2022, 12:51 AM<body>
<div id="root">...</div>
<div id="modals">...</div>
</body>
and my code might look like this:
@Composable
fun SomeWidget() {
var showModal by remember { mutableStateOf(false) }
...
Button(onClick = { showModal = true }
if (showModal) {
Modal { ... }
}
}
David Herman
12/22/2022, 12:52 AMModal
widget (which I'm going to write) to basically not really be under the SomeWidget
element in the tree, but rather moved into the modals
div on creation.David Herman
12/22/2022, 12:53 AMDavid Herman
12/22/2022, 12:54 AMonDispose
event.Zach Klippenstein (he/him) [MOD]
12/22/2022, 12:56 AMDavid Herman
12/22/2022, 12:56 AMDavid Herman
12/22/2022, 12:56 AMsetContent
, I'll take a lookZach Klippenstein (he/him) [MOD]
12/22/2022, 12:57 AMDavid Herman
12/22/2022, 1:00 AMref { element ->
val modals = document.getElementById("modals")!!
modals.append(element)
onDispose {
modals.removeChild(element)
}
}
and the callstack I'm getting when the modal goes away is:
"NullPointerException
at THROW_NPE (<http://localhost:8080/app.js:25496:11>)
at ensureNotNull (<http://localhost:8080/app.js:25489:7>)
at DomNodeWrapper.remove_fwj0yb_k$ (<http://localhost:8080/app.js:72921:25>)
at DomApplier.remove_fwj0yb_k$ (<http://localhost:8080/app.js:72974:34>)
at <http://localhost:8080/app.js:45642:15>
at applyChangesInLocked (<http://localhost:8080/app.js:48261:17>)
at CompositionImpl.applyChanges_yo70f0_k$ (<http://localhost:8080/app.js:49202:5>)
at <http://localhost:8080/app.js:51058:26>
at <http://localhost:8080/app.js:72227:20>"
David Herman
12/22/2022, 1:02 AMDavid Herman
12/22/2022, 1:03 AMremoveChild
in the onDispose
call.)Zach Klippenstein (he/him) [MOD]
12/22/2022, 1:03 AMDavid Herman
12/22/2022, 1:04 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:06 AMmodals
element in compose somehow, ie a modal host, you could hoist a snapshot state object that holds the list of current modals, add to it in the body of Modal
, and then in your modal host thing read that and compose based on the state.David Herman
12/22/2022, 1:07 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:07 AMDavid Herman
12/22/2022, 1:07 AMDavid Herman
12/22/2022, 1:08 AMsetContent
doesDavid Herman
12/22/2022, 1:09 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:10 AMDavid Herman
12/22/2022, 1:11 AMrenderComposable(rootElementId = "root") {
...
}
I believe this is the equivalent of Jetpack Compose's setContent
entry point, now that I'm looking at and recalling itDavid Herman
12/22/2022, 1:11 AMDavid Herman
12/22/2022, 1:12 AMDavid Herman
12/22/2022, 1:36 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:39 AMprivate val LocalModalState = staticCompositionLocalOf<ModalHostState?> { null }
private class ModalHostState {
private val modals = mutableStateListOf<@Composable () -> Unit>()
fun createModal(): Modal = Modal().also{
modals += it
}
inline fun forEachModal(block: (Modal) -> Unit) {
modals.forEach(block)
}
inner class Modal {
var content by mutableStateOf<(@Composable () -> Unit)?>(null)
fun dismiss() { modals -= this }
}
}
@Composable internal fun ModalHost(content: @Composable () -> Unit) {
val state = remember { ModalHostState() }
CompositionLocalProvider(LocalModalState provides state, content = content)
state.forEachModal { modal ->
modal.content?.let { content ->
YourModalDecorationBox {
content()
}
}
}
}
@Composable fun Modal(content: @Composable () -> Unit) {
val state = checkNotNull(LocalModalState.current) {
âCanât show modal outside of a ModalHostâ
}
val modal = remember(state) { state.createModal() }
modal.content = content
DisposableEffect(modal) {
onDispose {
modal.dismiss()
}
}
}
Zach Klippenstein (he/him) [MOD]
12/22/2022, 1:39 AMDavid Herman
12/22/2022, 1:39 AMDavid Herman
12/22/2022, 1:41 AMDavid Herman
12/22/2022, 1:42 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:42 AMDavid Herman
12/22/2022, 1:42 AMDavid Herman
12/22/2022, 1:42 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:47 AMDavid Herman
12/22/2022, 1:48 AMDavid Herman
12/22/2022, 1:48 AMsetContent
David Herman
12/22/2022, 1:50 AMfun createModal(): Modal = Modal().also{
modals += it
}
Modal is a function not a type. Is that supposed to be ModalHostState or something?Zach Klippenstein (he/him) [MOD]
12/22/2022, 1:50 AMDavid Herman
12/22/2022, 1:50 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:50 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:51 AMDavid Herman
12/22/2022, 1:51 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:51 AMModalHost
near the root of your composition, around whatever âbodyâ/non-modal content you haveDavid Herman
12/22/2022, 1:51 AMsetContent {
ModalHost {
OtherStuff()
}
}
?Zach Klippenstein (he/him) [MOD]
12/22/2022, 1:52 AMDavid Herman
12/22/2022, 1:52 AMDavid Herman
12/22/2022, 1:52 AMDavid Herman
12/22/2022, 1:52 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:52 AMModalHost
inside a Modal
đZach Klippenstein (he/him) [MOD]
12/22/2022, 1:53 AMDavid Herman
12/22/2022, 1:53 AMDavid Herman
12/22/2022, 1:53 AMDavid Herman
12/22/2022, 1:53 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 1:55 AMDavid Herman
12/22/2022, 1:57 AMDavid Herman
12/22/2022, 2:14 AMDavid Herman
12/22/2022, 2:14 AMDavid Herman
12/22/2022, 2:15 AMDavid Herman
12/22/2022, 2:37 AMZach Klippenstein (he/him) [MOD]
12/22/2022, 3:30 AMBox
.David Herman
12/22/2022, 4:20 AMDavid Herman
12/22/2022, 4:22 AMBox
in my library using a web grid but here it can also easily be done with a simple div
with something called an absolute position.David Herman
12/22/2022, 7:28 PMdeferRender
method, where it gets used like so (in one example): https://github.com/varabyte/kobweb/blob/d3dacc9e0aeefadeb6cb139f3fe00e540cb1e454/f[âŠ]ain/kotlin/com/varabyte/kobweb/silk/components/overlay/Modal.kt
fun Modal(modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit) {
deferRender {
Box(ModalBackdropStyle.toModifier(), contentAlignment = Alignment.TopCenter) {
Box(ModalStyle.toModifier().then(modifier)) {
content()
}
}
}
}
Seems to be working so far, and I'm really happy with the approach. Knock on wood!