mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-23 06:19:22 -05:00
Feat/back top (#169)
* feat: thaw_utils adds get_scroll_parent * feat: add BackTop * feat: BackTop scroll
This commit is contained in:
parent
95abde2b9d
commit
1b0f664dc7
15 changed files with 351 additions and 82 deletions
|
@ -49,6 +49,7 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
||||||
<Route path="/alert" view=AlertMdPage/>
|
<Route path="/alert" view=AlertMdPage/>
|
||||||
<Route path="/auto-complete" view=AutoCompleteMdPage/>
|
<Route path="/auto-complete" view=AutoCompleteMdPage/>
|
||||||
<Route path="/avatar" view=AvatarMdPage/>
|
<Route path="/avatar" view=AvatarMdPage/>
|
||||||
|
<Route path="/back-top" view=BackTopMdPage/>
|
||||||
<Route path="/badge" view=BadgeMdPage/>
|
<Route path="/badge" view=BadgeMdPage/>
|
||||||
<Route path="/breadcrumb" view=BreadcrumbMdPage/>
|
<Route path="/breadcrumb" view=BreadcrumbMdPage/>
|
||||||
<Route path="/button" view=ButtonMdPage/>
|
<Route path="/button" view=ButtonMdPage/>
|
||||||
|
|
|
@ -208,6 +208,10 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||||
MenuGroupOption {
|
MenuGroupOption {
|
||||||
label: "Navigation Components".into(),
|
label: "Navigation Components".into(),
|
||||||
children: vec![
|
children: vec![
|
||||||
|
MenuItemOption {
|
||||||
|
value: "back-top".into(),
|
||||||
|
label: "Back Top".into(),
|
||||||
|
},
|
||||||
MenuItemOption {
|
MenuItemOption {
|
||||||
value: "breadcrumb".into(),
|
value: "breadcrumb".into(),
|
||||||
label: "Breadcrumb".into(),
|
label: "Breadcrumb".into(),
|
||||||
|
|
46
demo_markdown/docs/back_top/mod.md
Normal file
46
demo_markdown/docs/back_top/mod.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Back Top
|
||||||
|
|
||||||
|
BackTop will find its first scrollable ascendant element and listen scroll event on it.
|
||||||
|
|
||||||
|
```rust demo
|
||||||
|
view! {
|
||||||
|
<BackTop />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Visibility height
|
||||||
|
|
||||||
|
```rust demo
|
||||||
|
view! {
|
||||||
|
<BackTop bottom=100 visibility_height=280>
|
||||||
|
<div style="width: 200px; text-align: center;">
|
||||||
|
"Visibility Height: 280px"
|
||||||
|
</div>
|
||||||
|
</BackTop>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change position
|
||||||
|
|
||||||
|
```rust demo
|
||||||
|
view! {
|
||||||
|
<BackTop right=40 bottom=160>
|
||||||
|
<div style="width: 200px; text-align: center;">
|
||||||
|
"Change Position"
|
||||||
|
</div>
|
||||||
|
</BackTop>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### BackTop Props
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the back top element. |
|
||||||
|
| right | `MaybeSignal<i32>` | `40` | The width of BackTop from the right side of the page. |
|
||||||
|
| bottom | `MaybeSignal<i32>` | `40` | The height of BackTop from the bottom of the page. |
|
||||||
|
| bottom | `MaybeSignal<i32>` | `180` | BackTop's trigger scroll top. |
|
||||||
|
| children | `Option<Children>` | `None` | BackTop's content. |
|
||||||
|
|
||||||
|
<div style="height: 600px">
|
||||||
|
</div>
|
|
@ -31,6 +31,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
|
||||||
"AlertMdPage" => "../docs/alert/mod.md",
|
"AlertMdPage" => "../docs/alert/mod.md",
|
||||||
"AutoCompleteMdPage" => "../docs/auto_complete/mod.md",
|
"AutoCompleteMdPage" => "../docs/auto_complete/mod.md",
|
||||||
"AvatarMdPage" => "../docs/avatar/mod.md",
|
"AvatarMdPage" => "../docs/avatar/mod.md",
|
||||||
|
"BackTopMdPage" => "../docs/back_top/mod.md",
|
||||||
"BadgeMdPage" => "../docs/badge/mod.md",
|
"BadgeMdPage" => "../docs/badge/mod.md",
|
||||||
"BreadcrumbMdPage" => "../docs/breadcrumb/mod.md",
|
"BreadcrumbMdPage" => "../docs/breadcrumb/mod.md",
|
||||||
"ButtonMdPage" => "../docs/button/mod.md",
|
"ButtonMdPage" => "../docs/button/mod.md",
|
||||||
|
|
|
@ -21,6 +21,8 @@ web-sys = { version = "0.3.69", features = [
|
||||||
"File",
|
"File",
|
||||||
"FileList",
|
"FileList",
|
||||||
"DataTransfer",
|
"DataTransfer",
|
||||||
|
"ScrollToOptions",
|
||||||
|
"ScrollBehavior",
|
||||||
] }
|
] }
|
||||||
wasm-bindgen = "0.2.92"
|
wasm-bindgen = "0.2.92"
|
||||||
icondata_core = "0.1.0"
|
icondata_core = "0.1.0"
|
||||||
|
|
60
thaw/src/back_top/back-top.css
Normal file
60
thaw/src/back_top/back-top.css
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
.thaw-back-top {
|
||||||
|
position: fixed;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border-radius: 22px;
|
||||||
|
height: 44px;
|
||||||
|
min-width: 44px;
|
||||||
|
box-shadow: 0 2px 8px 0px rgba(0, 0, 0, 0.12);
|
||||||
|
background-color: var(--thaw-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top.fade-in-scale-up-transition-leave-active {
|
||||||
|
transform-origin: inherit;
|
||||||
|
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1),
|
||||||
|
transform 0.2s cubic-bezier(0.4, 0, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top.fade-in-scale-up-transition-enter-active {
|
||||||
|
transform-origin: inherit;
|
||||||
|
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
|
||||||
|
transform 0.2s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top.fade-in-scale-up-transition-enter-from,
|
||||||
|
.thaw-back-top.fade-in-scale-up-transition-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top.fade-in-scale-up-transition-leave-from,
|
||||||
|
.thaw-back-top.fade-in-scale-up-transition-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top > svg {
|
||||||
|
font-size: 24px;
|
||||||
|
transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top:hover {
|
||||||
|
box-shadow: 0 2px 12px 0px #0000002e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top:hover > svg {
|
||||||
|
color: var(--thaw-icon-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top:active {
|
||||||
|
box-shadow: 0 2px 12px 0px #0000002e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-back-top:active svg {
|
||||||
|
color: var(--thaw-icon-color-active);
|
||||||
|
}
|
126
thaw/src/back_top/mod.rs
Normal file
126
thaw/src/back_top/mod.rs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
mod theme;
|
||||||
|
|
||||||
|
pub use theme::BackTopTheme;
|
||||||
|
|
||||||
|
use crate::{use_theme, Icon, Theme};
|
||||||
|
use leptos::{html::ToHtmlElement, *};
|
||||||
|
use thaw_components::{CSSTransition, Fallback, OptionComp, Teleport};
|
||||||
|
use thaw_utils::{
|
||||||
|
add_event_listener, class_list, get_scroll_parent, mount_style, EventListenerHandle, OptionalProp,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn BackTop(
|
||||||
|
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||||
|
#[prop(default=40.into(), into)] right: MaybeSignal<i32>,
|
||||||
|
#[prop(default=40.into(), into)] bottom: MaybeSignal<i32>,
|
||||||
|
#[prop(default=180.into(), into)] visibility_height: MaybeSignal<i32>,
|
||||||
|
#[prop(optional)] children: Option<Children>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
mount_style("back-top", include_str!("./back-top.css"));
|
||||||
|
let theme = use_theme(Theme::light);
|
||||||
|
let style = Memo::new(move |_| {
|
||||||
|
let mut style = String::new();
|
||||||
|
style.push_str(&format!("right: {}px;", right.get_untracked()));
|
||||||
|
style.push_str(&format!("bottom: {}px;", bottom.get_untracked()));
|
||||||
|
theme.with(|theme| {
|
||||||
|
style.push_str(&format!(
|
||||||
|
"--thaw-icon-color-hover: {};",
|
||||||
|
theme.common.color_primary_hover
|
||||||
|
));
|
||||||
|
style.push_str(&format!(
|
||||||
|
"--thaw-icon-color-active: {};",
|
||||||
|
theme.common.color_primary_active
|
||||||
|
));
|
||||||
|
style.push_str(&format!(
|
||||||
|
"--thaw-background-color: {};",
|
||||||
|
theme.back_top.background_color
|
||||||
|
));
|
||||||
|
});
|
||||||
|
style
|
||||||
|
});
|
||||||
|
let placeholder_ref = NodeRef::<html::Div>::new();
|
||||||
|
let back_top_ref = NodeRef::<html::Div>::new();
|
||||||
|
let is_show_back_top = RwSignal::new(false);
|
||||||
|
let scroll_top = RwSignal::new(0);
|
||||||
|
|
||||||
|
let _ = watch(
|
||||||
|
move || scroll_top.get(),
|
||||||
|
move |scroll_top, _, _| {
|
||||||
|
is_show_back_top.set(scroll_top > &visibility_height.get());
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let scroll_to_top = StoredValue::new(None::<Callback<()>>);
|
||||||
|
let scroll_handle = StoredValue::new(None::<EventListenerHandle>);
|
||||||
|
|
||||||
|
placeholder_ref.on_load(move |el| {
|
||||||
|
request_animation_frame(move || {
|
||||||
|
let scroll_el = get_scroll_parent(&el.into_any())
|
||||||
|
.unwrap_or_else(|| document().document_element().unwrap().to_leptos_element());
|
||||||
|
|
||||||
|
{
|
||||||
|
let scroll_el = scroll_el.clone();
|
||||||
|
scroll_to_top.set_value(Some(Callback::new(move |_| {
|
||||||
|
scroll_el.scroll_to_with_scroll_to_options(
|
||||||
|
web_sys::ScrollToOptions::new()
|
||||||
|
.top(0.0)
|
||||||
|
.behavior(web_sys::ScrollBehavior::Smooth),
|
||||||
|
);
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle = add_event_listener(scroll_el.clone(), ev::scroll, move |_| {
|
||||||
|
scroll_top.set(scroll_el.scroll_top());
|
||||||
|
});
|
||||||
|
scroll_handle.set_value(Some(handle));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
on_cleanup(move || {
|
||||||
|
scroll_handle.update_value(|handle| {
|
||||||
|
if let Some(handle) = handle.take() {
|
||||||
|
handle.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let on_click = move |_| {
|
||||||
|
scroll_to_top.with_value(|scroll_to_top| {
|
||||||
|
if let Some(scroll_to_top) = scroll_to_top {
|
||||||
|
scroll_to_top.call(());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div style="display: none" class="thaw-back-top-placeholder" ref=placeholder_ref>
|
||||||
|
<Teleport immediate=is_show_back_top>
|
||||||
|
<CSSTransition
|
||||||
|
node_ref=back_top_ref
|
||||||
|
name="fade-in-scale-up-transition"
|
||||||
|
appear=is_show_back_top.get_untracked()
|
||||||
|
show=is_show_back_top
|
||||||
|
let:display
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=class_list!["thaw-back-top", class.map(| c | move || c.get())]
|
||||||
|
ref=back_top_ref
|
||||||
|
style=move || {
|
||||||
|
display.get().map(|d| d.to_string()).unwrap_or_else(|| style.get())
|
||||||
|
}
|
||||||
|
on:click=on_click
|
||||||
|
>
|
||||||
|
<OptionComp value=children let:children>
|
||||||
|
<Fallback slot>
|
||||||
|
<Icon icon=icondata_ai::AiVerticalAlignTopOutlined/>
|
||||||
|
</Fallback>
|
||||||
|
{children()}
|
||||||
|
</OptionComp>
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
</Teleport>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
21
thaw/src/back_top/theme.rs
Normal file
21
thaw/src/back_top/theme.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::theme::ThemeMethod;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BackTopTheme {
|
||||||
|
pub background_color: String,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemeMethod for BackTopTheme {
|
||||||
|
fn light() -> Self {
|
||||||
|
Self {
|
||||||
|
background_color: "#fff".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dark() -> Self {
|
||||||
|
Self {
|
||||||
|
background_color: "#48484e".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
mod alert;
|
mod alert;
|
||||||
mod auto_complete;
|
mod auto_complete;
|
||||||
mod avatar;
|
mod avatar;
|
||||||
|
mod back_top;
|
||||||
mod badge;
|
mod badge;
|
||||||
mod breadcrumb;
|
mod breadcrumb;
|
||||||
mod button;
|
mod button;
|
||||||
|
@ -46,6 +47,7 @@ mod upload;
|
||||||
pub use alert::*;
|
pub use alert::*;
|
||||||
pub use auto_complete::*;
|
pub use auto_complete::*;
|
||||||
pub use avatar::*;
|
pub use avatar::*;
|
||||||
|
pub use back_top::*;
|
||||||
pub use badge::*;
|
pub use badge::*;
|
||||||
pub use breadcrumb::*;
|
pub use breadcrumb::*;
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
|
|
|
@ -3,10 +3,11 @@ mod common;
|
||||||
use self::common::CommonTheme;
|
use self::common::CommonTheme;
|
||||||
use crate::{
|
use crate::{
|
||||||
mobile::{NavBarTheme, TabbarTheme},
|
mobile::{NavBarTheme, TabbarTheme},
|
||||||
AlertTheme, AutoCompleteTheme, AvatarTheme, BreadcrumbTheme, ButtonTheme, CalendarTheme,
|
AlertTheme, AutoCompleteTheme, AvatarTheme, BackTopTheme, BreadcrumbTheme, ButtonTheme,
|
||||||
CollapseTheme, ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, MessageTheme,
|
CalendarTheme, CollapseTheme, ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme,
|
||||||
PopoverTheme, ProgressTheme, ScrollbarTheme, SelectTheme, SkeletionTheme, SliderTheme,
|
MessageTheme, PopoverTheme, ProgressTheme, ScrollbarTheme, SelectTheme, SkeletionTheme,
|
||||||
SpinnerTheme, SwitchTheme, TableTheme, TagTheme, TimePickerTheme, TypographyTheme, UploadTheme,
|
SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, TagTheme, TimePickerTheme, TypographyTheme,
|
||||||
|
UploadTheme,
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ pub struct Theme {
|
||||||
pub popover: PopoverTheme,
|
pub popover: PopoverTheme,
|
||||||
pub collapse: CollapseTheme,
|
pub collapse: CollapseTheme,
|
||||||
pub scrollbar: ScrollbarTheme,
|
pub scrollbar: ScrollbarTheme,
|
||||||
|
pub back_top: BackTopTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
|
@ -80,6 +82,7 @@ impl Theme {
|
||||||
popover: PopoverTheme::light(),
|
popover: PopoverTheme::light(),
|
||||||
collapse: CollapseTheme::light(),
|
collapse: CollapseTheme::light(),
|
||||||
scrollbar: ScrollbarTheme::light(),
|
scrollbar: ScrollbarTheme::light(),
|
||||||
|
back_top: BackTopTheme::light(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn dark() -> Self {
|
pub fn dark() -> Self {
|
||||||
|
@ -113,6 +116,7 @@ impl Theme {
|
||||||
popover: PopoverTheme::dark(),
|
popover: PopoverTheme::dark(),
|
||||||
collapse: CollapseTheme::dark(),
|
collapse: CollapseTheme::dark(),
|
||||||
scrollbar: ScrollbarTheme::dark(),
|
scrollbar: ScrollbarTheme::dark(),
|
||||||
|
back_top: BackTopTheme::dark(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,10 @@ pub use get_placement_style::FollowerPlacement;
|
||||||
|
|
||||||
use crate::Teleport;
|
use crate::Teleport;
|
||||||
use get_placement_style::{get_follower_placement_offset, FollowerPlacementOffset};
|
use get_placement_style::{get_follower_placement_offset, FollowerPlacementOffset};
|
||||||
use leptos::{
|
use leptos::{html::ElementDescriptor, leptos_dom::helpers::WindowListenerHandle, *};
|
||||||
html::{AnyElement, ElementDescriptor, ToHtmlElement},
|
use thaw_utils::{
|
||||||
leptos_dom::helpers::WindowListenerHandle,
|
add_event_listener, get_scroll_parent, mount_style, with_hydration_off, EventListenerHandle,
|
||||||
*,
|
|
||||||
};
|
};
|
||||||
use thaw_utils::{add_event_listener, mount_style, with_hydration_off, EventListenerHandle};
|
|
||||||
|
|
||||||
#[slot]
|
#[slot]
|
||||||
pub struct Follower {
|
pub struct Follower {
|
||||||
|
@ -74,26 +72,26 @@ pub fn Binder<El: ElementDescriptor + Clone + 'static>(
|
||||||
let resize_handle = store_value(None::<WindowListenerHandle>);
|
let resize_handle = store_value(None::<WindowListenerHandle>);
|
||||||
|
|
||||||
let ensure_scroll_listener = move || {
|
let ensure_scroll_listener = move || {
|
||||||
let mut cursor = target_ref.get_untracked().map(|target| target.into_any());
|
let Some(el) = target_ref.get_untracked().map(|target| target.into_any()) else {
|
||||||
let mut scrollable_element_vec = vec![];
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut handle_vec = vec![];
|
||||||
|
let mut cursor = get_scroll_parent(&el);
|
||||||
loop {
|
loop {
|
||||||
cursor = get_scroll_parent(cursor);
|
if let Some(el) = cursor.take() {
|
||||||
if let Some(cursor) = cursor.take() {
|
cursor = get_scroll_parent(&el);
|
||||||
scrollable_element_vec.push(cursor);
|
|
||||||
|
let handle = add_event_listener(el, ev::scroll, move |_| {
|
||||||
|
if let Some(scroll_listener) = scroll_listener.get_value() {
|
||||||
|
scroll_listener.call(());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handle_vec.push(handle);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let handle_vec = scrollable_element_vec
|
|
||||||
.into_iter()
|
|
||||||
.map(|ele| {
|
|
||||||
add_event_listener(ele, ev::scroll, move |_| {
|
|
||||||
if let Some(scroll_listener) = scroll_listener.get_value() {
|
|
||||||
scroll_listener.call(());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
scrollable_element_handle_vec.set_value(handle_vec);
|
scrollable_element_handle_vec.set_value(handle_vec);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -241,59 +239,3 @@ fn FollowerContainer<El: ElementDescriptor + Clone + 'static>(
|
||||||
|
|
||||||
view! { <Teleport element=children immediate=show/> }
|
view! { <Teleport element=children immediate=show/> }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_scroll_parent(element: Option<HtmlElement<AnyElement>>) -> Option<HtmlElement<AnyElement>> {
|
|
||||||
let Some(element) = element else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
fn get_parent_element(element: HtmlElement<AnyElement>) -> Option<HtmlElement<AnyElement>> {
|
|
||||||
if element.node_type() == 9 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
element.parent_element().map(|ele| ele.to_leptos_element())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let Some(parent_element) = get_parent_element(element) else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if parent_element.node_type() == 9 {
|
|
||||||
return Some(parent_element);
|
|
||||||
}
|
|
||||||
|
|
||||||
if parent_element.node_type() == 1 {
|
|
||||||
fn get_overflow(
|
|
||||||
parent_element: &HtmlElement<AnyElement>,
|
|
||||||
) -> Option<(String, String, String)> {
|
|
||||||
let Ok(Some(css_style_declaration)) = window().get_computed_style(parent_element)
|
|
||||||
else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let Ok(overflow) = css_style_declaration.get_property_value("overflow") else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let Ok(overflow_x) = css_style_declaration.get_property_value("overflowX") else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let Ok(overflow_y) = css_style_declaration.get_property_value("overflowY") else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
Some((overflow, overflow_x, overflow_y))
|
|
||||||
}
|
|
||||||
if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&parent_element) {
|
|
||||||
let overflow = format!("{overflow}{overflow_x}{overflow_y}");
|
|
||||||
if overflow.contains("auto") {
|
|
||||||
return Some(parent_element);
|
|
||||||
}
|
|
||||||
if overflow.contains("scroll") {
|
|
||||||
return Some(parent_element);
|
|
||||||
}
|
|
||||||
if overflow.contains("overlay") {
|
|
||||||
return Some(parent_element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get_scroll_parent(Some(parent_element))
|
|
||||||
}
|
|
||||||
|
|
55
thaw_utils/src/dom/get_scroll_parent.rs
Normal file
55
thaw_utils/src/dom/get_scroll_parent.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use leptos::{
|
||||||
|
html::{AnyElement, ToHtmlElement},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn get_scroll_parent(element: &HtmlElement<AnyElement>) -> Option<HtmlElement<AnyElement>> {
|
||||||
|
let Some(parent_element) = get_parent_element(element) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if parent_element.node_type() == 9 {
|
||||||
|
return Some(parent_element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent_element.node_type() == 1 {
|
||||||
|
if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&parent_element) {
|
||||||
|
let overflow = format!("{overflow}{overflow_x}{overflow_y}");
|
||||||
|
if overflow.contains("auto") {
|
||||||
|
return Some(parent_element);
|
||||||
|
}
|
||||||
|
if overflow.contains("scroll") {
|
||||||
|
return Some(parent_element);
|
||||||
|
}
|
||||||
|
if overflow.contains("overlay") {
|
||||||
|
return Some(parent_element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_scroll_parent(&parent_element)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_parent_element(element: &HtmlElement<AnyElement>) -> Option<HtmlElement<AnyElement>> {
|
||||||
|
if element.node_type() == 9 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
element.parent_element().map(|ele| ele.to_leptos_element())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_overflow(parent_element: &HtmlElement<AnyElement>) -> Option<(String, String, String)> {
|
||||||
|
let Ok(Some(css_style_declaration)) = window().get_computed_style(parent_element) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Ok(overflow) = css_style_declaration.get_property_value("overflow") else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Ok(overflow_x) = css_style_declaration.get_property_value("overflowX") else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Ok(overflow_y) = css_style_declaration.get_property_value("overflowY") else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some((overflow, overflow_x, overflow_y))
|
||||||
|
}
|
5
thaw_utils/src/dom/mod.rs
Normal file
5
thaw_utils/src/dom/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
mod get_scroll_parent;
|
||||||
|
mod mount_style;
|
||||||
|
|
||||||
|
pub use get_scroll_parent::get_scroll_parent;
|
||||||
|
pub use mount_style::mount_style;
|
|
@ -1,14 +1,14 @@
|
||||||
pub mod class_list;
|
pub mod class_list;
|
||||||
|
mod dom;
|
||||||
mod event_listener;
|
mod event_listener;
|
||||||
mod hooks;
|
mod hooks;
|
||||||
mod mount_style;
|
|
||||||
mod optional_prop;
|
mod optional_prop;
|
||||||
mod signals;
|
mod signals;
|
||||||
mod time;
|
mod time;
|
||||||
|
|
||||||
|
pub use dom::{get_scroll_parent, mount_style};
|
||||||
pub use event_listener::{add_event_listener, EventListenerHandle};
|
pub use event_listener::{add_event_listener, EventListenerHandle};
|
||||||
pub use hooks::{use_click_position, use_lock_html_scroll, use_next_frame, NextFrame};
|
pub use hooks::{use_click_position, use_lock_html_scroll, use_next_frame, NextFrame};
|
||||||
pub use mount_style::mount_style;
|
|
||||||
pub use optional_prop::OptionalProp;
|
pub use optional_prop::OptionalProp;
|
||||||
pub use signals::{
|
pub use signals::{
|
||||||
create_component_ref, ComponentRef, Model, OptionalMaybeSignal, SignalWatch, StoredMaybeSignal,
|
create_component_ref, ComponentRef, Model, OptionalMaybeSignal, SignalWatch, StoredMaybeSignal,
|
||||||
|
|
Loading…
Add table
Reference in a new issue