#![allow(clippy::too_many_arguments)] use crate::{ arena::ElementId, innerlude::{ElementRef, MountId, WriteMutations}, nodes::VNode, scopes::ScopeId, virtual_dom::VirtualDom, Template, TemplateNode, }; mod component; mod iterator; mod node; impl VirtualDom { pub(crate) fn create_children<'a>( &mut self, to: &mut impl WriteMutations, nodes: impl IntoIterator, parent: Option, ) -> usize { nodes .into_iter() .map(|child| child.create(self, to, parent.clone())) .sum() } fn remove_element_id(&mut self, to: &mut impl WriteMutations, id: ElementId, gen_muts: bool) { if gen_muts { to.remove_node(id); } self.reclaim(id) } /// Simply replace a placeholder with a list of nodes fn replace_placeholder<'a>( &mut self, to: &mut impl WriteMutations, placeholder_id: ElementId, r: impl IntoIterator, parent: Option, ) { let m = self.create_children(to, r, parent); to.replace_node_with(placeholder_id, m); self.reclaim(placeholder_id); } fn nodes_to_placeholder( &mut self, to: &mut impl WriteMutations, mount: MountId, dyn_node_idx: usize, old_nodes: &[VNode], ) { // Create the placeholder first, ensuring we get a dedicated ID for the placeholder let placeholder = self.next_element(); // Set the id of the placeholder self.mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx] = placeholder.0; to.create_placeholder(placeholder); self.replace_nodes(to, old_nodes, 1); } /// Replace many nodes with a number of nodes on the stack fn replace_nodes(&mut self, to: &mut impl WriteMutations, nodes: &[VNode], m: usize) { // We want to optimize the replace case to use one less mutation if possible // Since mutations are done in reverse, the last node removed will be the first in the stack // TODO: Instead of *just* removing it, we can use the replace mutation let first_element = nodes[0].find_first_element(self); to.insert_nodes_before(first_element, m); debug_assert!( !nodes.is_empty(), "replace_nodes must have at least one node" ); self.remove_nodes(to, nodes); } /// Remove these nodes from the dom /// Wont generate mutations for the inner nodes fn remove_nodes(&mut self, to: &mut impl WriteMutations, nodes: &[VNode]) { nodes .iter() .rev() .for_each(|node| node.remove_node(self, to, true)); } pub(crate) fn remove_component_node( &mut self, to: &mut impl WriteMutations, scope: ScopeId, gen_muts: bool, ) { // Remove the component from the dom if let Some(node) = self.scopes[scope.0].last_rendered_node.take() { node.remove_node(self, to, gen_muts) }; // Now drop all the resources self.drop_scope(scope); } /// Insert a new template into the VirtualDom's template registry // used in conditional compilation #[allow(unused_mut)] pub(crate) fn register_template( &mut self, to: &mut impl WriteMutations, mut template: Template, ) { let (path, byte_index) = template.name.rsplit_once(':').unwrap(); let byte_index = byte_index.parse::().unwrap(); // First, check if we've already seen this template if self .templates .get(&path) .filter(|set| set.contains_key(&byte_index)) .is_none() { // if hot reloading is enabled, then we need to check for a template that has overriten this one #[cfg(debug_assertions)] if let Some(mut new_template) = self .templates .get_mut(path) .and_then(|map| map.remove(&usize::MAX)) { // the byte index of the hot reloaded template could be different new_template.name = template.name; template = new_template; } self.templates .entry(path) .or_default() .insert(byte_index, template); // If it's all dynamic nodes, then we don't need to register it if !template.is_completely_dynamic() { to.register_template(template) } } } /// Insert a new template into the VirtualDom's template registry pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template) { // First, make sure we mark the template as seen, regardless if we process it let (path, _) = template.name.rsplit_once(':').unwrap(); if let Some((_, old_template)) = self .templates .entry(path) .or_default() .iter_mut() .min_by_key(|(byte_index, _)| **byte_index) { // the byte index of the hot reloaded template could be different template.name = old_template.name; *old_template = template; } else { // This is a template without any current instances self.templates .entry(path) .or_default() .insert(usize::MAX, template); } // If it's all dynamic nodes, then we don't need to register it if !template.is_completely_dynamic() { self.queued_templates.push(template); } } } /// We can apply various optimizations to dynamic nodes that are the single child of their parent. /// /// IE /// - for text - we can use SetTextContent /// - for clearing children we can use RemoveChildren /// - for appending children we can use AppendChildren #[allow(dead_code)] fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool { let template = node.template.get(); let path = template.node_paths[idx]; // use a loop to index every static node's children until the path has run out // only break if the last path index is a dynamic node let mut static_node = &template.roots[path[0] as usize]; for i in 1..path.len() - 1 { match static_node { TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize], _ => return false, } } match static_node { TemplateNode::Element { children, .. } => children.len() == 1, _ => false, } }