|
@@ -96,3 +96,412 @@ set_node(el, [1,2,3,4])
|
|
|
set_attribute("class", "foo")
|
|
|
append_child(1)
|
|
|
*/
|
|
|
+
|
|
|
+
|
|
|
+//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
|
|
|
+//!
|
|
|
+//! This module contains an internal API to generate these instructions.
|
|
|
+//!
|
|
|
+//! Beware that changing code in this module will break compatibility with
|
|
|
+//! interpreters for these types of DomEdits.
|
|
|
+
|
|
|
+use crate::innerlude::*;
|
|
|
+use std::{any::Any, fmt::Debug};
|
|
|
+
|
|
|
+/// ## Mutations
|
|
|
+///
|
|
|
+/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
|
|
|
+/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
|
|
|
+/// applied the edits.
|
|
|
+///
|
|
|
+/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
|
|
+pub struct Mutations<'a> {
|
|
|
+ /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM.
|
|
|
+ pub edits: Vec<DomEdit<'a>>,
|
|
|
+
|
|
|
+ /// The list of Scopes that were diffed, created, and removed during the Diff process.
|
|
|
+ pub dirty_scopes: FxHashSet<ScopeId>,
|
|
|
+
|
|
|
+ /// The list of nodes to connect to the RealDOM.
|
|
|
+ pub refs: Vec<NodeRefMutation<'a>>,
|
|
|
+}
|
|
|
+
|
|
|
+impl Debug for Mutations<'_> {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ f.debug_struct("Mutations")
|
|
|
+ .field("edits", &self.edits)
|
|
|
+ .field("noderefs", &self.refs)
|
|
|
+ .finish()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
|
|
|
+/// network or through FFI boundaries.
|
|
|
+#[derive(Debug, PartialEq)]
|
|
|
+#[cfg_attr(
|
|
|
+ feature = "serialize",
|
|
|
+ derive(serde::Serialize, serde::Deserialize),
|
|
|
+ serde(tag = "type")
|
|
|
+)]
|
|
|
+pub enum DomEdit<'bump> {
|
|
|
+ /// Pop the topmost node from our stack and append them to the node
|
|
|
+ /// at the top of the stack.
|
|
|
+ AppendChildren {
|
|
|
+ /// The parent to append nodes to.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The ids of the children to append.
|
|
|
+ children: Vec<u64>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Replace a given (single) node with a handful of nodes currently on the stack.
|
|
|
+ ReplaceWith {
|
|
|
+ /// The ID of the node to be replaced.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The ids of the nodes to replace the root with.
|
|
|
+ nodes: Vec<u64>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Insert a number of nodes after a given node.
|
|
|
+ InsertAfter {
|
|
|
+ /// The ID of the node to insert after.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The ids of the nodes to insert after the target node.
|
|
|
+ nodes: Vec<u64>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Insert a number of nodes before a given node.
|
|
|
+ InsertBefore {
|
|
|
+ /// The ID of the node to insert before.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The ids of the nodes to insert before the target node.
|
|
|
+ nodes: Vec<u64>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Remove a particular node from the DOM
|
|
|
+ Remove {
|
|
|
+ /// The ID of the node to remove.
|
|
|
+ root: Option<u64>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Create a new purely-text node
|
|
|
+ CreateTextNode {
|
|
|
+ /// The ID the new node should have.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The textcontent of the node
|
|
|
+ text: &'bump str,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Create a new purely-element node
|
|
|
+ CreateElement {
|
|
|
+ /// The ID the new node should have.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The tagname of the node
|
|
|
+ tag: &'bump str,
|
|
|
+
|
|
|
+ /// The number of children nodes that will follow this message.
|
|
|
+ children: u32,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Create a new purely-comment node with a given namespace
|
|
|
+ CreateElementNs {
|
|
|
+ /// The ID the new node should have.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The namespace of the node
|
|
|
+ tag: &'bump str,
|
|
|
+
|
|
|
+ /// The namespace of the node (like `SVG`)
|
|
|
+ ns: &'static str,
|
|
|
+
|
|
|
+ /// The number of children nodes that will follow this message.
|
|
|
+ children: u32,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Create a new placeholder node.
|
|
|
+ /// In most implementations, this will either be a hidden div or a comment node.
|
|
|
+ CreatePlaceholder {
|
|
|
+ /// The ID the new node should have.
|
|
|
+ root: Option<u64>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Create a new Event Listener.
|
|
|
+ NewEventListener {
|
|
|
+ /// The name of the event to listen for.
|
|
|
+ event_name: &'static str,
|
|
|
+
|
|
|
+ /// The ID of the node to attach the listener to.
|
|
|
+ scope: ScopeId,
|
|
|
+
|
|
|
+ /// The ID of the node to attach the listener to.
|
|
|
+ root: Option<u64>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Remove an existing Event Listener.
|
|
|
+ RemoveEventListener {
|
|
|
+ /// The ID of the node to remove.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The name of the event to remove.
|
|
|
+ event: &'static str,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Set the textcontent of a node.
|
|
|
+ SetText {
|
|
|
+ /// The ID of the node to set the textcontent of.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The textcontent of the node
|
|
|
+ text: &'bump str,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Set the value of a node's attribute.
|
|
|
+ SetAttribute {
|
|
|
+ /// The ID of the node to set the attribute of.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The name of the attribute to set.
|
|
|
+ field: &'static str,
|
|
|
+
|
|
|
+ /// The value of the attribute.
|
|
|
+ value: AttributeValue<'bump>,
|
|
|
+
|
|
|
+ // value: &'bump str,
|
|
|
+ /// The (optional) namespace of the attribute.
|
|
|
+ /// For instance, "style" is in the "style" namespace.
|
|
|
+ ns: Option<&'bump str>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Remove an attribute from a node.
|
|
|
+ RemoveAttribute {
|
|
|
+ /// The ID of the node to remove.
|
|
|
+ root: Option<u64>,
|
|
|
+
|
|
|
+ /// The name of the attribute to remove.
|
|
|
+ name: &'static str,
|
|
|
+
|
|
|
+ /// The namespace of the attribute.
|
|
|
+ ns: Option<&'bump str>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Clones a node.
|
|
|
+ CloneNode {
|
|
|
+ /// The ID of the node to clone.
|
|
|
+ id: Option<u64>,
|
|
|
+
|
|
|
+ /// The ID of the new node.
|
|
|
+ new_id: u64,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Clones the children of a node. (allows cloning fragments)
|
|
|
+ CloneNodeChildren {
|
|
|
+ /// The ID of the node to clone.
|
|
|
+ id: Option<u64>,
|
|
|
+
|
|
|
+ /// The ID of the new node.
|
|
|
+ new_ids: Vec<u64>,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Navigates to the last node to the first child of the current node.
|
|
|
+ FirstChild {},
|
|
|
+
|
|
|
+ /// Navigates to the last node to the last child of the current node.
|
|
|
+ NextSibling {},
|
|
|
+
|
|
|
+ /// Navigates to the last node to the parent of the current node.
|
|
|
+ ParentNode {},
|
|
|
+
|
|
|
+ /// Stores the last node with a new id.
|
|
|
+ StoreWithId {
|
|
|
+ /// The ID of the node to store.
|
|
|
+ id: u64,
|
|
|
+ },
|
|
|
+
|
|
|
+ /// Manually set the last node.
|
|
|
+ SetLastNode {
|
|
|
+ /// The ID to set the last node to.
|
|
|
+ id: u64,
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+use rustc_hash::FxHashSet;
|
|
|
+use DomEdit::*;
|
|
|
+
|
|
|
+#[allow(unused)]
|
|
|
+impl<'a> Mutations<'a> {
|
|
|
+ pub(crate) fn new() -> Self {
|
|
|
+ Self {
|
|
|
+ edits: Vec::new(),
|
|
|
+ refs: Vec::new(),
|
|
|
+ dirty_scopes: Default::default(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn replace_with(&mut self, root: Option<u64>, nodes: Vec<u64>) {
|
|
|
+ self.edits.push(ReplaceWith { nodes, root });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn insert_after(&mut self, root: Option<u64>, nodes: Vec<u64>) {
|
|
|
+ self.edits.push(InsertAfter { nodes, root });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn insert_before(&mut self, root: Option<u64>, nodes: Vec<u64>) {
|
|
|
+ self.edits.push(InsertBefore { nodes, root });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn append_children(&mut self, root: Option<u64>, children: Vec<u64>) {
|
|
|
+ self.edits.push(AppendChildren { root, children });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Remove Nodes from the dom
|
|
|
+ pub(crate) fn remove(&mut self, id: Option<u64>) {
|
|
|
+ self.edits.push(Remove { root: id });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create
|
|
|
+ pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option<u64>) {
|
|
|
+ self.edits.push(CreateTextNode { text, root: id });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn create_element(
|
|
|
+ &mut self,
|
|
|
+ tag: &'static str,
|
|
|
+ ns: Option<&'static str>,
|
|
|
+ id: Option<u64>,
|
|
|
+ children: u32,
|
|
|
+ ) {
|
|
|
+ match ns {
|
|
|
+ Some(ns) => self.edits.push(CreateElementNs {
|
|
|
+ root: id,
|
|
|
+ ns,
|
|
|
+ tag,
|
|
|
+ children,
|
|
|
+ }),
|
|
|
+ None => self.edits.push(CreateElement {
|
|
|
+ root: id,
|
|
|
+ tag,
|
|
|
+ children,
|
|
|
+ }),
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
|
|
|
+ pub(crate) fn create_placeholder(&mut self, id: Option<u64>) {
|
|
|
+ self.edits.push(CreatePlaceholder { root: id });
|
|
|
+ }
|
|
|
+
|
|
|
+ // events
|
|
|
+ pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
|
|
|
+ let Listener {
|
|
|
+ event,
|
|
|
+ mounted_node,
|
|
|
+ ..
|
|
|
+ } = listener;
|
|
|
+
|
|
|
+ let element_id = Some(mounted_node.get().unwrap().into());
|
|
|
+
|
|
|
+ self.edits.push(NewEventListener {
|
|
|
+ scope,
|
|
|
+ event_name: event,
|
|
|
+ root: element_id,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option<u64>) {
|
|
|
+ self.edits.push(RemoveEventListener { event, root });
|
|
|
+ }
|
|
|
+
|
|
|
+ // modify
|
|
|
+ pub(crate) fn set_text(&mut self, text: &'a str, root: Option<u64>) {
|
|
|
+ self.edits.push(SetText { text, root });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option<u64>) {
|
|
|
+ let Attribute {
|
|
|
+ value, attribute, ..
|
|
|
+ } = attribute;
|
|
|
+
|
|
|
+ self.edits.push(SetAttribute {
|
|
|
+ field: attribute.name,
|
|
|
+ value: value.clone(),
|
|
|
+ ns: attribute.namespace,
|
|
|
+ root,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option<u64>) {
|
|
|
+ let Attribute { attribute, .. } = attribute;
|
|
|
+
|
|
|
+ self.edits.push(RemoveAttribute {
|
|
|
+ name: attribute.name,
|
|
|
+ ns: attribute.namespace,
|
|
|
+ root,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
|
|
|
+ self.dirty_scopes.insert(scope);
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn clone_node(&mut self, id: Option<u64>, new_id: u64) {
|
|
|
+ self.edits.push(CloneNode { id, new_id });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn clone_node_children(&mut self, id: Option<u64>, new_ids: Vec<u64>) {
|
|
|
+ self.edits.push(CloneNodeChildren { id, new_ids });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn first_child(&mut self) {
|
|
|
+ self.edits.push(FirstChild {});
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn next_sibling(&mut self) {
|
|
|
+ self.edits.push(NextSibling {});
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn parent_node(&mut self) {
|
|
|
+ self.edits.push(ParentNode {});
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn store_with_id(&mut self, id: u64) {
|
|
|
+ self.edits.push(StoreWithId { id });
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn set_last_node(&mut self, id: u64) {
|
|
|
+ self.edits.push(SetLastNode { id });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// refs are only assigned once
|
|
|
+pub struct NodeRefMutation<'a> {
|
|
|
+ pub element: &'a mut Option<once_cell::sync::OnceCell<Box<dyn Any>>>,
|
|
|
+ pub element_id: ElementId,
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> std::fmt::Debug for NodeRefMutation<'a> {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ f.debug_struct("NodeRefMutation")
|
|
|
+ .field("element_id", &self.element_id)
|
|
|
+ .finish()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> NodeRefMutation<'a> {
|
|
|
+ pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
|
|
|
+ self.element
|
|
|
+ .as_ref()
|
|
|
+ .and_then(|f| f.get())
|
|
|
+ .and_then(|f| f.downcast_ref::<T>())
|
|
|
+ }
|
|
|
+ pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
|
|
+ self.element
|
|
|
+ .as_mut()
|
|
|
+ .and_then(|f| f.get_mut())
|
|
|
+ .and_then(|f| f.downcast_mut::<T>())
|
|
|
+ }
|
|
|
+}
|