Feat: include the helper
This commit is contained in:
1 parent
f2eec79fd4
commit
07341d2c65
22 files changed
+2990
-1311
No files matched your search
+14
-1162
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,359 @@
|
||||
//! Virtual Node Support
|
||||
//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers.
|
||||
//!
|
||||
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use bumpalo::Bump;
|
||||
pub use vcomponent::VComponent;
|
||||
pub use velement::VElement;
|
||||
pub use velement::{Attribute, Listener, NodeKey};
|
||||
pub use vnode::VNode;
|
||||
pub use vtext::VText;
|
||||
|
||||
/// Tools for the base unit of the virtual dom - the VNode
|
||||
/// VNodes are intended to be quickly-allocated, lightweight enum values.
|
||||
///
|
||||
/// Components will be generating a lot of these very quickly, so we want to
|
||||
/// limit the amount of heap allocations / overly large enum sizes.
|
||||
mod vnode {
|
||||
use super::*;
|
||||
|
||||
pub enum VNode<'src> {
|
||||
/// An element node (node type `ELEMENT_NODE`).
|
||||
Element(VElement<'src>),
|
||||
|
||||
/// A text node (node type `TEXT_NODE`).
|
||||
///
|
||||
/// Note: This wraps a `VText` instead of a plain `String` in
|
||||
/// order to enable custom methods like `create_text_node()` on the
|
||||
/// wrapped type.
|
||||
Text(VText<'src>),
|
||||
|
||||
/// A "suspended component"
|
||||
/// This is a masqeurade over an underlying future that needs to complete
|
||||
/// When the future is completed, the VNode will then trigger a render
|
||||
Suspended,
|
||||
|
||||
/// A User-defined componen node (node type COMPONENT_NODE)
|
||||
Component(VComponent),
|
||||
}
|
||||
|
||||
impl<'src> VNode<'src> {
|
||||
/// Create a new virtual element node with a given tag.
|
||||
///
|
||||
/// These get patched into the DOM using `document.createElement`
|
||||
///
|
||||
/// ```ignore
|
||||
/// let div = VNode::element("div");
|
||||
/// ```
|
||||
pub fn element(tag: &'static str) -> Self {
|
||||
VNode::Element(VElement::new(tag))
|
||||
}
|
||||
|
||||
/// Construct a new text node with the given text.
|
||||
#[inline]
|
||||
pub(crate) fn text(text: &'src str) -> VNode<'src> {
|
||||
VNode::Text(VText { text })
|
||||
}
|
||||
// /// Create a new virtual text node with the given text.
|
||||
// ///
|
||||
// /// These get patched into the DOM using `document.createTextNode`
|
||||
// ///
|
||||
// /// ```ignore
|
||||
// /// let div = VNode::text("div");
|
||||
// /// ```
|
||||
// pub fn text<S>(text: S) -> Self
|
||||
// where
|
||||
// S: Into<String>,
|
||||
// {
|
||||
// /*
|
||||
// TODO
|
||||
|
||||
// This is an opportunity to be extremely efficient when allocating/creating strings
|
||||
// To assemble a formatted string, we can, using the macro, borrow all the contents without allocating.
|
||||
|
||||
// String contents are therefore bump allocated automatically
|
||||
|
||||
// html!{
|
||||
// <>"Hello {world}"</>
|
||||
// }
|
||||
|
||||
// Should be
|
||||
|
||||
// ```
|
||||
// let mut root = VNode::text(["Hello", world]);
|
||||
|
||||
// ```
|
||||
|
||||
// */
|
||||
// VNode::Text(VText::new(text.into()))
|
||||
// }
|
||||
|
||||
// /// Return a [`VElement`] reference, if this is an [`Element`] variant.
|
||||
// ///
|
||||
// /// [`VElement`]: struct.VElement.html
|
||||
// /// [`Element`]: enum.VNode.html#variant.Element
|
||||
// pub fn as_velement_ref(&self) -> Option<&VElement> {
|
||||
// match self {
|
||||
// VNode::Element(ref element_node) => Some(element_node),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Return a mutable [`VElement`] reference, if this is an [`Element`] variant.
|
||||
// ///
|
||||
// /// [`VElement`]: struct.VElement.html
|
||||
// /// [`Element`]: enum.VNode.html#variant.Element
|
||||
// pub fn as_velement_mut(&mut self) -> Option<&mut VElement> {
|
||||
// match self {
|
||||
// VNode::Element(ref mut element_node) => Some(element_node),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Return a [`VText`] reference, if this is an [`Text`] variant.
|
||||
// ///
|
||||
// /// [`VText`]: struct.VText.html
|
||||
// /// [`Text`]: enum.VNode.html#variant.Text
|
||||
// pub fn as_vtext_ref(&self) -> Option<&VText> {
|
||||
// match self {
|
||||
// VNode::Text(ref text_node) => Some(text_node),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Return a mutable [`VText`] reference, if this is an [`Text`] variant.
|
||||
// ///
|
||||
// /// [`VText`]: struct.VText.html
|
||||
// /// [`Text`]: enum.VNode.html#variant.Text
|
||||
// pub fn as_vtext_mut(&mut self) -> Option<&mut VText> {
|
||||
// match self {
|
||||
// VNode::Text(ref mut text_node) => Some(text_node),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Used by html-macro to insert space before text that is inside of a block that came after
|
||||
// /// an open tag.
|
||||
// ///
|
||||
// /// html! { <div> {world}</div> }
|
||||
// ///
|
||||
// /// So that we end up with <div> world</div> when we're finished parsing.
|
||||
// pub fn insert_space_before_text(&mut self) {
|
||||
// match self {
|
||||
// VNode::Text(text_node) => {
|
||||
// text_node.text = " ".to_string() + &text_node.text;
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Used by html-macro to insert space after braced text if we know that the next block is
|
||||
// /// another block or a closing tag.
|
||||
// ///
|
||||
// /// html! { <div>{Hello} {world}</div> } -> <div>Hello world</div>
|
||||
// /// html! { <div>{Hello} </div> } -> <div>Hello </div>
|
||||
// ///
|
||||
// /// So that we end up with <div>Hello world</div> when we're finished parsing.
|
||||
// pub fn insert_space_after_text(&mut self) {
|
||||
// match self {
|
||||
// VNode::Text(text_node) => {
|
||||
// text_node.text += " ";
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// Convert from DOM elements to the primary enum
|
||||
// -----------------------------------------------
|
||||
// impl From<VText> for VNode {
|
||||
// fn from(other: VText) -> Self {
|
||||
// VNode::Text(other)
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl From<VElement> for VNode {
|
||||
// fn from(other: VElement) -> Self {
|
||||
// VNode::Element(other)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
mod velement {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct VElement<'a> {
|
||||
/// The HTML tag, such as "div"
|
||||
pub tag: &'a str,
|
||||
|
||||
pub tag_name: &'a str,
|
||||
pub attributes: &'a [Attribute<'a>],
|
||||
// todo: hook up listeners
|
||||
// pub listeners: &'a [Listener<'a>],
|
||||
// / HTML attributes such as id, class, style, etc
|
||||
// pub attrs: HashMap<String, String>,
|
||||
// TODO: @JON Get this to not heap allocate, but rather borrow
|
||||
// pub attrs: HashMap<&'static str, &'static str>,
|
||||
|
||||
// TODO @Jon, re-enable "events"
|
||||
//
|
||||
// /// Events that will get added to your real DOM element via `.addEventListener`
|
||||
// pub events: Events,
|
||||
// pub events: HashMap<String, ()>,
|
||||
|
||||
// /// The children of this `VNode`. So a <div> <em></em> </div> structure would
|
||||
// /// have a parent div and one child, em.
|
||||
// pub children: Vec<VNode>,
|
||||
}
|
||||
|
||||
impl<'a> VElement<'a> {
|
||||
// The tag of a component MUST be known at compile time
|
||||
pub fn new(tag: &'a str) -> Self {
|
||||
todo!()
|
||||
// VElement {
|
||||
// tag,
|
||||
// attrs: HashMap::new(),
|
||||
// events: HashMap::new(),
|
||||
// // events: Events(HashMap::new()),
|
||||
// children: vec![],
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or
|
||||
/// `href="https://example.com"`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Attribute<'a> {
|
||||
pub(crate) name: &'a str,
|
||||
pub(crate) value: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Attribute<'a> {
|
||||
/// Get this attribute's name, such as `"id"` in `<div id="my-thing" />`.
|
||||
#[inline]
|
||||
pub fn name(&self) -> &'a str {
|
||||
self.name
|
||||
}
|
||||
|
||||
/// The attribute value, such as `"my-thing"` in `<div id="my-thing" />`.
|
||||
#[inline]
|
||||
pub fn value(&self) -> &'a str {
|
||||
self.value
|
||||
}
|
||||
|
||||
/// Certain attributes are considered "volatile" and can change via user
|
||||
/// input that we can't see when diffing against the old virtual DOM. For
|
||||
/// these attributes, we want to always re-set the attribute on the physical
|
||||
/// DOM node, even if the old and new virtual DOM nodes have the same value.
|
||||
#[inline]
|
||||
pub(crate) fn is_volatile(&self) -> bool {
|
||||
match self.name {
|
||||
"value" | "checked" | "selected" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event listener.
|
||||
pub struct Listener<'a> {
|
||||
/// The type of event to listen for.
|
||||
pub(crate) event: &'a str,
|
||||
/// The callback to invoke when the event happens.
|
||||
pub(crate) callback: &'a (dyn Fn()),
|
||||
}
|
||||
|
||||
/// The key for keyed children.
|
||||
///
|
||||
/// Keys must be unique among siblings.
|
||||
///
|
||||
/// If any sibling is keyed, then they all must be keyed.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NodeKey(pub(crate) u32);
|
||||
|
||||
impl Default for NodeKey {
|
||||
fn default() -> NodeKey {
|
||||
NodeKey::NONE
|
||||
}
|
||||
}
|
||||
impl NodeKey {
|
||||
/// The default, lack of a key.
|
||||
pub const NONE: NodeKey = NodeKey(u32::MAX);
|
||||
|
||||
/// Is this key `NodeKey::NONE`?
|
||||
#[inline]
|
||||
pub fn is_none(&self) -> bool {
|
||||
*self == Self::NONE
|
||||
}
|
||||
|
||||
/// Is this key not `NodeKey::NONE`?
|
||||
#[inline]
|
||||
pub fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
|
||||
/// Create a new `NodeKey`.
|
||||
///
|
||||
/// `key` must not be `u32::MAX`.
|
||||
#[inline]
|
||||
pub fn new(key: u32) -> Self {
|
||||
debug_assert_ne!(key, u32::MAX);
|
||||
NodeKey(key)
|
||||
}
|
||||
}
|
||||
|
||||
// todo
|
||||
// use zst enum for element type. Something like ValidElements::div
|
||||
}
|
||||
|
||||
mod vtext {
|
||||
#[derive(PartialEq)]
|
||||
pub struct VText<'a> {
|
||||
pub text: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> VText<'a> {
|
||||
// / Create an new `VText` instance with the specified text.
|
||||
// pub fn new<S>(text: S) -> Self
|
||||
// where
|
||||
// S: Into<String>,
|
||||
// {
|
||||
// VText { text: text.into() }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtual Components for custom user-defined components
|
||||
/// Only supports the functional syntax
|
||||
mod vcomponent {
|
||||
use crate::virtual_dom::Properties;
|
||||
use std::{any::TypeId, fmt, future::Future};
|
||||
|
||||
use super::VNode;
|
||||
#[derive(PartialEq)]
|
||||
pub struct VComponent {
|
||||
// props_id: TypeId,
|
||||
// callerIDs are unsafely coerced to function pointers
|
||||
// This is okay because #1, we store the props_id and verify and 2# the html! macro rejects components not made this way
|
||||
//
|
||||
// Manually constructing the VComponent is not possible from 3rd party crates
|
||||
}
|
||||
|
||||
impl VComponent {
|
||||
// /// Construct a VComponent directly from a function component
|
||||
// /// This should be *very* fast - we store the function pointer and props type ID. It should also be small on the stack
|
||||
// pub fn from_fn<P: Properties>(f: FC<P>, props: P) -> Self {
|
||||
// // // Props needs to be static
|
||||
// // let props_id = std::any::TypeId::of::<P>();
|
||||
|
||||
// // // Cast the caller down
|
||||
|
||||
// // Self { props_id }
|
||||
// Self {}
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -241,3 +241,766 @@ mod old2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod text {
|
||||
//! Old methods that clouded the element implementation
|
||||
//! These all add a dedicated text renderer implementation
|
||||
|
||||
mod vnode {
|
||||
|
||||
impl From<&str> for VNode {
|
||||
fn from(other: &str) -> Self {
|
||||
VNode::text(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for VNode {
|
||||
fn from(other: String) -> Self {
|
||||
VNode::text(other.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// Allow VNodes to be iterated for map-based UI
|
||||
// -----------------------------------------------
|
||||
impl IntoIterator for VNode {
|
||||
type Item = VNode;
|
||||
// TODO: Is this possible with an array [VNode] instead of a vec?
|
||||
type IntoIter = ::std::vec::IntoIter<VNode>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
vec![self].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<::std::vec::IntoIter<VNode>> for VNode {
|
||||
fn into(self) -> ::std::vec::IntoIter<VNode> {
|
||||
self.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// Allow debug/display adherent to the HTML spec
|
||||
// -----------------------------------------------
|
||||
use std::fmt;
|
||||
impl fmt::Debug for VNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
VNode::Element(e) => write!(f, "Node::{:?}", e),
|
||||
VNode::Text(t) => write!(f, "Node::{:?}", t),
|
||||
VNode::Component(c) => write!(f, "Node::{:?}", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn a VNode into an HTML string (delegate impl to variants)
|
||||
impl fmt::Display for VNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
VNode::Element(element) => write!(f, "{}", element),
|
||||
VNode::Text(text) => write!(f, "{}", text),
|
||||
VNode::Component(c) => write!(f, "{}", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod velement {
|
||||
// -----------------------------------------------
|
||||
// Allow debug/display adherent to the HTML spec
|
||||
// -----------------------------------------------
|
||||
use std::fmt;
|
||||
impl fmt::Debug for VElement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Element(<{}>, attrs: {:?}, children: {:?})",
|
||||
self.tag, self.attrs, self.children,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VElement {
|
||||
// Turn a VElement and all of it's children (recursively) into an HTML string
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "<{}", self.tag).unwrap();
|
||||
|
||||
for (attr, value) in self.attrs.iter() {
|
||||
write!(f, r#" {}="{}""#, attr, value)?;
|
||||
}
|
||||
|
||||
write!(f, ">")?;
|
||||
|
||||
for child in self.children.iter() {
|
||||
write!(f, "{}", child.to_string())?;
|
||||
}
|
||||
|
||||
if !crate::validation::is_self_closing(&self.tag) {
|
||||
write!(f, "</{}>", self.tag)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod vtext {
|
||||
// -----------------------------------------------
|
||||
// Convert from primitives directly into VText
|
||||
// -----------------------------------------------
|
||||
impl From<&str> for VText {
|
||||
fn from(text: &str) -> Self {
|
||||
VText {
|
||||
text: text.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for VText {
|
||||
fn from(text: String) -> Self {
|
||||
VText { text }
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// Allow debug/display adherent to the HTML spec
|
||||
// -----------------------------------------------
|
||||
use std::fmt;
|
||||
impl fmt::Debug for VText {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Text({})", self.text)
|
||||
}
|
||||
}
|
||||
|
||||
// Turn a VText into an HTML string
|
||||
impl fmt::Display for VText {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod iterables {
|
||||
|
||||
// TODO @Jon
|
||||
// Set this up so instead of the view trait, we can just take functions
|
||||
// Functions with no context should just be rendered
|
||||
// But functions with a context should be treated as regular components
|
||||
|
||||
// impl<V: View> From<Vec<V>> for IterableNodes {
|
||||
// fn from(other: Vec<V>) -> Self {
|
||||
// IterableNodes(other.into_iter().map(|it| it.render()).collect())
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<V: View> From<&Vec<V>> for IterableNodes {
|
||||
// fn from(other: &Vec<V>) -> Self {
|
||||
// IterableNodes(other.iter().map(|it| it.render()).collect())
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<V: View> From<&[V]> for IterableNodes {
|
||||
// fn from(other: &[V]) -> Self {
|
||||
// IterableNodes(other.iter().map(|it| it.render()).collect())
|
||||
// }
|
||||
// }
|
||||
|
||||
impl From<&str> for IterableNodes {
|
||||
fn from(other: &str) -> Self {
|
||||
IterableNodes(vec![VNode::text(other)])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for IterableNodes {
|
||||
fn from(other: String) -> Self {
|
||||
IterableNodes(vec![VNode::text(other.as_str())])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn self_closing_tag_to_string() {
|
||||
let node = VNode::element("br");
|
||||
|
||||
// No </br> since self closing tag
|
||||
assert_eq!(&node.to_string(), "<br>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
let mut node = VNode::Element(VElement::new("div"));
|
||||
node.as_velement_mut()
|
||||
.unwrap()
|
||||
.attrs
|
||||
.insert("id".into(), "some-id".into());
|
||||
|
||||
let mut child = VNode::Element(VElement::new("span"));
|
||||
|
||||
let mut text = VNode::Text(VText::new("Hello world"));
|
||||
|
||||
child.as_velement_mut().unwrap().children.push(text);
|
||||
|
||||
node.as_velement_mut().unwrap().children.push(child);
|
||||
|
||||
let expected = r#"<div id="some-id"><span>Hello world</span></div>"#;
|
||||
|
||||
assert_eq!(node.to_string(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod ddiff {
|
||||
/// The diffing algorithm to compare two VNode trees and generate a list of patches to update the VDom.
|
||||
/// Currently, using an index-based patching algorithm
|
||||
///
|
||||
pub mod diff {
|
||||
use super::*;
|
||||
use crate::nodes::{VNode, VText};
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
|
||||
// pub use apply_patches::patch;
|
||||
|
||||
/// A Patch encodes an operation that modifies a real DOM element.
|
||||
///
|
||||
/// To update the real DOM that a user sees you'll want to first diff your
|
||||
/// old virtual dom and new virtual dom.
|
||||
///
|
||||
/// This diff operation will generate `Vec<Patch>` with zero or more patches that, when
|
||||
/// applied to your real DOM, will make your real DOM look like your new virtual dom.
|
||||
///
|
||||
/// Each Patch has a u32 node index that helps us identify the real DOM node that it applies to.
|
||||
///
|
||||
/// Our old virtual dom's nodes are indexed depth first, as shown in this illustration
|
||||
/// (0 being the root node, 1 being it's first child, 2 being it's first child's first child).
|
||||
///
|
||||
/// ```text
|
||||
/// .─.
|
||||
/// ( 0 )
|
||||
/// `┬'
|
||||
/// ┌────┴──────┐
|
||||
/// │ │
|
||||
/// ▼ ▼
|
||||
/// .─. .─.
|
||||
/// ( 1 ) ( 4 )
|
||||
/// `┬' `─'
|
||||
/// ┌────┴───┐ │
|
||||
/// │ │ ├─────┬─────┐
|
||||
/// ▼ ▼ │ │ │
|
||||
/// .─. .─. ▼ ▼ ▼
|
||||
/// ( 2 ) ( 3 ) .─. .─. .─.
|
||||
/// `─' `─' ( 5 ) ( 6 ) ( 7 )
|
||||
/// `─' `─' `─'
|
||||
/// ```
|
||||
///
|
||||
/// The patching process is tested in a real browser in crates/virtual-dom-rs/tests/diff_patch.rs
|
||||
#[derive(PartialEq)]
|
||||
pub enum Patch<'a> {
|
||||
/// Append a vector of child nodes to a parent node id.
|
||||
AppendChildren(NodeIdx, Vec<&'a VNode>),
|
||||
/// For a `node_i32`, remove all children besides the first `len`
|
||||
TruncateChildren(NodeIdx, usize),
|
||||
/// Replace a node with another node. This typically happens when a node's tag changes.
|
||||
/// ex: <div> becomes <span>
|
||||
Replace(NodeIdx, &'a VNode),
|
||||
/// Add attributes that the new node has that the old node does not
|
||||
AddAttributes(NodeIdx, HashMap<&'a str, &'a str>),
|
||||
/// Remove attributes that the old node had that the new node doesn't
|
||||
RemoveAttributes(NodeIdx, Vec<&'a str>),
|
||||
/// Change the text of a Text node.
|
||||
ChangeText(NodeIdx, &'a VText),
|
||||
}
|
||||
|
||||
type NodeIdx = usize;
|
||||
|
||||
impl<'a> Patch<'a> {
|
||||
/// Every Patch is meant to be applied to a specific node within the DOM. Get the
|
||||
/// index of the DOM node that this patch should apply to. DOM nodes are indexed
|
||||
/// depth first with the root node in the tree having index 0.
|
||||
pub fn node_idx(&self) -> usize {
|
||||
match self {
|
||||
Patch::AppendChildren(node_idx, _) => *node_idx,
|
||||
Patch::TruncateChildren(node_idx, _) => *node_idx,
|
||||
Patch::Replace(node_idx, _) => *node_idx,
|
||||
Patch::AddAttributes(node_idx, _) => *node_idx,
|
||||
Patch::RemoveAttributes(node_idx, _) => *node_idx,
|
||||
Patch::ChangeText(node_idx, _) => *node_idx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given two VNode's generate Patch's that would turn the old virtual node's
|
||||
/// real DOM node equivalent into the new VNode's real DOM node equivalent.
|
||||
pub fn diff_vnodes<'a>(old: &'a VNode, new: &'a VNode) -> Vec<Patch<'a>> {
|
||||
diff_recursive(&old, &new, &mut 0)
|
||||
}
|
||||
|
||||
fn diff_recursive<'a, 'b>(
|
||||
old: &'a VNode,
|
||||
new: &'a VNode,
|
||||
cur_node_idx: &'b mut usize,
|
||||
) -> Vec<Patch<'a>> {
|
||||
let mut patches = vec![];
|
||||
let mut replace = false;
|
||||
|
||||
// Different enum variants, replace!
|
||||
// VNodes are of different types, and therefore will cause a re-render.
|
||||
// TODO: Handle previously-mounted children so they don't get re-mounted
|
||||
if mem::discriminant(old) != mem::discriminant(new) {
|
||||
replace = true;
|
||||
}
|
||||
|
||||
if let (VNode::Element(old_element), VNode::Element(new_element)) = (old, new) {
|
||||
// Replace if there are different element tags
|
||||
if old_element.tag != new_element.tag {
|
||||
replace = true;
|
||||
}
|
||||
|
||||
// Replace if two elements have different keys
|
||||
// TODO: More robust key support. This is just an early stopgap to allow you to force replace
|
||||
// an element... say if it's event changed. Just change the key name for now.
|
||||
// In the future we want keys to be used to create a Patch::ReOrder to re-order siblings
|
||||
if old_element.attrs.get("key").is_some()
|
||||
&& old_element.attrs.get("key") != new_element.attrs.get("key")
|
||||
{
|
||||
replace = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle replacing of a node
|
||||
if replace {
|
||||
patches.push(Patch::Replace(*cur_node_idx, &new));
|
||||
if let VNode::Element(old_element_node) = old {
|
||||
for child in old_element_node.children.iter() {
|
||||
increment_node_idx_for_children(child, cur_node_idx);
|
||||
}
|
||||
}
|
||||
return patches;
|
||||
}
|
||||
|
||||
// The following comparison can only contain identical variants, other
|
||||
// cases have already been handled above by comparing variant
|
||||
// discriminants.
|
||||
match (old, new) {
|
||||
// We're comparing two text nodes
|
||||
(VNode::Text(old_text), VNode::Text(new_text)) => {
|
||||
if old_text != new_text {
|
||||
patches.push(Patch::ChangeText(*cur_node_idx, &new_text));
|
||||
}
|
||||
}
|
||||
|
||||
// We're comparing two element nodes
|
||||
(VNode::Element(old_element), VNode::Element(new_element)) => {
|
||||
let mut add_attributes: HashMap<&str, &str> = HashMap::new();
|
||||
let mut remove_attributes: Vec<&str> = vec![];
|
||||
|
||||
// TODO: -> split out into func
|
||||
for (new_attr_name, new_attr_val) in new_element.attrs.iter() {
|
||||
match old_element.attrs.get(new_attr_name) {
|
||||
Some(ref old_attr_val) => {
|
||||
if old_attr_val != &new_attr_val {
|
||||
add_attributes.insert(new_attr_name, new_attr_val);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
add_attributes.insert(new_attr_name, new_attr_val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: -> split out into func
|
||||
for (old_attr_name, old_attr_val) in old_element.attrs.iter() {
|
||||
if add_attributes.get(&old_attr_name[..]).is_some() {
|
||||
continue;
|
||||
};
|
||||
|
||||
match new_element.attrs.get(old_attr_name) {
|
||||
Some(ref new_attr_val) => {
|
||||
if new_attr_val != &old_attr_val {
|
||||
remove_attributes.push(old_attr_name);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
remove_attributes.push(old_attr_name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if add_attributes.len() > 0 {
|
||||
patches.push(Patch::AddAttributes(*cur_node_idx, add_attributes));
|
||||
}
|
||||
if remove_attributes.len() > 0 {
|
||||
patches.push(Patch::RemoveAttributes(*cur_node_idx, remove_attributes));
|
||||
}
|
||||
|
||||
let old_child_count = old_element.children.len();
|
||||
let new_child_count = new_element.children.len();
|
||||
|
||||
if new_child_count > old_child_count {
|
||||
let append_patch: Vec<&'a VNode> =
|
||||
new_element.children[old_child_count..].iter().collect();
|
||||
patches.push(Patch::AppendChildren(*cur_node_idx, append_patch))
|
||||
}
|
||||
|
||||
if new_child_count < old_child_count {
|
||||
patches.push(Patch::TruncateChildren(*cur_node_idx, new_child_count))
|
||||
}
|
||||
|
||||
let min_count = min(old_child_count, new_child_count);
|
||||
for index in 0..min_count {
|
||||
*cur_node_idx = *cur_node_idx + 1;
|
||||
let old_child = &old_element.children[index];
|
||||
let new_child = &new_element.children[index];
|
||||
patches.append(&mut diff_recursive(
|
||||
&old_child,
|
||||
&new_child,
|
||||
cur_node_idx,
|
||||
))
|
||||
}
|
||||
if new_child_count < old_child_count {
|
||||
for child in old_element.children[min_count..].iter() {
|
||||
increment_node_idx_for_children(child, cur_node_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
(VNode::Text(_), VNode::Element(_)) | (VNode::Element(_), VNode::Text(_)) => {
|
||||
unreachable!(
|
||||
"Unequal variant discriminants should already have been handled"
|
||||
);
|
||||
}
|
||||
_ => todo!("Diffing Not yet implemented for all node types"),
|
||||
};
|
||||
|
||||
// new_root.create_element()
|
||||
patches
|
||||
}
|
||||
|
||||
fn increment_node_idx_for_children<'a, 'b>(
|
||||
old: &'a VNode,
|
||||
cur_node_idx: &'b mut usize,
|
||||
) {
|
||||
*cur_node_idx += 1;
|
||||
if let VNode::Element(element_node) = old {
|
||||
for child in element_node.children.iter() {
|
||||
increment_node_idx_for_children(&child, cur_node_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::prelude::*;
|
||||
// type VirtualNode = VNode;
|
||||
|
||||
// /// Test that we generate the right Vec<Patch> for some start and end virtual dom.
|
||||
// pub struct DiffTestCase<'a> {
|
||||
// // ex: "Patching root level nodes works"
|
||||
// pub description: &'static str,
|
||||
// // ex: html! { <div> </div> }
|
||||
// pub old: VNode,
|
||||
// // ex: html! { <strong> </strong> }
|
||||
// pub new: VNode,
|
||||
// // ex: vec![Patch::Replace(0, &html! { <strong></strong> })],
|
||||
// pub expected: Vec<Patch<'a>>,
|
||||
// }
|
||||
|
||||
// impl<'a> DiffTestCase<'a> {
|
||||
// pub fn test(&self) {
|
||||
// // ex: vec![Patch::Replace(0, &html! { <strong></strong> })],
|
||||
// let patches = diff_vnodes(&self.old, &self.new);
|
||||
|
||||
// assert_eq!(patches, self.expected, "{}", self.description);
|
||||
// }
|
||||
// }
|
||||
// use super::*;
|
||||
// use crate::nodes::{VNode, VText};
|
||||
// use std::collections::HashMap;
|
||||
|
||||
// #[test]
|
||||
// fn replace_node() {
|
||||
// DiffTestCase {
|
||||
// description: "Replace the root if the tag changed",
|
||||
// old: html! { <div> </div> },
|
||||
// new: html! { <span> </span> },
|
||||
// expected: vec![Patch::Replace(0, &html! { <span></span> })],
|
||||
// }
|
||||
// .test();
|
||||
// DiffTestCase {
|
||||
// description: "Replace a child node",
|
||||
// old: html! { <div> <b></b> </div> },
|
||||
// new: html! { <div> <strong></strong> </div> },
|
||||
// expected: vec![Patch::Replace(1, &html! { <strong></strong> })],
|
||||
// }
|
||||
// .test();
|
||||
// DiffTestCase {
|
||||
// description: "Replace node with a child",
|
||||
// old: html! { <div> <b>1</b> <b></b> </div> },
|
||||
// new: html! { <div> <i>1</i> <i></i> </div>},
|
||||
// expected: vec![
|
||||
// Patch::Replace(1, &html! { <i>1</i> }),
|
||||
// Patch::Replace(3, &html! { <i></i> }),
|
||||
// ], //required to check correct index
|
||||
// }
|
||||
// .test();
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn add_children() {
|
||||
// DiffTestCase {
|
||||
// description: "Added a new node to the root node",
|
||||
// old: html! { <div> <b></b> </div> },
|
||||
// new: html! { <div> <b></b> <span></span> </div> },
|
||||
// expected: vec![Patch::AppendChildren(0, vec![&html! { <span></span> }])],
|
||||
// }
|
||||
// .test();
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn remove_nodes() {
|
||||
// DiffTestCase {
|
||||
// description: "Remove all child nodes at and after child sibling index 1",
|
||||
// old: html! { <div> <b></b> <span></span> </div> },
|
||||
// new: html! { <div> </div> },
|
||||
// expected: vec![Patch::TruncateChildren(0, 0)],
|
||||
// }
|
||||
// .test();
|
||||
// DiffTestCase {
|
||||
// description: "Remove a child and a grandchild node",
|
||||
// old: html! {
|
||||
// <div>
|
||||
// <span>
|
||||
// <b></b>
|
||||
// // This `i` tag will get removed
|
||||
// <i></i>
|
||||
// </span>
|
||||
// // This `strong` tag will get removed
|
||||
// <strong></strong>
|
||||
// </div> },
|
||||
// new: html! {
|
||||
// <div>
|
||||
// <span>
|
||||
// <b></b>
|
||||
// </span>
|
||||
// </div> },
|
||||
// expected: vec![Patch::TruncateChildren(0, 1), Patch::TruncateChildren(1, 1)],
|
||||
// }
|
||||
// .test();
|
||||
// DiffTestCase {
|
||||
// description: "Removing child and change next node after parent",
|
||||
// old: html! { <div> <b> <i></i> <i></i> </b> <b></b> </div> },
|
||||
// new: html! { <div> <b> <i></i> </b> <i></i> </div>},
|
||||
// expected: vec![
|
||||
// Patch::TruncateChildren(1, 1),
|
||||
// Patch::Replace(4, &html! { <i></i> }),
|
||||
// ], //required to check correct index
|
||||
// }
|
||||
// .test();
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn add_attributes() {
|
||||
// let mut attributes = HashMap::new();
|
||||
// attributes.insert("id", "hello");
|
||||
|
||||
// DiffTestCase {
|
||||
// old: html! { <div> </div> },
|
||||
// new: html! { <div id="hello"> </div> },
|
||||
// expected: vec![Patch::AddAttributes(0, attributes.clone())],
|
||||
// description: "Add attributes",
|
||||
// }
|
||||
// .test();
|
||||
|
||||
// DiffTestCase {
|
||||
// old: html! { <div id="foobar"> </div> },
|
||||
// new: html! { <div id="hello"> </div> },
|
||||
// expected: vec![Patch::AddAttributes(0, attributes)],
|
||||
// description: "Change attribute",
|
||||
// }
|
||||
// .test();
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn remove_attributes() {
|
||||
// DiffTestCase {
|
||||
// old: html! { <div id="hey-there"></div> },
|
||||
// new: html! { <div> </div> },
|
||||
// expected: vec![Patch::RemoveAttributes(0, vec!["id"])],
|
||||
// description: "Add attributes",
|
||||
// }
|
||||
// .test();
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn change_attribute() {
|
||||
// let mut attributes = HashMap::new();
|
||||
// attributes.insert("id", "changed");
|
||||
|
||||
// DiffTestCase {
|
||||
// description: "Add attributes",
|
||||
// old: html! { <div id="hey-there"></div> },
|
||||
// new: html! { <div id="changed"> </div> },
|
||||
// expected: vec![Patch::AddAttributes(0, attributes)],
|
||||
// }
|
||||
// .test();
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn replace_text_node() {
|
||||
// DiffTestCase {
|
||||
// description: "Replace text node",
|
||||
// old: html! { Old },
|
||||
// new: html! { New },
|
||||
// expected: vec![Patch::ChangeText(0, &VText::new("New"))],
|
||||
// }
|
||||
// .test();
|
||||
// }
|
||||
|
||||
// // Initially motivated by having two elements where all that changed was an event listener
|
||||
// // because right now we don't patch event listeners. So.. until we have a solution
|
||||
// // for that we can just give them different keys to force a replace.
|
||||
// #[test]
|
||||
// fn replace_if_different_keys() {
|
||||
// DiffTestCase {
|
||||
// description: "If two nodes have different keys always generate a full replace.",
|
||||
// old: html! { <div key="1"> </div> },
|
||||
// new: html! { <div key="2"> </div> },
|
||||
// expected: vec![Patch::Replace(0, &html! {<div key="2"> </div>})],
|
||||
// }
|
||||
// .test()
|
||||
// }
|
||||
|
||||
// // // TODO: Key support
|
||||
// // #[test]
|
||||
// // fn reorder_chldren() {
|
||||
// // let mut attributes = HashMap::new();
|
||||
// // attributes.insert("class", "foo");
|
||||
// //
|
||||
// // let old_children = vec![
|
||||
// // // old node 0
|
||||
// // html! { <div key="hello", id="same-id", style="",></div> },
|
||||
// // // removed
|
||||
// // html! { <div key="gets-removed",> { "This node gets removed"} </div>},
|
||||
// // // old node 2
|
||||
// // html! { <div key="world", class="changed-class",></div>},
|
||||
// // // removed
|
||||
// // html! { <div key="this-got-removed",> { "This node gets removed"} </div>},
|
||||
// // ];
|
||||
// //
|
||||
// // let new_children = vec![
|
||||
// // html! { <div key="world", class="foo",></div> },
|
||||
// // html! { <div key="new",> </div>},
|
||||
// // html! { <div key="hello", id="same-id",></div>},
|
||||
// // ];
|
||||
// //
|
||||
// // test(DiffTestCase {
|
||||
// // old: html! { <div> { old_children } </div> },
|
||||
// // new: html! { <div> { new_children } </div> },
|
||||
// // expected: vec![
|
||||
// // // TODO: Come up with the patch structure for keyed nodes..
|
||||
// // // keying should only work if all children have keys..
|
||||
// // ],
|
||||
// // description: "Add attributes",
|
||||
// // })
|
||||
// // }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
mod vcomponent {
|
||||
// -----------------------------------------------
|
||||
// Allow debug/display adherent to the HTML spec
|
||||
// -----------------------------------------------
|
||||
|
||||
impl fmt::Debug for VComponent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO: @JON Implement how components should be formatted when spit out to html
|
||||
// It probably can't be as straightforward as renderinng their VNodes
|
||||
// It _could_ be, but we can't really implement that directly
|
||||
// Instead, we should drop a vnode labeled with the component id/key
|
||||
|
||||
// write!(
|
||||
// f,
|
||||
// "Element(<{}>, attrs: {:?}, children: {:?})",
|
||||
// self.tag, self.attrs, self.children,
|
||||
// )
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VComponent {
|
||||
// Turn a VElement and all of it's children (recursively) into an HTML string
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// write!(f, "<{}", self.tag).unwrap();
|
||||
|
||||
// for (attr, value) in self.attrs.iter() {
|
||||
// write!(f, r#" {}="{}""#, attr, value)?;
|
||||
// }
|
||||
|
||||
// write!(f, ">")?;
|
||||
|
||||
// for child in self.children.iter() {
|
||||
// write!(f, "{}", child.to_string())?;
|
||||
// }
|
||||
|
||||
// if !crate::validation::is_self_closing(&self.tag) {
|
||||
// write!(f, "</{}>", self.tag)?;
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub mod iterables {
|
||||
// use super::*;
|
||||
|
||||
// /// Used by the html! macro for all braced child nodes so that we can use any type
|
||||
// /// that implements Into<IterableNodes>
|
||||
// ///
|
||||
// /// html! { <div> { nodes } </div> }
|
||||
// ///
|
||||
// /// nodes can be a String .. VNode .. Vec<VNode> ... etc
|
||||
// pub struct IterableNodes(Vec<VNode>);
|
||||
|
||||
// impl IterableNodes {
|
||||
// /// Retrieve the first node mutably
|
||||
// pub fn first(&mut self) -> &mut VNode {
|
||||
// self.0.first_mut().unwrap()
|
||||
// }
|
||||
|
||||
// /// Retrieve the last node mutably
|
||||
// pub fn last(&mut self) -> &mut VNode {
|
||||
// self.0.last_mut().unwrap()
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl IntoIterator for IterableNodes {
|
||||
// type Item = VNode;
|
||||
// // TODO: Is this possible with an array [VNode] instead of a vec?
|
||||
// type IntoIter = ::std::vec::IntoIter<VNode>;
|
||||
|
||||
// fn into_iter(self) -> Self::IntoIter {
|
||||
// self.0.into_iter()
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl From<VNode> for IterableNodes {
|
||||
// fn from(other: VNode) -> Self {
|
||||
// IterableNodes(vec![other])
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl From<Vec<VNode>> for IterableNodes {
|
||||
// fn from(other: Vec<VNode>) -> Self {
|
||||
// IterableNodes(other)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,37 @@
|
||||
/// TODO @Jon
|
||||
/// Figure out if validation should be its own crate, or embedded directly into dioxus
|
||||
///
|
||||
/// Should we even be bothered with validation?
|
||||
///
|
||||
///
|
||||
///
|
||||
mod validation {
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashSet;
|
||||
|
||||
// Used to uniquely identify elements that contain closures so that the DomUpdater can
|
||||
// look them up by their unique id.
|
||||
// When the DomUpdater sees that the element no longer exists it will drop all of it's
|
||||
// Rc'd Closures for those events.
|
||||
static SELF_CLOSING_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
||||
[
|
||||
"area", "base", "br", "col", "hr", "img", "input", "link", "meta", "param", "command",
|
||||
"keygen", "source",
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
});
|
||||
|
||||
/// Whether or not this tag is self closing
|
||||
///
|
||||
/// ```ignore
|
||||
/// use dioxus_core::validation::is_self_closing;
|
||||
/// assert_eq!(is_self_closing("br"), true);
|
||||
/// assert_eq!(is_self_closing("div"), false);
|
||||
/// ```
|
||||
pub fn is_self_closing(tag: &str) -> bool {
|
||||
SELF_CLOSING_TAGS.contains(tag)
|
||||
// SELF_CLOSING_TAGS.contains(tag) || is_self_closing_svg_tag(tag)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces.
|
||||
|
||||
The Dioxus VDom uses the same underlying mechanics as Dodrio (double buffering, bump dom, etc).
|
||||
Instead of making the allocator very obvious, we choose to parametrize over the DomTree trait. For our purposes,
|
||||
the DomTree trait is simply an abstraction over a lazy dom builder, much like the iterator trait.
|
||||
|
||||
This means we can accept DomTree anywhere as well as return it. All components therefore look like this:
|
||||
```ignore
|
||||
function Component(ctx: Context<()>) -> impl DomTree {
|
||||
html! {<div> "hello world" </div>}
|
||||
}
|
||||
```
|
||||
It's not quite as sexy as statics, but there's only so much you can do. The goal is to get statics working with the FC macro,
|
||||
so types don't get in the way of you and your component writing. Fortunately, this is all generic enough to be split out
|
||||
into its own lib (IE, lazy loading wasm chunks by function (exciting stuff!))
|
||||
|
||||
```ignore
|
||||
#[fc] // gets translated into a function.
|
||||
static Component: FC = |ctx| {
|
||||
html! {<div> "hello world" </div>}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
This module includes all life-cycle related mechanics, including the virtual dom, scopes, properties, and lifecycles.
|
||||
---
|
||||
The VirtualDom is designed as so:
|
||||
|
||||
VDOM contains:
|
||||
- An arena of component scopes.
|
||||
- A scope contains
|
||||
- lifecycle data
|
||||
- hook data
|
||||
- Event queue
|
||||
- An event
|
||||
|
||||
A VDOM is
|
||||
- constructed from anything that implements "component"
|
||||
|
||||
A "Component" is anything (normally functions) that can be ran with a context to produce VNodes
|
||||
- Must implement properties-builder trait which produces a properties builder
|
||||
|
||||
A Context
|
||||
- Is a consumable struct
|
||||
- Made of references to properties
|
||||
- Holds a reference (lockable) to the underlying scope
|
||||
- Is partially threadsafe
|
||||
*/
|
||||
use crate::nodes::VNode;
|
||||
use crate::prelude::*;
|
||||
use bumpalo::Bump;
|
||||
use generational_arena::Arena;
|
||||
use std::future::Future;
|
||||
|
||||
/// An integrated virtual node system that progresses events and diffs UI trees.
|
||||
/// Differences are converted into patches which a renderer can use to draw the UI.
|
||||
pub struct VirtualDom {
|
||||
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
||||
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||
components: Arena<Scope>,
|
||||
|
||||
/// Components generate lifecycle events
|
||||
event_queue: Vec<LifecycleEvent>,
|
||||
|
||||
buffers: [Bump; 2],
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
|
||||
///
|
||||
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
|
||||
/// The root component can access things like routing in its context.
|
||||
pub fn new<F: DomTree>(root: FC<(), F>) -> Self {
|
||||
Self::new_with_props(root)
|
||||
}
|
||||
|
||||
/// Start a new VirtualDom instance with a dependent props.
|
||||
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
||||
///
|
||||
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
||||
/// to toss out the entire tree.
|
||||
pub fn new_with_props<P: Properties, F: DomTree>(root: FC<P, F>) -> Self {
|
||||
Self {
|
||||
components: Arena::new(),
|
||||
event_queue: vec![],
|
||||
buffers: [Bump::new(), Bump::new()],
|
||||
}
|
||||
}
|
||||
|
||||
/// Pop an event off the even queue and process it
|
||||
pub fn progress_event() {}
|
||||
}
|
||||
|
||||
/// The internal lifecycle event system is managed by these
|
||||
/// All events need to be confused before swapping doms over
|
||||
pub enum LifecycleEvent {
|
||||
Add {},
|
||||
}
|
||||
|
||||
/// Anything that takes a "bump" and returns VNodes is a "DomTree"
|
||||
/// This is used as a "trait alias" for function return types to look less hair
|
||||
pub trait DomTree {
|
||||
fn render(self, b: &Bump) -> VNode;
|
||||
}
|
||||
|
||||
/// Implement DomTree for the type returned by the html! macro.
|
||||
/// This lets the caller of the static function evaluate the builder closure with its own bump.
|
||||
/// It keeps components pretty and removes the need for the user to get too involved with allocation.
|
||||
impl<F> DomTree for F
|
||||
where
|
||||
F: FnOnce(&Bump) -> VNode,
|
||||
{
|
||||
fn render(self, b: &Bump) -> VNode {
|
||||
self(b)
|
||||
}
|
||||
}
|
||||
|
||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
||||
pub type FC<P: Properties, F: DomTree> = fn(&Context<P>) -> F;
|
||||
|
||||
/// A gross AF helper that connects the dots for lifetimes
|
||||
/// sigh.. todo: move this into the html macro or core crates
|
||||
/// https://github.com/rust-lang/rust/issues/41078
|
||||
#[inline]
|
||||
pub fn __domtree_helper(
|
||||
arg: impl for<'a> FnOnce(&'a Bump) -> VNode<'a>,
|
||||
) -> impl for<'a> FnOnce(&'a Bump) -> VNode<'a> {
|
||||
arg
|
||||
}
|
||||
|
||||
/// The `Component` trait refers to any struct or funciton that can be used as a component
|
||||
/// We automatically implement Component for FC<T>
|
||||
pub trait Component {
|
||||
type Props: Properties;
|
||||
fn builder(&'static self) -> Self::Props;
|
||||
}
|
||||
|
||||
// Auto implement component for a FC
|
||||
// Calling the FC is the same as "rendering" it
|
||||
impl<'a, P: Properties, F: DomTree> Component for FC<P, F> {
|
||||
type Props = P;
|
||||
|
||||
fn builder(&self) -> Self::Props {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields.
|
||||
/// Components take a "properties" object
|
||||
pub trait Properties {
|
||||
fn new() -> Self;
|
||||
}
|
||||
|
||||
// Auto implement for no-prop components
|
||||
impl Properties for () {
|
||||
fn new() -> Self {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Compile Tests for FC/Component/Properties
|
||||
// ============================================
|
||||
#[cfg(test)]
|
||||
mod fc_test {
|
||||
use super::*;
|
||||
|
||||
// // Make sure this function builds properly.
|
||||
// fn test_static_fn<'a, P: Properties, F: DomTree>(b: &'a Bump, r: &FC<P, F>) -> VNode<'a> {
|
||||
// let p = P::new(); // new props
|
||||
// let c = Context { props: p }; // new context with props
|
||||
// let g = r(&c); // calling function with context
|
||||
// g.render(&b) // rendering closure with bump allocator
|
||||
// }
|
||||
|
||||
// fn test_component(ctx: &Context<()>) -> impl DomTree {
|
||||
// // todo: helper should be part of html! macro
|
||||
// html! { <div> </div> }
|
||||
// }
|
||||
|
||||
// fn test_component2(ctx: &Context<()>) -> impl DomTree {
|
||||
// __domtree_helper(move |bump: &Bump| VNode::text("blah"))
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn ensure_types_work() {
|
||||
// // TODO: Get the whole casting thing to work properly.
|
||||
// // For whatever reason, FC is not auto-implemented, depsite it being a static type
|
||||
// let b = Bump::new();
|
||||
|
||||
// let g: FC<_, _> = test_component;
|
||||
// let nodes0 = test_static_fn(&b, &g);
|
||||
// // Happiness! The VNodes are now allocated onto the bump vdom
|
||||
|
||||
// let g: FC<_, _> = test_component2;
|
||||
// let nodes1 = test_static_fn(&b, &g);
|
||||
// }
|
||||
}
|
||||
|
||||
/// The Scope that wraps a functional component
|
||||
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components
|
||||
/// The actualy contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
||||
pub struct Scope {
|
||||
hook_idx: i32,
|
||||
hooks: Vec<()>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
fn new<T>() -> Self {
|
||||
Self {
|
||||
hook_idx: 0,
|
||||
hooks: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HookState {}
|
||||
|
||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
||||
/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
|
||||
///
|
||||
/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[derive(Properties)]
|
||||
/// struct Props {
|
||||
/// name: String
|
||||
///
|
||||
/// }
|
||||
///
|
||||
/// fn example(ctx: &Context<Props>) -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {ctx.props.name}" </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Context<T> {
|
||||
// pub struct Context<'source, T> {
|
||||
/// Direct access to the properties used to create this component.
|
||||
pub props: T,
|
||||
// pub props: &'source T,
|
||||
}
|
||||
|
||||
impl<'a, T> Context<T> {
|
||||
// impl<'a, T> Context<'a, T> {
|
||||
/// Access the children elements passed into the component
|
||||
pub fn children(&self) -> Vec<VNode> {
|
||||
todo!("Children API not yet implemented for component Context")
|
||||
}
|
||||
|
||||
/// Access a parent context
|
||||
pub fn parent_context<C>(&self) -> C {
|
||||
todo!("Context API is not ready yet")
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
pub fn subscribe(&self) -> impl FnOnce() -> () {
|
||||
todo!("Subscription API is not ready yet");
|
||||
|| {}
|
||||
}
|
||||
|
||||
/// Create VNodes from macros that use efficient allcators
|
||||
/// The VDOM is configured to use a special allocator for VNodes
|
||||
pub fn view(&self, v: impl Fn(&'a Bump) -> VNode<'a>) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Create a suspended component from a future.
|
||||
///
|
||||
/// When the future completes, the component will be renderered
|
||||
pub fn suspend(
|
||||
&self,
|
||||
fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user