1use super::Namespace;
4use crate::virtual_dom::{At, AtValue, Attrs, El, Mailbox, Node, Style, Text};
5use std::borrow::Cow;
6use std::cmp::Ordering;
7use wasm_bindgen::JsCast;
8use web_sys::Document;
9
10fn set_style(el_ws: &web_sys::Node, style: &Style) {
12 el_ws
13 .dyn_ref::<web_sys::Element>()
14 .expect("Problem casting Node as Element while setting style")
15 .set_attribute("style", &style.to_string())
16 .expect("Problem setting style");
17}
18
19pub(crate) fn assign_ws_nodes_to_el<Ms>(document: &Document, el: &mut El<Ms>) {
20 let node_ws = make_websys_el(el, document);
21 el.node_ws = Some(node_ws);
22 for child in &mut el.children {
23 assign_ws_nodes(document, child);
24 }
25}
26pub(crate) fn assign_ws_nodes_to_text(document: &Document, text: &mut Text) {
27 text.node_ws = Some(
28 document
29 .create_text_node(&text.text)
30 .dyn_into::<web_sys::Node>()
31 .expect("Problem casting Text as Node."),
32 );
33}
34pub(crate) fn assign_ws_nodes<Ms>(document: &Document, node: &mut Node<Ms>) {
36 match node {
37 Node::Element(el) => assign_ws_nodes_to_el(document, el),
38 Node::Text(text) => assign_ws_nodes_to_text(document, text),
39 Node::Empty | Node::NoChange => (),
40 }
41}
42
43fn node_to_element(el_ws: &web_sys::Node) -> Result<&web_sys::Element, Cow<str>> {
44 if el_ws.node_type() == web_sys::Node::ELEMENT_NODE {
45 el_ws
46 .dyn_ref::<web_sys::Element>()
47 .ok_or_else(|| Cow::from("Problem casting Node as Element"))
48 } else {
49 Err(Cow::from("Node isn't Element!"))
50 }
51}
52
53fn set_attr_value(el_ws: &web_sys::Node, at: &At, at_value: &AtValue) {
54 match at_value {
55 AtValue::Some(value) => {
56 node_to_element(el_ws)
57 .and_then(|element| {
58 element.set_attribute(at.as_str(), value).map_err(|error| {
59 Cow::from(format!("Problem setting an attribute: {error:?}"))
60 })
61 })
62 .unwrap_or_else(|err| {
63 crate::error(err);
64 });
65 }
66 AtValue::None => {
67 node_to_element(el_ws)
68 .and_then(|element| {
69 element.set_attribute(at.as_str(), "").map_err(|error| {
70 Cow::from(format!("Problem setting an attribute: {error:?}"))
71 })
72 })
73 .unwrap_or_else(|err| {
74 crate::error(err);
75 });
76 }
77 AtValue::Ignored => {
78 node_to_element(el_ws)
79 .and_then(|element| {
80 element.remove_attribute(at.as_str()).map_err(|error| {
81 Cow::from(format!("Problem removing an attribute: {error:?}"))
82 })
83 })
84 .unwrap_or_else(|err| {
85 crate::error(err);
86 });
87 }
88 }
89}
90
91pub(crate) fn make_websys_el<Ms>(el: &mut El<Ms>, document: &web_sys::Document) -> web_sys::Node {
99 let tag = el.tag.as_str();
100
101 let el_ws = el.namespace.as_ref().map_or_else(
102 || {
103 document
104 .create_element(tag)
105 .expect("Problem creating web-sys element")
106 },
107 |ns| {
108 document
109 .create_element_ns(Some(ns.as_str()), tag)
110 .expect("Problem creating web-sys element with namespace")
111 },
112 );
113
114 fix_attrs_order(&mut el.attrs);
115 for (at, attr_value) in &el.attrs.vals {
116 set_attr_value(&el_ws, at, attr_value);
117 }
118 if let Some(ns) = &el.namespace {
119 el_ws
120 .dyn_ref::<web_sys::Element>()
121 .expect("Problem casting Node as Element while setting an attribute")
122 .set_attribute("xmlns", ns.as_str())
123 .expect("Problem setting xlmns attribute");
124 }
125
126 if el.style.vals.keys().len() > 0 {
129 set_style(&el_ws, &el.style);
130 }
131
132 el_ws.into()
133}
134
135pub fn attach_text_node(text: &mut Text, parent: &web_sys::Node) {
137 let node_ws = text.node_ws.take().expect("Missing websys node for Text");
138 parent
139 .append_child(&node_ws)
140 .expect("Problem appending text node");
141 text.node_ws.replace(node_ws);
142}
143
144pub fn attach_children<Ms>(
147 children: &mut [Node<Ms>],
148 parent: &web_sys::Node,
149 mailbox: &Mailbox<Ms>,
150) {
151 for child in children.iter_mut() {
152 match child {
153 Node::Element(child_el) => attach_el_and_children(child_el, parent, mailbox),
155 Node::Text(child_text) => attach_text_node(child_text, parent),
156 Node::Empty | Node::NoChange => (),
157 }
158 }
159}
160
161pub fn attach_el_and_children<Ms>(el: &mut El<Ms>, parent: &web_sys::Node, mailbox: &Mailbox<Ms>) {
165 let el_ws = el
168 .node_ws
169 .as_ref()
170 .expect("Missing websys el in attach_el_and_children");
171
172 if parent.append_child(el_ws).is_err() {
176 crate::error("Minor problem with html element (append)");
177 }
178
179 attach_children(&mut el.children, el_ws, mailbox);
180
181 set_default_element_state(el_ws, el);
184
185 wire_up_el(el, mailbox);
186}
187
188fn set_default_element_state<Ms>(el_ws: &web_sys::Node, el: &El<Ms>) {
189 if let Some(at_value) = el.attrs.vals.get(&At::AutoFocus) {
192 match at_value {
193 AtValue::Some(_) | AtValue::None => el_ws
194 .dyn_ref::<web_sys::HtmlElement>()
195 .expect("Problem casting Node as HtmlElement while focusing")
196 .focus()
197 .expect("Problem focusing to an element."),
198 AtValue::Ignored => (),
199 }
200 }
201
202 if let Some(textarea) = el_ws.dyn_ref::<web_sys::HtmlTextAreaElement>() {
205 if let Some(AtValue::Some(value)) = el.attrs.vals.get(&At::Value) {
206 textarea.set_value(value);
207 }
208 }
209}
210
211pub fn _remove_children(el: &web_sys::Node) {
213 while let Some(child) = el.last_child() {
214 el.remove_child(&child).expect("Problem removing child");
215 }
216}
217
218pub(crate) fn patch_el_details<Ms>(
222 old: &mut El<Ms>,
223 new: &mut El<Ms>,
224 old_el_ws: &web_sys::Node,
225 mailbox: &Mailbox<Ms>,
226) {
227 fix_attrs_order(&mut new.attrs);
228
229 for (key, new_val) in &new.attrs.vals {
230 old.attrs.vals.get(key).map_or_else(
231 || {
232 set_attr_value(old_el_ws, key, new_val);
233 },
234 |old_val| {
235 if old_val != new_val {
236 set_attr_value(old_el_ws, key, new_val);
237 }
238 },
239 );
240
241 match key {
244 At::Value => match new_val {
245 AtValue::Some(new_val) => crate::util::set_value(old_el_ws, new_val),
246 AtValue::None | AtValue::Ignored => crate::util::set_value(old_el_ws, ""),
247 },
248 At::Checked => match new_val {
249 AtValue::Some(_) | AtValue::None => crate::util::set_checked(old_el_ws, true),
250 AtValue::Ignored => crate::util::set_checked(old_el_ws, false),
251 },
252 _ => Ok(()),
253 }
254 .unwrap_or_else(|err| {
255 crate::error(err);
256 });
257 }
258 for (key, old_val) in &old.attrs.vals {
260 if new.attrs.vals.get(key).is_none() {
261 old_el_ws.dyn_ref::<web_sys::Element>().map_or_else(
263 || {
264 crate::error("Minor error on html element (setting attrs)");
265 },
266 |el| {
267 el.remove_attribute(key.as_str())
268 .expect("Removing an attribute");
269
270 match key {
273 At::Value => match old_val {
274 AtValue::Some(_) => crate::util::set_value(old_el_ws, ""),
275 _ => Ok(()),
276 },
277 At::Checked => match old_val {
278 AtValue::Some(_) | AtValue::None => {
279 crate::util::set_checked(old_el_ws, false)
280 }
281 AtValue::Ignored => Ok(()),
282 },
283 _ => Ok(()),
284 }
285 .unwrap_or_else(|err| {
286 crate::error(err);
287 });
288 },
289 );
290 }
291 }
292
293 new.event_handler_manager.attach_listeners(
295 old_el_ws.clone(),
296 Some(&mut old.event_handler_manager),
297 mailbox,
298 );
299
300 if old.style != new.style {
302 set_style(old_el_ws, &new.style);
304 }
305}
306
307#[allow(clippy::match_same_arms)]
311fn fix_attrs_order(attrs: &mut Attrs) {
312 attrs.vals.sort_by(|at_a, _, at_b, _| {
313 match (at_a, at_b) {
315 (At::Value, At::Value) => Ordering::Equal,
316 (At::Value, _) => Ordering::Greater,
317 (_, At::Value) => Ordering::Less,
318 _ => Ordering::Equal,
319 }
320 });
321}
322
323#[allow(clippy::too_many_lines)]
324impl<Ms> From<&web_sys::Element> for El<Ms> {
325 #[allow(clippy::too_many_lines)]
329 fn from(ws_el: &web_sys::Element) -> Self {
330 let namespace = ws_el.namespace_uri().map(Namespace::from);
331 let mut el = match namespace {
332 Some(Namespace::Html) => El::empty(ws_el.tag_name().to_lowercase().into()),
334 _ => El::empty(ws_el.tag_name().into()),
335 };
336
337 let mut attrs = Attrs::empty();
339 ws_el
340 .get_attribute_names()
341 .for_each(&mut |attr_name, _, _| {
342 let attr_name = attr_name
343 .as_string()
344 .expect("problem converting attr to string");
345 if let Some(attr_val) = ws_el.get_attribute(&attr_name) {
346 attrs.add(attr_name.into(), &attr_val);
347 }
348 });
349 el.attrs = attrs;
350
351 let svg_tags = [
355 "line",
356 "rect",
357 "circle",
358 "ellipse",
359 "polygon",
360 "polyline",
361 "mesh",
362 "path",
363 "defs",
364 "g",
365 "marker",
366 "mask",
367 "pattern",
368 "svg",
369 "switch",
370 "symbol",
371 "unknown",
372 "linearGradient",
373 "radialGradient",
374 "meshGradient",
375 "stop",
376 "image",
377 "use",
378 "altGlyph",
379 "altGlyphDef",
380 "altGlyphItem",
381 "glyph",
382 "glyphRef",
383 "textPath",
384 "text",
385 "tref",
386 "tspan",
387 "clipPath",
388 "cursor",
389 "filter",
390 "foreignObject",
391 "hathpath",
392 "meshPatch",
393 "meshRow",
394 "view",
395 "colorProfile",
396 "animate",
397 "animateColor",
398 "animateMotion",
399 "animateTransform",
400 "discard",
401 "mpath",
402 "set",
403 "desc",
404 "metadata",
405 "title",
406 "feBlend",
407 "feColorMatrix",
408 "feComponentTransfer",
409 "feComposite",
410 "feConvolveMatrix",
411 "feDiffuseLighting",
412 "feDisplacementMap",
413 "feDropShadow",
414 "feFlood",
415 "feFuncA",
416 "feFuncB",
417 "feFuncG",
418 "feFuncR",
419 "feGaussianBlur",
420 "feImage",
421 "feMerge",
422 "feMergeNode",
423 "feMorphology",
424 "feOffset",
425 "feSpecularLighting",
426 "feTile",
427 "feTurbulence",
428 "font",
429 "hkern",
430 "vkern",
431 "hatch",
432 "solidcolor",
433 ];
434
435 if svg_tags.contains(&ws_el.tag_name().as_str()) {
436 el.namespace = Some(Namespace::Svg);
437 }
438
439 if let Some(ref ns) = namespace {
440 if ns != &Namespace::Html {
442 el.namespace = namespace;
443 }
444 }
445
446 let children = ws_el.child_nodes();
447 for i in 0..children.length() {
448 let child = children
449 .get(i)
450 .expect("Can't find child in raw html element.");
451
452 if let Some(child_vdom) = node_from_ws(&child) {
453 el.children.push(child_vdom);
454 }
455 }
456 el
457 }
458}
459impl<Ms> From<&web_sys::Element> for Node<Ms> {
460 fn from(ws_el: &web_sys::Element) -> Node<Ms> {
461 Node::Element(ws_el.into())
462 }
463}
464
465pub fn node_from_ws<Ms>(node: &web_sys::Node) -> Option<Node<Ms>> {
469 match node.node_type() {
470 web_sys::Node::ELEMENT_NODE => {
471 let ws_el = node
473 .dyn_ref::<web_sys::Element>()
474 .expect("Problem casting Node as Element");
475
476 Some(ws_el.into())
478 }
479 web_sys::Node::TEXT_NODE => Some(Node::new_text(
480 node.text_content().expect("Can't find text"),
481 )),
482 web_sys::Node::COMMENT_NODE => None,
483 node_type => {
484 crate::error(format!(
485 "HTML node type {node_type} is not supported by Seed"
486 ));
487 None
488 }
489 }
490}
491
492pub(crate) fn insert_el_and_children<Ms>(
493 el: &mut El<Ms>,
494 parent: &web_sys::Node,
495 next: Option<web_sys::Node>,
496 mailbox: &Mailbox<Ms>,
497) {
498 let el_ws = el.node_ws.take().expect("Missing websys el in insert_el");
499
500 insert_node(&el_ws, parent, next);
501 attach_children(&mut el.children, &el_ws, mailbox);
502
503 el.node_ws.replace(el_ws);
504 wire_up_el(el, mailbox);
505}
506
507pub(crate) fn insert_node(
509 node: &web_sys::Node,
510 parent: &web_sys::Node,
511 next: Option<web_sys::Node>,
512) {
513 next.map_or_else(
514 || {
515 parent.append_child(node).expect("Problem inserting node");
516 },
517 |n| {
518 parent
519 .insert_before(node, Some(&n))
520 .expect("Problem inserting node");
521 },
522 );
523}
524
525pub(crate) fn remove_node(node: &web_sys::Node, parent: &web_sys::Node) {
526 parent
527 .remove_child(node)
528 .expect("Problem removing old el_ws when updating to empty");
529}
530
531pub(crate) fn replace_child(new: &web_sys::Node, old: &web_sys::Node, parent: &web_sys::Node) {
532 parent
533 .replace_child(new, old)
534 .expect("Problem replacing element");
535}
536
537#[inline]
538fn wire_up_el<Ms>(el: &mut El<Ms>, mailbox: &Mailbox<Ms>) {
539 let node_ws = el
540 .node_ws
541 .as_ref()
542 .expect("Missing websys el in attach_el_and_children");
543
544 for ref_ in &mut el.refs {
545 ref_.set(node_ws.clone());
546 }
547
548 el.event_handler_manager
549 .attach_listeners(node_ws.clone(), None, mailbox);
550
551 for handler in &el.insert_handlers {
552 let el_ws = node_ws
553 .dyn_ref::<web_sys::Element>()
554 .expect("Problem casting Node as Element while wiring up el");
555
556 let maybe_msg = handler.0(el_ws.clone());
557 mailbox.send(maybe_msg);
558 }
559}