diff --git a/demo/Cargo.toml b/demo/Cargo.toml index a0f09c4..622fca4 100644 --- a/demo/Cargo.toml +++ b/demo/Cargo.toml @@ -17,6 +17,7 @@ icondata = { version = "0.1.0", features = [ "AiCheckOutlined", "AiGithubOutlined", "AiUserOutlined", + "AiSearchOutlined", ] } prisms = { git = "https://github.com/luoxiaozero/prisms", rev = "16d4d34b93fc20578ebf03137d54ecc7eafa4d4b" } diff --git a/demo/src/components/site_header.rs b/demo/src/components/site_header.rs index 6039c4f..d667b20 100644 --- a/demo/src/components/site_header.rs +++ b/demo/src/components/site_header.rs @@ -66,6 +66,17 @@ pub fn SiteHeader() -> impl IntoView { let navigate = use_navigate(); navigate(&path, Default::default()); }; + let auto_complete_ref = create_component_ref::(); + let handle = window_event_listener(ev::keydown, move |event| { + let key = event.key(); + if key == *"/" { + if let Some(auto_complete_ref) = auto_complete_ref.get_untracked() { + event.prevent_default(); + auto_complete_ref.focus(); + } + } + }); + on_cleanup(move || handle.remove()); view! { @@ -84,11 +95,16 @@ pub fn SiteHeader() -> impl IntoView { + comp_ref=auto_complete_ref + > + + + + + + + + + + + {highlight_str!( + r#" + let value = create_rw_signal(String::from("o")); + let input_ref = create_component_ref::(); + + view! { + + + + + + + + } + "#, + "rust" + )} + + + +

"Prefix & Suffix"

@@ -197,6 +237,32 @@ pub fn InputPage() -> impl IntoView { +

"Input Ref"

+ + + + + + + + + + + + + + + + + + + + +
"Name""Type""Description"
"focus" + "Fn(&self)" + "Focus the input element."
"blur" + "Fn(&self)" + "Blur the input element."
} } diff --git a/src/auto_complete/mod.rs b/src/auto_complete/mod.rs index 3faf235..50cf334 100644 --- a/src/auto_complete/mod.rs +++ b/src/auto_complete/mod.rs @@ -5,7 +5,7 @@ use crate::{ components::{Binder, Follower, FollowerPlacement, FollowerWidth}, use_theme, utils::{mount_style, StoredMaybeSignal}, - Input, Theme, + ComponentRef, Input, InputPrefix, InputRef, InputSuffix, Theme, }; use leptos::*; pub use theme::AutoCompleteTheme; @@ -16,6 +16,16 @@ pub struct AutoCompleteOption { pub value: String, } +#[slot] +pub struct AutoCompletePrefix { + children: Children, +} + +#[slot] +pub struct AutoCompleteSuffix { + children: Children, +} + #[component] pub fn AutoComplete( #[prop(optional, into)] value: RwSignal, @@ -26,6 +36,9 @@ pub fn AutoComplete( #[prop(optional, into)] disabled: MaybeSignal, #[prop(optional, into)] invalid: MaybeSignal, #[prop(optional, into)] class: MaybeSignal, + #[prop(optional)] auto_complete_prefix: Option, + #[prop(optional)] auto_complete_suffix: Option, + #[prop(optional)] comp_ref: ComponentRef, ) -> impl IntoView { mount_style("auto-complete", include_str!("./auto-complete.css")); let theme = use_theme(Theme::light); @@ -108,6 +121,10 @@ pub fn AutoComplete( } } }; + let input_ref = ComponentRef::::new(); + input_ref.on_load(move |_| { + comp_ref.load(AutoCompleteRef { input_ref }); + }); let ssr_class = ssr_class(&class); view! { @@ -121,7 +138,27 @@ pub fn AutoComplete( on_focus=move |_| open_menu() on_blur=move |_| is_show_menu.set(false) allow_value - /> + comp_ref=input_ref + > + + { + if let Some(auto_complete_prefix) = auto_complete_prefix { + Some((auto_complete_prefix.children)()) + } else { + None + } + } + + + { + if let Some(auto_complete_suffix) = auto_complete_suffix { + Some((auto_complete_suffix.children)()) + } else { + None + } + } + + } } + +#[derive(Clone)] +pub struct AutoCompleteRef { + input_ref: ComponentRef, +} + +impl AutoCompleteRef { + pub fn focus(&self) { + if let Some(input_ref) = self.input_ref.get_untracked() { + input_ref.focus(); + } + } + + pub fn blur(&self) { + if let Some(input_ref) = self.input_ref.get_untracked() { + input_ref.blur(); + } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs index 5139bf3..ac0fd1a 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2,7 +2,7 @@ mod theme; use crate::{ theme::{use_theme, Theme}, - utils::mount_style, + utils::{mount_style, ComponentRef}, }; use leptos::*; pub use theme::InputTheme; @@ -25,11 +25,15 @@ impl InputVariant { #[slot] pub struct InputPrefix { + #[prop(default = true)] + if_: bool, children: Children, } #[slot] pub struct InputSuffix { + #[prop(default = true)] + if_: bool, children: Children, } @@ -45,6 +49,7 @@ pub fn Input( #[prop(optional, into)] invalid: MaybeSignal, #[prop(optional)] input_prefix: Option, #[prop(optional)] input_suffix: Option, + #[prop(optional)] comp_ref: ComponentRef, ) -> impl IntoView { let theme = use_theme(Theme::light); mount_style("input", include_str!("./input.css")); @@ -114,6 +119,10 @@ pub fn Input( }); css_vars }); + let input_ref = create_node_ref::(); + input_ref.on_load(move |_| { + comp_ref.load(InputRef { input_ref }); + }); view! {
- {if let Some(prefix) = input_prefix { + {if let Some(prefix) = input_prefix.map(|prefix| prefix.if_.then(|| prefix)).flatten() { view! {
{(prefix.children)()}
}.into() } else { None @@ -141,9 +150,10 @@ pub fn Input( class="thaw-input__input-el" disabled=move || disabled.get() placeholder=move || placeholder.get() + ref=input_ref /> - {if let Some(suffix) = input_suffix { + {if let Some(suffix) = input_suffix.map(|suffix| suffix.if_.then(|| suffix)).flatten() { view! {
{(suffix.children)()}
}.into() } else { None @@ -152,3 +162,22 @@ pub fn Input(
} } + +#[derive(Clone)] +pub struct InputRef { + input_ref: NodeRef, +} + +impl InputRef { + pub fn focus(&self) { + if let Some(input_el) = self.input_ref.get_untracked() { + _ = input_el.focus(); + } + } + + pub fn blur(&self) { + if let Some(input_el) = self.input_ref.get_untracked() { + _ = input_el.blur(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 783010e..9df80bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,4 +77,4 @@ pub use theme::*; pub use time_picker::*; pub use typography::*; pub use upload::*; -pub use utils::SignalWatch; +pub use utils::{create_component_ref, ComponentRef, SignalWatch}; diff --git a/src/utils/component_ref.rs b/src/utils/component_ref.rs index 66d0a34..8baf1c1 100644 --- a/src/utils/component_ref.rs +++ b/src/utils/component_ref.rs @@ -1,4 +1,8 @@ -use leptos::{create_rw_signal, RwSignal, SignalGetUntracked, SignalSet}; +use leptos::{ + create_render_effect, create_rw_signal, logging::debug_warn, RwSignal, SignalGet, + SignalGetUntracked, SignalUpdate, +}; +use std::cell::Cell; pub struct ComponentRef(RwSignal>); @@ -17,6 +21,17 @@ impl Clone for ComponentRef { impl Copy for ComponentRef {} impl ComponentRef { + pub fn new() -> Self { + Self::default() + } + + pub fn get(&self) -> Option + where + T: Clone, + { + self.0.get() + } + pub fn get_untracked(&self) -> Option where T: Clone, @@ -24,7 +39,33 @@ impl ComponentRef { self.0.get_untracked() } - pub fn load(&self, comp_ref: T) { - self.0.set(Some(comp_ref)); + pub fn load(&self, comp: T) { + self.0.update(|current| { + if current.is_some() { + debug_warn!( + "You are setting a ComponentRef that has already been filled. \ + It’s possible this is intentional." + ); + } + *current = Some(comp); + }); + } + + pub fn on_load(self, f: F) + where + T: Clone, + F: FnOnce(T) + 'static, + { + let f = Cell::new(Some(f)); + + create_render_effect(move |_| { + if let Some(comp) = self.get() { + f.take().unwrap()(comp); + } + }); } } + +pub fn create_component_ref() -> ComponentRef { + ComponentRef::default() +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c01e152..19e0ac9 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,7 +7,7 @@ mod signal; mod stored_maybe_signal; // pub use callback::AsyncCallback; -pub(crate) use component_ref::ComponentRef; +pub use component_ref::{create_component_ref, ComponentRef}; pub(crate) use dyn_classes::*; pub(crate) use event_listener::*; pub(crate) use mount_style::mount_style;