fix: Binder component display position

This commit is contained in:
luoxiao 2024-09-11 21:51:51 +08:00 committed by luoxiaozero
parent 360343e8cf
commit 3e59f506dd
3 changed files with 151 additions and 300 deletions

View file

@ -1,4 +1,4 @@
.thaw-binder-follower-container { .thaw-binder-follower {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;

View file

@ -1,7 +1,7 @@
use leptos::prelude::window; use leptos::prelude::window;
use web_sys::DomRect; use web_sys::DomRect;
#[derive(Clone)] #[derive(Clone, PartialEq)]
pub enum FollowerPlacement { pub enum FollowerPlacement {
Top, Top,
Bottom, Bottom,
@ -66,310 +66,152 @@ pub fn get_follower_placement_offset(
placement: FollowerPlacement, placement: FollowerPlacement,
target_rect: DomRect, target_rect: DomRect,
follower_rect: DomRect, follower_rect: DomRect,
content_rect: DomRect,
) -> Option<FollowerPlacementOffset> { ) -> Option<FollowerPlacementOffset> {
match placement { let (left, placement, top, transform) = match placement {
FollowerPlacement::Top => { FollowerPlacement::Top | FollowerPlacement::TopStart | FollowerPlacement::TopEnd => {
let left = target_rect.x() + target_rect.width() / 2.0; let Some(window_inner_height) = window_inner_height() else {
let (top, placement) = {
let follower_height = follower_rect.height();
let target_y = target_rect.y();
let target_height = target_rect.height();
let top = target_y - follower_height;
let Some(inner_height) = window_inner_height() else {
return None; return None;
}; };
let content_height = content_rect.height();
if top < 0.0 && target_y + target_height + follower_height <= inner_height { let target_top = target_rect.top();
(target_y + target_height, FollowerPlacement::Bottom) let target_bottom = target_rect.bottom();
let top = target_top - content_height;
let (top, new_placement) =
if top < 0.0 && target_bottom + content_height <= window_inner_height {
(target_bottom, FollowerPlacement::Bottom)
} else { } else {
(top, FollowerPlacement::Top) (top, FollowerPlacement::Top)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::from("translateX(-50%)"),
placement,
})
}
FollowerPlacement::TopStart => {
let left = target_rect.x();
let (top, placement) = {
let follower_height = follower_rect.height();
let target_y = target_rect.y();
let target_height = target_rect.height();
let top = target_y - follower_height;
let Some(inner_height) = window_inner_height() else {
return None;
}; };
if top < 0.0 && target_y + target_height + follower_height <= inner_height { if placement == FollowerPlacement::Top {
(target_y + target_height, FollowerPlacement::BottomStart) let left = target_rect.left() + target_rect.width() / 2.0;
let transform = String::from("translateX(-50%)");
(left, new_placement, top, transform)
} else if placement == FollowerPlacement::TopStart {
let left = target_rect.left();
let transform = String::new();
(left, new_placement, top, transform)
} else if placement == FollowerPlacement::TopEnd {
let left = target_rect.right();
let transform = String::from("translateX(-100%)");
(left, new_placement, top, transform)
} else { } else {
(top, FollowerPlacement::TopStart) unreachable!()
} }
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::new(),
placement,
})
} }
FollowerPlacement::TopEnd => { FollowerPlacement::Bottom
let left = target_rect.x() + target_rect.width(); | FollowerPlacement::BottomStart
let (top, placement) = { | FollowerPlacement::BottomEnd => {
let follower_height = follower_rect.height(); let Some(window_inner_height) = window_inner_height() else {
let target_y = target_rect.y();
let target_height = target_rect.height();
let top = target_y - follower_height;
let Some(inner_height) = window_inner_height() else {
return None; return None;
}; };
let content_height = content_rect.height();
if top < 0.0 && target_y + target_height + follower_height <= inner_height { let target_top = target_rect.top();
(target_y + target_height, FollowerPlacement::BottomEnd) let target_bottom = target_rect.bottom();
} else { let top = target_bottom;
(top, FollowerPlacement::TopEnd) let (top, new_placement) = if top + content_height > window_inner_height
} && target_top - content_height >= 0.0
}; {
Some(FollowerPlacementOffset { (target_top - content_height, FollowerPlacement::Top)
top,
left,
transform: String::from("translateX(-100%)"),
placement,
})
}
FollowerPlacement::Left => {
let top = target_rect.y() + target_rect.height() / 2.0;
let (left, placement) = {
let follower_width = follower_rect.width();
let target_x = target_rect.x();
let target_width = target_rect.width();
let left = target_x - follower_width;
let Some(inner_width) = window_inner_width() else {
return None;
};
if left < 0.0 && target_x + target_width + follower_width > inner_width {
(target_x + follower_width, FollowerPlacement::Right)
} else {
(left, FollowerPlacement::Left)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::from("translateY(-50%)"),
placement,
})
}
FollowerPlacement::LeftStart => {
let top = target_rect.y();
let (left, placement) = {
let follower_width = follower_rect.width();
let target_x = target_rect.x();
let target_width = target_rect.width();
let left = target_x - follower_width;
let Some(inner_width) = window_inner_width() else {
return None;
};
if left < 0.0 && target_x + target_width + follower_width > inner_width {
(target_x + follower_width, FollowerPlacement::RightStart)
} else {
(left, FollowerPlacement::LeftStart)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::new(),
placement,
})
}
FollowerPlacement::LeftEnd => {
let top = target_rect.y() + target_rect.height();
let (left, placement) = {
let follower_width = follower_rect.width();
let target_x = target_rect.x();
let target_width = target_rect.width();
let left = target_x - follower_width;
let Some(inner_width) = window_inner_width() else {
return None;
};
if left < 0.0 && target_x + target_width + follower_width > inner_width {
(target_x + follower_width, FollowerPlacement::RightEnd)
} else {
(left, FollowerPlacement::LeftEnd)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::from("translateY(-100%)"),
placement,
})
}
FollowerPlacement::Right => {
let top = target_rect.y() + target_rect.height() / 2.0;
let (left, placement) = {
let follower_width = follower_rect.width();
let target_x = target_rect.x();
let target_width = target_rect.width();
let left = target_x + target_width;
let Some(inner_width) = window_inner_width() else {
return None;
};
if left + follower_width > inner_width && target_x - follower_width >= 0.0 {
(target_x - follower_width, FollowerPlacement::Left)
} else {
(left, FollowerPlacement::Right)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::from("translateY(-50%)"),
placement,
})
}
FollowerPlacement::RightStart => {
let top = target_rect.y();
let (left, placement) = {
let follower_width = follower_rect.width();
let target_x = target_rect.x();
let target_width = target_rect.width();
let left = target_x + target_width;
let Some(inner_width) = window_inner_width() else {
return None;
};
if left + follower_width > inner_width && target_x - follower_width >= 0.0 {
(target_x - follower_width, FollowerPlacement::LeftStart)
} else {
(left, FollowerPlacement::RightStart)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::new(),
placement,
})
}
FollowerPlacement::RightEnd => {
let top = target_rect.y() + target_rect.height();
let (left, placement) = {
let follower_width = follower_rect.width();
let target_x = target_rect.x();
let target_width = target_rect.width();
let left = target_x + target_width;
let Some(inner_width) = window_inner_width() else {
return None;
};
if left + follower_width > inner_width && target_x - follower_width >= 0.0 {
(target_x - follower_width, FollowerPlacement::LeftEnd)
} else {
(left, FollowerPlacement::RightEnd)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::from("translateY(-100%)"),
placement,
})
}
FollowerPlacement::Bottom => {
let left = target_rect.x() + target_rect.width() / 2.0;
let (top, placement) = {
let follower_height = follower_rect.height();
let target_y = target_rect.y();
let target_height = target_rect.height();
let top = target_y + target_height;
let Some(inner_height) = window_inner_height() else {
return None;
};
if top + follower_height > inner_height && target_y - follower_height >= 0.0 {
(target_y - follower_height, FollowerPlacement::Top)
} else { } else {
(top, FollowerPlacement::Bottom) (top, FollowerPlacement::Bottom)
}
}; };
Some(FollowerPlacementOffset { if placement == FollowerPlacement::Bottom {
top, let left = target_rect.left() + target_rect.width() / 2.0;
left, let transform = String::from("translateX(-50%)");
transform: String::from("translateX(-50%)"), (left, new_placement, top, transform)
placement, } else if placement == FollowerPlacement::BottomStart {
}) let left = target_rect.left();
let transform = String::new();
(left, new_placement, top, transform)
} else if placement == FollowerPlacement::BottomEnd {
let left = target_rect.right();
let transform = String::from("translateX(-100%)");
(left, new_placement, top, transform)
} else {
unreachable!()
} }
FollowerPlacement::BottomStart => { }
let left = target_rect.x(); FollowerPlacement::Left | FollowerPlacement::LeftStart | FollowerPlacement::LeftEnd => {
let (top, placement) = { let Some(window_inner_width) = window_inner_width() else {
let follower_height = follower_rect.height(); return None;
let target_y = target_rect.y(); };
let target_height = target_rect.height(); let content_width = content_rect.width();
let top = target_y + target_height; let target_left = target_rect.left();
let target_right = target_rect.right();
let Some(inner_height) = window_inner_height() else { let left = target_left - content_width;
leptos::logging::log!(
"{}-{} {} {}",
left,
target_right,
content_width,
window_inner_width
);
let (left, new_placement) =
if left < 0.0 && target_right + content_width <= window_inner_width {
(target_right, FollowerPlacement::Right)
} else {
(left, FollowerPlacement::Left)
};
if placement == FollowerPlacement::Left {
let top = target_rect.top() + target_rect.height() / 2.0;
let transform = String::from("translateY(-50%)");
(left, new_placement, top, transform)
} else if placement == FollowerPlacement::LeftStart {
let top = target_rect.top();
let transform = String::new();
(left, new_placement, top, transform)
} else if placement == FollowerPlacement::LeftEnd {
let top = target_rect.bottom();
let transform = String::from("translateY(-100%)");
(left, new_placement, top, transform)
} else {
unreachable!()
}
}
FollowerPlacement::Right | FollowerPlacement::RightStart | FollowerPlacement::RightEnd => {
let Some(window_inner_width) = window_inner_width() else {
return None; return None;
}; };
if top + follower_height > inner_height && target_y - follower_height >= 0.0 { let content_width = content_rect.width();
(target_y - follower_height, FollowerPlacement::TopStart) let target_left = target_rect.left();
let target_right = target_rect.right();
let left = target_right;
let (left, new_placement) = if left + content_width > window_inner_width
&& target_left - content_width >= 0.0
{
(target_left - content_width, FollowerPlacement::Left)
} else { } else {
(top, FollowerPlacement::BottomStart) (left, FollowerPlacement::Right)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::new(),
placement,
})
}
FollowerPlacement::BottomEnd => {
let left = target_rect.x() + target_rect.width();
let (top, placement) = {
let follower_height = follower_rect.height();
let target_y = target_rect.y();
let target_height = target_rect.height();
let top = target_y + target_height;
let Some(inner_height) = window_inner_height() else {
return None;
}; };
if top + follower_height > inner_height && target_y - follower_height >= 0.0 { if placement == FollowerPlacement::Right {
(target_y - follower_height, FollowerPlacement::TopEnd) let top = target_rect.top() + target_rect.height() / 2.0;
let transform = String::from("translateY(-50%)");
(left, new_placement, top, transform)
} else if placement == FollowerPlacement::RightStart {
let top = target_rect.top();
let transform = String::new();
(left, new_placement, top, transform)
} else if placement == FollowerPlacement::RightEnd {
let top = target_rect.bottom();
let transform = String::from("translateY(-100%)");
(left, new_placement, top, transform)
} else { } else {
(top, FollowerPlacement::BottomEnd) unreachable!()
}
} }
}; };
Some(FollowerPlacementOffset { Some(FollowerPlacementOffset {
top, top: top - follower_rect.top(),
left, left: left - follower_rect.left(),
transform: String::from("translateX(-100%)"),
placement, placement,
transform,
}) })
} }
}
}
fn window_inner_width() -> Option<f64> { fn window_inner_width() -> Option<f64> {
let Ok(inner_width) = window().inner_width() else { let Ok(inner_width) = window().inner_width() else {

View file

@ -82,16 +82,21 @@ where
let scrollable_element_handle_vec = StoredValue::<Vec<EventListenerHandle>>::new(vec![]); let scrollable_element_handle_vec = StoredValue::<Vec<EventListenerHandle>>::new(vec![]);
let resize_handle = StoredValue::new(None::<WindowListenerHandle>); let resize_handle = StoredValue::new(None::<WindowListenerHandle>);
let follower_ref = NodeRef::<html::Div>::new();
let content_ref = NodeRef::<html::Div>::new(); let content_ref = NodeRef::<html::Div>::new();
let content_style = RwSignal::new(String::new()); let content_style = RwSignal::new(String::new());
let placement_str = RwSignal::new(follower_placement.as_str()); let placement_str = RwSignal::new(follower_placement.as_str());
let sync_position = move || { let sync_position = move || {
let Some(follower_el) = follower_ref.get_untracked() else {
return;
};
let Some(content_ref) = content_ref.get_untracked() else { let Some(content_ref) = content_ref.get_untracked() else {
return; return;
}; };
let Some(target_ref) = target_ref.get_untracked() else { let Some(target_ref) = target_ref.get_untracked() else {
return; return;
}; };
let follower_rect = follower_el.get_bounding_client_rect();
let target_rect = target_ref.get_bounding_client_rect(); let target_rect = target_ref.get_bounding_client_rect();
let content_rect = content_ref.get_bounding_client_rect(); let content_rect = content_ref.get_bounding_client_rect();
let mut style = String::new(); let mut style = String::new();
@ -108,8 +113,12 @@ where
left, left,
transform, transform,
placement, placement,
}) = get_follower_placement_offset(follower_placement, target_rect, content_rect) }) = get_follower_placement_offset(
{ follower_placement,
target_rect,
follower_rect,
content_rect,
) {
placement_str.set(placement.as_str()); placement_str.set(placement.as_str());
style.push_str(&format!( style.push_str(&format!(
"transform-origin: {};", "transform-origin: {};",
@ -197,18 +206,18 @@ where
view! { view! {
{children()} {children()}
<Teleport immediate=follower_show> <Teleport immediate=follower_show>
<Provider value=follower_injection> <div class="thaw-binder-follower" node_ref=follower_ref>
<div class="thaw-binder-follower-container">
<div <div
class="thaw-binder-follower-content" class="thaw-binder-follower-content"
data-thaw-placement=move || placement_str.get() data-thaw-placement=move || placement_str.get()
node_ref=content_ref node_ref=content_ref
style=move || content_style.get() style=move || content_style.get()
> >
<Provider value=follower_injection>
{follower_children()} {follower_children()}
</div>
</div>
</Provider> </Provider>
</div>
</div>
</Teleport> </Teleport>
} }
} }