diff --git a/crates/ui/src/popover.rs b/crates/ui/src/popover.rs index 8c7fa85c..2be7f6d7 100644 --- a/crates/ui/src/popover.rs +++ b/crates/ui/src/popover.rs @@ -121,7 +121,10 @@ impl Popover { self } - /// Set the content of the popover. + /// Set the content builder for content of the Popover. + /// + /// This callback will called every time on render the popover. + /// So, you should avoid creating new elements or entities in the content closure. pub fn content(mut self, content: F) -> Self where E: IntoElement, @@ -144,7 +147,8 @@ impl Popover { self } - /// Bind the focus handle to track focus inside the popover. + /// Bind the focus handle to receive focus when the popover is opened. + /// If you not set this, a new focus handle will be created for the popover to /// /// If popover is opened, the focus will be moved to the focus handle. pub fn track_focus(mut self, handle: &FocusHandle) -> Self { @@ -225,7 +229,16 @@ impl PopoverState { if self.open { let state = cx.entity(); self.previous_focus = window.focused(cx); - self.focus_handle(cx).focus(window); + let focus_handle = if let Some(tracked_focus_handle) = self.tracked_focus_handle.clone() + { + tracked_focus_handle + } else { + self.focus_handle.clone() + }; + + cx.defer_in(window, move |_, window, _| { + focus_handle.focus(window); + }); self._dismiss_subscription = Some( @@ -246,9 +259,7 @@ impl PopoverState { if let Some(callback) = self.on_open_change.as_ref() { callback(&self.open, window, cx); } - cx.notify(); - window.refresh(); } fn on_action_cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context) { @@ -258,11 +269,7 @@ impl PopoverState { impl Focusable for PopoverState { fn focus_handle(&self, _: &App) -> FocusHandle { - if let Some(tracked_focus_handle) = &self.tracked_focus_handle { - tracked_focus_handle.clone() - } else { - self.focus_handle.clone() - } + self.focus_handle.clone() } } @@ -294,7 +301,7 @@ impl RenderOnce for Popover { }); let open = state.read(cx).open; - let focus_handle = state.read(cx).focus_handle.clone(); + let focus_handle = state.focus_handle(cx); let trigger_bounds = state.read(cx).trigger_bounds; let Some(trigger) = self.trigger else { @@ -310,6 +317,9 @@ impl RenderOnce for Popover { let state = state.clone(); move |_, window, cx| { state.update(cx, |state, cx| { + // We force set open to false to toggle it correctly. + // Because if the mouse down out will toggle open first. + state.open = open; state.toggle_open(window, cx); }); cx.notify(parent_view_id); diff --git a/docs/docs/components/popover.md b/docs/docs/components/popover.md index 9bdfb084..40684235 100644 --- a/docs/docs/components/popover.md +++ b/docs/docs/components/popover.md @@ -59,6 +59,40 @@ Popover::new("form-popover") .child(view.clone()) ``` +### Add content by `content` method + +The `content` method allows you to create more complex popover content using a closure. This is useful when +you need to build dynamic content or need access to the popover's context. + +This method will let us to have `&mut PopoverState`, `&mut Window` and `&mut Context` parameters in the +closure is to allow you to interact with the popover's state and the overall application context if needed. + +:::warning +This `content` callback will called every time on render the popover. +So, you should avoid creating new elements or entities in the content closure +or other heavy operations that may impact performance. +::: + +And `content` will works with `child`, `children` methods together. + +```rust +use gpui::ParentElement as _; +use gpui_component::popover::Popover; + +Popover::new("complex-popover") + .anchor(Corner::BottomLeft) + .trigger(Button::new("complex").label("Complex Content").outline()) + .content(|_, _, _| { + div() + .child("This popover has complex content.") + .child( + Button::new("action-btn") + .label("Perform Action") + .outline() + ) + }) +``` + ### Right-Click Popover Sometimes you may want to show a popover on right-click, for example, to create a special your ownen context menu. The `mouse_button` method allows you to specify which mouse button triggers the popover.