mirror of
https://github.com/leptos-rs/leptos.git
synced 2026-01-09 08:14:52 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e09f3d102 | ||
|
|
e6fe7fef07 | ||
|
|
629f4f9d0f | ||
|
|
ff5b612e12 | ||
|
|
61571ed24b | ||
|
|
4f3a26ce88 | ||
|
|
83a848b5ec | ||
|
|
eec9edf517 | ||
|
|
861dcf354c | ||
|
|
af3d6cba22 |
@@ -2,7 +2,6 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
# utilities
|
||||
"oco",
|
||||
"any_spawner",
|
||||
"const_str_slice_concat",
|
||||
"either_of",
|
||||
|
||||
@@ -9,6 +9,3 @@ routing when you use islands.
|
||||
This uses *only* server rendering, with no actual islands, but still maintains client-side state across page navigations.
|
||||
It does this by building on the fact that we now have a statically-typed view tree to do pretty smart updates with
|
||||
new HTML from the client, with extremely minimal diffing.
|
||||
|
||||
The demo itself works, but the feature that supports it is incomplete. A couple people have accidentally
|
||||
used it and broken their applications in ways they don't understand, so I've renamed the feature to `dont-use-islands-router`.
|
||||
|
||||
@@ -5,4 +5,4 @@ test cases that typically happens at integration.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run `cargo leptos watch` to run this example.
|
||||
Run `cargo leptos watch --split` to run this example.
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::Router;
|
||||
use axum::{
|
||||
http::{HeaderName, HeaderValue},
|
||||
Router,
|
||||
};
|
||||
use leptos::{logging::log, prelude::*};
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use ssr_modes_axum::app::*;
|
||||
@@ -17,7 +20,24 @@ async fn main() {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
})
|
||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||
.fallback(leptos_axum::file_and_error_handler_with_context(
|
||||
move || {
|
||||
// if you want to add custom headers to the static file handler response,
|
||||
// you can do that by providing `ResponseOptions` via context
|
||||
let opts = use_context::<leptos_axum::ResponseOptions>()
|
||||
.unwrap_or_default();
|
||||
opts.insert_header(
|
||||
HeaderName::from_static("cross-origin-opener-policy"),
|
||||
HeaderValue::from_static("same-origin"),
|
||||
);
|
||||
opts.insert_header(
|
||||
HeaderName::from_static("cross-origin-embedder-policy"),
|
||||
HeaderValue::from_static("require-corp"),
|
||||
);
|
||||
provide_context(opts);
|
||||
},
|
||||
shell,
|
||||
))
|
||||
.with_state(leptos_options);
|
||||
|
||||
// run our app with hyper
|
||||
|
||||
@@ -2050,7 +2050,20 @@ where
|
||||
let res = res.await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
let owner = Owner::new();
|
||||
owner.with(|| {
|
||||
additional_context();
|
||||
let res = res.into_response();
|
||||
if let Some(response_options) =
|
||||
use_context::<ResponseOptions>()
|
||||
{
|
||||
let mut res = AxumResponse(res);
|
||||
res.extend_response(&response_options);
|
||||
res.0
|
||||
} else {
|
||||
res
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let mut res = handle_response_inner(
|
||||
move || {
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
use futures::{channel::oneshot, select, FutureExt};
|
||||
use hydration_context::SerializedDataId;
|
||||
use leptos_macro::component;
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
computed::{
|
||||
suspense::{LocalResourceNotifier, SuspenseContext},
|
||||
@@ -14,10 +15,10 @@ use reactive_graph::{
|
||||
effect::RenderEffect,
|
||||
owner::{provide_context, use_context, Owner},
|
||||
signal::ArcRwSignal,
|
||||
traits::{Dispose, Get, Read, Track, With, WriteValue},
|
||||
traits::{Dispose, Get, Read, ReadUntracked, Track, With, WriteValue},
|
||||
};
|
||||
use slotmap::{DefaultKey, SlotMap};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tachys::{
|
||||
either::Either,
|
||||
html::attribute::{any_attribute::AnyAttribute, Attribute},
|
||||
@@ -320,23 +321,66 @@ where
|
||||
|
||||
// walk over the tree of children once to make sure that all resource loads are registered
|
||||
self.children.dry_resolve();
|
||||
let children = Arc::new(Mutex::new(Some(self.children)));
|
||||
|
||||
// check the set of tasks to see if it is empty, now or later
|
||||
let eff = reactive_graph::effect::Effect::new_isomorphic({
|
||||
move |_| {
|
||||
tasks.track();
|
||||
if let Some(tasks) = tasks.try_read() {
|
||||
if tasks.is_empty() {
|
||||
if let Some(tx) = tasks_tx.take() {
|
||||
// If the receiver has dropped, it means the ScopedFuture has already
|
||||
// dropped, so it doesn't matter if we manage to send this.
|
||||
_ = tx.send(());
|
||||
}
|
||||
if let Some(tx) = notify_error_boundary.take() {
|
||||
_ = tx.send(());
|
||||
let children = Arc::clone(&children);
|
||||
move |double_checking: Option<bool>| {
|
||||
// on the first run, always track the tasks
|
||||
if double_checking.is_none() {
|
||||
tasks.track();
|
||||
}
|
||||
|
||||
if let Some(curr_tasks) = tasks.try_read_untracked() {
|
||||
if curr_tasks.is_empty() {
|
||||
if double_checking == Some(true) {
|
||||
// we have finished loading, and checking the children again told us there are
|
||||
// no more pending tasks. so we can render both the children and the error boundary
|
||||
|
||||
if let Some(tx) = tasks_tx.take() {
|
||||
// If the receiver has dropped, it means the ScopedFuture has already
|
||||
// dropped, so it doesn't matter if we manage to send this.
|
||||
_ = tx.send(());
|
||||
}
|
||||
if let Some(tx) = notify_error_boundary.take() {
|
||||
_ = tx.send(());
|
||||
}
|
||||
} else {
|
||||
// release the read guard on tasks, as we'll be updating it again
|
||||
drop(curr_tasks);
|
||||
// check the children for additional pending tasks
|
||||
// the will catch additional resource reads nested inside a conditional depending on initial resource reads
|
||||
if let Some(children) =
|
||||
children.lock().or_poisoned().as_mut()
|
||||
{
|
||||
children.dry_resolve();
|
||||
}
|
||||
|
||||
if tasks
|
||||
.try_read()
|
||||
.map(|n| n.is_empty())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
// there are no additional pending tasks, and we can simply return
|
||||
if let Some(tx) = tasks_tx.take() {
|
||||
// If the receiver has dropped, it means the ScopedFuture has already
|
||||
// dropped, so it doesn't matter if we manage to send this.
|
||||
_ = tx.send(());
|
||||
}
|
||||
if let Some(tx) = notify_error_boundary.take() {
|
||||
_ = tx.send(());
|
||||
}
|
||||
}
|
||||
|
||||
// tell ourselves that we're just double-checking
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
tasks.track();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
@@ -362,12 +406,17 @@ where
|
||||
None
|
||||
}
|
||||
_ = tasks_rx => {
|
||||
let children = {
|
||||
let mut children_lock = children.lock().or_poisoned();
|
||||
children_lock.take().expect("children should not be removed until we render here")
|
||||
};
|
||||
|
||||
// if we ran this earlier, reactive reads would always be registered as None
|
||||
// this is fine in the case where we want to use Suspend and .await on some future
|
||||
// but in situations like a <For each=|| some_resource.snapshot()/> we actually
|
||||
// want to be able to 1) synchronously read a resource's value, but still 2) wait
|
||||
// for it to load before we render everything
|
||||
let mut children = Box::pin(self.children.resolve().fuse());
|
||||
let mut children = Box::pin(children.resolve().fuse());
|
||||
|
||||
// we continue racing the children against the "do we have any local
|
||||
// resources?" Future
|
||||
|
||||
@@ -103,6 +103,76 @@ fn test_classes() {
|
||||
assert_eq!(rendered.to_html(), "<div class=\"my big red car\"></div>");
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[test]
|
||||
fn test_class_with_class_directive_merge() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
// class= followed by class: should merge
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! {
|
||||
<div class="foo" class:bar=true></div>
|
||||
};
|
||||
|
||||
assert_eq!(rendered.to_html(), "<div class=\"foo bar\"></div>");
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[test]
|
||||
fn test_solo_class_directive() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
// Solo class: directive should work without class attribute
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! {
|
||||
<div class:foo=true></div>
|
||||
};
|
||||
|
||||
assert_eq!(rendered.to_html(), "<div class=\"foo\"></div>");
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[test]
|
||||
fn test_class_directive_with_static_class() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
// class:foo comes after class= due to macro sorting
|
||||
// The class= clears buffer, then class:foo appends
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! {
|
||||
<div class:foo=true class="bar"></div>
|
||||
};
|
||||
|
||||
// After macro sorting: class="bar" class:foo=true
|
||||
// Expected: "bar foo"
|
||||
assert_eq!(rendered.to_html(), "<div class=\"bar foo\"></div>");
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[test]
|
||||
fn test_global_class_applied() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
// Test that a global class is properly applied
|
||||
let rendered: View<HtmlElement<_, _, _>> = view! { class="global",
|
||||
<div></div>
|
||||
};
|
||||
|
||||
assert_eq!(rendered.to_html(), "<div class=\"global\"></div>");
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[test]
|
||||
fn test_multiple_class_attributes_overwrite() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
// When multiple class attributes are applied, the last one should win (browser behavior)
|
||||
// This simulates what happens when attributes are combined programmatically
|
||||
let el = leptos::html::div().class("first").class("second");
|
||||
|
||||
let html = el.to_html();
|
||||
|
||||
// The second class attribute should overwrite the first
|
||||
assert_eq!(html, "<div class=\"second\"></div>");
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[test]
|
||||
fn ssr_with_styles() {
|
||||
|
||||
@@ -188,6 +188,39 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
thread_local! {
|
||||
static RESOURCE_SOURCE_SIGNAL_ACTIVE: AtomicBool = const { AtomicBool::new(false) };
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
/// Returns whether the current thread is currently running a resource source signal.
|
||||
pub fn in_resource_source_signal() -> bool {
|
||||
RESOURCE_SOURCE_SIGNAL_ACTIVE
|
||||
.with(|scope| scope.load(std::sync::atomic::Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Set a static to true whilst running the given function.
|
||||
/// [`is_in_effect_scope`] will return true whilst the function is running.
|
||||
fn run_in_resource_source_signal<T>(fun: impl FnOnce() -> T) -> T {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// For the theoretical nested case, set back to initial value rather than false:
|
||||
let initial = RESOURCE_SOURCE_SIGNAL_ACTIVE.with(|scope| {
|
||||
scope.swap(true, std::sync::atomic::Ordering::Relaxed)
|
||||
});
|
||||
let result = fun();
|
||||
RESOURCE_SOURCE_SIGNAL_ACTIVE.with(|scope| {
|
||||
scope.store(initial, std::sync::atomic::Ordering::Relaxed)
|
||||
});
|
||||
result
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
fun()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> ReadUntracked for ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
@@ -202,7 +235,9 @@ where
|
||||
computed::suspense::SuspenseContext, effect::in_effect_scope,
|
||||
owner::use_context,
|
||||
};
|
||||
if !in_effect_scope() && use_context::<SuspenseContext>().is_none()
|
||||
if !in_effect_scope()
|
||||
&& !in_resource_source_signal()
|
||||
&& use_context::<SuspenseContext>().is_none()
|
||||
{
|
||||
let location = std::panic::Location::caller();
|
||||
reactive_graph::log_warning(format_args!(
|
||||
@@ -271,7 +306,7 @@ where
|
||||
let refetch = ArcRwSignal::new(0);
|
||||
let source = ArcMemo::new({
|
||||
let refetch = refetch.clone();
|
||||
move |_| (refetch.get(), source())
|
||||
move |_| (refetch.get(), run_in_resource_source_signal(&source))
|
||||
});
|
||||
let fun = {
|
||||
let source = source.clone();
|
||||
@@ -909,7 +944,9 @@ where
|
||||
computed::suspense::SuspenseContext, effect::in_effect_scope,
|
||||
owner::use_context,
|
||||
};
|
||||
if !in_effect_scope() && use_context::<SuspenseContext>().is_none()
|
||||
if !in_effect_scope()
|
||||
&& !in_resource_source_signal()
|
||||
&& use_context::<SuspenseContext>().is_none()
|
||||
{
|
||||
let location = std::panic::Location::caller();
|
||||
reactive_graph::log_warning(format_args!(
|
||||
|
||||
@@ -110,10 +110,12 @@ fn effect_base() -> (Receiver, Owner, Arc<RwLock<EffectInner>>) {
|
||||
(rx, owner, inner)
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
thread_local! {
|
||||
static EFFECT_SCOPE_ACTIVE: AtomicBool = const { AtomicBool::new(false) };
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
/// Returns whether the current thread is currently running an effect.
|
||||
pub fn in_effect_scope() -> bool {
|
||||
EFFECT_SCOPE_ACTIVE
|
||||
@@ -123,14 +125,22 @@ pub fn in_effect_scope() -> bool {
|
||||
/// Set a static to true whilst running the given function.
|
||||
/// [`is_in_effect_scope`] will return true whilst the function is running.
|
||||
fn run_in_effect_scope<T>(fun: impl FnOnce() -> T) -> T {
|
||||
// For the theoretical nested case, set back to initial value rather than false:
|
||||
let initial = EFFECT_SCOPE_ACTIVE
|
||||
.with(|scope| scope.swap(true, std::sync::atomic::Ordering::Relaxed));
|
||||
let result = fun();
|
||||
EFFECT_SCOPE_ACTIVE.with(|scope| {
|
||||
scope.store(initial, std::sync::atomic::Ordering::Relaxed)
|
||||
});
|
||||
result
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// For the theoretical nested case, set back to initial value rather than false:
|
||||
let initial = EFFECT_SCOPE_ACTIVE.with(|scope| {
|
||||
scope.swap(true, std::sync::atomic::Ordering::Relaxed)
|
||||
});
|
||||
let result = fun();
|
||||
EFFECT_SCOPE_ACTIVE.with(|scope| {
|
||||
scope.store(initial, std::sync::atomic::Ordering::Relaxed)
|
||||
});
|
||||
result
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
fun()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Effect<S>
|
||||
|
||||
@@ -57,6 +57,10 @@ where
|
||||
_style: &mut String,
|
||||
_inner_html: &mut String,
|
||||
) {
|
||||
// If this is a class="..." attribute (not class:name=value), clear previous value
|
||||
if self.class.should_overwrite() {
|
||||
class.clear();
|
||||
}
|
||||
class.push(' ');
|
||||
self.class.to_html(class);
|
||||
}
|
||||
@@ -156,6 +160,12 @@ pub trait IntoClass: Send {
|
||||
/// Renders the class to HTML.
|
||||
fn to_html(self, class: &mut String);
|
||||
|
||||
/// Whether this class attribute should overwrite previous class values.
|
||||
/// Returns `true` for `class="..."` attributes, `false` for `class:name=value` directives.
|
||||
fn should_overwrite(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Renders the class to HTML for a `<template>`.
|
||||
#[allow(unused)] // it's used with `nightly` feature
|
||||
fn to_template(class: &mut String) {}
|
||||
@@ -289,6 +299,10 @@ impl IntoClass for &str {
|
||||
class.push_str(self);
|
||||
}
|
||||
|
||||
fn should_overwrite(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
@@ -346,6 +360,10 @@ impl IntoClass for Cow<'_, str> {
|
||||
IntoClass::to_html(&*self, class);
|
||||
}
|
||||
|
||||
fn should_overwrite(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
@@ -403,6 +421,10 @@ impl IntoClass for String {
|
||||
IntoClass::to_html(self.as_str(), class);
|
||||
}
|
||||
|
||||
fn should_overwrite(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
@@ -460,6 +482,10 @@ impl IntoClass for Arc<str> {
|
||||
IntoClass::to_html(self.as_ref(), class);
|
||||
}
|
||||
|
||||
fn should_overwrite(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
el: &crate::renderer::types::Element,
|
||||
|
||||
@@ -47,11 +47,13 @@ pub fn directive<T, P, D>(handler: D, param: P) -> Directive<T, D, P>
|
||||
where
|
||||
D: IntoDirective<T, P>,
|
||||
{
|
||||
Directive(Some(SendWrapper::new(DirectiveInner {
|
||||
handler,
|
||||
param,
|
||||
t: PhantomData,
|
||||
})))
|
||||
Directive((!cfg!(feature = "ssr")).then(|| {
|
||||
SendWrapper::new(DirectiveInner {
|
||||
handler,
|
||||
param,
|
||||
t: PhantomData,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// Custom logic that runs in the browser when the element is created or hydrated.
|
||||
@@ -151,13 +153,7 @@ where
|
||||
Directive(inner)
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
// dry_resolve() only runs during SSR, and we should use it to
|
||||
// synchronously remove and drop the SendWrapper value
|
||||
// we don't need this value during SSR and leaving it here could drop it
|
||||
// from a different thread
|
||||
self.0.take();
|
||||
}
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
|
||||
@@ -113,7 +113,7 @@ where
|
||||
event,
|
||||
#[cfg(feature = "reactive_graph")]
|
||||
owner: reactive_graph::owner::Owner::current().unwrap_or_default(),
|
||||
cb: Some(SendWrapper::new(cb)),
|
||||
cb: (!cfg!(feature = "ssr")).then(|| SendWrapper::new(cb)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,13 +352,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
// dry_resolve() only runs during SSR, and we should use it to
|
||||
// synchronously remove and drop the SendWrapper value
|
||||
// we don't need this value during SSR and leaving it here could drop it
|
||||
// from a different thread
|
||||
self.cb.take();
|
||||
}
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
|
||||
@@ -22,7 +22,7 @@ where
|
||||
{
|
||||
Property {
|
||||
key,
|
||||
value: Some(SendWrapper::new(value)),
|
||||
value: (!cfg!(feature = "ssr")).then(|| SendWrapper::new(value)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,13 +115,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {
|
||||
// dry_resolve() only runs during SSR, and we should use it to
|
||||
// synchronously remove and drop the SendWrapper value
|
||||
// we don't need this value during SSR and leaving it here could drop it
|
||||
// from a different thread
|
||||
self.value.take();
|
||||
}
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
|
||||
@@ -7,6 +7,9 @@ use std::cell::Cell;
|
||||
use std::{cell::RefCell, panic::Location, rc::Rc};
|
||||
use web_sys::{Comment, Element, Node, Text};
|
||||
|
||||
#[cfg(feature = "mark_branches")]
|
||||
const COMMENT_NODE: u16 = 8;
|
||||
|
||||
/// Hydration works by walking over the DOM, adding interactivity as needed.
|
||||
///
|
||||
/// This cursor tracks the location in the DOM that is currently being hydrated. Each that type
|
||||
@@ -43,13 +46,27 @@ where
|
||||
///
|
||||
/// Does nothing if there is no child.
|
||||
pub fn child(&self) {
|
||||
//crate::log("advancing to next child of ");
|
||||
//Rndr::log_node(&self.current());
|
||||
let mut inner = self.0.borrow_mut();
|
||||
if let Some(node) = Rndr::first_child(&inner) {
|
||||
*inner = node;
|
||||
}
|
||||
//drop(inner);
|
||||
|
||||
#[cfg(feature = "mark_branches")]
|
||||
{
|
||||
while inner.node_type() == COMMENT_NODE {
|
||||
if let Some(content) = inner.text_content() {
|
||||
if content.starts_with("bo") || content.starts_with("bc") {
|
||||
if let Some(sibling) = Rndr::next_sibling(&inner) {
|
||||
*inner = sibling;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
// //drop(inner);
|
||||
//crate::log(">> which is ");
|
||||
//Rndr::log_node(&self.current());
|
||||
}
|
||||
@@ -58,12 +75,25 @@ where
|
||||
///
|
||||
/// Does nothing if there is no sibling.
|
||||
pub fn sibling(&self) {
|
||||
//crate::log("advancing to next sibling of ");
|
||||
//Rndr::log_node(&self.current());
|
||||
let mut inner = self.0.borrow_mut();
|
||||
if let Some(node) = Rndr::next_sibling(&inner) {
|
||||
*inner = node;
|
||||
}
|
||||
|
||||
#[cfg(feature = "mark_branches")]
|
||||
{
|
||||
while inner.node_type() == COMMENT_NODE {
|
||||
if let Some(content) = inner.text_content() {
|
||||
if content.starts_with("bo") || content.starts_with("bc") {
|
||||
if let Some(sibling) = Rndr::next_sibling(&inner) {
|
||||
*inner = sibling;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
//drop(inner);
|
||||
//crate::log(">> which is ");
|
||||
//Rndr::log_node(&self.current());
|
||||
|
||||
@@ -575,15 +575,7 @@ impl RenderHtml for AnyView {
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
if FROM_SERVER {
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
let state =
|
||||
(self.hydrate_from_server)(self.value, cursor, position);
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
state
|
||||
(self.hydrate_from_server)(self.value, cursor, position)
|
||||
} else {
|
||||
panic!(
|
||||
"hydrating AnyView from inside a ViewTemplate is not \
|
||||
@@ -609,14 +601,8 @@ impl RenderHtml for AnyView {
|
||||
) -> Self::State {
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
let state =
|
||||
(self.hydrate_async)(self.value, cursor, position).await;
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
state
|
||||
}
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
|
||||
@@ -411,21 +411,14 @@ where
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
let state = match self {
|
||||
match self {
|
||||
Either::Left(left) => {
|
||||
Either::Left(left.hydrate::<FROM_SERVER>(cursor, position))
|
||||
}
|
||||
Either::Right(right) => {
|
||||
Either::Right(right.hydrate::<FROM_SERVER>(cursor, position))
|
||||
}
|
||||
};
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
async fn hydrate_async(
|
||||
@@ -433,21 +426,14 @@ where
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
let state = match self {
|
||||
match self {
|
||||
Either::Left(left) => {
|
||||
Either::Left(left.hydrate_async(cursor, position).await)
|
||||
}
|
||||
Either::Right(right) => {
|
||||
Either::Right(right.hydrate_async(cursor, position).await)
|
||||
}
|
||||
};
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
@@ -973,17 +959,11 @@ macro_rules! tuples {
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
let state = match self {
|
||||
$([<EitherOf $num>]::$ty(this) => {
|
||||
[<EitherOf $num>]::$ty(this.hydrate::<FROM_SERVER>(cursor, position))
|
||||
})*
|
||||
};
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
|
||||
Self::State { state }
|
||||
}
|
||||
@@ -993,17 +973,11 @@ macro_rules! tuples {
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
let state = match self {
|
||||
$([<EitherOf $num>]::$ty(this) => {
|
||||
[<EitherOf $num>]::$ty(this.hydrate_async(cursor, position).await)
|
||||
})*
|
||||
};
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
|
||||
Self::State { state }
|
||||
}
|
||||
|
||||
@@ -322,10 +322,6 @@ where
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
|
||||
// get parent and position
|
||||
let current = cursor.current();
|
||||
let parent = if position.get() == Position::FirstChild {
|
||||
@@ -346,22 +342,12 @@ where
|
||||
for (index, item) in items.enumerate() {
|
||||
hashed_items.insert((self.key_fn)(&item));
|
||||
let (set_index, view) = (self.view_fn)(index, item);
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
let item = view.hydrate::<FROM_SERVER>(cursor, position);
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
rendered_items.push(Some((set_index, item)));
|
||||
}
|
||||
let marker = cursor.next_placeholder(position);
|
||||
position.set(Position::NextChild);
|
||||
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
|
||||
KeyedState {
|
||||
parent: Some(parent),
|
||||
marker,
|
||||
@@ -375,10 +361,6 @@ where
|
||||
cursor: &Cursor,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
|
||||
// get parent and position
|
||||
let current = cursor.current();
|
||||
let parent = if position.get() == Position::FirstChild {
|
||||
@@ -399,22 +381,12 @@ where
|
||||
for (index, item) in items.enumerate() {
|
||||
hashed_items.insert((self.key_fn)(&item));
|
||||
let (set_index, view) = (self.view_fn)(index, item);
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
let item = view.hydrate_async(cursor, position).await;
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
rendered_items.push(Some((set_index, item)));
|
||||
}
|
||||
let marker = cursor.next_placeholder(position);
|
||||
position.set(Position::NextChild);
|
||||
|
||||
if cfg!(feature = "mark_branches") {
|
||||
cursor.advance_to_placeholder(position);
|
||||
}
|
||||
|
||||
KeyedState {
|
||||
parent: Some(parent),
|
||||
marker,
|
||||
|
||||
Reference in New Issue
Block a user