Merge branch 'main' of https://github.com/gbj/leptos
This commit is contained in:
commit
9d8627b337
|
@ -155,7 +155,13 @@ impl Scope {
|
|||
// Internals
|
||||
|
||||
impl Scope {
|
||||
pub(crate) fn dispose(self) {
|
||||
/// Disposes of this reactive scope.
|
||||
///
|
||||
/// This will
|
||||
/// 1. dispose of all child `Scope`s
|
||||
/// 2. run all cleanup functions defined for this scope by [on_cleanup](crate::on_cleanup).
|
||||
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
|
||||
pub fn dispose(self) {
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
// dispose of all child scopes
|
||||
let children = {
|
||||
|
@ -282,9 +288,9 @@ impl Scope {
|
|||
with_runtime(self.runtime, |runtime| runtime.all_resources())
|
||||
}
|
||||
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope that are
|
||||
/// pending from the server.
|
||||
pub fn pending_resources(&self) -> Vec<ResourceId> {
|
||||
/// Returns IDs for all [Resource](crate::Resource)s found on any scope that are
|
||||
/// pending from the server.
|
||||
pub fn pending_resources(&self) -> Vec<ResourceId> {
|
||||
with_runtime(self.runtime, |runtime| runtime.pending_resources())
|
||||
}
|
||||
|
||||
|
@ -323,7 +329,7 @@ impl Scope {
|
|||
Box::pin(async move {
|
||||
rx.next().await;
|
||||
resolver()
|
||||
})
|
||||
}),
|
||||
),
|
||||
);
|
||||
})
|
||||
|
@ -344,4 +350,4 @@ impl fmt::Debug for ScopeDisposer {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("ScopeDisposer").finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -781,10 +781,18 @@ impl SignalId {
|
|||
pub(crate) fn subscribe(&self, runtime: &Runtime) {
|
||||
// add subscriber
|
||||
if let Some(observer) = runtime.observer.get() {
|
||||
// add this observer to the signal's dependencies (to allow notification)
|
||||
let mut subs = runtime.signal_subscribers.borrow_mut();
|
||||
if let Some(subs) = subs.entry(*self) {
|
||||
subs.or_default().borrow_mut().insert(observer);
|
||||
}
|
||||
|
||||
// add this signal to the effect's sources (to allow cleanup)
|
||||
let mut effect_sources = runtime.effect_sources.borrow_mut();
|
||||
if let Some(effect_sources) = effect_sources.entry(observer) {
|
||||
let sources = effect_sources.or_default();
|
||||
sources.borrow_mut().insert(*self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
use cfg_if::cfg_if;
|
||||
use leptos::{Scope, component, IntoView};
|
||||
use std::{rc::Rc, cell::{RefCell, Cell}, collections::HashMap};
|
||||
use leptos::{component, IntoView, Scope};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{use_head, TextProp};
|
||||
|
||||
/// Manages all of the `<meta>` elements set by [Meta] components.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct MetaTagsContext {
|
||||
next_id: Cell<MetaTagId>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
next_id: Cell<MetaTagId>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
els: Rc<RefCell<HashMap<MetaTagId, (Option<MetaTag>, Option<web_sys::HtmlMetaElement>)>>>,
|
||||
}
|
||||
|
||||
|
@ -16,25 +20,25 @@ pub struct MetaTagsContext {
|
|||
struct MetaTagId(usize);
|
||||
|
||||
impl MetaTagsContext {
|
||||
fn get_next_id(&self) -> MetaTagId {
|
||||
let current_id = self.next_id.get();
|
||||
let next_id = MetaTagId(current_id.0 + 1);
|
||||
self.next_id.set(next_id);
|
||||
next_id
|
||||
}
|
||||
fn get_next_id(&self) -> MetaTagId {
|
||||
let current_id = self.next_id.get();
|
||||
let next_id = MetaTagId(current_id.0 + 1);
|
||||
self.next_id.set(next_id);
|
||||
next_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum MetaTag {
|
||||
Charset(TextProp),
|
||||
HttpEquiv {
|
||||
http_equiv: TextProp,
|
||||
content: Option<TextProp>
|
||||
},
|
||||
Name {
|
||||
name: TextProp,
|
||||
content: TextProp
|
||||
}
|
||||
Charset(TextProp),
|
||||
HttpEquiv {
|
||||
http_equiv: TextProp,
|
||||
content: Option<TextProp>,
|
||||
},
|
||||
Name {
|
||||
name: TextProp,
|
||||
content: TextProp,
|
||||
},
|
||||
}
|
||||
|
||||
impl MetaTagsContext {
|
||||
|
@ -86,22 +90,21 @@ impl MetaTagsContext {
|
|||
/// ```
|
||||
#[component(transparent)]
|
||||
pub fn Meta(
|
||||
cx: Scope,
|
||||
cx: Scope,
|
||||
/// The [`charset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset) attribute.
|
||||
#[prop(optional, into)]
|
||||
charset: Option<TextProp>,
|
||||
/// The [`name`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-name) attribute.
|
||||
#[prop(optional, into)]
|
||||
name: Option<TextProp>,
|
||||
/// The [`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) attribute.
|
||||
#[prop(optional, into)]
|
||||
http_equiv: Option<TextProp>,
|
||||
/// The [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) attribute.
|
||||
#[prop(optional, into)]
|
||||
content: Option<TextProp>
|
||||
/// The [`name`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-name) attribute.
|
||||
#[prop(optional, into)]
|
||||
name: Option<TextProp>,
|
||||
/// The [`http-equiv`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-http-equiv) attribute.
|
||||
#[prop(optional, into)]
|
||||
http_equiv: Option<TextProp>,
|
||||
/// The [`content`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-content) attribute.
|
||||
#[prop(optional, into)]
|
||||
content: Option<TextProp>,
|
||||
) -> impl IntoView {
|
||||
|
||||
let tag = match (charset, name, http_equiv, content) {
|
||||
let tag = match (charset, name, http_equiv, content) {
|
||||
(Some(charset), _, _, _) => MetaTag::Charset(charset),
|
||||
(_, _, Some(http_equiv), content) => MetaTag::HttpEquiv { http_equiv, content },
|
||||
(_, Some(name), _, Some(content)) => MetaTag::Name { name, content },
|
||||
|
@ -113,69 +116,76 @@ pub fn Meta(
|
|||
use leptos::{document, JsCast, UnwrapThrowExt, create_effect};
|
||||
|
||||
let meta = use_head(cx);
|
||||
let meta_tags = meta.meta_tags;
|
||||
let id = meta_tags.get_next_id();
|
||||
let meta_tags = meta.meta_tags;
|
||||
let id = meta_tags.get_next_id();
|
||||
|
||||
let el = if let Ok(Some(el)) = document().query_selector(&format!("[data-leptos-meta='{}']", id.0)) {
|
||||
el
|
||||
} else {
|
||||
document().create_element("meta").unwrap_throw()
|
||||
};
|
||||
let el = if let Ok(Some(el)) = document().query_selector(&format!("[data-leptos-meta='{}']", id.0)) {
|
||||
el
|
||||
} else {
|
||||
document().create_element("meta").unwrap_throw()
|
||||
};
|
||||
|
||||
match tag {
|
||||
MetaTag::Charset(charset) => {
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("charset", &charset.get());
|
||||
}
|
||||
})
|
||||
},
|
||||
MetaTag::HttpEquiv { http_equiv, content } => {
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("http-equiv", &http_equiv.get());
|
||||
}
|
||||
});
|
||||
if let Some(content) = content {
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("content", &content.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
MetaTag::Name { name, content } => {
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("name", &name.get());
|
||||
}
|
||||
});
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("content", &content.get());
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
match tag {
|
||||
MetaTag::Charset(charset) => {
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("charset", &charset.get());
|
||||
}
|
||||
})
|
||||
},
|
||||
MetaTag::HttpEquiv { http_equiv, content } => {
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("http-equiv", &http_equiv.get());
|
||||
}
|
||||
});
|
||||
if let Some(content) = content {
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("content", &content.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
MetaTag::Name { name, content } => {
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("name", &name.get());
|
||||
}
|
||||
});
|
||||
create_effect(cx, {
|
||||
let el = el.clone();
|
||||
move |_| {
|
||||
_ = el.set_attribute("content", &content.get());
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// add to head
|
||||
document()
|
||||
.query_selector("head")
|
||||
.unwrap_throw()
|
||||
.unwrap_throw()
|
||||
.append_child(&el)
|
||||
.unwrap_throw();
|
||||
// add to head
|
||||
let head = document()
|
||||
.query_selector("head")
|
||||
.unwrap_throw()
|
||||
.unwrap_throw();
|
||||
head.append_child(&el)
|
||||
.unwrap_throw();
|
||||
|
||||
// add to meta tags
|
||||
meta_tags.els.borrow_mut().insert(id, (None, Some(el.unchecked_into())));
|
||||
leptos::on_cleanup(cx, {
|
||||
let el = el.clone();
|
||||
move || {
|
||||
head.remove_child(&el);
|
||||
}
|
||||
});
|
||||
|
||||
// add to meta tags
|
||||
meta_tags.els.borrow_mut().insert(id, (None, Some(el.unchecked_into())));
|
||||
} else {
|
||||
let meta = use_head(cx);
|
||||
let meta_tags = meta.meta_tags;
|
||||
let meta_tags = meta.meta_tags;
|
||||
meta_tags.els.borrow_mut().insert(meta_tags.get_next_id(), (Some(tag), None));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,37 @@
|
|||
use crate::use_head;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// Manages all of the stylesheets set by [Stylesheet] components.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct StylesheetContext {
|
||||
#[allow(clippy::type_complexity)]
|
||||
els: Rc<RefCell<HashMap<(Option<String>, String), Option<web_sys::HtmlLinkElement>>>>,
|
||||
// key is (id, href)
|
||||
els: Rc<RefCell<HashMap<StyleSheetData, Option<web_sys::HtmlLinkElement>>>>,
|
||||
next_id: Rc<Cell<StylesheetId>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
struct StylesheetId(usize);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct StyleSheetData {
|
||||
id: String,
|
||||
href: String,
|
||||
}
|
||||
|
||||
impl StylesheetContext {
|
||||
fn get_next_id(&self) -> StylesheetId {
|
||||
let current_id = self.next_id.get();
|
||||
let next_id = StylesheetId(current_id.0 + 1);
|
||||
self.next_id.set(next_id);
|
||||
next_id
|
||||
}
|
||||
}
|
||||
|
||||
impl StylesheetContext {
|
||||
|
@ -16,12 +40,8 @@ impl StylesheetContext {
|
|||
self.els
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|((id, href), _)| {
|
||||
if let Some(id) = id {
|
||||
format!(r#"<link rel="stylesheet" id="{id}" href="{href}">"#)
|
||||
} else {
|
||||
format!(r#"<link rel="stylesheet" href="{href}">"#)
|
||||
}
|
||||
.map(|(StyleSheetData { id, href }, _)| {
|
||||
format!(r#"<link rel="stylesheet" id="{id}" href="{href}">"#)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -55,50 +75,50 @@ pub fn Stylesheet(
|
|||
#[prop(optional, into)]
|
||||
id: Option<String>,
|
||||
) -> impl IntoView {
|
||||
let meta = use_head(cx);
|
||||
let stylesheets = &meta.stylesheets;
|
||||
let next_id = stylesheets.get_next_id();
|
||||
let id = id.unwrap_or_else(|| format!("leptos-style-{}", next_id.0));
|
||||
let key = StyleSheetData { id, href };
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
use leptos::document;
|
||||
|
||||
let meta = use_head(cx);
|
||||
let element_to_hydrate = document().get_element_by_id(&key.id);
|
||||
|
||||
// TODO I guess this will create a duplicated <link> when hydrating
|
||||
let existing_el = {
|
||||
let els = meta.stylesheets.els.borrow();
|
||||
let key = (id.clone(), href.clone());
|
||||
els.get(&key).cloned()
|
||||
};
|
||||
if let Some(Some(_)) = existing_el {
|
||||
leptos::leptos_dom::debug_warn!("<Stylesheet/> already loaded stylesheet {href}");
|
||||
} else {
|
||||
let element_to_hydrate = id.as_ref()
|
||||
.and_then(|id| {
|
||||
document().get_element_by_id(&id)
|
||||
});
|
||||
let el = element_to_hydrate.unwrap_or_else(|| {
|
||||
let el = document().create_element("link").unwrap_throw();
|
||||
el.set_attribute("rel", "stylesheet").unwrap_throw();
|
||||
el.set_attribute("id", &key.id).unwrap_throw();
|
||||
el.set_attribute("href", &key.href).unwrap_throw();
|
||||
let head = document().head().unwrap_throw();
|
||||
head
|
||||
.append_child(el.unchecked_ref())
|
||||
.unwrap_throw();
|
||||
|
||||
let el = element_to_hydrate.unwrap_or_else(|| {
|
||||
let el = document().create_element("link").unwrap_throw();
|
||||
el.set_attribute("rel", "stylesheet").unwrap_throw();
|
||||
if let Some(id_val) = &id{
|
||||
el.set_attribute("id", id_val).unwrap_throw();
|
||||
}
|
||||
el.set_attribute("href", &href).unwrap_throw();
|
||||
document()
|
||||
.query_selector("head")
|
||||
.unwrap_throw()
|
||||
.unwrap_throw()
|
||||
.append_child(el.unchecked_ref())
|
||||
.unwrap_throw();
|
||||
el
|
||||
});
|
||||
el
|
||||
});
|
||||
|
||||
on_cleanup(cx, {
|
||||
let el = el.clone();
|
||||
let els = meta.stylesheets.els.clone();
|
||||
let key = key.clone();
|
||||
move || {
|
||||
let head = document().head().unwrap_throw();
|
||||
_ = head.remove_child(&el);
|
||||
els.borrow_mut().remove(&key);
|
||||
}
|
||||
});
|
||||
|
||||
meta.stylesheets
|
||||
.els
|
||||
.borrow_mut()
|
||||
.insert(key, Some(el.unchecked_into()));
|
||||
|
||||
meta.stylesheets
|
||||
.els
|
||||
.borrow_mut()
|
||||
.insert((id, href), Some(el.unchecked_into()));
|
||||
}
|
||||
} else {
|
||||
let meta = use_head(cx);
|
||||
meta.stylesheets.els.borrow_mut().insert((id,href), None);
|
||||
meta.stylesheets.els.borrow_mut().insert(key, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,24 +106,40 @@ pub fn Title(
|
|||
}
|
||||
|
||||
let el = {
|
||||
let el_ref = meta.title.el.borrow_mut();
|
||||
let mut el_ref = meta.title.el.borrow_mut();
|
||||
let el = if let Some(el) = &*el_ref {
|
||||
let prev_text = el.inner_text();
|
||||
on_cleanup(cx, {
|
||||
let el = el.clone();
|
||||
move || {
|
||||
_ = el.set_text(&prev_text);
|
||||
}
|
||||
});
|
||||
|
||||
el.clone()
|
||||
} else {
|
||||
match document().query_selector("title") {
|
||||
Ok(Some(title)) => title.unchecked_into(),
|
||||
_ => {
|
||||
let el = document().create_element("title").unwrap_throw();
|
||||
document()
|
||||
.query_selector("head")
|
||||
.unwrap_throw()
|
||||
.unwrap_throw()
|
||||
.append_child(el.unchecked_ref())
|
||||
let head = document().head().unwrap_throw();
|
||||
head.append_child(el.unchecked_ref())
|
||||
.unwrap_throw();
|
||||
|
||||
on_cleanup(cx, {
|
||||
let el = el.clone();
|
||||
move || {
|
||||
_ = head.remove_child(&el);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
el.unchecked_into()
|
||||
}
|
||||
}
|
||||
};
|
||||
*el_ref = Some(el.clone().unchecked_into());
|
||||
|
||||
el
|
||||
};
|
||||
|
||||
|
|
|
@ -8,18 +8,21 @@ use leptos::*;
|
|||
#[component]
|
||||
pub fn Outlet(cx: Scope) -> impl IntoView {
|
||||
let route = use_route(cx);
|
||||
let is_showing = Rc::new(Cell::new(None));
|
||||
let is_showing = Rc::new(Cell::new(None::<(usize, Scope)>));
|
||||
let (outlet, set_outlet) = create_signal(cx, None);
|
||||
create_effect(cx, move |_| {
|
||||
create_isomorphic_effect(cx, move |_| {
|
||||
match (route.child(), &is_showing.get()) {
|
||||
(None, _) => {
|
||||
set_outlet.set(None);
|
||||
}
|
||||
(Some(child), Some(is_showing_val)) if child.id() == *is_showing_val => {
|
||||
(Some(child), Some((is_showing_val, _))) if child.id() == *is_showing_val => {
|
||||
// do nothing: we don't need to rerender the component, because it's the same
|
||||
}
|
||||
(Some(child), _) => {
|
||||
is_showing.set(Some(child.id()));
|
||||
(Some(child), prev) => {
|
||||
if let Some(prev_scope) = prev.map(|(_, scope)| scope) {
|
||||
prev_scope.dispose();
|
||||
}
|
||||
is_showing.set(Some((child.id(), child.cx())));
|
||||
provide_context(child.cx(), child.clone());
|
||||
set_outlet.set(Some(child.outlet().into_view(cx)))
|
||||
}
|
||||
|
|
|
@ -140,7 +140,8 @@ pub fn Routes(
|
|||
|
||||
if disposers.borrow().len() > i + 1 {
|
||||
let mut disposers = disposers.borrow_mut();
|
||||
let old_route_disposer = std::mem::replace(&mut disposers[i], disposer);
|
||||
let old_route_disposer =
|
||||
std::mem::replace(&mut disposers[i + 1], disposer);
|
||||
old_route_disposer.dispose();
|
||||
} else {
|
||||
disposers.borrow_mut().push(disposer);
|
||||
|
|
Loading…
Reference in New Issue