feat: Portals in 0.7

This commit is contained in:
Greg Johnston 2024-05-10 16:29:34 -04:00
parent 802fcc5c2a
commit 274e31018b
7 changed files with 60 additions and 36 deletions

View File

@ -8,6 +8,7 @@ leptos = { path = "../../leptos", features = ["csr"] }
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2"
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

View File

@ -1,9 +1,11 @@
use leptos::*;
use leptos::control_flow::Show;
use leptos::portal::Portal;
use leptos::prelude::*;
#[component]
pub fn App() -> impl IntoView {
let (show_overlay, set_show_overlay) = create_signal(false);
let (show_inside_overlay, set_show_inside_overlay) = create_signal(false);
let (show_overlay, set_show_overlay) = signal(false);
let (show_inside_overlay, set_show_inside_overlay) = signal(false);
view! {
<div>

View File

@ -1,15 +1,16 @@
use leptos::*;
use leptos::prelude::*;
use portal::App;
use wasm_bindgen::JsCast;
fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to(
leptos::document()
let handle = mount_to(
helpers::document()
.get_element_by_id("app")
.unwrap()
.unchecked_into(),
|| view! { <App/> },
)
App,
);
handle.forget();
}

View File

@ -49,6 +49,7 @@ wasm-bindgen = "0.2"
serde_qs = "0.12.0"
slotmap = "1.0.7"
futures = "0.3.30"
send_wrapper = "0.6.0"
[features]
default = ["serde"]

View File

@ -205,6 +205,9 @@ pub mod control_flow {
mod for_loop;
mod show;
/// A component that allows rendering a component somewhere else.
pub mod portal;
/// Components to enable server-side rendering and client-side hydration.
pub mod hydration;
@ -247,10 +250,10 @@ pub mod context {
}
pub use leptos_server as server;
/// HTML element types.
pub use tachys::html::element as html;
/// HTML attribute types.
pub use tachys::html::attribute as attr;
/// HTML element types.
pub use tachys::html::element as html;
/// HTML event types.
#[doc(no_inline)]
pub use tachys::html::event as ev;

View File

@ -130,12 +130,16 @@ where
///
/// If you are using it to create the root of an application, you should use
/// [`UnmountHandle::forget`] to leak it.
#[must_use]
#[must_use = "Dropping an `UnmountHandle` will unmount the view and cancel the \
reactive system. You should either call `.forget()` to keep the \
view permanently mounted, or store the `UnmountHandle` somewhere \
and drop it when you'd like to unmount the view."]
pub struct UnmountHandle<M, R>
where
M: Mountable<R>,
R: Renderer,
{
#[allow(dead_code)]
owner: Owner,
mountable: M,
rndr: PhantomData<R>,

View File

@ -1,12 +1,17 @@
use crate::ChildrenFn;
use crate::{
children::{ChildrenFn, TypedChildrenFn},
mount, IntoView,
};
use cfg_if::cfg_if;
use leptos_dom::IntoView;
use leptos_dom::helpers::document;
use leptos_macro::component;
#[cfg(all(
target_arch = "wasm32",
any(feature = "hydrate", feature = "csr")
))]
use leptos_reactive::untrack;
use reactive_graph::untrack;
use reactive_graph::{effect::Effect, owner::Owner};
use std::sync::Arc;
/// Renders components somewhere else in the DOM.
///
@ -19,7 +24,7 @@ use leptos_reactive::untrack;
tracing::instrument(level = "trace", skip_all)
)]
#[component]
pub fn Portal(
pub fn Portal<V>(
/// Target element where the children will be appended
#[prop(into, optional)]
mount: Option<web_sys::Element>,
@ -30,18 +35,25 @@ pub fn Portal(
#[prop(optional)]
is_svg: bool,
/// The children to teleport into the `mount` element
children: ChildrenFn,
) -> impl IntoView {
cfg_if! { if #[cfg(all(target_arch = "wasm32", any(feature = "hydrate", feature = "csr")))] {
use leptos_dom::{document, Mountable};
use leptos_reactive::{create_effect, on_cleanup};
children: TypedChildrenFn<V>,
) -> impl IntoView
where
V: IntoView + 'static,
{
if cfg!(target_arch = "wasm32")
&& Owner::current_shared_context()
.map(|sc| sc.is_browser())
.unwrap_or(true)
{
use send_wrapper::SendWrapper;
use wasm_bindgen::JsCast;
let mount = mount
.unwrap_or_else(|| document().body().expect("body to exist").unchecked_into());
let mount = mount.unwrap_or_else(|| {
document().body().expect("body to exist").unchecked_into()
});
let children = children.into_inner();
create_effect(move |_| {
leptos::logging::log!("inside Portal effect");
Effect::new(move |_| {
let tag = if is_svg { "g" } else { "div" };
let container = document()
@ -59,23 +71,23 @@ pub fn Portal(
container.clone()
};
let children = untrack(|| children().into_view().get_mountable_node());
let _ = render_root.append_child(&children);
let _ = mount.append_child(&container);
let handle = SendWrapper::new((
mount::mount_to(render_root.unchecked_into(), {
let children = Arc::clone(&children);
move || untrack(children())
}),
mount.clone(),
container,
));
on_cleanup({
let mount = mount.clone();
Owner::on_cleanup({
move || {
let (handle, mount, container) = handle.take();
drop(handle);
let _ = mount.remove_child(&container);
}
})
});
} else {
let _ = mount;
let _ = use_shadow;
let _ = is_svg;
let _ = children;
}}
}
}