event.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. use std::{any::Any, collections::HashMap};
  2. use dioxus_html::{
  3. point_interaction::{
  4. InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
  5. },
  6. DragData, FileEngine, FormData, HasDragData, HasFileData, HasFormData, HasImageData,
  7. HasMouseData, HtmlEventConverter, ImageData, MountedData, PlatformEventData, ScrollData,
  8. };
  9. use js_sys::Array;
  10. use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
  11. use web_sys::{Document, DragEvent, Element, Event, MouseEvent};
  12. pub(crate) struct WebEventConverter;
  13. #[inline(always)]
  14. fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {
  15. event
  16. .downcast::<GenericWebSysEvent>()
  17. .expect("event should be a GenericWebSysEvent")
  18. }
  19. impl HtmlEventConverter for WebEventConverter {
  20. #[inline(always)]
  21. fn convert_animation_data(
  22. &self,
  23. event: &dioxus_html::PlatformEventData,
  24. ) -> dioxus_html::AnimationData {
  25. downcast_event(event).raw.clone().into()
  26. }
  27. #[inline(always)]
  28. fn convert_clipboard_data(
  29. &self,
  30. event: &dioxus_html::PlatformEventData,
  31. ) -> dioxus_html::ClipboardData {
  32. downcast_event(event).raw.clone().into()
  33. }
  34. #[inline(always)]
  35. fn convert_composition_data(
  36. &self,
  37. event: &dioxus_html::PlatformEventData,
  38. ) -> dioxus_html::CompositionData {
  39. downcast_event(event).raw.clone().into()
  40. }
  41. #[inline(always)]
  42. fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData {
  43. let event = downcast_event(event);
  44. DragData::new(WebDragData::new(event.raw.clone().unchecked_into()))
  45. }
  46. #[inline(always)]
  47. fn convert_focus_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FocusData {
  48. downcast_event(event).raw.clone().into()
  49. }
  50. #[inline(always)]
  51. fn convert_form_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FormData {
  52. let event = downcast_event(event);
  53. FormData::new(WebFormData::new(event.element.clone(), event.raw.clone()))
  54. }
  55. #[inline(always)]
  56. fn convert_image_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::ImageData {
  57. let event = downcast_event(event);
  58. let error = event.raw.type_() == "error";
  59. ImageData::new(WebImageEvent::new(event.raw.clone(), error))
  60. }
  61. #[inline(always)]
  62. fn convert_keyboard_data(
  63. &self,
  64. event: &dioxus_html::PlatformEventData,
  65. ) -> dioxus_html::KeyboardData {
  66. downcast_event(event).raw.clone().into()
  67. }
  68. #[inline(always)]
  69. fn convert_media_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MediaData {
  70. downcast_event(event).raw.clone().into()
  71. }
  72. #[allow(unused_variables)]
  73. #[inline(always)]
  74. fn convert_mounted_data(&self, event: &dioxus_html::PlatformEventData) -> MountedData {
  75. #[cfg(feature = "mounted")]
  76. {
  77. MountedData::from(
  78. event
  79. .downcast::<web_sys::Element>()
  80. .expect("event should be a web_sys::Element"),
  81. )
  82. }
  83. #[cfg(not(feature = "mounted"))]
  84. {
  85. panic!("mounted events are not supported without the mounted feature on the dioxus-web crate enabled")
  86. }
  87. }
  88. #[inline(always)]
  89. fn convert_mouse_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MouseData {
  90. downcast_event(event).raw.clone().into()
  91. }
  92. #[inline(always)]
  93. fn convert_pointer_data(
  94. &self,
  95. event: &dioxus_html::PlatformEventData,
  96. ) -> dioxus_html::PointerData {
  97. downcast_event(event).raw.clone().into()
  98. }
  99. #[inline(always)]
  100. fn convert_scroll_data(
  101. &self,
  102. event: &dioxus_html::PlatformEventData,
  103. ) -> dioxus_html::ScrollData {
  104. ScrollData::from(downcast_event(event).raw.clone())
  105. }
  106. #[inline(always)]
  107. fn convert_selection_data(
  108. &self,
  109. event: &dioxus_html::PlatformEventData,
  110. ) -> dioxus_html::SelectionData {
  111. downcast_event(event).raw.clone().into()
  112. }
  113. #[inline(always)]
  114. fn convert_toggle_data(
  115. &self,
  116. event: &dioxus_html::PlatformEventData,
  117. ) -> dioxus_html::ToggleData {
  118. downcast_event(event).raw.clone().into()
  119. }
  120. #[inline(always)]
  121. fn convert_touch_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::TouchData {
  122. downcast_event(event).raw.clone().into()
  123. }
  124. #[inline(always)]
  125. fn convert_transition_data(
  126. &self,
  127. event: &dioxus_html::PlatformEventData,
  128. ) -> dioxus_html::TransitionData {
  129. downcast_event(event).raw.clone().into()
  130. }
  131. #[inline(always)]
  132. fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData {
  133. downcast_event(event).raw.clone().into()
  134. }
  135. }
  136. /// A extension trait for web-sys events that provides a way to get the event as a web-sys event.
  137. pub trait WebEventExt<E> {
  138. /// Get the event as a web-sys event.
  139. fn web_event(&self) -> &E;
  140. }
  141. impl WebEventExt<web_sys::AnimationEvent> for dioxus_html::AnimationData {
  142. fn web_event(&self) -> &web_sys::AnimationEvent {
  143. self.downcast::<web_sys::AnimationEvent>()
  144. .expect("event should be a WebAnimationEvent")
  145. }
  146. }
  147. impl WebEventExt<web_sys::Event> for dioxus_html::ClipboardData {
  148. fn web_event(&self) -> &web_sys::Event {
  149. self.downcast::<web_sys::Event>()
  150. .expect("event should be a web_sys::Event")
  151. }
  152. }
  153. impl WebEventExt<web_sys::CompositionEvent> for dioxus_html::CompositionData {
  154. fn web_event(&self) -> &web_sys::CompositionEvent {
  155. self.downcast::<web_sys::CompositionEvent>()
  156. .expect("event should be a WebCompositionEvent")
  157. }
  158. }
  159. impl WebEventExt<web_sys::MouseEvent> for dioxus_html::DragData {
  160. fn web_event(&self) -> &web_sys::MouseEvent {
  161. &self
  162. .downcast::<WebDragData>()
  163. .expect("event should be a WebMouseEvent")
  164. .raw
  165. }
  166. }
  167. impl WebEventExt<web_sys::FocusEvent> for dioxus_html::FocusData {
  168. fn web_event(&self) -> &web_sys::FocusEvent {
  169. self.downcast::<web_sys::FocusEvent>()
  170. .expect("event should be a WebFocusEvent")
  171. }
  172. }
  173. impl WebEventExt<web_sys::Event> for dioxus_html::FormData {
  174. fn web_event(&self) -> &web_sys::Event {
  175. self.downcast::<web_sys::Event>()
  176. .expect("event should be a WebFormData")
  177. }
  178. }
  179. impl WebEventExt<WebImageEvent> for dioxus_html::ImageData {
  180. fn web_event(&self) -> &WebImageEvent {
  181. self.downcast::<WebImageEvent>()
  182. .expect("event should be a WebImageEvent")
  183. }
  184. }
  185. impl WebEventExt<web_sys::KeyboardEvent> for dioxus_html::KeyboardData {
  186. fn web_event(&self) -> &web_sys::KeyboardEvent {
  187. self.downcast::<web_sys::KeyboardEvent>()
  188. .expect("event should be a WebKeyboardEvent")
  189. }
  190. }
  191. impl WebEventExt<web_sys::Event> for dioxus_html::MediaData {
  192. fn web_event(&self) -> &web_sys::Event {
  193. self.downcast::<web_sys::Event>()
  194. .expect("event should be a WebMediaEvent")
  195. }
  196. }
  197. impl WebEventExt<web_sys::Element> for MountedData {
  198. fn web_event(&self) -> &web_sys::Element {
  199. self.downcast::<web_sys::Element>()
  200. .expect("event should be a web_sys::Element")
  201. }
  202. }
  203. impl WebEventExt<web_sys::MouseEvent> for dioxus_html::MouseData {
  204. fn web_event(&self) -> &web_sys::MouseEvent {
  205. self.downcast::<web_sys::MouseEvent>()
  206. .expect("event should be a WebMouseEvent")
  207. }
  208. }
  209. impl WebEventExt<web_sys::PointerEvent> for dioxus_html::PointerData {
  210. fn web_event(&self) -> &web_sys::PointerEvent {
  211. self.downcast::<web_sys::PointerEvent>()
  212. .expect("event should be a WebPointerEvent")
  213. }
  214. }
  215. impl WebEventExt<web_sys::Event> for ScrollData {
  216. fn web_event(&self) -> &web_sys::Event {
  217. self.downcast::<web_sys::Event>()
  218. .expect("event should be a WebScrollEvent")
  219. }
  220. }
  221. impl WebEventExt<web_sys::Event> for dioxus_html::SelectionData {
  222. fn web_event(&self) -> &web_sys::Event {
  223. self.downcast::<web_sys::Event>()
  224. .expect("event should be a WebSelectionEvent")
  225. }
  226. }
  227. impl WebEventExt<web_sys::Event> for dioxus_html::ToggleData {
  228. fn web_event(&self) -> &web_sys::Event {
  229. self.downcast::<web_sys::Event>()
  230. .expect("event should be a WebToggleEvent")
  231. }
  232. }
  233. impl WebEventExt<web_sys::TouchEvent> for dioxus_html::TouchData {
  234. fn web_event(&self) -> &web_sys::TouchEvent {
  235. self.downcast::<web_sys::TouchEvent>()
  236. .expect("event should be a WebTouchEvent")
  237. }
  238. }
  239. impl WebEventExt<web_sys::TransitionEvent> for dioxus_html::TransitionData {
  240. fn web_event(&self) -> &web_sys::TransitionEvent {
  241. self.downcast::<web_sys::TransitionEvent>()
  242. .expect("event should be a WebTransitionEvent")
  243. }
  244. }
  245. impl WebEventExt<web_sys::WheelEvent> for dioxus_html::WheelData {
  246. fn web_event(&self) -> &web_sys::WheelEvent {
  247. self.downcast::<web_sys::WheelEvent>()
  248. .expect("event should be a WebWheelEvent")
  249. }
  250. }
  251. struct GenericWebSysEvent {
  252. raw: Event,
  253. element: Element,
  254. }
  255. // todo: some of these events are being casted to the wrong event type.
  256. // We need tests that simulate clicks/etc and make sure every event type works.
  257. pub(crate) fn virtual_event_from_websys_event(
  258. event: web_sys::Event,
  259. target: Element,
  260. ) -> PlatformEventData {
  261. PlatformEventData::new(Box::new(GenericWebSysEvent {
  262. raw: event,
  263. element: target,
  264. }))
  265. }
  266. pub(crate) fn load_document() -> Document {
  267. web_sys::window()
  268. .expect("should have access to the Window")
  269. .document()
  270. .expect("should have access to the Document")
  271. }
  272. struct WebImageEvent {
  273. raw: Event,
  274. error: bool,
  275. }
  276. impl WebImageEvent {
  277. fn new(raw: Event, error: bool) -> Self {
  278. Self { raw, error }
  279. }
  280. }
  281. impl HasImageData for WebImageEvent {
  282. fn load_error(&self) -> bool {
  283. self.error
  284. }
  285. fn as_any(&self) -> &dyn Any {
  286. &self.raw as &dyn Any
  287. }
  288. }
  289. struct WebFormData {
  290. element: Element,
  291. raw: Event,
  292. }
  293. impl WebFormData {
  294. fn new(element: Element, raw: Event) -> Self {
  295. Self { element, raw }
  296. }
  297. }
  298. impl HasFormData for WebFormData {
  299. fn value(&self) -> String {
  300. let target = &self.element;
  301. target
  302. .dyn_ref()
  303. .map(|input: &web_sys::HtmlInputElement| {
  304. // todo: special case more input types
  305. match input.type_().as_str() {
  306. "checkbox" => {
  307. match input.checked() {
  308. true => "true".to_string(),
  309. false => "false".to_string(),
  310. }
  311. },
  312. _ => {
  313. input.value()
  314. }
  315. }
  316. })
  317. .or_else(|| {
  318. target
  319. .dyn_ref()
  320. .map(|input: &web_sys::HtmlTextAreaElement| input.value())
  321. })
  322. // select elements are NOT input events - because - why woudn't they be??
  323. .or_else(|| {
  324. target
  325. .dyn_ref()
  326. .map(|input: &web_sys::HtmlSelectElement| input.value())
  327. })
  328. .or_else(|| {
  329. target
  330. .dyn_ref::<web_sys::HtmlElement>()
  331. .unwrap()
  332. .text_content()
  333. })
  334. .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
  335. }
  336. fn values(&self) -> HashMap<String, String> {
  337. let mut values = HashMap::new();
  338. fn insert_value(map: &mut HashMap<String, String>, key: String, new_value: String) {
  339. if let Some(value) = map.get(&key) {
  340. map.insert(key, format!("{},{}", value, new_value));
  341. } else {
  342. map.insert(key, new_value);
  343. }
  344. }
  345. // try to fill in form values
  346. if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
  347. let form_data = get_form_data(form);
  348. for value in form_data.entries().into_iter().flatten() {
  349. if let Ok(array) = value.dyn_into::<Array>() {
  350. if let Some(name) = array.get(0).as_string() {
  351. if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
  352. item_values
  353. .iter()
  354. .filter_map(|v| v.as_string())
  355. .for_each(|v| insert_value(&mut values, name.clone(), v));
  356. } else if let Ok(item_value) = array.get(1).dyn_into::<JsValue>() {
  357. insert_value(&mut values, name, item_value.as_string().unwrap());
  358. }
  359. }
  360. }
  361. }
  362. } else if let Some(select) = self.element.dyn_ref::<web_sys::HtmlSelectElement>() {
  363. // try to fill in select element values
  364. let options = get_select_data(select).join(",");
  365. values.insert("options".to_string(), options);
  366. }
  367. values
  368. }
  369. fn as_any(&self) -> &dyn Any {
  370. &self.raw as &dyn Any
  371. }
  372. }
  373. impl HasFileData for WebFormData {
  374. fn files(&self) -> Option<std::sync::Arc<dyn FileEngine>> {
  375. #[cfg(not(feature = "file_engine"))]
  376. let files = None;
  377. #[cfg(feature = "file_engine")]
  378. let files = self
  379. .element
  380. .dyn_ref()
  381. .and_then(|input: &web_sys::HtmlInputElement| {
  382. input.files().and_then(|files| {
  383. #[allow(clippy::arc_with_non_send_sync)]
  384. crate::file_engine::WebFileEngine::new(files).map(|f| {
  385. std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
  386. })
  387. })
  388. });
  389. files
  390. }
  391. }
  392. struct WebDragData {
  393. raw: MouseEvent,
  394. }
  395. impl WebDragData {
  396. fn new(raw: MouseEvent) -> Self {
  397. Self { raw }
  398. }
  399. }
  400. impl HasDragData for WebDragData {
  401. fn as_any(&self) -> &dyn std::any::Any {
  402. &self.raw as &dyn std::any::Any
  403. }
  404. }
  405. impl HasMouseData for WebDragData {
  406. fn as_any(&self) -> &dyn std::any::Any {
  407. &self.raw as &dyn std::any::Any
  408. }
  409. }
  410. impl PointerInteraction for WebDragData {
  411. fn trigger_button(&self) -> Option<dioxus_html::input_data::MouseButton> {
  412. self.raw.trigger_button()
  413. }
  414. fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet {
  415. self.raw.held_buttons()
  416. }
  417. }
  418. impl ModifiersInteraction for WebDragData {
  419. fn modifiers(&self) -> dioxus_html::prelude::Modifiers {
  420. self.raw.modifiers()
  421. }
  422. }
  423. impl InteractionElementOffset for WebDragData {
  424. fn coordinates(&self) -> dioxus_html::geometry::Coordinates {
  425. self.raw.coordinates()
  426. }
  427. fn element_coordinates(&self) -> dioxus_html::geometry::ElementPoint {
  428. self.raw.element_coordinates()
  429. }
  430. }
  431. impl InteractionLocation for WebDragData {
  432. fn client_coordinates(&self) -> dioxus_html::geometry::ClientPoint {
  433. self.raw.client_coordinates()
  434. }
  435. fn screen_coordinates(&self) -> dioxus_html::geometry::ScreenPoint {
  436. self.raw.screen_coordinates()
  437. }
  438. fn page_coordinates(&self) -> dioxus_html::geometry::PagePoint {
  439. self.raw.page_coordinates()
  440. }
  441. }
  442. impl HasFileData for WebDragData {
  443. fn files(&self) -> Option<std::sync::Arc<dyn FileEngine>> {
  444. #[cfg(not(feature = "file_engine"))]
  445. let files = None;
  446. #[cfg(feature = "file_engine")]
  447. let files = self.raw.dyn_ref::<DragEvent>().and_then(|drag_event| {
  448. drag_event.data_transfer().and_then(|dt| {
  449. dt.files().and_then(|files| {
  450. #[allow(clippy::arc_with_non_send_sync)]
  451. crate::file_engine::WebFileEngine::new(files).map(|f| {
  452. std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
  453. })
  454. })
  455. })
  456. });
  457. files
  458. }
  459. }
  460. // web-sys does not expose the keys api for form data, so we need to manually bind to it
  461. #[wasm_bindgen(inline_js = r#"
  462. export function get_form_data(form) {
  463. let values = new Map();
  464. const formData = new FormData(form);
  465. for (let name of formData.keys()) {
  466. values.set(name, formData.getAll(name));
  467. }
  468. return values;
  469. }
  470. "#)]
  471. extern "C" {
  472. fn get_form_data(form: &web_sys::HtmlFormElement) -> js_sys::Map;
  473. }
  474. // web-sys does not expose the keys api for select data, so we need to manually bind to it
  475. #[wasm_bindgen(inline_js = r#"
  476. export function get_select_data(select) {
  477. let values = [];
  478. for (let i = 0; i < select.options.length; i++) {
  479. let option = select.options[i];
  480. if (option.selected) {
  481. values.push(option.value.toString());
  482. }
  483. }
  484. return values;
  485. }
  486. "#)]
  487. extern "C" {
  488. fn get_select_data(select: &web_sys::HtmlSelectElement) -> Vec<String>;
  489. }