Compare commits

...

9 commits

Author SHA1 Message Date
e9d1630473 feat: add target to Link
Some checks failed
Deploy demo / deploy (push) Has been cancelled
2024-09-11 22:44:51 -04:00
luoxiao
09ef9f8630 feat: Spinner adds children prop 2024-09-11 23:44:00 +08:00
luoxiao
b04feef0d1 fix: Binder component sync position 2024-09-11 23:44:00 +08:00
luoxiao
3e59f506dd fix: Binder component display position 2024-09-11 23:44:00 +08:00
luoxiao
360343e8cf fix: Adjust the z-index of the Binder popup 2024-09-11 23:44:00 +08:00
luoxiao
1cda944691 feat: v0.3.4 CHANGELOG 2024-09-11 15:21:35 +08:00
luoxiaozero
5f235339ff
feat: adds Flex component (#256) 2024-09-09 21:44:16 +08:00
luoxiaozero
40761706c2
feat: update leptos to v0.7.0-beta5 (#255) 2024-09-09 14:24:04 +08:00
Adam
406fefb69b
fix: leptos 0.7-beta5 changes method trigger to notify (#253) 2024-09-09 13:48:59 +08:00
35 changed files with 616 additions and 464 deletions

View file

@ -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) ## [0.4.0-beta2](https://github.com/thaw-ui/thaw/compare/v0.4.0-beta...v0.4.0-beta2) (2024-09-02)
### Features ### Features

View file

@ -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_macro = { version = "0.1.0-beta2", path = "./thaw_macro" }
thaw_utils = { version = "0.1.0-beta2", path = "./thaw_utils" } thaw_utils = { version = "0.1.0-beta2", path = "./thaw_utils" }
leptos = { version = "0.7.0-beta4" } leptos = { version = "0.7.0-beta5" }
leptos_meta = { version = "0.7.0-beta4" } leptos_meta = { version = "0.7.0-beta5" }
leptos_router = { version = "0.7.0-beta4" } leptos_router = { version = "0.7.0-beta5" }

View file

@ -59,7 +59,6 @@ fn TheRouter() -> impl IntoView {
<Route path=path!("/development/components") view=DevelopmentComponentsMdPage/> <Route path=path!("/development/components") view=DevelopmentComponentsMdPage/>
</ParentRoute> </ParentRoute>
<ParentRoute path=path!("/components") view=ComponentsPage> <ParentRoute path=path!("/components") view=ComponentsPage>
{view!{
<Route path=path!("/accordion") view=AccordionMdPage/> <Route path=path!("/accordion") view=AccordionMdPage/>
<Route path=path!("/anchor") view=AnchorMdPage/> <Route path=path!("/anchor") view=AnchorMdPage/>
<Route path=path!("/auto-complete") view=AutoCompleteMdPage/> <Route path=path!("/auto-complete") view=AutoCompleteMdPage/>
@ -74,13 +73,12 @@ fn TheRouter() -> impl IntoView {
<Route path=path!("/color-picker") view=ColorPickerMdPage/> <Route path=path!("/color-picker") view=ColorPickerMdPage/>
<Route path=path!("/combobox") view=ComboboxMdPage/> <Route path=path!("/combobox") view=ComboboxMdPage/>
<Route path=path!("/config-provider") view=ConfigProviderMdPage/> <Route path=path!("/config-provider") view=ConfigProviderMdPage/>
}.into_inner()}
{view!{
<Route path=path!("/date-picker") view=DatePickerMdPage/> <Route path=path!("/date-picker") view=DatePickerMdPage/>
<Route path=path!("/dialog") view=DialogMdPage/> <Route path=path!("/dialog") view=DialogMdPage/>
<Route path=path!("/divider") view=DividerMdPage/> <Route path=path!("/divider") view=DividerMdPage/>
<Route path=path!("/drawer") view=DrawerMdPage/> <Route path=path!("/drawer") view=DrawerMdPage/>
<Route path=path!("/field") view=FieldMdPage/> <Route path=path!("/field") view=FieldMdPage/>
<Route path=path!("/flex") view=FlexMdPage/>
<Route path=path!("/menu") view=MenuMdPage/> <Route path=path!("/menu") view=MenuMdPage/>
<Route path=path!("/grid") view=GridMdPage/> <Route path=path!("/grid") view=GridMdPage/>
<Route path=path!("/icon") view=IconMdPage/> <Route path=path!("/icon") view=IconMdPage/>
@ -91,8 +89,6 @@ fn TheRouter() -> impl IntoView {
<Route path=path!("/loading-bar") view=LoadingBarMdPage/> <Route path=path!("/loading-bar") view=LoadingBarMdPage/>
<Route path=path!("/message-bar") view=MessageBarMdPage/> <Route path=path!("/message-bar") view=MessageBarMdPage/>
<Route path=path!("/nav") view=NavMdPage/> <Route path=path!("/nav") view=NavMdPage/>
}.into_inner()}
{view!{
<Route path=path!("/pagination") view=PaginationMdPage/> <Route path=path!("/pagination") view=PaginationMdPage/>
<Route path=path!("/popover") view=PopoverMdPage/> <Route path=path!("/popover") view=PopoverMdPage/>
<Route path=path!("/progress-bar") view=ProgressBarMdPage/> <Route path=path!("/progress-bar") view=ProgressBarMdPage/>
@ -107,8 +103,6 @@ fn TheRouter() -> impl IntoView {
<Route path=path!("/switch") view=SwitchMdPage/> <Route path=path!("/switch") view=SwitchMdPage/>
<Route path=path!("/tab-list") view=TabListMdPage/> <Route path=path!("/tab-list") view=TabListMdPage/>
<Route path=path!("/table") view=TableMdPage/> <Route path=path!("/table") view=TableMdPage/>
}.into_inner()}
{view!{
<Route path=path!("/tag") view=TagMdPage/> <Route path=path!("/tag") view=TagMdPage/>
<Route path=path!("/tag-group") view=TagGroupMdPage/> <Route path=path!("/tag-group") view=TagGroupMdPage/>
<Route path=path!("/tag-picker") view=TagPickerMdPage/> <Route path=path!("/tag-picker") view=TagPickerMdPage/>
@ -118,7 +112,6 @@ fn TheRouter() -> impl IntoView {
<Route path=path!("/toast") view=ToastMdPage /> <Route path=path!("/toast") view=ToastMdPage />
<Route path=path!("/tooltip") view=TooltipMdPage /> <Route path=path!("/tooltip") view=TooltipMdPage />
<Route path=path!("/upload") view=UploadMdPage/> <Route path=path!("/upload") view=UploadMdPage/>
}.into_inner()}
</ParentRoute> </ParentRoute>
</Routes> </Routes>
</Router> </Router>

View file

@ -272,6 +272,11 @@ pub(crate) fn gen_nav_data() -> Vec<NavGroupOption> {
value: "/components/field", value: "/components/field",
label: "Field", label: "Field",
}, },
NavItemOption {
group: None,
value: "/components/flex",
label: "Flex",
},
NavItemOption { NavItemOption {
group: None, group: None,
value: "/components/grid", value: "/components/grid",

View file

@ -42,6 +42,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
"DividerMdPage" => "../../thaw/src/divider/docs/mod.md", "DividerMdPage" => "../../thaw/src/divider/docs/mod.md",
"DrawerMdPage" => "../../thaw/src/drawer/docs/mod.md", "DrawerMdPage" => "../../thaw/src/drawer/docs/mod.md",
"FieldMdPage" => "../../thaw/src/field/docs/mod.md", "FieldMdPage" => "../../thaw/src/field/docs/mod.md",
"FlexMdPage" => "../../thaw/src/flex/docs/mod.md",
"GridMdPage" => "../../thaw/src/grid/docs/mod.md", "GridMdPage" => "../../thaw/src/grid/docs/mod.md",
"IconMdPage" => "../../thaw/src/icon/docs/mod.md", "IconMdPage" => "../../thaw/src/icon/docs/mod.md",
"ImageMdPage" => "../../thaw/src/image/docs/mod.md", "ImageMdPage" => "../../thaw/src/image/docs/mod.md",

View file

@ -9,10 +9,10 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
axum = { version = "0.7", optional = true } axum = { version = "0.7", optional = true }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
leptos = { version = "0.7.0-beta4", features = ["experimental-islands"] } leptos = { version = "0.7.0-beta5", features = ["experimental-islands"] }
leptos_axum = { version = "0.7.0-beta4", optional = true } leptos_axum = { version = "0.7.0-beta5", optional = true }
leptos_meta = { version = "0.7.0-beta4" } leptos_meta = { version = "0.7.0-beta5" }
leptos_router = { version = "0.7.0-beta4" } leptos_router = { version = "0.7.0-beta5" }
tokio = { version = "1", features = ["rt-multi-thread"], optional = true } tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
tower = { version = "0.4", optional = true } tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true } tower-http = { version = "0.5", features = ["fs"], optional = true }

View file

@ -9,10 +9,10 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
axum = { version = "0.7.5", optional = true } axum = { version = "0.7.5", optional = true }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
leptos = { version = "0.7.0-beta4" } leptos = { version = "0.7.0-beta5" }
leptos_axum = { version = "0.7.0-beta4", optional = true } leptos_axum = { version = "0.7.0-beta5", optional = true }
leptos_meta = { version = "0.7.0-beta4" } leptos_meta = { version = "0.7.0-beta5" }
leptos_router = { version = "0.7.0-beta4" } leptos_router = { version = "0.7.0-beta5" }
tokio = { version = "1", features = ["rt-multi-thread"], optional = true } tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
tower = { version = "0.5.0", optional = true } tower = { version = "0.5.0", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true } tower-http = { version = "0.5", features = ["fs"], optional = true }

View file

@ -2,7 +2,7 @@ use crate::{ConfigInjection, Icon};
use leptos::{either::Either, ev, html, prelude::*}; use leptos::{either::Either, ev, html, prelude::*};
use thaw_components::{CSSTransition, Teleport}; use thaw_components::{CSSTransition, Teleport};
use thaw_utils::{ 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, EventListenerHandle,
}; };
@ -43,7 +43,7 @@ pub fn BackTop(
}; };
request_animation_frame(move || { 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()); .unwrap_or_else(|| document().document_element().unwrap());
{ {

View file

@ -55,7 +55,7 @@ pub fn Combobox(
let Some(clear_icon_el) = clear_icon_ref.get() else { let Some(clear_icon_el) = clear_icon_ref.get() else {
return; 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() { if disabled.get_untracked() {
return; return;
} }

View file

@ -75,7 +75,7 @@ impl ListboxInjection {
#[inline] #[inline]
pub fn trigger(&self) { pub fn trigger(&self) {
self.0.trigger(); self.0.notify();
} }
} }

110
thaw/src/flex/docs/mod.md Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
mod flex;
pub use flex::*;

View file

@ -19,6 +19,7 @@ mod dialog;
mod divider; mod divider;
mod drawer; mod drawer;
mod field; mod field;
mod flex;
mod grid; mod grid;
mod icon; mod icon;
mod image; mod image;
@ -73,6 +74,7 @@ pub use dialog::*;
pub use divider::*; pub use divider::*;
pub use drawer::*; pub use drawer::*;
pub use field::*; pub use field::*;
pub use flex::*;
pub use grid::*; pub use grid::*;
pub use icon::*; pub use icon::*;
pub use image::*; pub use image::*;

View file

@ -77,3 +77,4 @@ view! {
| disabled | `MaybeSignal<bool>` | `false` | Whether the link is disabled. | | 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. | | disabled_focusable | `MaybeSignal<bool>` | `false` | When set, allows the link to be focusable even when it has been disabled. |
| children | `Children` | | | | children | `Children` | | |
| target | `MaybeSignal<String>` | | Specifies where to open the linked document. |

View file

@ -16,6 +16,9 @@ pub fn Link(
#[prop(optional, into)] #[prop(optional, into)]
disabled_focusable: MaybeSignal<bool>, disabled_focusable: MaybeSignal<bool>,
children: Children, children: Children,
/// Specifies where to open the linked document.
#[prop(optional, into)]
target: MaybeSignal<String>,
) -> impl IntoView { ) -> impl IntoView {
mount_style("link", include_str!("./link.css")); mount_style("link", include_str!("./link.css"));
@ -46,6 +49,7 @@ pub fn Link(
href=href href=href
tabindex=tabindex tabindex=tabindex
aria-disabled=move || link_disabled.get().then_some("true") aria-disabled=move || link_disabled.get().then_some("true")
target=target
> >
{children()} {children()}
</a> </a>

View file

@ -80,7 +80,7 @@ pub fn Menu(
let Some(target_el) = target_ref.get() else { let Some(target_el) = target_ref.get() else {
return; 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 { if trigger_type != MenuTriggerType::Click {
return; return;
} }

View file

@ -99,7 +99,7 @@ pub fn Popover(
let Some(target_el) = target_ref.get() else { let Some(target_el) = target_ref.get() else {
return; 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 { if trigger_type != PopoverTriggerType::Click {
return; return;
} }

View file

@ -23,10 +23,22 @@ view! {
} }
``` ```
### Custom label
```rust demo
view! {
<Spinner label="Label"/>
<Spinner>
<b style="color: blue">"Label"</b>
</Spinner>
}
```
### Spinner Props ### Spinner Props
| Name | Type | Default | Description | | Name | Type | Default | Description |
| ----- | -------------------------- | --------------------- | ---------------------------------- | | -------- | -------------------------- | --------------------- | ---------------------------------- |
| class | `MaybeProp<String>` | `Default::default()` | | | class | `MaybeProp<String>` | `Default::default()` | |
| label | `MaybeProp<String>` | `Default::default()` | An optional label for the Spinner. | | label | `MaybeProp<String>` | `Default::default()` | An optional label for the Spinner. |
| size | `MaybeSignal<SpinnerSize>` | `SpinnerSize::Medium` | The size of the spinner. | | size | `MaybeSignal<SpinnerSize>` | `SpinnerSize::Medium` | The size of the spinner. |
| children | `Option<Children>` | `None` | |

View file

@ -38,14 +38,16 @@ pub fn Spinner(
/// The size of the spinner. /// The size of the spinner.
#[prop(optional, into)] #[prop(optional, into)]
size: MaybeSignal<SpinnerSize>, size: MaybeSignal<SpinnerSize>,
#[prop(optional)] children: Option<Children>,
) -> impl IntoView { ) -> impl IntoView {
mount_style("spinner", include_str!("./spinner.css")); mount_style("spinner", include_str!("./spinner.css"));
let id = StoredValue::new(uuid::Uuid::new_v4().to_string()); let id = StoredValue::new(uuid::Uuid::new_v4().to_string());
let spinner_label = label.clone(); let spinner_label = label.clone();
let children_flag = children.is_some();
let labelledby = move || { let labelledby = move || {
spinner_label.with(|label| { spinner_label.with(|label| {
if label.is_some() { if label.is_some() || children_flag {
Some(id.get_value()) Some(id.get_value())
} else { } else {
None None
@ -66,7 +68,16 @@ pub fn Spinner(
<span class="thaw-spinner__spinner"> <span class="thaw-spinner__spinner">
<span class="thaw-spinner__spinner-tail"></span> <span class="thaw-spinner__spinner-tail"></span>
</span> </span>
{move || { {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() { if let Some(label) = label.get() {
view! { view! {
<label class="thaw-spinner__label" id=id.get_value()> <label class="thaw-spinner__label" id=id.get_value()>
@ -77,6 +88,9 @@ pub fn Spinner(
} else { } else {
None None
} }
}
}
.into_any()
}} }}
</div> </div>
} }

View file

@ -47,7 +47,7 @@ pub fn TagPickerInput(#[prop(optional, into)] class: MaybeProp<String>) -> impl
on:blur=on_blur on:blur=on_blur
on:input=on_input on:input=on_input
prop:value=move || { prop:value=move || {
value_trigger.trigger(); value_trigger.notify();
"" ""
} }
/> />

View file

@ -48,7 +48,7 @@ pub fn Textarea(
let input_value = event_target_value(&ev); let input_value = event_target_value(&ev);
if let Some(allow_value) = allow_value.as_ref() { if let Some(allow_value) = allow_value.as_ref() {
if !allow_value(input_value.clone()) { if !allow_value(input_value.clone()) {
value_trigger.trigger(); value_trigger.notify();
return; return;
} }
} }

View file

@ -43,7 +43,7 @@ impl ToasterInjection {
pub fn dispatch_toast(&self, any_view: AnyView<Dom>, options: ToastOptions) { pub fn dispatch_toast(&self, any_view: AnyView<Dom>, options: ToastOptions) {
self.sender self.sender
.with_value(|sender| sender.send((any_view, options)).unwrap_throw()); .with_value(|sender| sender.send((any_view, options)).unwrap_throw());
self.trigger.with_value(|trigger| trigger.trigger()); self.trigger.with_value(|trigger| trigger.notify());
} }
} }

View file

@ -34,7 +34,7 @@ pub fn Upload(
let Some(trigger_el) = trigger_ref.get() else { let Some(trigger_el) = trigger_ref.get() else {
return; 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() { if let Some(input_ref) = input_ref.get_untracked() {
input_ref.click(); input_ref.click();
} }

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;
@ -9,5 +9,5 @@
.thaw-binder-follower-content { .thaw-binder-follower-content {
position: absolute; position: absolute;
z-index: 2000; z-index: 1000000;
} }

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

View file

@ -14,7 +14,7 @@ use leptos::{
leptos_dom::helpers::WindowListenerHandle, leptos_dom::helpers::WindowListenerHandle,
prelude::*, 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] #[slot]
pub struct Follower { pub struct Follower {
@ -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: {};",
@ -132,12 +141,12 @@ where
}; };
let mut handle_vec = vec![]; let mut handle_vec = vec![];
let mut cursor = get_scroll_parent(&el); let mut cursor = get_scroll_parent_node(&el);
loop { loop {
if let Some(el) = cursor.take() { if let Some(node) = cursor.take() {
cursor = get_scroll_parent(&el); 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(); sync_position();
}); });
handle_vec.push(handle); handle_vec.push(handle);
@ -197,18 +206,16 @@ 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()
> >
{follower_children()} <Provider value=follower_injection>{follower_children()}</Provider>
</div> </div>
</div> </div>
</Provider>
</Teleport> </Teleport>
} }
} }

View file

@ -92,13 +92,13 @@ where
} }
}; };
let handle = match types { let handle = match types {
AnimationTypes::Transition => add_event_listener( AnimationTypes::Transition => {
el.deref().clone().into(), add_event_listener(el.deref().clone(), ev::transitionend, move |_| {
ev::transitionend, event_listener()
move |_| event_listener(), })
), }
AnimationTypes::Animation => { AnimationTypes::Animation => {
add_event_listener(el.deref().clone().into(), ev::animationend, move |_| { add_event_listener(el.deref().clone(), ev::animationend, move |_| {
event_listener() event_listener()
}) })
} }

View file

@ -1,16 +1,35 @@
use leptos::prelude::*; 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> { pub fn get_scroll_parent_node(node: &Node) -> Option<Node> {
let Some(parent_element) = get_parent_element(element) else { let parent_node = node.parent_node()?;
return None;
};
if parent_element.node_type() == 9 { let node_type = parent_node.node_type();
return Some(parent_element); 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_node);
}
if overflow.contains("scroll") {
return Some(parent_node);
}
if overflow.contains("overlay") {
return Some(parent_node);
}
}
} else if node_type == Node::DOCUMENT_NODE {
return Some(document().into());
} }
if parent_element.node_type() == 1 { 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) { if let Some((overflow, overflow_x, overflow_y)) = get_overflow(&parent_element) {
let overflow = format!("{overflow}{overflow_x}{overflow_y}"); let overflow = format!("{overflow}{overflow_x}{overflow_y}");
if overflow.contains("auto") { if overflow.contains("auto") {
@ -23,17 +42,8 @@ pub fn get_scroll_parent(element: &Element) -> Option<Element> {
return Some(parent_element); return Some(parent_element);
} }
} }
}
get_scroll_parent(&parent_element) get_scroll_parent_element(&parent_element)
}
fn get_parent_element(element: &Element) -> Option<Element> {
if element.node_type() == 9 {
None
} else {
element.parent_element()
}
} }
fn get_overflow(parent_element: &Element) -> Option<(String, String, String)> { fn get_overflow(parent_element: &Element) -> Option<(String, String, String)> {

View file

@ -2,6 +2,6 @@ mod get_scroll_parent;
mod mount_style; mod mount_style;
mod scroll_into_view; 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 mount_style::{mount_dynamic_style, mount_style};
pub use scroll_into_view::scroll_into_view; pub use scroll_into_view::scroll_into_view;

View file

@ -3,8 +3,8 @@ use web_sys::HtmlElement;
pub fn scroll_into_view(el: &HtmlElement) { pub fn scroll_into_view(el: &HtmlElement) {
cfg_if! { if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] { cfg_if! { if #[cfg(all(target_arch = "wasm32", any(feature = "csr", feature = "hydrate")))] {
use super::get_scroll_parent; use super::get_scroll_parent_element;
if let Some(parent) = get_scroll_parent(el) { if let Some(parent) = get_scroll_parent_element(el) {
let parent_rect = parent.get_bounding_client_rect(); let parent_rect = parent.get_bounding_client_rect();
let el_rect = el.get_bounding_client_rect(); let el_rect = el.get_bounding_client_rect();
if el_rect.y() < parent_rect.y() { if el_rect.y() < parent_rect.y() {

View file

@ -1,9 +1,9 @@
use ::wasm_bindgen::{prelude::Closure, JsCast}; use ::wasm_bindgen::{prelude::Closure, JsCast};
use leptos::ev; use leptos::ev;
use web_sys::{Element, EventTarget}; use web_sys::EventTarget;
pub fn add_event_listener<E>( pub fn add_event_listener<E>(
target: Element, target: impl Into<EventTarget>,
event: E, event: E,
cb: impl Fn(E::EventType) + 'static, cb: impl Fn(E::EventType) + 'static,
) -> EventListenerHandle ) -> EventListenerHandle
@ -31,12 +31,12 @@ impl EventListenerHandle {
} }
fn add_event_listener_untyped( fn add_event_listener_untyped(
target: Element, target: impl Into<EventTarget>,
event_name: &str, event_name: &str,
cb: impl Fn(web_sys::Event) + 'static, cb: impl Fn(web_sys::Event) + 'static,
) -> EventListenerHandle { ) -> EventListenerHandle {
fn wel( fn wel(
target: Element, target: EventTarget,
cb: Box<dyn FnMut(web_sys::Event)>, cb: Box<dyn FnMut(web_sys::Event)>,
event_name: &str, event_name: &str,
) -> EventListenerHandle { ) -> 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>( pub fn add_event_listener_with_bool<E: ev::EventDescriptor + 'static>(
target: impl IntoEventTarget, target: impl Into<EventTarget>,
event: E, event: E,
cb: impl Fn(E::EventType) + 'static, cb: impl Fn(E::EventType) + 'static,
use_capture: bool, use_capture: bool,
@ -67,7 +67,7 @@ where
E::EventType: JsCast, E::EventType: JsCast,
{ {
add_event_listener_untyped_with_bool( add_event_listener_untyped_with_bool(
target.into_event_target(), target,
&event.name(), &event.name(),
move |e| cb(e.unchecked_into::<E::EventType>()), move |e| cb(e.unchecked_into::<E::EventType>()),
use_capture, use_capture,
@ -75,7 +75,7 @@ where
} }
fn add_event_listener_untyped_with_bool( fn add_event_listener_untyped_with_bool(
target: EventTarget, target: impl Into<EventTarget>,
event_name: &str, event_name: &str,
cb: impl Fn(web_sys::Event) + 'static, cb: impl Fn(web_sys::Event) + 'static,
use_capture: bool, 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 { // pub trait IntoEventTarget {
fn into_event_target(self) -> EventTarget; // fn into_event_target(self) -> EventTarget;
} // }
impl IntoEventTarget for EventTarget { // impl IntoEventTarget for EventTarget {
fn into_event_target(self) -> EventTarget { // fn into_event_target(self) -> EventTarget {
self // self
} // }
} // }
impl IntoEventTarget for web_sys::Document { // impl IntoEventTarget for web_sys::Document {
fn into_event_target(self) -> EventTarget { // fn into_event_target(self) -> EventTarget {
self.into() // self.into()
} // }
} // }
// impl IntoEventTarget for Element {
// fn into_event_target(self) -> EventTarget {
// self.into()
// }
// }
// impl IntoEventTarget for HtmlElement<AnyElement> { // impl IntoEventTarget for HtmlElement<AnyElement> {
// fn into_event_target(self) -> EventTarget { // fn into_event_target(self) -> EventTarget {

View file

@ -4,6 +4,6 @@ mod signal_watch;
mod stored_maybe_signal; mod stored_maybe_signal;
pub use component_ref::ComponentRef; 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 signal_watch::SignalWatch;
pub use stored_maybe_signal::StoredMaybeSignal; pub use stored_maybe_signal::StoredMaybeSignal;

View file

@ -58,7 +58,9 @@ impl<T: Send + Sync> OptionModel<T> {
pub fn with_untracked<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> O) -> O { pub fn with_untracked<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> O) -> O {
match self { match self {
Self::T(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::T(value))), 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)))
}
} }
} }
} }

View file

@ -67,10 +67,7 @@ impl<T: Send + Sync> VecModel<T> {
} }
} }
pub fn with<O>( pub fn with<O>(&self, fun: impl FnOnce(VecModelWithValue<T>) -> O) -> O {
&self,
fun: impl FnOnce(VecModelWithValue<T>) -> O,
) -> O {
match self { match self {
Self::T(read, _, _) => read.with(|value| fun(VecModelWithValue::T(value))), Self::T(read, _, _) => read.with(|value| fun(VecModelWithValue::T(value))),
Self::Option(read, _, _) => read.with(|value| fun(VecModelWithValue::Option(value))), Self::Option(read, _, _) => read.with(|value| fun(VecModelWithValue::Option(value))),