diff --git a/thaw/src/dialog/dialog.rs b/thaw/src/dialog/dialog.rs index dadc1efa..eaa0c5c8 100644 --- a/thaw/src/dialog/dialog.rs +++ b/thaw/src/dialog/dialog.rs @@ -2,7 +2,7 @@ use crate::ConfigInjection; use leptos::{context::Provider, ev, prelude::*}; use thaw_components::{FocusTrap, Teleport}; use leptos_transition_group::CSSTransition; -use thaw_utils::{class_list, mount_style, Model}; +use thaw_utils::{class_list, mount_style, use_lock_html_scroll, Model}; #[component] pub fn Dialog( @@ -21,6 +21,11 @@ pub fn Dialog( mount_style("dialog", include_str!("./dialog.css")); let config_provider = ConfigInjection::expect_context(); + // Prevent the page behind the backdrop from scrolling while the dialog + // is open. Same as OverlayDrawer — the Dialog was the only overlay + // component missing this. + use_lock_html_scroll(open.signal()); + let on_mask_click = move |_| { if mask_closeable.get_untracked() { open.set(false); diff --git a/thaw_utils/src/hooks/use_lock_html_scroll.rs b/thaw_utils/src/hooks/use_lock_html_scroll.rs index 35c30b1a..4aca74c1 100644 --- a/thaw_utils/src/hooks/use_lock_html_scroll.rs +++ b/thaw_utils/src/hooks/use_lock_html_scroll.rs @@ -24,8 +24,8 @@ pub fn use_lock_html_scroll(is_lock: Signal) { let style = document() .create_element("style") .expect("create style element error"); - _ = style.set_attribute("data-id", &format!("thaw-lock-html-scroll")); - style.set_text_content(Some("html { overflow: hidden; }")); + _ = style.set_attribute("data-id", "thaw-lock-html-scroll"); + style.set_text_content(Some(&lock_scroll_css())); _ = head.append_child(&style); style_el.update_value(move |el| { *el = SendWrapper::new(Some(style)); @@ -48,3 +48,31 @@ pub fn use_lock_html_scroll(is_lock: Signal) { _ = is_lock; } } + +/// Build the CSS rule that locks scrolling and compensates for the scrollbar. +/// +/// When `overflow: hidden` removes the scrollbar, the viewport widens by +/// the scrollbar's width, causing `margin: auto` centered content to shift. +/// Adding `padding-right` equal to the scrollbar width keeps the layout stable. +#[cfg(any(feature = "csr", feature = "hydrate"))] +fn lock_scroll_css() -> String { + use leptos::prelude::document; + + let scrollbar_width = document() + .document_element() + .map(|html| { + let client_w = html.client_width(); + let window_w = web_sys::window() + .and_then(|w| w.inner_width().ok()) + .and_then(|v| v.as_f64()) + .unwrap_or(0.0) as i32; + window_w - client_w + }) + .unwrap_or(0); + + if scrollbar_width > 0 { + format!("html {{ overflow: hidden; padding-right: {scrollbar_width}px; }}") + } else { + "html { overflow: hidden; }".to_string() + } +}