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 AMsetContentDavid 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!