mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
feat: support SSR
This commit is contained in:
parent
66791d88f2
commit
e86d273502
6 changed files with 135 additions and 105 deletions
|
@ -1,8 +1,5 @@
|
|||
use super::switch_version::SwitchVersion;
|
||||
use leptos::{
|
||||
ev::{self, MouseEvent},
|
||||
prelude::*,
|
||||
};
|
||||
use leptos::{ev::MouseEvent, prelude::*};
|
||||
use leptos_meta::Style;
|
||||
use leptos_router::hooks::use_navigate;
|
||||
// use leptos_use::{storage::use_local_storage, utils::FromToStringCodec};
|
||||
|
@ -77,16 +74,20 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
}
|
||||
};
|
||||
let auto_complete_ref = ComponentRef::<AutoCompleteRef>::new();
|
||||
let handle = window_event_listener(ev::keydown, move |event| {
|
||||
let key = event.key();
|
||||
if key == *"/" {
|
||||
if let Some(auto_complete_ref) = auto_complete_ref.get_untracked() {
|
||||
event.prevent_default();
|
||||
auto_complete_ref.focus();
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
{
|
||||
use leptos::ev;
|
||||
let handle = window_event_listener(ev::keydown, move |event| {
|
||||
let key = event.key();
|
||||
if key == *"/" {
|
||||
if let Some(auto_complete_ref) = auto_complete_ref.get_untracked() {
|
||||
event.prevent_default();
|
||||
auto_complete_ref.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
on_cleanup(move || handle.remove());
|
||||
});
|
||||
on_cleanup(move || handle.remove());
|
||||
}
|
||||
|
||||
// let menu_value = use_menu_value(change_theme);
|
||||
view! {
|
||||
|
|
|
@ -20,7 +20,8 @@ wasm-bindgen = "=0.2.92"
|
|||
thiserror = "1"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
http = "1"
|
||||
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
demo = { path = "../../demo", default-features = false }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -4,6 +4,7 @@ pub mod app;
|
|||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
use crate::app::*;
|
||||
let _ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
leptos::mount::hydrate_body(App);
|
||||
}
|
||||
|
|
|
@ -2,17 +2,17 @@ mod anchor_link;
|
|||
|
||||
pub use anchor_link::AnchorLink;
|
||||
|
||||
use leptos::{context::Provider, ev, html, prelude::*};
|
||||
use std::cmp::Ordering;
|
||||
use thaw_utils::{add_event_listener_with_bool, class_list, mount_style, throttle};
|
||||
use leptos::{context::Provider, html, prelude::*};
|
||||
use thaw_utils::{class_list, mount_style};
|
||||
use web_sys::{DomRect, Element};
|
||||
|
||||
#[component]
|
||||
pub fn Anchor(
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
/// The element or selector used to calc offset of link elements.
|
||||
/// The element or selector used to calc offset of link elements.
|
||||
/// If you are not scrolling the entire document but only a part of it, you may need to set this.
|
||||
#[prop(into, optional)] offset_target: Option<OffsetTarget>,
|
||||
#[prop(into, optional)]
|
||||
offset_target: Option<OffsetTarget>,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
mount_style("anchor", include_str!("./anchor.css"));
|
||||
|
@ -21,72 +21,102 @@ pub fn Anchor(
|
|||
let element_ids = RwSignal::new(Vec::<String>::new());
|
||||
let active_id = RwSignal::new(None::<String>);
|
||||
|
||||
let offset_target = send_wrapper::SendWrapper::new(offset_target);
|
||||
let on_scroll = move || {
|
||||
element_ids.with(|ids| {
|
||||
let offset_target_top = if let Some(offset_target) = offset_target.as_ref() {
|
||||
if let Some(rect) = offset_target.get_bounding_client_rect() {
|
||||
rect.top()
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
{
|
||||
use leptos::ev;
|
||||
use std::cmp::Ordering;
|
||||
use thaw_utils::{add_event_listener_with_bool, throttle};
|
||||
|
||||
let mut links: Vec<LinkInfo> = vec![];
|
||||
for id in ids.iter() {
|
||||
if let Some(link_el) = document().get_element_by_id(id) {
|
||||
let link_rect = link_el.get_bounding_client_rect();
|
||||
links.push(LinkInfo {
|
||||
top: link_rect.top() - offset_target_top,
|
||||
id: id.clone(),
|
||||
});
|
||||
struct LinkInfo {
|
||||
top: f64,
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl OffsetTarget {
|
||||
fn get_bounding_client_rect(&self) -> Option<DomRect> {
|
||||
match self {
|
||||
OffsetTarget::Selector(selector) => {
|
||||
let el = document().query_selector(selector).ok().flatten()?;
|
||||
Some(el.get_bounding_client_rect())
|
||||
}
|
||||
OffsetTarget::Element(el) => Some(el.get_bounding_client_rect()),
|
||||
}
|
||||
}
|
||||
links.sort_by(|a, b| {
|
||||
if a.top > b.top {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut temp_link = None::<LinkInfo>;
|
||||
for link in links.into_iter() {
|
||||
if link.top >= 0.0 {
|
||||
if link.top <= 12.0 {
|
||||
temp_link = Some(link);
|
||||
break;
|
||||
} else if temp_link.is_some() {
|
||||
break;
|
||||
let offset_target = send_wrapper::SendWrapper::new(offset_target);
|
||||
|
||||
let on_scroll = move || {
|
||||
element_ids.with(|ids| {
|
||||
let offset_target_top = if let Some(offset_target) = offset_target.as_ref() {
|
||||
if let Some(rect) = offset_target.get_bounding_client_rect() {
|
||||
rect.top()
|
||||
} else {
|
||||
temp_link = None;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
temp_link = Some(link);
|
||||
0.0
|
||||
};
|
||||
|
||||
let mut links: Vec<LinkInfo> = vec![];
|
||||
for id in ids.iter() {
|
||||
if let Some(link_el) = document().get_element_by_id(id) {
|
||||
let link_rect = link_el.get_bounding_client_rect();
|
||||
links.push(LinkInfo {
|
||||
top: link_rect.top() - offset_target_top,
|
||||
id: id.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
active_id.set(temp_link.map(|link| link.id));
|
||||
links.sort_by(|a, b| {
|
||||
if a.top > b.top {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
});
|
||||
|
||||
let mut temp_link = None::<LinkInfo>;
|
||||
for link in links.into_iter() {
|
||||
if link.top >= 0.0 {
|
||||
if link.top <= 12.0 {
|
||||
temp_link = Some(link);
|
||||
break;
|
||||
} else if temp_link.is_some() {
|
||||
break;
|
||||
} else {
|
||||
temp_link = None;
|
||||
}
|
||||
} else {
|
||||
temp_link = Some(link);
|
||||
}
|
||||
}
|
||||
active_id.set(temp_link.map(|link| link.id));
|
||||
});
|
||||
};
|
||||
let cb = throttle(
|
||||
move || {
|
||||
on_scroll();
|
||||
},
|
||||
std::time::Duration::from_millis(200),
|
||||
);
|
||||
let scroll_handle = add_event_listener_with_bool(
|
||||
document(),
|
||||
ev::scroll,
|
||||
move |_| {
|
||||
cb();
|
||||
},
|
||||
true,
|
||||
);
|
||||
on_cleanup(move || {
|
||||
scroll_handle.remove();
|
||||
});
|
||||
};
|
||||
let cb = throttle(
|
||||
move || {
|
||||
on_scroll();
|
||||
},
|
||||
std::time::Duration::from_millis(200),
|
||||
);
|
||||
let scroll_handle = add_event_listener_with_bool(
|
||||
document(),
|
||||
ev::scroll,
|
||||
move |_| {
|
||||
cb();
|
||||
},
|
||||
true,
|
||||
);
|
||||
on_cleanup(move || {
|
||||
scroll_handle.remove();
|
||||
});
|
||||
}
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
{
|
||||
let _ = offset_target;
|
||||
}
|
||||
|
||||
view! {
|
||||
<div
|
||||
class=class_list!["thaw-anchor", class]
|
||||
|
@ -177,27 +207,12 @@ impl AnchorInjection {
|
|||
}
|
||||
}
|
||||
|
||||
struct LinkInfo {
|
||||
top: f64,
|
||||
id: String,
|
||||
}
|
||||
|
||||
pub enum OffsetTarget {
|
||||
Selector(String),
|
||||
Element(Element),
|
||||
}
|
||||
|
||||
impl OffsetTarget {
|
||||
fn get_bounding_client_rect(&self) -> Option<DomRect> {
|
||||
match self {
|
||||
OffsetTarget::Selector(selector) => {
|
||||
let el = document().query_selector(selector).ok().flatten()?;
|
||||
Some(el.get_bounding_client_rect())
|
||||
}
|
||||
OffsetTarget::Element(el) => Some(el.get_bounding_client_rect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<&'static str> for OffsetTarget {
|
||||
fn from(value: &'static str) -> Self {
|
||||
|
|
|
@ -3,7 +3,9 @@ use leptos::{
|
|||
reactive_graph::traits::{Get, Update, With, WithUntracked},
|
||||
tachys::renderer::DomRenderer,
|
||||
};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use std::collections::HashSet;
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ClassList {
|
||||
|
@ -21,6 +23,7 @@ impl ClassList {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
pub fn add(mut self, value: impl IntoClass) -> Self {
|
||||
let class = value.into_class();
|
||||
match class {
|
||||
|
@ -188,11 +191,18 @@ where
|
|||
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
|
||||
let el = el.to_owned();
|
||||
RenderEffect::new(move |prev| {
|
||||
if let Some(_) = prev {
|
||||
unreachable!()
|
||||
let mut class = String::new();
|
||||
self.write_class_string(&mut class);
|
||||
|
||||
if let Some(state) = prev {
|
||||
let (el, prev_class) = state;
|
||||
if class != prev_class {
|
||||
R::set_attribute(&el, "class", &class);
|
||||
(el, class)
|
||||
} else {
|
||||
(el, prev_class)
|
||||
}
|
||||
} else {
|
||||
let mut class = String::new();
|
||||
self.write_class_string(&mut class);
|
||||
if !class.is_empty() {
|
||||
if !FROM_SERVER {
|
||||
R::set_attribute(&el, "class", &class);
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use cfg_if::cfg_if;
|
||||
|
||||
pub fn mount_style(id: &str, content: &'static str) {
|
||||
let id = format!("thaw-id-{id}");
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos::{tachys::view::Render, view};
|
||||
use leptos::view;
|
||||
use leptos_meta::Style;
|
||||
|
||||
let _ = view! {
|
||||
<Style attr:data-thaw-id=id>
|
||||
<Style id=id>
|
||||
{content}
|
||||
</Style>
|
||||
};
|
||||
|
@ -15,7 +16,7 @@ pub fn mount_style(id: &str, content: &'static str) {
|
|||
use leptos::prelude::document;
|
||||
let head = document().head().expect("head no exist");
|
||||
let style = head
|
||||
.query_selector(&format!("style[data-thaw-id=\"{id}\"]"))
|
||||
.query_selector(&format!("style#{id}"))
|
||||
.expect("query style element error");
|
||||
|
||||
if style.is_some() {
|
||||
|
@ -25,7 +26,7 @@ pub fn mount_style(id: &str, content: &'static str) {
|
|||
let style = document()
|
||||
.create_element("style")
|
||||
.expect("create style element error");
|
||||
_ = style.set_attribute("data-thaw-id", id);
|
||||
_ = style.set_attribute("id", &id);
|
||||
style.set_text_content(Some(content));
|
||||
_ = head.prepend_with_node_1(&style);
|
||||
}
|
||||
|
@ -33,13 +34,14 @@ pub fn mount_style(id: &str, content: &'static str) {
|
|||
}
|
||||
|
||||
pub fn mount_dynamic_style<T: Fn() -> String + Send + Sync + 'static>(id: String, f: T) {
|
||||
let id = format!("thaw-id-{id}");
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos::{tachys::view::Render, view};
|
||||
use leptos::view;
|
||||
use leptos_meta::Style;
|
||||
|
||||
let _ = view! {
|
||||
<Style attr:data-thaw-id=id>
|
||||
<Style id=id>
|
||||
{f()}
|
||||
</Style>
|
||||
};
|
||||
|
@ -49,12 +51,12 @@ pub fn mount_dynamic_style<T: Fn() -> String + Send + Sync + 'static>(id: String
|
|||
|
||||
let head = document().head().expect("head no exist");
|
||||
let style = head
|
||||
.query_selector(&format!("style[data-thaw-id=\"{id}\"]"))
|
||||
.query_selector(&format!("style#{id}"))
|
||||
.expect("query style element error").unwrap_or_else(|| {
|
||||
let style = document()
|
||||
.create_element("style")
|
||||
.expect("create style element error");
|
||||
_ = style.set_attribute("data-thaw-id", &id);
|
||||
_ = style.set_attribute("id", &id);
|
||||
_ = head.prepend_with_node_1(&style);
|
||||
|
||||
style
|
||||
|
|
Loading…
Add table
Reference in a new issue