mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-23 06:19:22 -05:00
Compare commits
9 commits
aa818b2823
...
e9d1630473
Author | SHA1 | Date | |
---|---|---|---|
e9d1630473 | |||
|
09ef9f8630 | ||
|
b04feef0d1 | ||
|
3e59f506dd | ||
|
360343e8cf | ||
|
1cda944691 | ||
|
5f235339ff | ||
|
40761706c2 | ||
|
406fefb69b |
35 changed files with 616 additions and 464 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -1,3 +1,16 @@
|
|||
## [0.3.4](https://github.com/thaw-ui/thaw/compare/v0.3.3...v0.3.4) (2024-09-11)
|
||||
|
||||
### Features
|
||||
|
||||
* Adds `Flex` component.
|
||||
* Update leptos to v0.6.14.
|
||||
|
||||
### Bug Fixs
|
||||
|
||||
* Allow creating custom theme [#242](https://github.com/thaw-ui/thaw/pull/242).
|
||||
* `CheckboxGroup` does not display default value.
|
||||
* `Binder` component display position.
|
||||
|
||||
## [0.4.0-beta2](https://github.com/thaw-ui/thaw/compare/v0.4.0-beta...v0.4.0-beta2) (2024-09-02)
|
||||
|
||||
### Features
|
||||
|
|
|
@ -16,6 +16,6 @@ thaw_components = { version = "0.2.0-beta2", path = "./thaw_components" }
|
|||
thaw_macro = { version = "0.1.0-beta2", path = "./thaw_macro" }
|
||||
thaw_utils = { version = "0.1.0-beta2", path = "./thaw_utils" }
|
||||
|
||||
leptos = { version = "0.7.0-beta4" }
|
||||
leptos_meta = { version = "0.7.0-beta4" }
|
||||
leptos_router = { version = "0.7.0-beta4" }
|
||||
leptos = { version = "0.7.0-beta5" }
|
||||
leptos_meta = { version = "0.7.0-beta5" }
|
||||
leptos_router = { version = "0.7.0-beta5" }
|
||||
|
|
113
demo/src/app.rs
113
demo/src/app.rs
|
@ -59,66 +59,59 @@ fn TheRouter() -> impl IntoView {
|
|||
<Route path=path!("/development/components") view=DevelopmentComponentsMdPage/>
|
||||
</ParentRoute>
|
||||
<ParentRoute path=path!("/components") view=ComponentsPage>
|
||||
{view!{
|
||||
<Route path=path!("/accordion") view=AccordionMdPage/>
|
||||
<Route path=path!("/anchor") view=AnchorMdPage/>
|
||||
<Route path=path!("/auto-complete") view=AutoCompleteMdPage/>
|
||||
<Route path=path!("/avatar") view=AvatarMdPage/>
|
||||
<Route path=path!("/back-top") view=BackTopMdPage/>
|
||||
<Route path=path!("/badge") view=BadgeMdPage/>
|
||||
<Route path=path!("/breadcrumb") view=BreadcrumbMdPage/>
|
||||
<Route path=path!("/button") view=ButtonMdPage/>
|
||||
<Route path=path!("/calendar") view=CalendarMdPage/>
|
||||
<Route path=path!("/card") view=CardMdPage/>
|
||||
<Route path=path!("/checkbox") view=CheckboxMdPage/>
|
||||
<Route path=path!("/color-picker") view=ColorPickerMdPage/>
|
||||
<Route path=path!("/combobox") view=ComboboxMdPage/>
|
||||
<Route path=path!("/config-provider") view=ConfigProviderMdPage/>
|
||||
}.into_inner()}
|
||||
{view!{
|
||||
<Route path=path!("/date-picker") view=DatePickerMdPage/>
|
||||
<Route path=path!("/dialog") view=DialogMdPage/>
|
||||
<Route path=path!("/divider") view=DividerMdPage/>
|
||||
<Route path=path!("/drawer") view=DrawerMdPage/>
|
||||
<Route path=path!("/field") view=FieldMdPage/>
|
||||
<Route path=path!("/menu") view=MenuMdPage/>
|
||||
<Route path=path!("/grid") view=GridMdPage/>
|
||||
<Route path=path!("/icon") view=IconMdPage/>
|
||||
<Route path=path!("/image") view=ImageMdPage/>
|
||||
<Route path=path!("/input") view=InputMdPage/>
|
||||
<Route path=path!("/layout") view=LayoutMdPage/>
|
||||
<Route path=path!("/link") view=LinkMdPage/>
|
||||
<Route path=path!("/loading-bar") view=LoadingBarMdPage/>
|
||||
<Route path=path!("/message-bar") view=MessageBarMdPage/>
|
||||
<Route path=path!("/nav") view=NavMdPage/>
|
||||
}.into_inner()}
|
||||
{view!{
|
||||
<Route path=path!("/pagination") view=PaginationMdPage/>
|
||||
<Route path=path!("/popover") view=PopoverMdPage/>
|
||||
<Route path=path!("/progress-bar") view=ProgressBarMdPage/>
|
||||
<Route path=path!("/radio") view=RadioMdPage/>
|
||||
<Route path=path!("/scrollbar") view=ScrollbarMdPage/>
|
||||
<Route path=path!("/select") view=SelectMdPage/>
|
||||
<Route path=path!("/skeleton") view=SkeletonMdPage/>
|
||||
<Route path=path!("/slider") view=SliderMdPage/>
|
||||
<Route path=path!("/space") view=SpaceMdPage/>
|
||||
<Route path=path!("/spin-button") view=SpinButtonMdPage/>
|
||||
<Route path=path!("/spinner") view=SpinnerMdPage/>
|
||||
<Route path=path!("/switch") view=SwitchMdPage/>
|
||||
<Route path=path!("/tab-list") view=TabListMdPage/>
|
||||
<Route path=path!("/table") view=TableMdPage/>
|
||||
}.into_inner()}
|
||||
{view!{
|
||||
<Route path=path!("/tag") view=TagMdPage/>
|
||||
<Route path=path!("/tag-group") view=TagGroupMdPage/>
|
||||
<Route path=path!("/tag-picker") view=TagPickerMdPage/>
|
||||
<Route path=path!("/text") view=TextMdPage/>
|
||||
<Route path=path!("/textarea") view=TextareaMdPage/>
|
||||
<Route path=path!("/time-picker") view=TimePickerMdPage/>
|
||||
<Route path=path!("/toast") view=ToastMdPage />
|
||||
<Route path=path!("/tooltip") view=TooltipMdPage />
|
||||
<Route path=path!("/upload") view=UploadMdPage/>
|
||||
}.into_inner()}
|
||||
<Route path=path!("/accordion") view=AccordionMdPage/>
|
||||
<Route path=path!("/anchor") view=AnchorMdPage/>
|
||||
<Route path=path!("/auto-complete") view=AutoCompleteMdPage/>
|
||||
<Route path=path!("/avatar") view=AvatarMdPage/>
|
||||
<Route path=path!("/back-top") view=BackTopMdPage/>
|
||||
<Route path=path!("/badge") view=BadgeMdPage/>
|
||||
<Route path=path!("/breadcrumb") view=BreadcrumbMdPage/>
|
||||
<Route path=path!("/button") view=ButtonMdPage/>
|
||||
<Route path=path!("/calendar") view=CalendarMdPage/>
|
||||
<Route path=path!("/card") view=CardMdPage/>
|
||||
<Route path=path!("/checkbox") view=CheckboxMdPage/>
|
||||
<Route path=path!("/color-picker") view=ColorPickerMdPage/>
|
||||
<Route path=path!("/combobox") view=ComboboxMdPage/>
|
||||
<Route path=path!("/config-provider") view=ConfigProviderMdPage/>
|
||||
<Route path=path!("/date-picker") view=DatePickerMdPage/>
|
||||
<Route path=path!("/dialog") view=DialogMdPage/>
|
||||
<Route path=path!("/divider") view=DividerMdPage/>
|
||||
<Route path=path!("/drawer") view=DrawerMdPage/>
|
||||
<Route path=path!("/field") view=FieldMdPage/>
|
||||
<Route path=path!("/flex") view=FlexMdPage/>
|
||||
<Route path=path!("/menu") view=MenuMdPage/>
|
||||
<Route path=path!("/grid") view=GridMdPage/>
|
||||
<Route path=path!("/icon") view=IconMdPage/>
|
||||
<Route path=path!("/image") view=ImageMdPage/>
|
||||
<Route path=path!("/input") view=InputMdPage/>
|
||||
<Route path=path!("/layout") view=LayoutMdPage/>
|
||||
<Route path=path!("/link") view=LinkMdPage/>
|
||||
<Route path=path!("/loading-bar") view=LoadingBarMdPage/>
|
||||
<Route path=path!("/message-bar") view=MessageBarMdPage/>
|
||||
<Route path=path!("/nav") view=NavMdPage/>
|
||||
<Route path=path!("/pagination") view=PaginationMdPage/>
|
||||
<Route path=path!("/popover") view=PopoverMdPage/>
|
||||
<Route path=path!("/progress-bar") view=ProgressBarMdPage/>
|
||||
<Route path=path!("/radio") view=RadioMdPage/>
|
||||
<Route path=path!("/scrollbar") view=ScrollbarMdPage/>
|
||||
<Route path=path!("/select") view=SelectMdPage/>
|
||||
<Route path=path!("/skeleton") view=SkeletonMdPage/>
|
||||
<Route path=path!("/slider") view=SliderMdPage/>
|
||||
<Route path=path!("/space") view=SpaceMdPage/>
|
||||
<Route path=path!("/spin-button") view=SpinButtonMdPage/>
|
||||
<Route path=path!("/spinner") view=SpinnerMdPage/>
|
||||
<Route path=path!("/switch") view=SwitchMdPage/>
|
||||
<Route path=path!("/tab-list") view=TabListMdPage/>
|
||||
<Route path=path!("/table") view=TableMdPage/>
|
||||
<Route path=path!("/tag") view=TagMdPage/>
|
||||
<Route path=path!("/tag-group") view=TagGroupMdPage/>
|
||||
<Route path=path!("/tag-picker") view=TagPickerMdPage/>
|
||||
<Route path=path!("/text") view=TextMdPage/>
|
||||
<Route path=path!("/textarea") view=TextareaMdPage/>
|
||||
<Route path=path!("/time-picker") view=TimePickerMdPage/>
|
||||
<Route path=path!("/toast") view=ToastMdPage />
|
||||
<Route path=path!("/tooltip") view=TooltipMdPage />
|
||||
<Route path=path!("/upload") view=UploadMdPage/>
|
||||
</ParentRoute>
|
||||
</Routes>
|
||||
</Router>
|
||||
|
|
|
@ -272,6 +272,11 @@ pub(crate) fn gen_nav_data() -> Vec<NavGroupOption> {
|
|||
value: "/components/field",
|
||||
label: "Field",
|
||||
},
|
||||
NavItemOption {
|
||||
group: None,
|
||||
value: "/components/flex",
|
||||
label: "Flex",
|
||||
},
|
||||
NavItemOption {
|
||||
group: None,
|
||||
value: "/components/grid",
|
||||
|
|
|
@ -42,6 +42,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||
"DividerMdPage" => "../../thaw/src/divider/docs/mod.md",
|
||||
"DrawerMdPage" => "../../thaw/src/drawer/docs/mod.md",
|
||||
"FieldMdPage" => "../../thaw/src/field/docs/mod.md",
|
||||
"FlexMdPage" => "../../thaw/src/flex/docs/mod.md",
|
||||
"GridMdPage" => "../../thaw/src/grid/docs/mod.md",
|
||||
"IconMdPage" => "../../thaw/src/icon/docs/mod.md",
|
||||
"ImageMdPage" => "../../thaw/src/image/docs/mod.md",
|
||||
|
|
|
@ -9,10 +9,10 @@ crate-type = ["cdylib", "rlib"]
|
|||
[dependencies]
|
||||
axum = { version = "0.7", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { version = "0.7.0-beta4", features = ["experimental-islands"] }
|
||||
leptos_axum = { version = "0.7.0-beta4", optional = true }
|
||||
leptos_meta = { version = "0.7.0-beta4" }
|
||||
leptos_router = { version = "0.7.0-beta4" }
|
||||
leptos = { version = "0.7.0-beta5", features = ["experimental-islands"] }
|
||||
leptos_axum = { version = "0.7.0-beta5", optional = true }
|
||||
leptos_meta = { version = "0.7.0-beta5" }
|
||||
leptos_router = { version = "0.7.0-beta5" }
|
||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
|
|
|
@ -9,10 +9,10 @@ crate-type = ["cdylib", "rlib"]
|
|||
[dependencies]
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { version = "0.7.0-beta4" }
|
||||
leptos_axum = { version = "0.7.0-beta4", optional = true }
|
||||
leptos_meta = { version = "0.7.0-beta4" }
|
||||
leptos_router = { version = "0.7.0-beta4" }
|
||||
leptos = { version = "0.7.0-beta5" }
|
||||
leptos_axum = { version = "0.7.0-beta5", optional = true }
|
||||
leptos_meta = { version = "0.7.0-beta5" }
|
||||
leptos_router = { version = "0.7.0-beta5" }
|
||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||
tower = { version = "0.5.0", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{ConfigInjection, Icon};
|
|||
use leptos::{either::Either, ev, html, prelude::*};
|
||||
use thaw_components::{CSSTransition, Teleport};
|
||||
use thaw_utils::{
|
||||
add_event_listener, class_list, get_scroll_parent, mount_style, BoxCallback,
|
||||
add_event_listener, class_list, get_scroll_parent_element, mount_style, BoxCallback,
|
||||
EventListenerHandle,
|
||||
};
|
||||
|
||||
|
@ -43,7 +43,7 @@ pub fn BackTop(
|
|||
};
|
||||
|
||||
request_animation_frame(move || {
|
||||
let scroll_el = get_scroll_parent(&placeholder_el)
|
||||
let scroll_el = get_scroll_parent_element(&placeholder_el)
|
||||
.unwrap_or_else(|| document().document_element().unwrap());
|
||||
|
||||
{
|
||||
|
|
|
@ -55,7 +55,7 @@ pub fn Combobox(
|
|||
let Some(clear_icon_el) = clear_icon_ref.get() else {
|
||||
return;
|
||||
};
|
||||
let handler = add_event_listener(clear_icon_el.into(), ev::click, move |e| {
|
||||
let handler = add_event_listener(clear_icon_el, ev::click, move |e| {
|
||||
if disabled.get_untracked() {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ impl ListboxInjection {
|
|||
|
||||
#[inline]
|
||||
pub fn trigger(&self) {
|
||||
self.0.trigger();
|
||||
self.0.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
110
thaw/src/flex/docs/mod.md
Normal file
110
thaw/src/flex/docs/mod.md
Normal file
|
@ -0,0 +1,110 @@
|
|||
# Flex
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Flex>
|
||||
<Button>"1"</Button>
|
||||
<Button>"2"</Button>
|
||||
<Button>"3"</Button>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<For
|
||||
each=move || 0..4
|
||||
key=move |i| i.clone()
|
||||
let:i
|
||||
>
|
||||
<Button>{i}</Button>
|
||||
</For>
|
||||
</Flex>
|
||||
}
|
||||
```
|
||||
|
||||
### Vertical
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Flex vertical=true>
|
||||
<Button>"1"</Button>
|
||||
<Button>"2"</Button>
|
||||
<Button>"3"</Button>
|
||||
</Flex>
|
||||
}
|
||||
```
|
||||
|
||||
### Gap
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Flex gap=FlexGap::Large>
|
||||
<Button>"1"</Button>
|
||||
<Button>"2"</Button>
|
||||
<Button>"3"</Button>
|
||||
</Flex>
|
||||
<Flex gap=FlexGap::WH(36, 36)>
|
||||
<Button>"1"</Button>
|
||||
<Button>"2"</Button>
|
||||
<Button>"3"</Button>
|
||||
</Flex>
|
||||
}
|
||||
```
|
||||
|
||||
### Align
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Flex vertical=true>
|
||||
<Flex align=FlexAlign::Start>
|
||||
<Button attr:style="height: 60px">"Start"</Button>
|
||||
<Button attr:style="height: 40px">"Start"</Button>
|
||||
<Button>"Start"</Button>
|
||||
</Flex>
|
||||
<Flex align=FlexAlign::Center>
|
||||
<Button attr:style="height: 60px">"Center"</Button>
|
||||
<Button attr:style="height: 40px">"Center"</Button>
|
||||
<Button>"Center"</Button>
|
||||
</Flex>
|
||||
<Flex align=FlexAlign::End>
|
||||
<Button attr:style="height: 60px">"End"</Button>
|
||||
<Button attr:style="height: 40px">"End"</Button>
|
||||
<Button>"End"</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
}
|
||||
```
|
||||
|
||||
### Justify
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Flex vertical=true>
|
||||
<Flex justify=FlexJustify::SpaceAround>
|
||||
<Button>"SpaceAround"</Button>
|
||||
<Button>"SpaceAround"</Button>
|
||||
<Button>"SpaceAround"</Button>
|
||||
</Flex>
|
||||
<Flex justify=FlexJustify::Center>
|
||||
<Button>"Center"</Button>
|
||||
<Button>"Center"</Button>
|
||||
<Button>"Center"</Button>
|
||||
</Flex>
|
||||
<Flex justify=FlexJustify::End>
|
||||
<Button>"End"</Button>
|
||||
<Button>"End"</Button>
|
||||
<Button>"End"</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
}
|
||||
```
|
||||
|
||||
### Flex Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | ------------------------ | -------------------- | -------------------------------------- |
|
||||
| class | `MaybeProp<String>` | `Default::default()` | |
|
||||
| style | `MaybeProp<String>` | `Default::default()` | |
|
||||
| inline | `MaybeSignal<bool>` | `false` | Whether it's display is `inline-flex`. |
|
||||
| vertical | `bool` | `false` | Whether to lay out vertically. |
|
||||
| gap | `FlexGap` | `FlexGap::Medium` | Flex's gap. |
|
||||
| align | `MaybeProp<FlexAlign>` | `None` | Vertical arrangement. |
|
||||
| justify | `MaybeProp<FlexJustify>` | `None` | Horizontal arrangement. |
|
||||
| children | `Children` | | |
|
130
thaw/src/flex/flex.rs
Normal file
130
thaw/src/flex/flex.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use leptos::prelude::*;
|
||||
use thaw_utils::class_list;
|
||||
|
||||
#[component]
|
||||
pub fn Flex(
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
#[prop(optional, into)] style: MaybeProp<String>,
|
||||
/// Flex's gap.
|
||||
#[prop(optional)]
|
||||
gap: FlexGap,
|
||||
/// Whether to lay out vertically.
|
||||
#[prop(optional)]
|
||||
vertical: bool,
|
||||
/// Whether it's display is `inline-flex`.
|
||||
#[prop(optional, into)]
|
||||
inline: MaybeSignal<bool>,
|
||||
/// Vertical arrangement.
|
||||
#[prop(optional, into)]
|
||||
align: MaybeProp<FlexAlign>,
|
||||
/// Horizontal arrangement.
|
||||
#[prop(optional, into)]
|
||||
justify: MaybeProp<FlexJustify>,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
let style = Memo::new(move |_| {
|
||||
let mut s = String::new();
|
||||
let display = if inline.get() {
|
||||
"display: inline-flex;"
|
||||
} else {
|
||||
"display: flex;"
|
||||
};
|
||||
s.push_str(display);
|
||||
let direction = if vertical {
|
||||
"flex-direction: column;"
|
||||
} else {
|
||||
"flex-direction: row;"
|
||||
};
|
||||
let gap = match gap {
|
||||
FlexGap::Small => "gap: 4px 8px;",
|
||||
FlexGap::Medium => "gap: 8px 12px;",
|
||||
FlexGap::Large => "gap: 12px 16px;",
|
||||
FlexGap::Size(size) => &format!("gap: {size}px {size}px;"),
|
||||
FlexGap::WH(width, height) => &format!("gap: {width}px {height}px;"),
|
||||
};
|
||||
s.push_str(direction);
|
||||
s.push_str(gap);
|
||||
if let Some(align) = align.get() {
|
||||
s.push_str(&format!("align-items: {};", align.as_str()));
|
||||
}
|
||||
if let Some(justify) = justify.get() {
|
||||
s.push_str(&format!("justify-content: {};", justify.as_str()));
|
||||
}
|
||||
style.with(|style| {
|
||||
if let Some(style) = style.as_ref() {
|
||||
s.push_str(style);
|
||||
}
|
||||
});
|
||||
|
||||
s
|
||||
});
|
||||
|
||||
view! {
|
||||
<div class=class_list!["thaw-flex", class] style=move || style.get()>
|
||||
{children()}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum FlexGap {
|
||||
Small,
|
||||
#[default]
|
||||
Medium,
|
||||
Large,
|
||||
Size(u16),
|
||||
/// width and height
|
||||
WH(u16, u16),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum FlexAlign {
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
Baseline,
|
||||
Stretch,
|
||||
}
|
||||
|
||||
impl FlexAlign {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::FlexStart => "flex-start",
|
||||
Self::FlexEnd => "flex-end",
|
||||
Self::Start => "start",
|
||||
Self::End => "end",
|
||||
Self::Center => "center",
|
||||
Self::Baseline => "baseline",
|
||||
Self::Stretch => "stretch",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum FlexJustify {
|
||||
FlexStart,
|
||||
FlexEnd,
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
SpaceAround,
|
||||
SpaceBetween,
|
||||
SpaceEvenly,
|
||||
}
|
||||
|
||||
impl FlexJustify {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::FlexStart => "flex-start",
|
||||
Self::FlexEnd => "flex-end",
|
||||
Self::Start => "start",
|
||||
Self::End => "end",
|
||||
Self::Center => "center",
|
||||
Self::SpaceAround => "space-around",
|
||||
Self::SpaceBetween => "space-between",
|
||||
Self::SpaceEvenly => "space-evenly",
|
||||
}
|
||||
}
|
||||
}
|
3
thaw/src/flex/mod.rs
Normal file
3
thaw/src/flex/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod flex;
|
||||
|
||||
pub use flex::*;
|
|
@ -19,6 +19,7 @@ mod dialog;
|
|||
mod divider;
|
||||
mod drawer;
|
||||
mod field;
|
||||
mod flex;
|
||||
mod grid;
|
||||
mod icon;
|
||||
mod image;
|
||||
|
@ -73,6 +74,7 @@ pub use dialog::*;
|
|||
pub use divider::*;
|
||||
pub use drawer::*;
|
||||
pub use field::*;
|
||||
pub use flex::*;
|
||||
pub use grid::*;
|
||||
pub use icon::*;
|
||||
pub use image::*;
|
||||
|
|
|
@ -77,3 +77,4 @@ view! {
|
|||
| disabled | `MaybeSignal<bool>` | `false` | Whether the link is disabled. |
|
||||
| disabled_focusable | `MaybeSignal<bool>` | `false` | When set, allows the link to be focusable even when it has been disabled. |
|
||||
| children | `Children` | | |
|
||||
| target | `MaybeSignal<String>` | | Specifies where to open the linked document. |
|
||||
|
|
|
@ -16,6 +16,9 @@ pub fn Link(
|
|||
#[prop(optional, into)]
|
||||
disabled_focusable: MaybeSignal<bool>,
|
||||
children: Children,
|
||||
/// Specifies where to open the linked document.
|
||||
#[prop(optional, into)]
|
||||
target: MaybeSignal<String>,
|
||||
) -> impl IntoView {
|
||||
mount_style("link", include_str!("./link.css"));
|
||||
|
||||
|
@ -46,6 +49,7 @@ pub fn Link(
|
|||
href=href
|
||||
tabindex=tabindex
|
||||
aria-disabled=move || link_disabled.get().then_some("true")
|
||||
target=target
|
||||
>
|
||||
{children()}
|
||||
</a>
|
||||
|
|
|
@ -80,7 +80,7 @@ pub fn Menu(
|
|||
let Some(target_el) = target_ref.get() else {
|
||||
return;
|
||||
};
|
||||
let handler = add_event_listener(target_el.into(), ev::click, move |event| {
|
||||
let handler = add_event_listener(target_el, ev::click, move |event| {
|
||||
if trigger_type != MenuTriggerType::Click {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ pub fn Popover(
|
|||
let Some(target_el) = target_ref.get() else {
|
||||
return;
|
||||
};
|
||||
let handler = add_event_listener(target_el.into(), ev::click, move |event| {
|
||||
let handler = add_event_listener(target_el, ev::click, move |event| {
|
||||
if trigger_type != PopoverTriggerType::Click {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -23,10 +23,22 @@ view! {
|
|||
}
|
||||
```
|
||||
|
||||
### Custom label
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Spinner label="Label"/>
|
||||
<Spinner>
|
||||
<b style="color: blue">"Label"</b>
|
||||
</Spinner>
|
||||
}
|
||||
```
|
||||
|
||||
### Spinner Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ----- | -------------------------- | --------------------- | ---------------------------------- |
|
||||
| class | `MaybeProp<String>` | `Default::default()` | |
|
||||
| label | `MaybeProp<String>` | `Default::default()` | An optional label for the Spinner. |
|
||||
| size | `MaybeSignal<SpinnerSize>` | `SpinnerSize::Medium` | The size of the spinner. |
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | -------------------------- | --------------------- | ---------------------------------- |
|
||||
| class | `MaybeProp<String>` | `Default::default()` | |
|
||||
| label | `MaybeProp<String>` | `Default::default()` | An optional label for the Spinner. |
|
||||
| size | `MaybeSignal<SpinnerSize>` | `SpinnerSize::Medium` | The size of the spinner. |
|
||||
| children | `Option<Children>` | `None` | |
|
||||
|
|
|
@ -38,14 +38,16 @@ pub fn Spinner(
|
|||
/// The size of the spinner.
|
||||
#[prop(optional, into)]
|
||||
size: MaybeSignal<SpinnerSize>,
|
||||
#[prop(optional)] children: Option<Children>,
|
||||
) -> impl IntoView {
|
||||
mount_style("spinner", include_str!("./spinner.css"));
|
||||
let id = StoredValue::new(uuid::Uuid::new_v4().to_string());
|
||||
|
||||
let spinner_label = label.clone();
|
||||
let children_flag = children.is_some();
|
||||
let labelledby = move || {
|
||||
spinner_label.with(|label| {
|
||||
if label.is_some() {
|
||||
if label.is_some() || children_flag {
|
||||
Some(id.get_value())
|
||||
} else {
|
||||
None
|
||||
|
@ -66,17 +68,29 @@ pub fn Spinner(
|
|||
<span class="thaw-spinner__spinner">
|
||||
<span class="thaw-spinner__spinner-tail"></span>
|
||||
</span>
|
||||
{move || {
|
||||
if let Some(label) = label.get() {
|
||||
view! {
|
||||
<label class="thaw-spinner__label" id=id.get_value()>
|
||||
{label}
|
||||
</label>
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
{if let Some(children) = children {
|
||||
view! {
|
||||
<label class="thaw-spinner__label" id=id.get_value()>
|
||||
{children()}
|
||||
</label>
|
||||
}
|
||||
.into_any()
|
||||
} else {
|
||||
{
|
||||
move || {
|
||||
if let Some(label) = label.get() {
|
||||
view! {
|
||||
<label class="thaw-spinner__label" id=id.get_value()>
|
||||
{label}
|
||||
</label>
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
.into_any()
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ pub fn TagPickerInput(#[prop(optional, into)] class: MaybeProp<String>) -> impl
|
|||
on:blur=on_blur
|
||||
on:input=on_input
|
||||
prop:value=move || {
|
||||
value_trigger.trigger();
|
||||
value_trigger.notify();
|
||||
""
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -48,7 +48,7 @@ pub fn Textarea(
|
|||
let input_value = event_target_value(&ev);
|
||||
if let Some(allow_value) = allow_value.as_ref() {
|
||||
if !allow_value(input_value.clone()) {
|
||||
value_trigger.trigger();
|
||||
value_trigger.notify();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ impl ToasterInjection {
|
|||
pub fn dispatch_toast(&self, any_view: AnyView<Dom>, options: ToastOptions) {
|
||||
self.sender
|
||||
.with_value(|sender| sender.send((any_view, options)).unwrap_throw());
|
||||
self.trigger.with_value(|trigger| trigger.trigger());
|
||||
self.trigger.with_value(|trigger| trigger.notify());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ pub fn Upload(
|
|||
let Some(trigger_el) = trigger_ref.get() else {
|
||||
return;
|
||||
};
|
||||
let handle = add_event_listener(trigger_el.into(), ev::click, move |_| {
|
||||
let handle = add_event_listener(trigger_el, ev::click, move |_| {
|
||||
if let Some(input_ref) = input_ref.get_untracked() {
|
||||
input_ref.click();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.thaw-binder-follower-container {
|
||||
.thaw-binder-follower {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
@ -9,5 +9,5 @@
|
|||
|
||||
.thaw-binder-follower-content {
|
||||
position: absolute;
|
||||
z-index: 2000;
|
||||
z-index: 1000000;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
return None;
|
||||
};
|
||||
|
||||
if top < 0.0 && target_y + target_height + follower_height <= inner_height {
|
||||
(target_y + target_height, FollowerPlacement::Bottom)
|
||||
let (left, placement, top, transform) = match placement {
|
||||
FollowerPlacement::Top | FollowerPlacement::TopStart | FollowerPlacement::TopEnd => {
|
||||
let Some(window_inner_height) = window_inner_height() else {
|
||||
return None;
|
||||
};
|
||||
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)
|
||||
} else {
|
||||
(top, FollowerPlacement::TopStart)
|
||||
}
|
||||
};
|
||||
Some(FollowerPlacementOffset {
|
||||
top,
|
||||
left,
|
||||
transform: String::new(),
|
||||
placement,
|
||||
})
|
||||
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 {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return None;
|
||||
};
|
||||
|
||||
if top < 0.0 && target_y + target_height + follower_height <= inner_height {
|
||||
(target_y + target_height, FollowerPlacement::BottomEnd)
|
||||
} else {
|
||||
(top, FollowerPlacement::TopEnd)
|
||||
}
|
||||
FollowerPlacement::Bottom
|
||||
| FollowerPlacement::BottomStart
|
||||
| FollowerPlacement::BottomEnd => {
|
||||
let Some(window_inner_height) = window_inner_height() else {
|
||||
return None;
|
||||
};
|
||||
Some(FollowerPlacementOffset {
|
||||
top,
|
||||
left,
|
||||
transform: String::from("translateX(-100%)"),
|
||||
placement,
|
||||
})
|
||||
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)
|
||||
};
|
||||
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 => {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
};
|
||||
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,
|
||||
})
|
||||
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::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)
|
||||
}
|
||||
FollowerPlacement::Right | FollowerPlacement::RightStart | FollowerPlacement::RightEnd => {
|
||||
let Some(window_inner_width) = window_inner_width() else {
|
||||
return None;
|
||||
};
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
(left, FollowerPlacement::Right)
|
||||
};
|
||||
Some(FollowerPlacementOffset {
|
||||
top,
|
||||
left,
|
||||
transform: String::from("translateY(-50%)"),
|
||||
placement,
|
||||
})
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
} 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
Some(FollowerPlacementOffset {
|
||||
top: top - follower_rect.top(),
|
||||
left: left - follower_rect.left(),
|
||||
placement,
|
||||
transform,
|
||||
})
|
||||
}
|
||||
|
||||
fn window_inner_width() -> Option<f64> {
|
||||
|
|
|
@ -14,7 +14,7 @@ use leptos::{
|
|||
leptos_dom::helpers::WindowListenerHandle,
|
||||
prelude::*,
|
||||
};
|
||||
use thaw_utils::{add_event_listener, get_scroll_parent, mount_style, EventListenerHandle};
|
||||
use thaw_utils::{add_event_listener, get_scroll_parent_node, mount_style, EventListenerHandle};
|
||||
|
||||
#[slot]
|
||||
pub struct Follower {
|
||||
|
@ -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: {};",
|
||||
|
@ -132,12 +141,12 @@ where
|
|||
};
|
||||
|
||||
let mut handle_vec = vec![];
|
||||
let mut cursor = get_scroll_parent(&el);
|
||||
let mut cursor = get_scroll_parent_node(&el);
|
||||
loop {
|
||||
if let Some(el) = cursor.take() {
|
||||
cursor = get_scroll_parent(&el);
|
||||
if let Some(node) = cursor.take() {
|
||||
cursor = get_scroll_parent_node(&node);
|
||||
|
||||
let handle = add_event_listener(el, ev::scroll, move |_| {
|
||||
let handle = add_event_listener(node, ev::scroll, move |_| {
|
||||
sync_position();
|
||||
});
|
||||
handle_vec.push(handle);
|
||||
|
@ -197,18 +206,16 @@ where
|
|||
view! {
|
||||
{children()}
|
||||
<Teleport immediate=follower_show>
|
||||
<Provider value=follower_injection>
|
||||
<div class="thaw-binder-follower-container">
|
||||
<div
|
||||
class="thaw-binder-follower-content"
|
||||
data-thaw-placement=move || placement_str.get()
|
||||
node_ref=content_ref
|
||||
style=move || content_style.get()
|
||||
>
|
||||
{follower_children()}
|
||||
</div>
|
||||
<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()}</Provider>
|
||||
</div>
|
||||
</Provider>
|
||||
</div>
|
||||
</Teleport>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,13 +92,13 @@ where
|
|||
}
|
||||
};
|
||||
let handle = match types {
|
||||
AnimationTypes::Transition => add_event_listener(
|
||||
el.deref().clone().into(),
|
||||
ev::transitionend,
|
||||
move |_| event_listener(),
|
||||
),
|
||||
AnimationTypes::Transition => {
|
||||
add_event_listener(el.deref().clone(), ev::transitionend, move |_| {
|
||||
event_listener()
|
||||
})
|
||||
}
|
||||
AnimationTypes::Animation => {
|
||||
add_event_listener(el.deref().clone().into(), ev::animationend, move |_| {
|
||||
add_event_listener(el.deref().clone(), ev::animationend, move |_| {
|
||||
event_listener()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,39 +1,49 @@
|
|||
use leptos::prelude::*;
|
||||
use web_sys::Element;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
pub fn get_scroll_parent(element: &Element) -> Option<Element> {
|
||||
let Some(parent_element) = get_parent_element(element) else {
|
||||
return None;
|
||||
};
|
||||
pub fn get_scroll_parent_node(node: &Node) -> Option<Node> {
|
||||
let parent_node = node.parent_node()?;
|
||||
|
||||
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 node_type = parent_node.node_type();
|
||||
if node_type == Node::ELEMENT_NODE {
|
||||
let el = parent_node.clone().dyn_into::<Element>().unwrap();
|
||||
if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&el) {
|
||||
let overflow = format!("{overflow}{overflow_x}{overflow_y}");
|
||||
if overflow.contains("auto") {
|
||||
return Some(parent_element);
|
||||
return Some(parent_node);
|
||||
}
|
||||
if overflow.contains("scroll") {
|
||||
return Some(parent_element);
|
||||
return Some(parent_node);
|
||||
}
|
||||
if overflow.contains("overlay") {
|
||||
return Some(parent_element);
|
||||
return Some(parent_node);
|
||||
}
|
||||
}
|
||||
} else if node_type == Node::DOCUMENT_NODE {
|
||||
return Some(document().into());
|
||||
}
|
||||
|
||||
get_scroll_parent_node(&parent_node)
|
||||
}
|
||||
|
||||
pub fn get_scroll_parent_element(element: &Element) -> Option<Element> {
|
||||
let parent_element = element.parent_element()?;
|
||||
|
||||
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: &Element) -> Option<Element> {
|
||||
if element.node_type() == 9 {
|
||||
None
|
||||
} else {
|
||||
element.parent_element()
|
||||
}
|
||||
get_scroll_parent_element(&parent_element)
|
||||
}
|
||||
|
||||
fn get_overflow(parent_element: &Element) -> Option<(String, String, String)> {
|
||||
|
|
|
@ -2,6 +2,6 @@ mod get_scroll_parent;
|
|||
mod mount_style;
|
||||
mod scroll_into_view;
|
||||
|
||||
pub use get_scroll_parent::get_scroll_parent;
|
||||
pub use get_scroll_parent::{get_scroll_parent_element, get_scroll_parent_node};
|
||||
pub use mount_style::{mount_dynamic_style, mount_style};
|
||||
pub use scroll_into_view::scroll_into_view;
|
||||
|
|
|
@ -3,8 +3,8 @@ use web_sys::HtmlElement;
|
|||
|
||||
pub fn scroll_into_view(el: &HtmlElement) {
|
||||
cfg_if! { if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] {
|
||||
use super::get_scroll_parent;
|
||||
if let Some(parent) = get_scroll_parent(el) {
|
||||
use super::get_scroll_parent_element;
|
||||
if let Some(parent) = get_scroll_parent_element(el) {
|
||||
let parent_rect = parent.get_bounding_client_rect();
|
||||
let el_rect = el.get_bounding_client_rect();
|
||||
if el_rect.y() < parent_rect.y() {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use ::wasm_bindgen::{prelude::Closure, JsCast};
|
||||
use leptos::ev;
|
||||
use web_sys::{Element, EventTarget};
|
||||
use web_sys::EventTarget;
|
||||
|
||||
pub fn add_event_listener<E>(
|
||||
target: Element,
|
||||
target: impl Into<EventTarget>,
|
||||
event: E,
|
||||
cb: impl Fn(E::EventType) + 'static,
|
||||
) -> EventListenerHandle
|
||||
|
@ -31,12 +31,12 @@ impl EventListenerHandle {
|
|||
}
|
||||
|
||||
fn add_event_listener_untyped(
|
||||
target: Element,
|
||||
target: impl Into<EventTarget>,
|
||||
event_name: &str,
|
||||
cb: impl Fn(web_sys::Event) + 'static,
|
||||
) -> EventListenerHandle {
|
||||
fn wel(
|
||||
target: Element,
|
||||
target: EventTarget,
|
||||
cb: Box<dyn FnMut(web_sys::Event)>,
|
||||
event_name: &str,
|
||||
) -> EventListenerHandle {
|
||||
|
@ -54,11 +54,11 @@ fn add_event_listener_untyped(
|
|||
})
|
||||
}
|
||||
|
||||
wel(target, Box::new(cb), event_name)
|
||||
wel(target.into(), Box::new(cb), event_name)
|
||||
}
|
||||
|
||||
pub fn add_event_listener_with_bool<E: ev::EventDescriptor + 'static>(
|
||||
target: impl IntoEventTarget,
|
||||
target: impl Into<EventTarget>,
|
||||
event: E,
|
||||
cb: impl Fn(E::EventType) + 'static,
|
||||
use_capture: bool,
|
||||
|
@ -67,7 +67,7 @@ where
|
|||
E::EventType: JsCast,
|
||||
{
|
||||
add_event_listener_untyped_with_bool(
|
||||
target.into_event_target(),
|
||||
target,
|
||||
&event.name(),
|
||||
move |e| cb(e.unchecked_into::<E::EventType>()),
|
||||
use_capture,
|
||||
|
@ -75,7 +75,7 @@ where
|
|||
}
|
||||
|
||||
fn add_event_listener_untyped_with_bool(
|
||||
target: EventTarget,
|
||||
target: impl Into<EventTarget>,
|
||||
event_name: &str,
|
||||
cb: impl Fn(web_sys::Event) + 'static,
|
||||
use_capture: bool,
|
||||
|
@ -107,24 +107,30 @@ fn add_event_listener_untyped_with_bool(
|
|||
})
|
||||
}
|
||||
|
||||
wel(target, Box::new(cb), event_name, use_capture)
|
||||
wel(target.into(), Box::new(cb), event_name, use_capture)
|
||||
}
|
||||
|
||||
pub trait IntoEventTarget {
|
||||
fn into_event_target(self) -> EventTarget;
|
||||
}
|
||||
// pub trait IntoEventTarget {
|
||||
// fn into_event_target(self) -> EventTarget;
|
||||
// }
|
||||
|
||||
impl IntoEventTarget for EventTarget {
|
||||
fn into_event_target(self) -> EventTarget {
|
||||
self
|
||||
}
|
||||
}
|
||||
// impl IntoEventTarget for EventTarget {
|
||||
// fn into_event_target(self) -> EventTarget {
|
||||
// self
|
||||
// }
|
||||
// }
|
||||
|
||||
impl IntoEventTarget for web_sys::Document {
|
||||
fn into_event_target(self) -> EventTarget {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
// impl IntoEventTarget for web_sys::Document {
|
||||
// fn into_event_target(self) -> EventTarget {
|
||||
// self.into()
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl IntoEventTarget for Element {
|
||||
// fn into_event_target(self) -> EventTarget {
|
||||
// self.into()
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl IntoEventTarget for HtmlElement<AnyElement> {
|
||||
// fn into_event_target(self) -> EventTarget {
|
||||
|
|
|
@ -4,6 +4,6 @@ mod signal_watch;
|
|||
mod stored_maybe_signal;
|
||||
|
||||
pub use component_ref::ComponentRef;
|
||||
pub use model::{Model, OptionModel, VecModel, VecModelWithValue, OptionModelWithValue};
|
||||
pub use model::{Model, OptionModel, OptionModelWithValue, VecModel, VecModelWithValue};
|
||||
pub use signal_watch::SignalWatch;
|
||||
pub use stored_maybe_signal::StoredMaybeSignal;
|
||||
|
|
|
@ -58,7 +58,9 @@ impl<T: Send + Sync> OptionModel<T> {
|
|||
pub fn with_untracked<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> O) -> O {
|
||||
match self {
|
||||
Self::T(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::T(value))),
|
||||
Self::Option(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::Option(value))),
|
||||
Self::Option(read, _, _) => {
|
||||
read.with_untracked(|value| fun(OptionModelWithValue::Option(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,10 +67,7 @@ impl<T: Send + Sync> VecModel<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with<O>(
|
||||
&self,
|
||||
fun: impl FnOnce(VecModelWithValue<T>) -> O,
|
||||
) -> O {
|
||||
pub fn with<O>(&self, fun: impl FnOnce(VecModelWithValue<T>) -> O) -> O {
|
||||
match self {
|
||||
Self::T(read, _, _) => read.with(|value| fun(VecModelWithValue::T(value))),
|
||||
Self::Option(read, _, _) => read.with(|value| fun(VecModelWithValue::Option(value))),
|
||||
|
|
Loading…
Add table
Reference in a new issue