Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions packages/dom/src/platform/get_offset_parent.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use floating_ui_utils::dom::{
get_computed_style, get_containing_block, get_parent_node, get_window, is_containing_block,
is_element, is_html_element, is_last_traversable_node, is_table_element, is_top_layer,
get_computed_style, get_containing_block, get_document_element, get_parent_node, get_window,
is_containing_block, is_element, is_html_element, is_last_traversable_node, is_table_element,
is_top_layer, DomNodeOrWindow,
};
use floating_ui_utils::OwnedElementOrWindow;
use web_sys::Window;
Expand All @@ -24,7 +25,26 @@ pub fn get_true_offset_parent(element: &Element, polyfill: &Option<Polyfill>) ->
if let Some(polyfill) = polyfill {
polyfill(element)
} else {
element.offset_parent()
let raw_offset_parent = element.offset_parent();

// Firefox returns the <html> element as the offsetParent if it's non-static, while Chrome and Safari return the <body> element.
// The <body> element must be used to perform the correct calculations even if the <html> element is non-static.
if let Some(raw_offset_parent) = raw_offset_parent.as_ref() {
if get_document_element(Some(DomNodeOrWindow::Node(raw_offset_parent)))
== *raw_offset_parent
{
return Some(
raw_offset_parent
.owner_document()
.expect("Element should have owner document.")
.body()
.expect("Document should have body.")
.unchecked_into::<Element>(),
);
}
}

raw_offset_parent
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/dom/src/utils/get_document_rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn get_document_rect(element: &Element) -> Rect {
.max()
.expect("Iterator is not empty.") as f64;

let mut x = -scroll.scroll_left + get_window_scroll_bar_x(element);
let mut x = -scroll.scroll_left + get_window_scroll_bar_x(element, None);
let y = -scroll.scroll_top;

if is_rtl(&body) {
Expand Down
19 changes: 16 additions & 3 deletions packages/dom/src/utils/get_rect_relative_to_offset_parent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,26 @@ pub fn get_rect_relative_to_offset_parent(
offsets.y = offset_rect.y + offset_parent.client_top() as f64;
}
DomElementOrWindow::Window(_) => {
offsets.x = get_window_scroll_bar_x(&document_element);
// If the <body> scrollbar appears on the left (e.g. RTL systems).
// Use Firefox with layout.scrollbar.side = 3 in about:config to test this.
offsets.x = get_window_scroll_bar_x(&document_element, None);
}
}
}

let x = rect.left + scroll.scroll_left - offsets.x;
let y = rect.top + scroll.scroll_top - offsets.y;
let mut html_x = 0.0;
let mut html_y = 0.0;

if !is_offset_parent_an_element && !is_fixed {
let html_rect = document_element.get_bounding_client_rect();
html_y = html_rect.top() as f64 + scroll.scroll_top;
html_x = html_rect.left()as f64 + scroll.scroll_left
// RTL <body> scrollbar.
- get_window_scroll_bar_x(&document_element, Some(html_rect));
}

let x = rect.left + scroll.scroll_left - offsets.x - html_x;
let y = rect.top + scroll.scroll_top - offsets.y - html_y;

Rect {
x,
Expand Down
27 changes: 17 additions & 10 deletions packages/dom/src/utils/get_window_scroll_bar_x.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use floating_ui_utils::dom::{get_document_element, get_node_scroll};
use web_sys::Element;
use web_sys::{DomRect, Element};

use crate::utils::get_bounding_client_rect::get_bounding_client_rect;

pub fn get_window_scroll_bar_x(element: &Element) -> f64 {
get_bounding_client_rect(
(&get_document_element(Some(element.into()))).into(),
false,
false,
None,
)
.left
+ get_node_scroll(element.into()).scroll_left
// If <html> has a CSS width greater than the viewport, then this will be incorrect for RTL.
pub fn get_window_scroll_bar_x(element: &Element, rect: Option<DomRect>) -> f64 {
let left_scroll = get_node_scroll(element.into()).scroll_left;

if let Some(rect) = rect {
rect.left() + left_scroll
} else {
get_bounding_client_rect(
(&get_document_element(Some(element.into()))).into(),
false,
false,
None,
)
.left
+ left_scroll
}
}
8 changes: 8 additions & 0 deletions packages/leptos/tests/visual/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ main {
display: none;
}

.resize {
resize: both;
max-height: 480px;
max-width: 480px;
min-height: 120px;
min-width: 120px;
}

.prose {
font-size: 1.125rem;
color: #555;
Expand Down
39 changes: 38 additions & 1 deletion packages/leptos/tests/visual/src/spec/relative.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub fn Relative() -> impl IntoView {
let floating_ref = create_node_ref::<Div>();

let (node, set_node) = create_signal(Node::None);
let (offset, set_offset) = create_signal(0);

let UseFloatingReturn {
x,
Expand All @@ -38,14 +39,21 @@ pub fn Relative() -> impl IntoView {
.document_element()
.map(|element| element.unchecked_into::<web_sys::HtmlElement>()),
Node::Body => document().body(),
_ => None,
_ => document()
.query_selector(".container")
.expect("Document should be queried.")
.map(|element| element.unchecked_into::<web_sys::HtmlElement>()),
};

if let Some(element) = element {
element
.style()
.set_property("position", "relative")
.expect("Style should be updated.");
element
.style()
.set_property("top", &format!("{}px", -offset.get()))
.expect("Style should be updated.");
}

update();
Expand All @@ -65,6 +73,10 @@ pub fn Relative() -> impl IntoView {
.style()
.remove_property("position")
.expect("Style should be updated.");
element
.style()
.remove_property("top")
.expect("Style should be updated.");
}
});

Expand Down Expand Up @@ -95,6 +107,7 @@ pub fn Relative() -> impl IntoView {
</div>
</div>

<h2>Node</h2>
<div class="controls">
<For
each=|| ALL_NODES
Expand All @@ -116,5 +129,29 @@ pub fn Relative() -> impl IntoView {
}
/>
</div>

<h2>Offset</h2>
<div class="controls">
<For
each=|| [0, 100]
key=|local_offset| format!("{:?}", local_offset)
children=move |local_offset| {
view! {
<button
data-testid=format!("offset-{local_offset}")
style:background-color=move || match offset() == local_offset {
true => "black",
false => ""
}
on:click=move |_| {
set_offset(local_offset);
}
>
{format!("{local_offset}")}
</button>
}
}
/>
</div>
}
}
Loading