fix: Binder component sync position

This commit is contained in:
luoxiao 2024-09-11 22:37:26 +08:00 committed by luoxiaozero
parent 3e59f506dd
commit b04feef0d1
14 changed files with 87 additions and 74 deletions

View file

@ -2,7 +2,7 @@ use crate::{ConfigInjection, Icon};
use leptos::{either::Either, ev, html, prelude::*}; use leptos::{either::Either, ev, html, prelude::*};
use thaw_components::{CSSTransition, Teleport}; use thaw_components::{CSSTransition, Teleport};
use thaw_utils::{ use thaw_utils::{
add_event_listener, class_list, get_scroll_parent, mount_style, BoxCallback, add_event_listener, class_list, get_scroll_parent_element, mount_style, BoxCallback,
EventListenerHandle, EventListenerHandle,
}; };
@ -43,7 +43,7 @@ pub fn BackTop(
}; };
request_animation_frame(move || { request_animation_frame(move || {
let scroll_el = get_scroll_parent(&placeholder_el) let scroll_el = get_scroll_parent_element(&placeholder_el)
.unwrap_or_else(|| document().document_element().unwrap()); .unwrap_or_else(|| document().document_element().unwrap());
{ {

View file

@ -55,7 +55,7 @@ pub fn Combobox(
let Some(clear_icon_el) = clear_icon_ref.get() else { let Some(clear_icon_el) = clear_icon_ref.get() else {
return; return;
}; };
let handler = add_event_listener(clear_icon_el.into(), ev::click, move |e| { let handler = add_event_listener(clear_icon_el, ev::click, move |e| {
if disabled.get_untracked() { if disabled.get_untracked() {
return; return;
} }

View file

@ -80,7 +80,7 @@ pub fn Menu(
let Some(target_el) = target_ref.get() else { let Some(target_el) = target_ref.get() else {
return; return;
}; };
let handler = add_event_listener(target_el.into(), ev::click, move |event| { let handler = add_event_listener(target_el, ev::click, move |event| {
if trigger_type != MenuTriggerType::Click { if trigger_type != MenuTriggerType::Click {
return; return;
} }

View file

@ -99,7 +99,7 @@ pub fn Popover(
let Some(target_el) = target_ref.get() else { let Some(target_el) = target_ref.get() else {
return; return;
}; };
let handler = add_event_listener(target_el.into(), ev::click, move |event| { let handler = add_event_listener(target_el, ev::click, move |event| {
if trigger_type != PopoverTriggerType::Click { if trigger_type != PopoverTriggerType::Click {
return; return;
} }

View file

@ -34,7 +34,7 @@ pub fn Upload(
let Some(trigger_el) = trigger_ref.get() else { let Some(trigger_el) = trigger_ref.get() else {
return; return;
}; };
let handle = add_event_listener(trigger_el.into(), ev::click, move |_| { let handle = add_event_listener(trigger_el, ev::click, move |_| {
if let Some(input_ref) = input_ref.get_untracked() { if let Some(input_ref) = input_ref.get_untracked() {
input_ref.click(); input_ref.click();
} }

View file

@ -14,7 +14,7 @@ use leptos::{
leptos_dom::helpers::WindowListenerHandle, leptos_dom::helpers::WindowListenerHandle,
prelude::*, prelude::*,
}; };
use thaw_utils::{add_event_listener, get_scroll_parent, mount_style, EventListenerHandle}; use thaw_utils::{add_event_listener, get_scroll_parent_node, mount_style, EventListenerHandle};
#[slot] #[slot]
pub struct Follower { pub struct Follower {
@ -141,12 +141,12 @@ where
}; };
let mut handle_vec = vec![]; let mut handle_vec = vec![];
let mut cursor = get_scroll_parent(&el); let mut cursor = get_scroll_parent_node(&el);
loop { loop {
if let Some(el) = cursor.take() { if let Some(node) = cursor.take() {
cursor = get_scroll_parent(&el); cursor = get_scroll_parent_node(&node);
let handle = add_event_listener(el, ev::scroll, move |_| { let handle = add_event_listener(node, ev::scroll, move |_| {
sync_position(); sync_position();
}); });
handle_vec.push(handle); handle_vec.push(handle);
@ -213,9 +213,7 @@ where
node_ref=content_ref node_ref=content_ref
style=move || content_style.get() style=move || content_style.get()
> >
<Provider value=follower_injection> <Provider value=follower_injection>{follower_children()}</Provider>
{follower_children()}
</Provider>
</div> </div>
</div> </div>
</Teleport> </Teleport>

View file

@ -92,13 +92,13 @@ where
} }
}; };
let handle = match types { let handle = match types {
AnimationTypes::Transition => add_event_listener( AnimationTypes::Transition => {
el.deref().clone().into(), add_event_listener(el.deref().clone(), ev::transitionend, move |_| {
ev::transitionend, event_listener()
move |_| event_listener(), })
), }
AnimationTypes::Animation => { AnimationTypes::Animation => {
add_event_listener(el.deref().clone().into(), ev::animationend, move |_| { add_event_listener(el.deref().clone(), ev::animationend, move |_| {
event_listener() event_listener()
}) })
} }

View file

@ -1,16 +1,35 @@
use leptos::prelude::*; use leptos::prelude::*;
use web_sys::Element; use wasm_bindgen::JsCast;
use web_sys::{Element, Node};
pub fn get_scroll_parent(element: &Element) -> Option<Element> { pub fn get_scroll_parent_node(node: &Node) -> Option<Node> {
let Some(parent_element) = get_parent_element(element) else { let parent_node = node.parent_node()?;
return None;
};
if parent_element.node_type() == 9 { let node_type = parent_node.node_type();
return Some(parent_element); if node_type == Node::ELEMENT_NODE {
let el = parent_node.clone().dyn_into::<Element>().unwrap();
if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&el) {
let overflow = format!("{overflow}{overflow_x}{overflow_y}");
if overflow.contains("auto") {
return Some(parent_node);
}
if overflow.contains("scroll") {
return Some(parent_node);
}
if overflow.contains("overlay") {
return Some(parent_node);
}
}
} else if node_type == Node::DOCUMENT_NODE {
return Some(document().into());
} }
if parent_element.node_type() == 1 { get_scroll_parent_node(&parent_node)
}
pub fn get_scroll_parent_element(element: &Element) -> Option<Element> {
let parent_element = element.parent_element()?;
if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&parent_element) { if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&parent_element) {
let overflow = format!("{overflow}{overflow_x}{overflow_y}"); let overflow = format!("{overflow}{overflow_x}{overflow_y}");
if overflow.contains("auto") { if overflow.contains("auto") {
@ -23,17 +42,8 @@ pub fn get_scroll_parent(element: &Element) -> Option<Element> {
return Some(parent_element); return Some(parent_element);
} }
} }
}
get_scroll_parent(&parent_element) get_scroll_parent_element(&parent_element)
}
fn get_parent_element(element: &Element) -> Option<Element> {
if element.node_type() == 9 {
None
} else {
element.parent_element()
}
} }
fn get_overflow(parent_element: &Element) -> Option<(String, String, String)> { fn get_overflow(parent_element: &Element) -> Option<(String, String, String)> {

View file

@ -2,6 +2,6 @@ mod get_scroll_parent;
mod mount_style; mod mount_style;
mod scroll_into_view; mod scroll_into_view;
pub use get_scroll_parent::get_scroll_parent; pub use get_scroll_parent::{get_scroll_parent_element, get_scroll_parent_node};
pub use mount_style::{mount_dynamic_style, mount_style}; pub use mount_style::{mount_dynamic_style, mount_style};
pub use scroll_into_view::scroll_into_view; pub use scroll_into_view::scroll_into_view;

View file

@ -3,8 +3,8 @@ use web_sys::HtmlElement;
pub fn scroll_into_view(el: &HtmlElement) { pub fn scroll_into_view(el: &HtmlElement) {
cfg_if! { if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] { cfg_if! { if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] {
use super::get_scroll_parent; use super::get_scroll_parent_element;
if let Some(parent) = get_scroll_parent(el) { if let Some(parent) = get_scroll_parent_element(el) {
let parent_rect = parent.get_bounding_client_rect(); let parent_rect = parent.get_bounding_client_rect();
let el_rect = el.get_bounding_client_rect(); let el_rect = el.get_bounding_client_rect();
if el_rect.y() < parent_rect.y() { if el_rect.y() < parent_rect.y() {

View file

@ -1,9 +1,9 @@
use ::wasm_bindgen::{prelude::Closure, JsCast}; use ::wasm_bindgen::{prelude::Closure, JsCast};
use leptos::ev; use leptos::ev;
use web_sys::{Element, EventTarget}; use web_sys::EventTarget;
pub fn add_event_listener<E>( pub fn add_event_listener<E>(
target: Element, target: impl Into<EventTarget>,
event: E, event: E,
cb: impl Fn(E::EventType) + 'static, cb: impl Fn(E::EventType) + 'static,
) -> EventListenerHandle ) -> EventListenerHandle
@ -31,12 +31,12 @@ impl EventListenerHandle {
} }
fn add_event_listener_untyped( fn add_event_listener_untyped(
target: Element, target: impl Into<EventTarget>,
event_name: &str, event_name: &str,
cb: impl Fn(web_sys::Event) + 'static, cb: impl Fn(web_sys::Event) + 'static,
) -> EventListenerHandle { ) -> EventListenerHandle {
fn wel( fn wel(
target: Element, target: EventTarget,
cb: Box<dyn FnMut(web_sys::Event)>, cb: Box<dyn FnMut(web_sys::Event)>,
event_name: &str, event_name: &str,
) -> EventListenerHandle { ) -> EventListenerHandle {
@ -54,11 +54,11 @@ fn add_event_listener_untyped(
}) })
} }
wel(target, Box::new(cb), event_name) wel(target.into(), Box::new(cb), event_name)
} }
pub fn add_event_listener_with_bool<E: ev::EventDescriptor + 'static>( pub fn add_event_listener_with_bool<E: ev::EventDescriptor + 'static>(
target: impl IntoEventTarget, target: impl Into<EventTarget>,
event: E, event: E,
cb: impl Fn(E::EventType) + 'static, cb: impl Fn(E::EventType) + 'static,
use_capture: bool, use_capture: bool,
@ -67,7 +67,7 @@ where
E::EventType: JsCast, E::EventType: JsCast,
{ {
add_event_listener_untyped_with_bool( add_event_listener_untyped_with_bool(
target.into_event_target(), target,
&event.name(), &event.name(),
move |e| cb(e.unchecked_into::<E::EventType>()), move |e| cb(e.unchecked_into::<E::EventType>()),
use_capture, use_capture,
@ -75,7 +75,7 @@ where
} }
fn add_event_listener_untyped_with_bool( fn add_event_listener_untyped_with_bool(
target: EventTarget, target: impl Into<EventTarget>,
event_name: &str, event_name: &str,
cb: impl Fn(web_sys::Event) + 'static, cb: impl Fn(web_sys::Event) + 'static,
use_capture: bool, use_capture: bool,
@ -107,24 +107,30 @@ fn add_event_listener_untyped_with_bool(
}) })
} }
wel(target, Box::new(cb), event_name, use_capture) wel(target.into(), Box::new(cb), event_name, use_capture)
} }
pub trait IntoEventTarget { // pub trait IntoEventTarget {
fn into_event_target(self) -> EventTarget; // fn into_event_target(self) -> EventTarget;
} // }
impl IntoEventTarget for EventTarget { // impl IntoEventTarget for EventTarget {
fn into_event_target(self) -> EventTarget { // fn into_event_target(self) -> EventTarget {
self // self
} // }
} // }
impl IntoEventTarget for web_sys::Document { // impl IntoEventTarget for web_sys::Document {
fn into_event_target(self) -> EventTarget { // fn into_event_target(self) -> EventTarget {
self.into() // self.into()
} // }
} // }
// impl IntoEventTarget for Element {
// fn into_event_target(self) -> EventTarget {
// self.into()
// }
// }
// impl IntoEventTarget for HtmlElement<AnyElement> { // impl IntoEventTarget for HtmlElement<AnyElement> {
// fn into_event_target(self) -> EventTarget { // fn into_event_target(self) -> EventTarget {

View file

@ -4,6 +4,6 @@ mod signal_watch;
mod stored_maybe_signal; mod stored_maybe_signal;
pub use component_ref::ComponentRef; pub use component_ref::ComponentRef;
pub use model::{Model, OptionModel, VecModel, VecModelWithValue, OptionModelWithValue}; pub use model::{Model, OptionModel, OptionModelWithValue, VecModel, VecModelWithValue};
pub use signal_watch::SignalWatch; pub use signal_watch::SignalWatch;
pub use stored_maybe_signal::StoredMaybeSignal; pub use stored_maybe_signal::StoredMaybeSignal;

View file

@ -58,7 +58,9 @@ impl<T: Send + Sync> OptionModel<T> {
pub fn with_untracked<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> O) -> O { pub fn with_untracked<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> O) -> O {
match self { match self {
Self::T(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::T(value))), Self::T(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::T(value))),
Self::Option(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::Option(value))), Self::Option(read, _, _) => {
read.with_untracked(|value| fun(OptionModelWithValue::Option(value)))
}
} }
} }
} }

View file

@ -67,10 +67,7 @@ impl<T: Send + Sync> VecModel<T> {
} }
} }
pub fn with<O>( pub fn with<O>(&self, fun: impl FnOnce(VecModelWithValue<T>) -> O) -> O {
&self,
fun: impl FnOnce(VecModelWithValue<T>) -> O,
) -> O {
match self { match self {
Self::T(read, _, _) => read.with(|value| fun(VecModelWithValue::T(value))), Self::T(read, _, _) => read.with(|value| fun(VecModelWithValue::T(value))),
Self::Option(read, _, _) => read.with(|value| fun(VecModelWithValue::Option(value))), Self::Option(read, _, _) => read.with(|value| fun(VecModelWithValue::Option(value))),