Compare commits

..

8 Commits

Author SHA1 Message Date
Greg Johnston
635825a3fd fix warnings 2023-04-21 14:24:03 -04:00
Greg Johnston
c9d42bf92b cargo fmt 2023-04-21 13:52:47 -04:00
Greg Johnston
d411cbf861 back/forward issues 2023-04-21 13:52:08 -04:00
Greg Johnston
fbaead4081 Merge branch 'main' into router-animationend-check 2023-04-21 12:47:08 -04:00
Greg Johnston
d3a577c365 cargo fmt 2023-04-21 12:45:08 -04:00
Greg Johnston
f672493a5f fix: forward/back animations (closes #881) 2023-04-19 20:48:12 -04:00
Greg Johnston
b1247b59da fix: multiple navigations before animation is complete (closes #873) 2023-04-19 09:01:30 -04:00
Greg Johnston
6f5902aa62 fix: check that the target of the animation event is actually this element 2023-04-17 21:11:27 -04:00
9 changed files with 92 additions and 52 deletions

View File

@@ -41,11 +41,11 @@ a[aria-current] {
}
.slideIn {
animation: 0.125s slideIn forwards;
animation: 0.25s slideIn forwards;
}
.slideOut {
animation: 0.125s slideOut forwards;
animation: 0.25s slideOut forwards;
}
@keyframes slideIn {
@@ -67,11 +67,11 @@ a[aria-current] {
}
.slideInBack {
animation: 0.125s slideInBack forwards;
animation: 0.25s slideInBack forwards;
}
.slideOutBack {
animation: 0.125s slideOutBack forwards;
animation: 0.25s slideOutBack forwards;
}
@keyframes slideInBack {

View File

@@ -658,14 +658,14 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
}
/// Adds a class to an element.
///
/// **Note**: In the builder syntax, this will be overwritten by the `class`
/// attribute if you use `.attr("class", /* */)`. In the `view` macro, they
///
/// **Note**: In the builder syntax, this will be overwritten by the `class`
/// attribute if you use `.attr("class", /* */)`. In the `view` macro, they
/// are automatically re-ordered so that this over-writing does not happen.
///
///
/// # Panics
/// This directly uses the browsers `classList` API, which means it will throw
/// a runtime error if you pass more than a single class name. If you want to
/// This directly uses the browsers `classList` API, which means it will throw
/// a runtime error if you pass more than a single class name. If you want to
/// pass more than one class name at a time, you can use [HtmlElement::classes].
#[track_caller]
pub fn class(

View File

@@ -843,7 +843,9 @@ fn element_to_tokens(
let attrs = node.attributes.iter().filter_map(|node| {
if let Node::Attribute(node) = node {
let name = node.key.to_string();
if name.trim().starts_with("class:") || fancy_class_name(&name, cx, node).is_some() {
if name.trim().starts_with("class:")
|| fancy_class_name(&name, cx, node).is_some()
{
None
} else {
Some(attribute_to_tokens(cx, node, global_class))
@@ -857,8 +859,7 @@ fn element_to_tokens(
let name = node.key.to_string();
if let Some((fancy, _, _)) = fancy_class_name(&name, cx, node) {
Some(fancy)
}
else if name.trim().starts_with("class:") {
} else if name.trim().starts_with("class:") {
Some(attribute_to_tokens(cx, node, global_class))
} else {
None

View File

@@ -166,14 +166,25 @@ pub fn AnimatedOutlet(
animation_class.to_string()
}
};
let node_ref = create_node_ref::<html::Div>(cx);
let animationend = move |ev: AnimationEvent| {
ev.stop_propagation();
let current = current_animation.get();
set_animation_state.update(|current_state| {
let (next, _) =
animation.next_state(&current, is_back.get_untracked());
*current_state = next;
});
use wasm_bindgen::JsCast;
if let Some(target) = ev.target() {
let node_ref = node_ref.get();
if node_ref.is_none()
|| target
.unchecked_ref::<web_sys::Node>()
.is_same_node(Some(&*node_ref.unwrap()))
{
ev.stop_propagation();
let current = current_animation.get();
set_animation_state.update(|current_state| {
let (next, _) =
animation.next_state(&current, is_back.get_untracked());
*current_state = next;
});
}
}
};
view! { cx,

View File

@@ -55,6 +55,7 @@ pub(crate) struct RouterContextInner {
state: ReadSignal<State>,
set_state: WriteSignal<State>,
pub(crate) is_back: RwSignal<bool>,
pub(crate) path_stack: StoredValue<Vec<String>>,
}
impl std::fmt::Debug for RouterContextInner {
@@ -68,6 +69,7 @@ impl std::fmt::Debug for RouterContextInner {
.field("referrers", &self.referrers)
.field("state", &self.state)
.field("set_state", &self.set_state)
.field("path_stack", &self.path_stack)
.finish()
}
}
@@ -111,7 +113,6 @@ impl RouterContext {
replace: true,
scroll: false,
state: State(None),
back: false,
});
}
}
@@ -154,6 +155,10 @@ impl RouterContext {
let inner = Rc::new(RouterContextInner {
base_path: base_path.into_owned(),
path_stack: store_value(
cx,
vec![location.pathname.get_untracked()],
),
location,
base,
history: Box::new(history),
@@ -203,7 +208,6 @@ impl RouterContextInner {
self: Rc<Self>,
to: &str,
options: &NavigateOptions,
back: bool,
) -> Result<(), NavigationError> {
let cx = self.cx;
let this = Rc::clone(&self);
@@ -231,7 +235,6 @@ impl RouterContextInner {
replace: options.replace,
scroll: options.scroll,
state: self.state.get(),
back,
});
}
let len = self.referrers.borrow().len();
@@ -249,13 +252,17 @@ impl RouterContextInner {
let next_state = state.clone();
move |state| *state = next_state
});
self.path_stack.update_value(|stack| {
stack.push(resolved_to.clone())
});
if referrers.borrow().len() == len {
this.navigate_end(LocationChange {
value: resolved_to,
replace: false,
scroll: true,
state,
back,
})
}
}
@@ -280,6 +287,8 @@ impl RouterContextInner {
#[cfg(not(feature = "ssr"))]
pub(crate) fn handle_anchor_click(self: Rc<Self>, ev: web_sys::Event) {
use wasm_bindgen::JsValue;
let ev = ev.unchecked_into::<web_sys::MouseEvent>();
if ev.default_prevented()
|| ev.button() != 0
@@ -343,7 +352,7 @@ impl RouterContextInner {
leptos_dom::helpers::get_property(a.unchecked_ref(), "state")
.ok()
.and_then(|value| {
if value == wasm_bindgen::JsValue::UNDEFINED {
if value == JsValue::UNDEFINED {
None
} else {
Some(value)
@@ -365,7 +374,6 @@ impl RouterContextInner {
scroll: !a.has_attribute("noscroll"),
state: State(state),
},
false,
) {
leptos::error!("{e:#?}");
}

View File

@@ -121,7 +121,10 @@ pub fn AnimatedRoutes(
create_signal(cx, AnimationState::Finally);
let next_route = router.pathname();
let is_complete = Rc::new(Cell::new(true));
let animation_and_route = create_memo(cx, {
let is_complete = Rc::clone(&is_complete);
move |prev: Option<&(AnimationState, String)>| {
let animation_state = animation_state.get();
let next_route = next_route.get();
@@ -140,7 +143,7 @@ pub fn AnimatedRoutes(
let (next_state, can_advance) = animation
.next_state(prev_state, is_back.get_untracked());
if can_advance {
if can_advance || !is_complete.get() {
(next_state, next_route)
} else {
(next_state, prev_route.to_owned())
@@ -158,8 +161,10 @@ pub fn AnimatedRoutes(
let route_states = route_states(cx, &router, current_route, &root_equal);
let root = root_route(cx, base_route, route_states, root_equal);
let node_ref = create_node_ref::<html::Div>(cx);
html::div(cx)
.node_ref(node_ref)
.attr(
"class",
(cx, move || {
@@ -171,6 +176,7 @@ pub fn AnimatedRoutes(
AnimationState::OutroBack => outro_back.unwrap_or_default(),
AnimationState::IntroBack => intro_back.unwrap_or_default(),
};
is_complete.set(animation_class == finally.unwrap_or_default());
if let Some(class) = &class {
format!("{} {animation_class}", class.get())
} else {
@@ -178,13 +184,21 @@ pub fn AnimatedRoutes(
}
}),
)
.on(leptos::ev::animationend, move |_| {
let current = current_animation.get();
set_animation_state.update(|current_state| {
let (next, _) =
animation.next_state(&current, is_back.get_untracked());
*current_state = next;
})
.on(leptos::ev::animationend, move |ev| {
use wasm_bindgen::JsCast;
if let Some(target) = ev.target() {
if target
.unchecked_ref::<web_sys::Node>()
.is_same_node(Some(&*node_ref.get().unwrap()))
{
let current = current_animation.get();
set_animation_state.update(|current_state| {
let (next, _) = animation
.next_state(&current, is_back.get_untracked());
*current_state = next;
})
}
}
})
.child(move || root.get())
.into_view(cx)

View File

@@ -62,8 +62,6 @@ pub struct LocationChange {
pub scroll: bool,
/// The [`state`](https://developer.mozilla.org/en-US/docs/Web/API/History/state) that will be added during navigation.
pub state: State,
/// Whether the navigation is a “back” navigation.
pub back: bool,
}
impl Default for LocationChange {
@@ -73,7 +71,6 @@ impl Default for LocationChange {
replace: true,
scroll: true,
state: Default::default(),
back: false,
}
}
}

View File

@@ -35,7 +35,7 @@ pub trait History {
pub struct BrowserIntegration {}
impl BrowserIntegration {
fn current(back: bool) -> LocationChange {
fn current() -> LocationChange {
let loc = leptos_dom::helpers::location();
LocationChange {
value: loc.pathname().unwrap_or_default()
@@ -43,8 +43,7 @@ impl BrowserIntegration {
+ &loc.hash().unwrap_or_default(),
replace: true,
scroll: true,
state: State(None), // TODO
back,
state: State(None),
}
}
}
@@ -53,14 +52,28 @@ impl History for BrowserIntegration {
fn location(&self, cx: Scope) -> ReadSignal<LocationChange> {
use crate::{NavigateOptions, RouterContext};
let (location, set_location) = create_signal(cx, Self::current(false));
let (location, set_location) = create_signal(cx, Self::current());
leptos::window_event_listener("popstate", move |_| {
let router = use_context::<RouterContext>(cx);
if let Some(router) = router {
let path_stack = router.inner.path_stack;
let is_back = router.inner.is_back;
let change = Self::current(true);
is_back.set(true);
let change = Self::current();
let is_navigating_back = path_stack.with_value(|stack| {
stack.len() == 1
|| stack.get(stack.len() - 2) == Some(&change.value)
});
if is_navigating_back {
path_stack.update_value(|stack| {
stack.pop();
});
}
is_back.set(is_navigating_back);
request_animation_frame(move || {
is_back.set(false);
});
@@ -72,11 +85,10 @@ impl History for BrowserIntegration {
scroll: change.scroll,
state: change.state,
},
true,
) {
leptos::error!("{e:#?}");
}
set_location.set(Self::current(true));
set_location.set(Self::current());
} else {
leptos::warn!("RouterContext not found");
}
@@ -97,12 +109,10 @@ impl History for BrowserIntegration {
)
.unwrap_throw();
} else {
// push the "forward direction" marker
let state = &loc.state.to_js_value();
history
.push_state_with_url(
&loc.state.to_js_value(),
"",
Some(&loc.value),
)
.push_state_with_url(state, "", Some(&loc.value))
.unwrap_throw();
}
// scroll to el
@@ -172,7 +182,6 @@ impl History for ServerIntegration {
replace: false,
scroll: true,
state: State(None),
back: false,
},
)
.0

View File

@@ -81,7 +81,7 @@ pub fn use_navigate(
) -> impl Fn(&str, NavigateOptions) -> Result<(), NavigationError> {
let router = use_router(cx);
move |to, options| {
Rc::clone(&router.inner).navigate_from_route(to, &options, false)
Rc::clone(&router.inner).navigate_from_route(to, &options)
}
}