yew_autocomplete/
autocomplete.rs1use std::{future::Future, pin::Pin, rc::Rc};
2
3use yew::prelude::*;
4
5use crate::{
6 autocomplete_state::{AutocompleteConfig, AutocompleteState, HighlightDirection},
7 view::{self, InputCallbacks, RenderHtml},
8};
9
10pub type ItemResolverResult<T> = Pin<Box<dyn Future<Output = Result<Vec<T>, ()>>>>;
12
13pub type ItemResolver<T> = Callback<String, ItemResolverResult<T>>;
16
17pub struct Autocomplete<T: Clone + PartialEq + RenderHtml + 'static> {
19 state: AutocompleteState<T>,
20}
21
22#[derive(PartialEq, Properties, Clone)]
24pub struct Props<T: PartialEq> {
25 pub resolve_items: ItemResolver<T>,
26 pub onchange: Callback<Vec<T>>,
27 pub children: Children, #[prop_or(true)]
30 pub auto: bool,
31 #[prop_or(false)]
32 pub show_selected: bool,
33 #[prop_or(false)]
34 pub multi_select: bool,
35}
36
37#[derive(Debug, PartialEq)]
39pub enum Msg {
40 OnInput(String),
41 OnKeydown(u32),
42 SelectItem(usize),
43 Resolve,
44 Noop(bool),
45}
46
47impl<T> Component for Autocomplete<T>
48where
49 T: 'static + PartialEq + Clone + RenderHtml,
50{
51 type Message = Msg;
52
53 type Properties = Props<T>;
54
55 fn create(ctx: &Context<Self>) -> Self {
56 Self {
57 state: AutocompleteState::new(AutocompleteConfig::new(
58 ctx.props().auto,
59 ctx.props().multi_select,
60 ctx.props().onchange.clone(),
61 ctx.link().callback(Msg::Noop),
62 ctx.props().resolve_items.clone(),
63 )),
64 }
65 }
66
67 fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
68 match msg {
69 Msg::OnInput(value) => {
70 self.state.oninput(value.as_str());
71 true
72 }
73 Msg::OnKeydown(key) => {
74 match key {
75 13 => {
76 self.state.select_current();
77 true
78 }
79 38 => {
80 self.state.set_highlight_item(&HighlightDirection::Previous);
81 true
82 }
83 40 => {
84 self.state.set_highlight_item(&HighlightDirection::Next);
85 true
86 }
87 _ => false, }
89 }
90 Msg::SelectItem(index) => {
91 self.state.select_item(index);
92 true
93 }
94 Msg::Resolve => {
95 self.state.resolve();
96 false
97 }
98 Msg::Noop(reload) => reload,
99 }
100 }
101
102 fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
103 if old_props != ctx.props() {
104 self.state.update_config(AutocompleteConfig::new(
105 ctx.props().auto,
106 ctx.props().multi_select,
107 ctx.props().onchange.clone(),
108 ctx.link().callback(Msg::Noop),
109 ctx.props().resolve_items.clone(),
110 ));
111 }
112 true
113 }
114
115 #[allow(clippy::let_underscore_untyped)]
116 fn view(&self, ctx: &Context<Self>) -> Html {
117 let input_callbacks = InputCallbacks {
118 on_input: ctx.link().callback(Msg::OnInput),
119 on_keydown: ctx.link().callback(|e: KeyboardEvent| {
120 let code = e.which();
121
122 match code {
123 13 | 38 | 40 => e.prevent_default(),
128 _ => (),
129 };
130
131 Msg::OnKeydown(code)
132 }),
133 resolve: ctx.link().callback(|_| Msg::Resolve),
134 select_item: ctx.link().callback(Msg::SelectItem),
135 };
136 let selected_items = if ctx.props().show_selected {
137 Rc::new(self.state.selected_items())
138 } else {
139 Rc::new(Vec::new())
140 };
141
142 let view_context = view::Context {
143 value: self.state.input(),
144 callbacks: input_callbacks,
145 items: Rc::new(self.state.items()),
146 highlighted: self.state.highlighted_item(),
147 selected_items,
148 auto: ctx.props().auto,
149 };
150
151 html! {
152 <ContextProvider<view::Context<T>> context={view_context}>
153 {for ctx.props().children.iter() }
154 </ContextProvider<view::Context<T>>>
155 }
156 }
157}