Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions examples/portals/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use wasm_bindgen::JsCast;
use web_sys::{Element, ShadowRootInit, ShadowRootMode};
use yew::{create_portal, html, Children, Component, Context, Html, NodeRef, Properties};
use yew::prelude::*;

#[derive(Properties, PartialEq)]
pub struct ShadowDOMProps {
Expand Down Expand Up @@ -49,15 +49,16 @@ impl Component for ShadowDOMHost {
}

fn view(&self, ctx: &Context<Self>) -> Html {
let contents = if let Some(ref inner_host) = self.inner_host {
create_portal(
let contents = match self.inner_host {
Some(ref m) => {
let children = ctx.props().children.clone();
html! {
{for ctx.props().children.iter()}
},
inner_host.clone(),
)
} else {
html! { <></> }
<Portal host={m.clone()}>
{children}
</Portal>
}
}
None => Html::default(),
};
html! {
<div ref={self.host_ref.clone()}>
Expand All @@ -76,15 +77,17 @@ impl Component for App {
type Properties = ();

fn create(_ctx: &Context<Self>) -> Self {
let document_head = gloo_utils::document()
let document_head: Element = gloo_utils::document()
.head()
.expect("head element to be present");
let style_html = create_portal(
html! {
<style>{"p { color: red; }"}</style>
},
document_head.into(),
);
.expect("head element to be present")
.into();
let style_html = html! {
<Portal host={document_head}>
<style>
{"p { color: red; }"}
</style>
</Portal>
};
Self { style_html }
}

Expand Down
19 changes: 8 additions & 11 deletions packages/yew/src/dom_bundle/bsuspense.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::{BNode, Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::virtual_dom::{Key, VSuspense};
use crate::NodeRef;
use gloo::utils::document;
use web_sys::Element;

/// The bundle implementation to [VSuspense]
Expand Down Expand Up @@ -56,11 +57,12 @@ impl Reconcilable for VSuspense {
let VSuspense {
children,
fallback,
detached_parent,
suspended,
key,
} = self;
let detached_parent = detached_parent.expect("no detached parent?");
let detached_parent = document()
.create_element("div")
.expect("failed to create detached element");

// When it's suspended, we render children into an element that is detached from the dom
// tree while rendering fallback UI into the original place where children resides in.
Expand Down Expand Up @@ -100,10 +102,7 @@ impl Reconcilable for VSuspense {
) -> NodeRef {
match bundle {
// We only preserve the child state if they are the same suspense.
BNode::Suspense(m)
if m.key == self.key
&& self.detached_parent.as_ref() == Some(&m.detached_parent) =>
{
BNode::Suspense(m) if m.key == self.key => {
self.reconcile(parent_scope, parent, next_sibling, m)
}
_ => self.replace(parent_scope, parent, next_sibling, bundle),
Expand All @@ -120,11 +119,9 @@ impl Reconcilable for VSuspense {
let VSuspense {
children,
fallback,
detached_parent,
suspended,
key: _,
} = self;
let detached_parent = detached_parent.expect("no detached parent?");

let children_bundle = &mut suspense.children_bundle;
// no need to update key & detached_parent
Expand All @@ -136,7 +133,7 @@ impl Reconcilable for VSuspense {
(true, Some(fallback_bundle)) => {
children.reconcile_node(
parent_scope,
&detached_parent,
&suspense.detached_parent,
NodeRef::default(),
children_bundle,
);
Expand All @@ -149,11 +146,11 @@ impl Reconcilable for VSuspense {
}
// Freshly suspended. Shift children into the detached parent, then add fallback to the DOM
(true, None) => {
children_bundle.shift(&detached_parent, NodeRef::default());
children_bundle.shift(&suspense.detached_parent, NodeRef::default());

children.reconcile_node(
parent_scope,
&detached_parent,
&suspense.detached_parent,
NodeRef::default(),
children_bundle,
);
Expand Down
6 changes: 3 additions & 3 deletions packages/yew/src/html/component/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::scope::{AnyScope, Scope};
use super::BaseComponent;
use crate::html::{Html, RenderError};
use crate::scheduler::{self, Runnable, Shared};
use crate::suspense::{Suspense, Suspension};
use crate::suspense::{BaseSuspense, Suspension};
use crate::{Callback, Context, HtmlResult};
use std::any::Any;
use std::rc::Rc;
Expand Down Expand Up @@ -387,7 +387,7 @@ impl RenderRunner {
let comp_scope = state.inner.any_scope();

let suspense_scope = comp_scope
.find_parent_scope::<Suspense>()
.find_parent_scope::<BaseSuspense>()
.expect("To suspend rendering, a <Suspense /> component is required.");
let suspense = suspense_scope.get_component().unwrap();

Expand Down Expand Up @@ -419,7 +419,7 @@ impl RenderRunner {
if let Some(m) = state.suspension.take() {
let comp_scope = state.inner.any_scope();

let suspense_scope = comp_scope.find_parent_scope::<Suspense>().unwrap();
let suspense_scope = comp_scope.find_parent_scope::<BaseSuspense>().unwrap();
let suspense = suspense_scope.get_component().unwrap();

suspense.resume(m);
Expand Down
2 changes: 1 addition & 1 deletion packages/yew/src/html/component/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ mod feat_ssr {
}

#[cfg(not(any(feature = "ssr", feature = "csr")))]
mod feat_no_render_ssr {
mod feat_no_csr_ssr {
use super::*;

// Skeleton code to provide public methods when no renderer are enabled.
Expand Down
12 changes: 2 additions & 10 deletions packages/yew/src/html/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ pub use error::*;
pub use listener::*;

use crate::sealed::Sealed;
use crate::virtual_dom::{VNode, VPortal};
use crate::virtual_dom::VNode;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::JsValue;
use web_sys::{Element, Node};
use web_sys::Node;

/// A type which expected as a result of `view` function implementation.
pub type Html = VNode;
Expand Down Expand Up @@ -172,14 +172,6 @@ mod feat_csr {
}
}

/// Render children into a DOM node that exists outside the hierarchy of the parent
/// component.
/// ## Relevant examples
/// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals)
pub fn create_portal(child: Html, host: Element) -> Html {
VNode::VPortal(VPortal::new(child, host))
}

#[cfg(feature = "wasm_test")]
#[cfg(test)]
mod tests {
Expand Down
7 changes: 5 additions & 2 deletions packages/yew/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ mod dom_bundle;
pub mod functional;
pub mod html;
mod io_coop;
pub mod portal;
pub mod scheduler;
mod sealed;
#[cfg(feature = "ssr")]
Expand All @@ -280,6 +281,7 @@ pub mod utils;
pub mod virtual_dom;
#[cfg(feature = "ssr")]
pub use server_renderer::*;

#[cfg(feature = "csr")]
mod app_handle;
#[cfg(feature = "csr")]
Expand Down Expand Up @@ -323,10 +325,11 @@ pub mod prelude {
pub use crate::context::{ContextHandle, ContextProvider};
pub use crate::events::*;
pub use crate::html::{
create_portal, BaseComponent, Children, ChildrenWithProps, Classes, Component, Context,
Html, HtmlResult, IntoComponent, NodeRef, Properties,
BaseComponent, Children, ChildrenWithProps, Classes, Component, Context, Html, HtmlResult,
IntoComponent, NodeRef, Properties,
};
pub use crate::macros::{classes, html, html_nested};
pub use crate::portal::Portal;
pub use crate::suspense::Suspense;
pub use crate::virtual_dom::AttrValue;

Expand Down
53 changes: 53 additions & 0 deletions packages/yew/src/portal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! a module to create a "Portal" that attaches its children to the specified host element.

use crate::functional::{use_effect_with_deps, use_state};
use crate::html::{Children, Html, Properties};
use crate::virtual_dom::{VNode, VPortal};
use crate::{function_component, html};

use web_sys::Element;

/// Properties of [Portal].
#[derive(Debug, Properties, PartialEq, Clone)]
pub struct PortalProps {
/// Children to be rendered to the host element.
#[prop_or_default]
pub children: Children,

/// The host element of the portal.
pub host: Element,
}

/// Render children into a DOM node that exists outside the hierarchy of the parent
/// component.
/// ## Relevant examples
/// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals)
#[function_component]
pub fn Portal(props: &PortalProps) -> Html {
let rendered = use_state(|| false);

// Delay render of portals until after first render.
//
// This automatically excludes portals during server-side rendering and defers
// it to be attached after hydration is completed.
{
let rendered = rendered.clone();
use_effect_with_deps(
move |_| {
rendered.set(true);

|| {}
},
(),
);
}

if *rendered {
let PortalProps { children, host } = props.clone();
let children = html! {<>{children}</>};

VNode::VPortal(VPortal::new(children, host))
} else {
Html::default()
}
}
1 change: 1 addition & 0 deletions packages/yew/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct Scheduler {

/// Stacks to ensure child calls are always before parent calls
rendered_first: Vec<Box<dyn Runnable>>,

#[cfg(feature = "csr")]
rendered: RenderedScheduler,
}
Expand Down
Loading