1use std::{any::Any, collections::HashMap, rc::Rc, sync::Arc};
4
5use blitz_dom::{
6 local_name, namespace_url,
7 net::Resource,
8 node::{Attribute, NodeSpecificData},
9 ns, Atom, BaseDocument, ElementNodeData, Node, NodeData, QualName, DEFAULT_CSS,
10};
11
12use blitz_traits::{net::NetProvider, ColorScheme, Document, DomEvent, DomEventData, Viewport};
13use dioxus_core::{ElementId, Event, VirtualDom};
14use dioxus_html::{set_event_converter, FormValue, PlatformEventData};
15use futures_util::{pin_mut, FutureExt};
16
17use super::event_handler::{BlitzKeyboardData, NativeClickData, NativeConverter, NativeFormData};
18use crate::mutation_writer::{DioxusState, MutationWriter};
19use crate::NodeId;
20
21pub(crate) fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName {
22 QualName {
23 prefix: None,
24 ns: namespace.map(Atom::from).unwrap_or(ns!(html)),
25 local: Atom::from(local_name),
26 }
27}
28
29pub struct DioxusDocument {
30 pub(crate) vdom: VirtualDom,
31 pub(crate) vdom_state: DioxusState,
32 pub(crate) inner: BaseDocument,
33
34 #[allow(unused)]
35 pub(crate) html_element_id: NodeId,
36 #[allow(unused)]
37 pub(crate) head_element_id: NodeId,
38 #[allow(unused)]
39 pub(crate) body_element_id: NodeId,
40 #[allow(unused)]
41 pub(crate) main_element_id: NodeId,
42}
43
44impl AsRef<BaseDocument> for DioxusDocument {
47 fn as_ref(&self) -> &BaseDocument {
48 &self.inner
49 }
50}
51impl AsMut<BaseDocument> for DioxusDocument {
52 fn as_mut(&mut self) -> &mut BaseDocument {
53 &mut self.inner
54 }
55}
56impl From<DioxusDocument> for BaseDocument {
57 fn from(doc: DioxusDocument) -> BaseDocument {
58 doc.inner
59 }
60}
61impl Document for DioxusDocument {
62 type Doc = BaseDocument;
63
64 fn poll(&mut self, mut cx: std::task::Context) -> bool {
65 {
66 let fut = self.vdom.wait_for_work();
67 pin_mut!(fut);
68
69 match fut.poll_unpin(&mut cx) {
70 std::task::Poll::Ready(_) => {}
71 std::task::Poll::Pending => return false,
72 }
73 }
74
75 let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
76 self.vdom.render_immediate(&mut writer);
77
78 true
79 }
80
81 fn id(&self) -> usize {
82 self.inner.id()
83 }
84
85 fn handle_event(&mut self, event: &mut DomEvent) {
86 let chain = self.inner.node_chain(event.target);
87
88 set_event_converter(Box::new(NativeConverter {}));
89
90 let renderer_event = event.clone();
91
92 let mut prevent_default = false;
93 let mut stop_propagation = false;
94
95 match &event.data {
96 DomEventData::MouseMove(data)
97 | DomEventData::MouseDown(data)
98 | DomEventData::MouseUp(data) => {
99 let click_event_data = wrap_event_data(NativeClickData(data.clone()));
100
101 for node_id in chain.clone().into_iter() {
102 let node = &self.inner.tree()[node_id];
103 let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
104
105 if let Some(id) = dioxus_id {
106 let click_event = Event::new(click_event_data.clone(), true);
107 self.vdom
108 .runtime()
109 .handle_event(event.name(), click_event.clone(), id);
110 prevent_default |= !click_event.default_action_enabled();
111 stop_propagation |= !click_event.propagates();
112 }
113
114 if !event.bubbles || stop_propagation {
115 break;
116 }
117 }
118 }
119
120 DomEventData::Click(data) => {
121 let click_event_data = wrap_event_data(NativeClickData(data.clone()));
122
123 for node_id in chain.clone().into_iter() {
124 let node = &self.inner.tree()[node_id];
125 let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
126 let mut trigger_label = false;
127
128 if let Some(id) = dioxus_id {
129 let click_event = Event::new(click_event_data.clone(), true);
131 self.vdom
132 .runtime()
133 .handle_event("click", click_event.clone(), id);
134 prevent_default |= !click_event.default_action_enabled();
135 stop_propagation |= !click_event.propagates();
136
137 if !prevent_default {
138 let mut default_event =
139 DomEvent::new(node_id, renderer_event.data.clone());
140 self.inner.as_mut().handle_event(&mut default_event);
141 prevent_default = true;
142
143 let element = self.inner.tree()[node_id].element_data().unwrap();
144 trigger_label = element.name.local == *"label";
145
146 let triggers_input_event = element.name.local == local_name!("input")
148 && element.attr(local_name!("type")) == Some("checkbox");
149 if triggers_input_event {
150 let form_data =
151 wrap_event_data(self.input_event_form_data(&chain, element));
152 let input_event = Event::new(form_data, true);
153 self.vdom.runtime().handle_event("input", input_event, id);
154 }
155 }
156 }
157
158 if trigger_label {
160 if let Some((dioxus_id, node_id)) = self.label_bound_input_element(node_id)
161 {
162 let click_event = Event::new(click_event_data.clone(), true);
163 self.vdom.runtime().handle_event(
164 "click",
165 click_event.clone(),
166 dioxus_id,
167 );
168
169 if click_event.default_action_enabled() {
171 let DomEventData::Click(event) = &renderer_event.data else {
172 unreachable!();
173 };
174 let input_click_data = self
175 .inner
176 .get_node(node_id)
177 .unwrap()
178 .synthetic_click_event(event.mods);
179 let mut default_event = DomEvent::new(node_id, input_click_data);
180 self.inner.as_mut().handle_event(&mut default_event);
181 prevent_default = true;
182
183 let element_data = self
188 .inner
189 .get_node(node_id)
190 .unwrap()
191 .element_data()
192 .unwrap();
193 let triggers_input_event =
194 element_data.attr(local_name!("type")) == Some("checkbox");
195 let form_data = wrap_event_data(
196 self.input_event_form_data(&chain, element_data),
197 );
198 if triggers_input_event {
199 let input_event = Event::new(form_data, true);
200 self.vdom.runtime().handle_event(
201 "input",
202 input_event,
203 dioxus_id,
204 );
205 }
206 }
207 }
208 }
209
210 if !event.bubbles || stop_propagation {
211 break;
212 }
213 }
214 }
215
216 DomEventData::KeyPress(kevent) => {
217 let key_event_data = wrap_event_data(BlitzKeyboardData(kevent.clone()));
218
219 for node_id in chain.clone().into_iter() {
220 let node = &self.inner.tree()[node_id];
221 let dioxus_id = node.element_data().and_then(DioxusDocument::dioxus_id);
222 println!("{} {:?}", node_id, dioxus_id);
223
224 if let Some(id) = dioxus_id {
225 if kevent.state.is_pressed() {
226 let event = Event::new(key_event_data.clone(), true);
228 self.vdom
229 .runtime()
230 .handle_event("keydown", event.clone(), id);
231 prevent_default |= !event.default_action_enabled();
232 stop_propagation |= !event.propagates();
233
234 if !prevent_default && kevent.text.is_some() {
235 let event = Event::new(key_event_data.clone(), true);
237 self.vdom
238 .runtime()
239 .handle_event("keypress", event.clone(), id);
240 prevent_default |= !event.default_action_enabled();
241 stop_propagation |= !event.propagates();
242
243 if !prevent_default {
244 let mut default_event =
246 DomEvent::new(node_id, renderer_event.data.clone());
247 self.inner.as_mut().handle_event(&mut default_event);
248 prevent_default = true;
249
250 let element =
252 self.inner.tree()[node_id].element_data().unwrap();
253 let triggers_input_event = &element.name.local == "input"
254 && matches!(
255 element.attr(local_name!("type")),
256 None | Some("text" | "password" | "email" | "search")
257 );
258 if triggers_input_event {
259 let form_data = wrap_event_data(dbg!(
260 self.input_event_form_data(&chain, element)
261 ));
262 let input_event = Event::new(form_data, true);
263 self.vdom.runtime().handle_event("input", input_event, id);
264 }
265 }
266 }
267 } else {
268 let event = Event::new(key_event_data.clone(), true);
270 self.vdom.runtime().handle_event("keyup", event.clone(), id);
271 prevent_default |= !event.default_action_enabled();
272 stop_propagation |= !event.propagates();
273 }
274 }
275
276 if !event.bubbles || stop_propagation {
277 break;
278 }
279 }
280 }
281
282 DomEventData::Hover => {}
283
284 DomEventData::Ime(_) => {}
286 }
287
288 if !event.cancelable || !prevent_default {
289 self.inner.as_mut().handle_event(event);
290 }
291 }
292}
293
294fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
295 Rc::new(PlatformEventData::new(Box::new(value)))
296}
297
298impl DioxusDocument {
299 pub fn input_event_form_data(
302 &self,
303 parent_chain: &[usize],
304 element_node_data: &ElementNodeData,
305 ) -> NativeFormData {
306 let parent_form = parent_chain.iter().find_map(|id| {
307 let node = self.inner.get_node(*id)?;
308 let element_data = node.element_data()?;
309 if element_data.name.local == local_name!("form") {
310 Some(node)
311 } else {
312 None
313 }
314 });
315 let values = if let Some(parent_form) = parent_form {
316 let mut values = HashMap::<String, FormValue>::new();
317 for form_input in self.input_descendents(parent_form).into_iter() {
318 if let Some(name) = form_input.attr(local_name!("name")) {
323 if form_input.attr(local_name!("type")) == Some("checkbox")
324 && form_input
325 .element_data()
326 .and_then(|data| data.checkbox_input_checked())
327 .unwrap_or(false)
328 {
329 let value = form_input
330 .attr(local_name!("value"))
331 .unwrap_or("on")
332 .to_string();
333 values.insert(name.to_string(), FormValue(vec![value]));
334 }
335 }
336 }
337 values
338 } else {
339 Default::default()
340 };
341 let value = match &element_node_data.node_specific_data {
342 NodeSpecificData::CheckboxInput(checked) => checked.to_string(),
343 NodeSpecificData::TextInput(input_data) => input_data.editor.text().to_string(),
344 _ => element_node_data
345 .attr(local_name!("value"))
346 .unwrap_or_default()
347 .to_string(),
348 };
349
350 NativeFormData { value, values }
351 }
352
353 fn input_descendents(&self, node: &Node) -> Vec<&Node> {
355 node.children
356 .iter()
357 .flat_map(|id| {
358 let mut res = Vec::<&Node>::new();
359 let Some(n) = self.inner.get_node(*id) else {
360 return res;
361 };
362 let Some(element_data) = n.element_data() else {
363 return res;
364 };
365 if element_data.name.local == local_name!("input") {
366 res.push(n);
367 }
368 res.extend(self.input_descendents(n).iter());
369 res
370 })
371 .collect()
372 }
373
374 pub fn new(
375 vdom: VirtualDom,
376 net_provider: Option<Arc<dyn NetProvider<Data = Resource>>>,
377 ) -> Self {
378 let viewport = Viewport::new(0, 0, 1.0, ColorScheme::Light);
379 let mut doc = BaseDocument::new(viewport);
380
381 if let Some(net_provider) = net_provider {
383 doc.set_net_provider(net_provider);
384 }
385
386 let html_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
389 qual_name("html", None),
390 Vec::new(),
391 )));
392 let root_node_id = doc.root_node().id;
393 let html_element = doc.get_node_mut(html_element_id).unwrap();
394 html_element.parent = Some(root_node_id);
395 let root_node = doc.get_node_mut(root_node_id).unwrap();
396 root_node.children.push(html_element_id);
397
398 let body_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
400 qual_name("body", None),
401 Vec::new(),
402 )));
403 let html_element = doc.get_node_mut(html_element_id).unwrap();
404 html_element.children.push(body_element_id);
405 let body_element = doc.get_node_mut(body_element_id).unwrap();
406 body_element.parent = Some(html_element_id);
407
408 let main_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
410 qual_name("div", Some("id")),
411 vec![blitz_dom::node::Attribute {
412 name: qual_name("id", None),
413 value: "main".to_string(),
414 }],
415 )));
416 let body_element = doc.get_node_mut(body_element_id).unwrap();
417 body_element.children.push(main_element_id);
418 let main_element = doc.get_node_mut(main_element_id).unwrap();
419 main_element.parent = Some(body_element_id);
420
421 let head_element_id = doc.create_node(NodeData::Element(ElementNodeData::new(
423 qual_name("head", None),
424 Vec::new(),
425 )));
426 let head_element = doc.get_node_mut(head_element_id).unwrap();
427 head_element.parent = Some(html_element_id);
428 let html_element = doc.get_node_mut(html_element_id).unwrap();
429 html_element.children.push(head_element_id);
430
431 doc.add_user_agent_stylesheet(DEFAULT_CSS);
433
434 let state = DioxusState::create(&mut doc, main_element_id);
435 let mut doc = Self {
436 vdom,
437 vdom_state: state,
438 inner: doc,
439 html_element_id,
440 head_element_id,
441 body_element_id,
442 main_element_id,
443 };
444
445 doc.inner.set_base_url("dioxus://index.html");
446
447 doc
452 }
453
454 pub fn initial_build(&mut self) {
455 }
462
463 pub fn label_bound_input_element(&self, label_node_id: NodeId) -> Option<(ElementId, NodeId)> {
464 let bound_input_elements = self.inner.label_bound_input_elements(label_node_id);
465
466 bound_input_elements.into_iter().find_map(|n| {
468 let target_element_data = n.element_data()?;
469 let node_id = n.id;
470 let dioxus_id = DioxusDocument::dioxus_id(target_element_data)?;
471 Some((dioxus_id, node_id))
472 })
473 }
474
475 fn dioxus_id(element_node_data: &ElementNodeData) -> Option<ElementId> {
476 Some(ElementId(
477 element_node_data
478 .attrs
479 .iter()
480 .find(|attr| *attr.name.local == *"data-dioxus-id")?
481 .value
482 .parse::<usize>()
483 .ok()?,
484 ))
485 }
486
487 pub fn create_head_element(
488 &mut self,
489 name: &str,
490 attributes: &[(String, String)],
491 contents: &Option<String>,
492 ) {
493 let mut stylesheet = None;
494 let mut title = None;
495 if name == "link" {
496 let is_stylesheet = attributes
497 .iter()
498 .any(|(name, value)| name == "rel" && value == "stylesheet");
499 if is_stylesheet {
500 stylesheet = attributes
501 .iter()
502 .find(|(name, _value)| name == "href")
503 .map(|(_name, value)| value.clone());
504 }
505 }
506
507 if name == "title" {
508 title = attributes
509 .iter()
510 .find(|(name, _value)| name == "text")
511 .and_then(|(_name, _value)| contents.clone());
512 }
513
514 let attributes = attributes
515 .iter()
516 .map(|(name, value)| Attribute {
517 name: qual_name(name, None),
518 value: value.clone(),
519 })
520 .collect();
521
522 let new_element = self
523 .inner
524 .create_node(NodeData::Element(ElementNodeData::new(
525 qual_name(name, None),
526 attributes,
527 )));
528
529 if let Some(contents) = contents {
530 let text_node = self.inner.create_text_node(contents);
531 self.inner
532 .get_node_mut(new_element)
533 .unwrap()
534 .children
535 .push(text_node);
536 }
537
538 self.inner
539 .get_node_mut(self.head_element_id)
540 .unwrap()
541 .children
542 .push(new_element);
543
544 if let Some(url) = stylesheet {
545 crate::assets::fetch_linked_stylesheet(&self.inner, new_element, url);
546 }
547
548 if let Some(_title) = title {
549 println!("todo: set title");
550 }
551 }
552
553 }