impulse_thaw/auto_complete/
mod.rs1mod auto_complete_option;
2mod types;
3
4pub use auto_complete_option::AutoCompleteOption;
5pub use types::*;
6
7use crate::{
8 combobox::listbox::{listbox_keyboard_event, Listbox},
9 ComponentRef, Input, InputPrefix, InputRef, InputSuffix,
10 _aria::use_active_descendant,
11};
12use leptos::{context::Provider, either::Either, html, prelude::*};
13use std::collections::HashMap;
14use thaw_components::{Follower, FollowerPlacement, FollowerWidth};
15use thaw_utils::{class_list, mount_style, ArcOneCallback, BoxOneCallback, Model};
16
17#[component]
18pub fn AutoComplete(
19 #[prop(optional, into)] class: MaybeProp<String>,
20 #[prop(optional, into)]
22 value: Model<String>,
23 #[prop(optional, into)]
25 placeholder: MaybeProp<String>,
26 #[prop(optional, into)] clear_after_select: Signal<bool>,
28 #[prop(optional, into)]
30 blur_after_select: Signal<bool>,
31 #[prop(optional, into)] on_select: Option<BoxOneCallback<String>>,
33 #[prop(optional, into)]
35 disabled: Signal<bool>,
36 #[prop(optional, into)]
38 size: Signal<AutoCompleteSize>,
39 #[prop(optional)] auto_complete_prefix: Option<AutoCompletePrefix>,
40 #[prop(optional)] auto_complete_suffix: Option<AutoCompleteSuffix>,
41 #[prop(optional)] comp_ref: ComponentRef<AutoCompleteRef>,
42 #[prop(optional)] children: Option<Children>,
43) -> impl IntoView {
44 mount_style("auto-complete", include_str!("./auto-complete.css"));
45 let input_ref = ComponentRef::<InputRef>::new();
46 let listbox_ref = NodeRef::<html::Div>::new();
47 let open_listbox = RwSignal::new(false);
48 let options = StoredValue::new(HashMap::<String, String>::new());
49
50 let allow_value = move |_| {
51 if !open_listbox.get_untracked() {
52 open_listbox.set(true);
53 }
54 true
55 };
56
57 let select_option = ArcOneCallback::new(move |option_value: String| {
58 if clear_after_select.get_untracked() {
59 value.set(String::new());
60 } else {
61 value.set(option_value.clone());
62 }
63 if let Some(on_select) = on_select.as_ref() {
64 on_select(option_value);
65 }
66
67 open_listbox.set(false);
68 if blur_after_select.get_untracked() {
69 if let Some(input_ref) = input_ref.get_untracked() {
70 input_ref.blur();
71 }
72 }
73 });
74
75 let (set_listbox, active_descendant_controller) =
76 use_active_descendant(move |el| el.class_list().contains("thaw-auto-complete-option"));
77 let on_blur = {
78 let active_descendant_controller = active_descendant_controller.clone();
79 move |_| {
80 active_descendant_controller.blur();
81 open_listbox.set(false);
82 }
83 };
84 let on_keydown = {
85 let select_option = select_option.clone();
86 move |e| {
87 let select_option = select_option.clone();
88 listbox_keyboard_event(
89 e,
90 open_listbox,
91 false,
92 &active_descendant_controller,
93 move |option| {
94 options.with_value(|options| {
95 if let Some(value) = options.get(&option.id()) {
96 select_option(value.clone());
97 }
98 });
99 },
100 );
101 }
102 };
103
104 comp_ref.load(AutoCompleteRef { input_ref });
105
106 view! {
107 <crate::_binder::Binder>
108 <div class=class_list!["thaw-auto-complete", class] on:keydown=on_keydown>
109 <Input
110 value
111 placeholder
112 disabled
113 on_focus=move |_| open_listbox.set(true)
114 on_blur=on_blur
115 allow_value
116 size=Signal::derive(move || size.get().into())
117 comp_ref=input_ref
118 >
119 <InputPrefix if_=auto_complete_prefix.is_some() slot>
120
121 {if let Some(auto_complete_prefix) = auto_complete_prefix {
122 Some((auto_complete_prefix.children)())
123 } else {
124 None
125 }}
126
127 </InputPrefix>
128 <InputSuffix if_=auto_complete_suffix.is_some() slot>
129
130 {if let Some(auto_complete_suffix) = auto_complete_suffix {
131 Some((auto_complete_suffix.children)())
132 } else {
133 None
134 }}
135
136 </InputSuffix>
137 </Input>
138 </div>
139 <Follower
140 slot
141 show=open_listbox
142 placement=FollowerPlacement::BottomStart
143 width=FollowerWidth::Target
144 auto_height=true
145 >
146 <Provider value=AutoCompleteInjection {
147 value,
148 select_option,
149 options,
150 }>
151 <Listbox set_listbox listbox_ref class="thaw-auto-complete__listbox">
152 {if let Some(children) = children {
153 Either::Left(children())
154 } else {
155 Either::Right(())
156 }}
157 </Listbox>
158 </Provider>
159 </Follower>
160 </crate::_binder::Binder>
161 }
162}