euv_ui/component/input/hook/
impl.rs1use super::*;
2
3impl UseEuvInput {
5 pub fn use_toggle(signal: Signal<bool>) -> Option<Rc<dyn Fn(Event)>> {
19 Some(Rc::new(move |_: Event| {
20 let current: bool = signal.get();
21 signal.set(!current);
22 }))
23 }
24
25 pub fn on_input_value(signal: Signal<String>) -> Option<Rc<dyn Fn(Event)>> {
35 Some(Rc::new(move |event: Event| {
36 let value: Option<String> = event.target().and_then(|target: EventTarget| {
37 if let Ok(input) = target.clone().dyn_into::<HtmlInputElement>() {
38 return Some(input.value());
39 }
40 if let Ok(textarea) = target.clone().dyn_into::<HtmlTextAreaElement>() {
41 return Some(textarea.value());
42 }
43 if let Ok(select) = target.clone().dyn_into::<HtmlSelectElement>() {
44 return Some(select.value());
45 }
46 None
47 });
48 if let Some(value) = value {
49 signal.set(value);
50 }
51 }))
52 }
53
54 pub fn on_change_value(signal: Signal<String>) -> Option<Rc<dyn Fn(Event)>> {
64 Some(Rc::new(move |event: Event| {
65 let value: Option<String> = event.target().and_then(|target: EventTarget| {
66 if let Ok(input) = target.clone().dyn_into::<HtmlInputElement>() {
67 return Some(input.value());
68 }
69 if let Ok(select) = target.clone().dyn_into::<HtmlSelectElement>() {
70 return Some(select.value());
71 }
72 if let Ok(textarea) = target.clone().dyn_into::<HtmlTextAreaElement>() {
73 return Some(textarea.value());
74 }
75 None
76 });
77 if let Some(value) = value {
78 signal.set(value);
79 }
80 }))
81 }
82
83 pub fn on_change_checked(signal: Signal<bool>) -> Option<Rc<dyn Fn(Event)>> {
93 Some(Rc::new(move |event: Event| {
94 if let Some(target) = event.target()
95 && let Ok(input) = target.clone().dyn_into::<HtmlInputElement>()
96 {
97 signal.set(input.checked());
98 }
99 }))
100 }
101
102 const FOCUS_GAP_PX: f64 = 12.0;
106
107 const FOCUS_SCROLL_DELAY_MILLIS: i32 = 220;
110
111 pub fn on_focus_scroll_into_view() -> Option<Rc<dyn Fn(Event)>> {
123 Some(Rc::new(move |event: Event| {
124 let Some(target) = event.target() else {
125 return;
126 };
127 let Ok(element) = target.dyn_into::<HtmlElement>() else {
128 return;
129 };
130 let Some(window) = window() else {
131 return;
132 };
133 let element_clone: HtmlElement = element.clone();
134 let window_clone: Window = window.clone();
135 if let Ok(Some(main_el)) = element.closest("main")
136 && let Ok(main) = main_el.dyn_into::<HtmlElement>()
137 {
138 let _: Result<(), JsValue> = main
139 .style()
140 .set_property("padding-bottom", "var(--euv-keyboard-height, 0px)");
141 }
142 let closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
143 let rect: DomRect = element_clone.get_bounding_client_rect();
144 let input_bottom: f64 = rect.bottom();
145 let viewport_height: f64 = window_clone
146 .visual_viewport()
147 .map(|viewport: VisualViewport| viewport.height())
148 .unwrap_or_else(|| {
149 window_clone
150 .inner_height()
151 .map(|height: JsValue| height.as_f64().unwrap_or_default())
152 .unwrap_or_default()
153 });
154 let document_value: Document = match window_clone.document() {
155 Some(doc) => doc,
156 None => return,
157 };
158 let probe: Element = match document_value.create_element("div") {
159 Ok(el) => el,
160 Err(_) => return,
161 };
162 let keyboard_height: f64 = window_clone
163 .get_computed_style(&probe)
164 .ok()
165 .flatten()
166 .and_then(|style| style.get_property_value("--euv-keyboard-height").ok())
167 .and_then(|raw| {
168 let trimmed = raw.trim().trim_end_matches("px").to_string();
169 trimmed.parse::<f64>().ok()
170 })
171 .unwrap_or(0.0);
172 let visible_bottom: f64 = viewport_height - keyboard_height - Self::FOCUS_GAP_PX;
173 if input_bottom > visible_bottom && visible_bottom > 0.0 {
174 let scroll_amount: f64 = input_bottom - visible_bottom;
175 window_clone.scroll_by_with_x_and_y(0.0, scroll_amount);
176 }
177 }));
178 let _: Result<i32, JsValue> = window
179 .set_timeout_with_callback_and_timeout_and_arguments_0(
180 closure.as_ref().unchecked_ref::<Function>(),
181 Self::FOCUS_SCROLL_DELAY_MILLIS,
182 );
183 closure.forget();
184 }))
185 }
186
187 pub fn on_blur_restore_height() -> Option<Rc<dyn Fn(Event)>> {
191 Some(Rc::new(move |event: Event| {
192 let Some(target) = event.target() else {
193 return;
194 };
195 let Ok(element) = target.dyn_into::<HtmlElement>() else {
196 return;
197 };
198 if let Ok(Some(main_el)) = element.closest("main")
199 && let Ok(main) = main_el.dyn_into::<HtmlElement>()
200 {
201 let _: Result<String, JsValue> = main.style().remove_property("padding-bottom");
202 }
203 }))
204 }
205}