Hey, is there some easy way how to hide Popover wh...
# kobweb
f
Hey, is there some easy way how to hide Popover when user clicks outside of it's bounds? I am trying to replicate shadcn/ui Popover component. I am playing with
AdvancedPopover
but I doesn't seem to support it out of the box. Am I missing some strategy?
Maybe It could somehow be done through the focus? 🤔
s
What you can do is add a click event listener to document which closes the popup with a manual strategy. and you might need another click event on the popup itself just to stop propagation
f
Oh right, click events propagate, right? So click on anything eventually propagates to document think smart
s
unless e.g. a button also has stopPropagation but in this case the focus shift should close the popup (I think)
d
Do this: •
git clone <https://github.com/varabyte/kobweb>
kobweb run playground/site
• Go to the WIDGETS tab
You'll see we have a bunch of popups in there. One of them closes when you click out of bounds.
I believe it's the KeepPopupOpen strategy or something you can use for that
(Eventually we'll create docs for our widgets; popovers will be extremely useful to document. They're powerful but can be a bit finnicky. Still, I promise it is easier to use them than to write it yourself! That was an exhausting component to write.)
f
Nope, there does not seem to be anything similar in the playground 🤔 But I used the document listener method like this:
Copy code
DisposableEffect(Unit) {
    val callback: (Event) -> Unit = { popoverStrategy.isOpen = false }
    document.addEventListener("click", callback)
    onDispose { document.removeEventListener("click", callback) }
}
👍🏻 1
d
Well I was assuming you could use the "Click to close tooltip" as an example (which uses a manual "keep popup open" strategy). https://github.com/varabyte/kobweb/blob/324d614776b65626a6c88f521cd3170ede9c0b3c/playground/site/src/jsMain/kotlin/playground/pages/Widgets.kt#L534
f
Yeah, I am using that. But that alone does not get me the behavior I wanted
d
OK!
s
yes, just that you add this logic in the onclick listener of the document
d
I mean with manual strategies you can get any behavior you want
👍🏻 1
f
True true, I was just asking if there is something like that out of the box. Seems to me like it's common pattern
d
Are you making something like a modal?
f
I am doing pretty much exactly this 😄
Or something like Android context menu
Btw I spent way too much time stuck on popover that I wasn't able to close using the manual
openCloseStrategy
. Only after a while I figured out I also need to override
keepOpenStrategy
to be
never()
. I thought that something named "open close" should be all I need to control opening and closing. So take it as API feedback but maybe I am just dumb 😄
d
I think the API just needs to be documented more carefully, with examples. It used to be a single strategy but definitely splitting them into two was necessary for reasons I can't recall right now. I'm surprised
keepOpenStrategy
with
never
worked though. It's been a while since I've looked at that code.
👌 1
s
one thing that sometimes catches me offguard is that OpenClose doesn't auto reset once it's closed. so you need something like this
Copy code
ref = disposableRef {
            onDispose {
                openCloseStrategy.isOpen = false
            }
        }
thank you color 1
f
This is what I ended up with:
Copy code
@Composable
fun DismissablePopover(
    isVisible: Boolean,
    onDismiss: () -> Unit,
    target: ElementTarget = ElementTarget.PreviousSibling,
    placementStrategy: PopupPlacementStrategy = PopupPlacementStrategy.of(PopupPlacement.Bottom),
    content: @Composable PopupScope.() -> Unit,
) {
    val popoverStrategy = remember { OpenClosePopupStrategy.manual() }
    SideEffect { popoverStrategy.isOpen = isVisible }

    DisposableEffect(Unit) {
        val callback: (Event) -> Unit = { onDismiss() }
        document.addEventListener("click", callback)
        onDispose { document.removeEventListener("click", callback) }
    }

    AdvancedPopover(
        target = target,
        placementStrategy = placementStrategy,
        openCloseStrategy = popoverStrategy,
        keepOpenStrategy = remember { KeepPopupOpenStrategy.never() },
        ref = disposableRef { onDispose { onDismiss() } },
        content = content,
    )
}