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,309 +66,151 @@ 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) = { return None;
let follower_height = follower_rect.height(); };
let target_y = target_rect.y(); let content_height = content_rect.height();
let target_height = target_rect.height(); let target_top = target_rect.top();
let top = target_y - follower_height; let target_bottom = target_rect.bottom();
let top = target_top - content_height;
let Some(inner_height) = window_inner_height() else { let (top, new_placement) =
return None; if top < 0.0 && target_bottom + content_height <= window_inner_height {
}; (target_bottom, FollowerPlacement::Bottom)
if top < 0.0 && target_y + target_height + follower_height <= inner_height {
(target_y + target_height, 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;
} else { let transform = String::from("translateX(-50%)");
(top, FollowerPlacement::TopStart) (left, new_placement, top, transform)
} } else if placement == FollowerPlacement::TopStart {
}; let left = target_rect.left();
Some(FollowerPlacementOffset { let transform = String::new();
top, (left, new_placement, top, transform)
left, } else if placement == FollowerPlacement::TopEnd {
transform: String::new(), let left = target_rect.right();
placement, let transform = String::from("translateX(-100%)");
}) (left, new_placement, top, transform)
} else {
unreachable!()
}
} }
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(); return None;
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::BottomEnd)
} else {
(top, FollowerPlacement::TopEnd)
}
}; };
Some(FollowerPlacementOffset { let content_height = content_rect.height();
top, let target_top = target_rect.top();
left, let target_bottom = target_rect.bottom();
transform: String::from("translateX(-100%)"), let top = target_bottom;
placement, 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)
};
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::Left => { FollowerPlacement::Left | FollowerPlacement::LeftStart | FollowerPlacement::LeftEnd => {
let top = target_rect.y() + target_rect.height() / 2.0; let Some(window_inner_width) = window_inner_width() else {
let (left, placement) = { return None;
let follower_width = follower_rect.width(); };
let target_x = target_rect.x(); let content_width = content_rect.width();
let target_width = target_rect.width(); let target_left = target_rect.left();
let left = target_x - follower_width; let target_right = target_rect.right();
let left = target_left - content_width;
let Some(inner_width) = window_inner_width() else { leptos::logging::log!(
return None; "{}-{} {} {}",
}; left,
target_right,
if left < 0.0 && target_x + target_width + follower_width > inner_width { content_width,
(target_x + follower_width, FollowerPlacement::Right) window_inner_width
);
let (left, new_placement) =
if left < 0.0 && target_right + content_width <= window_inner_width {
(target_right, FollowerPlacement::Right)
} else { } else {
(left, FollowerPlacement::Left) (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 placement == FollowerPlacement::Left {
if left < 0.0 && target_x + target_width + follower_width > inner_width { let top = target_rect.top() + target_rect.height() / 2.0;
(target_x + follower_width, FollowerPlacement::RightStart) let transform = String::from("translateY(-50%)");
} else { (left, new_placement, top, transform)
(left, FollowerPlacement::LeftStart) } else if placement == FollowerPlacement::LeftStart {
} let top = target_rect.top();
}; let transform = String::new();
Some(FollowerPlacementOffset { (left, new_placement, top, transform)
top, } else if placement == FollowerPlacement::LeftEnd {
left, let top = target_rect.bottom();
transform: String::new(), let transform = String::from("translateY(-100%)");
placement, (left, new_placement, top, transform)
}) } else {
unreachable!()
}
} }
FollowerPlacement::LeftEnd => { FollowerPlacement::Right | FollowerPlacement::RightStart | FollowerPlacement::RightEnd => {
let top = target_rect.y() + target_rect.height(); let Some(window_inner_width) = window_inner_width() else {
let (left, placement) = { return None;
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 { let content_width = content_rect.width();
return None; let target_left = target_rect.left();
}; let target_right = target_rect.right();
let left = target_right;
if left + follower_width > inner_width && target_x - follower_width >= 0.0 { let (left, new_placement) = if left + content_width > window_inner_width
(target_x - follower_width, FollowerPlacement::Left) && target_left - content_width >= 0.0
} else { {
(left, FollowerPlacement::Right) (target_left - content_width, FollowerPlacement::Left)
} } else {
(left, FollowerPlacement::Right)
}; };
Some(FollowerPlacementOffset {
top, if placement == FollowerPlacement::Right {
left, let top = target_rect.top() + target_rect.height() / 2.0;
transform: String::from("translateY(-50%)"), let transform = String::from("translateY(-50%)");
placement, (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 {
unreachable!()
}
} }
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 { Some(FollowerPlacementOffset {
return None; top: top - follower_rect.top(),
}; left: left - follower_rect.left(),
placement,
if left + follower_width > inner_width && target_x - follower_width >= 0.0 { transform,
(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 {
(top, FollowerPlacement::Bottom)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::from("translateX(-50%)"),
placement,
})
}
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 {
return None;
};
if top + follower_height > inner_height && target_y - follower_height >= 0.0 {
(target_y - follower_height, FollowerPlacement::TopStart)
} 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;
};
if top + follower_height > inner_height && target_y - follower_height >= 0.0 {
(target_y - follower_height, FollowerPlacement::TopEnd)
} else {
(top, FollowerPlacement::BottomEnd)
}
};
Some(FollowerPlacementOffset {
top,
left,
transform: String::from("translateX(-100%)"),
placement,
})
}
}
} }
fn window_inner_width() -> Option<f64> { fn window_inner_width() -> Option<f64> {

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> </Provider>
</div> </div>
</Provider> </div>
</Teleport> </Teleport>
} }
} }