perspective_viewer/components/
empty_row.rs1use std::collections::HashSet;
14use std::rc::Rc;
15
16use derivative::Derivative;
17use perspective_client::clone;
18use wasm_bindgen::JsCast;
19use web_sys::*;
20use yew::prelude::*;
21
22use crate::components::style::LocalStyle;
23use crate::css;
24use crate::custom_elements::FilterDropDownElement;
25
26#[derive(Default)]
27pub struct EmptyRow {
28 input_ref: NodeRef,
29}
30
31#[derive(Properties, Derivative)]
32#[derivative(Debug)]
33pub struct EmptyRowProps {
34 #[derivative(Debug = "ignore")]
35 pub dropdown: Rc<FilterDropDownElement>,
36 pub exclude: HashSet<String>,
37 pub on_select: Callback<String>,
38 pub focused: bool,
39 pub index: usize,
40 pub set_focused_index: Callback<Option<usize>>,
41 pub value: String,
42 pub column_name: String,
43}
44
45impl PartialEq for EmptyRowProps {
46 fn eq(&self, _other: &Self) -> bool {
47 false
48 }
49}
50
51pub enum EmptyRowMsg {
52 KeyDown(u32),
53 Blur,
54 Input(String),
55 Focus,
56}
57
58use EmptyRowMsg::*;
59
60impl Component for EmptyRow {
61 type Message = EmptyRowMsg;
62 type Properties = EmptyRowProps;
63
64 fn view(&self, ctx: &Context<Self>) -> Html {
65 let onblur = ctx.link().callback(|_| Blur);
66 let oninput = ctx.link().callback(|e: InputEvent| {
67 let value = e
68 .target()
69 .unwrap()
70 .unchecked_into::<HtmlInputElement>()
71 .value();
72 Input(value)
73 });
74 let onkeydown = ctx
75 .link()
76 .callback(|event: KeyboardEvent| KeyDown(event.key_code()));
77
78 if ctx.props().focused {
79 ctx.link()
81 .send_message_batch(vec![Focus, Input(ctx.props().value.clone())]);
82 }
83 let onfocus = {
84 clone!(ctx.props().value);
85 ctx.link().callback(move |_| Input(value.clone()))
86 };
87
88 html! {
89 <div class="pivot-column column-empty">
90 <LocalStyle href={css!("empty-column")} />
91 <input
92 spellcheck="false"
93 ref={self.input_ref.clone()}
94 {onblur}
95 {onkeydown}
96 {oninput}
97 {onfocus}
98 class="column-empty-input"
99 />
100 </div>
101 }
102 }
103
104 fn changed(&mut self, _ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
105 if let Some(elem) = self.input_ref.cast::<HtmlInputElement>() {
106 elem.blur().unwrap();
107 }
108 false
109 }
110
111 fn destroy(&mut self, ctx: &Context<Self>) {
112 ctx.props().dropdown.hide().unwrap();
113 }
114
115 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
116 let p = ctx.props();
117 match msg {
118 Focus => {
119 if let Some(elem) = self.input_ref.cast::<HtmlInputElement>() {
120 elem.set_value(&ctx.props().value);
121 elem.focus().unwrap();
122 }
123
124 false
125 },
126 Blur => {
127 p.dropdown.hide().unwrap();
128 if let Some(elem) = self.input_ref.cast::<HtmlInputElement>() {
129 elem.set_value("");
130 }
131 p.set_focused_index.emit(Some(p.index + 1));
132
133 false
134 },
135 KeyDown(40) => {
136 p.dropdown.item_down();
137 false
138 },
139 KeyDown(38) => {
140 p.dropdown.item_up();
141 false
142 },
143 KeyDown(13) => {
144 p.dropdown.item_select();
145 p.dropdown.hide().unwrap();
146 p.set_focused_index.emit(Some(p.index + 1));
147 true
148 },
149 KeyDown(_) => false,
150 Input(value) => {
151 if let Some(elem) = self.input_ref.cast::<HtmlElement>() {
152 ctx.props().dropdown.autocomplete(
153 (ctx.props().index, ctx.props().column_name.clone()),
154 value,
155 ctx.props().exclude.clone(),
156 elem,
157 ctx.props().on_select.clone(),
158 );
159 }
160
161 false
162 },
163 }
164 }
165
166 fn create(_ctx: &Context<Self>) -> Self {
167 Self::default()
168 }
169}