Files
dioxus/packages/core/src.old/mutations.rs
T

508 lines
15 KiB
Rust

//! 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::*;
/// A renderer for Dioxus to modify during diffing
///
/// The renderer should implement a Stack Machine. IE each call to the below methods are modifications to the renderer's
/// internal stack for creating and modifying nodes.
///
/// Dioxus guarantees that the stack is always in a valid state.
pub trait Renderer<'a> {
/// Load this element onto the stack
fn push_root(&mut self, root: ElementId);
/// Pop the topmost element from the stack
fn pop_root(&mut self);
/// Replace the given element with the next m elements on the stack
fn replace_with(&mut self, root: ElementId, m: u32);
/// Insert the next m elements on the stack after the given element
fn insert_after(&mut self, root: ElementId, n: u32);
/// Insert the next m elements on the stack before the given element
fn insert_before(&mut self, root: ElementId, n: u32);
/// Append the next n elements on the stack to the n+1 element on the stack
fn append_children(&mut self, n: u32);
/// Create a new element with the given text and ElementId
fn create_text_node(&mut self, text: &'a str, root: ElementId);
/// Create an element with the given tag name, optional namespace, and ElementId
/// Note that namespaces do not cascade down the tree, so the renderer must handle this if it implements namespaces
fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId);
/// Create a hidden element to be used later for replacement.
/// Used in suspense, lists, and other places where we need to hide a node before it is ready to be shown.
/// This is up to the renderer to implement, but it should not be visible to the user.
fn create_placeholder(&mut self, id: ElementId);
/// Remove the targeted node from the DOM
fn remove(&mut self, root: ElementId);
/// Remove an attribute from an existing element
fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId);
/// Remove all the children of the given element
fn remove_children(&mut self, root: ElementId);
/// Attach a new listener to the dom
fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId);
/// Remove an existing listener from the dom
fn remove_event_listener(&mut self, event: &'static str, root: ElementId);
/// Set the text content of a node
fn set_text(&mut self, text: &'a str, root: ElementId);
/// Set an attribute on an element
fn set_attribute(
&mut self,
name: &'static str,
value: AttributeValue<'a>,
namespace: Option<&'a str>,
root: ElementId,
);
/// General statistics for doing things that extend outside of the renderer
fn mark_dirty_scope(&mut self, scope: ScopeId);
/// Save the current n nodes to the ID to be loaded later
fn save(&mut self, id: &'static str, num: u32);
/// Loads a set of saved nodes from the ID into a scratch space
fn load(&mut self, id: &'static str, index: u32);
/// Assign the element on the stack's descendent the given ID
fn assign_id(&mut self, descendent: &'static [u8], id: ElementId);
/// Replace the given element of the topmost element with the next m elements on the stack
/// Is essentially a combination of assign_id and replace_with
fn replace_descendant(&mut self, descendent: &'static [u8], m: u32);
}
/*
div {
div {
div {
div {}
}
}
}
push_child(0)
push_child(1)
push_child(3)
push_child(4)
pop
pop
clone_node(0)
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>())
}
}