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;
left: 0;
right: 0;

View file

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

View file

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