mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Subtree memorization / reactive templates (#488)
This commit adds subtree memoization to Dioxus. Subtree memoization is basically a compile-time step that drastically reduces the amount of work the diffing engine needs to do at runtime by extracting non-changing nodes out into a static "template." Templates are then understood by the various renderers in the ecosystem as a faster way of rendering the same items. For example, in the web, templates are simply a set of DOM Nodes created once and then cloned later. This is the same pattern frameworks like Lithtml and SolidJS use to achieve near-perfect performance. Subtree memoization adds an additional level of complexity to Dioxus. The RSX macro needs to be much smarter to identify changing/nonchanging nodes and generate a mapping between the Template and its runtime counterparts. This commit represents a working starter point for this work, adding support for templates for the web, desktop, liveview, ssr, and native-core renderers. In the future we will try to shrink code generation, generally improve performance, and simplify our implementation.
This commit is contained in:
parent
b32fd2d2cd
commit
047ed1e553
80 changed files with 6774 additions and 2552 deletions
|
@ -17,7 +17,6 @@ members = [
|
|||
"packages/liveview",
|
||||
"packages/autofmt",
|
||||
"packages/rsx",
|
||||
"packages/rsx_interpreter",
|
||||
"packages/native-core",
|
||||
"packages/native-core-macro",
|
||||
"docs/guide",
|
||||
|
@ -42,7 +41,7 @@ rust-version = "1.60.0"
|
|||
|
||||
[dev-dependencies]
|
||||
dioxus = { path = "./packages/dioxus" }
|
||||
dioxus-desktop = { path = "./packages/desktop" }
|
||||
dioxus-desktop = { path = "./packages/desktop", features = ["hot-reload"] }
|
||||
dioxus-ssr = { path = "./packages/ssr" }
|
||||
dioxus-router = { path = "./packages/router" }
|
||||
fermi = { path = "./packages/fermi" }
|
||||
|
@ -60,3 +59,8 @@ reqwest = { version = "0.11.9", features = ["json"] }
|
|||
fern = { version = "0.6.0", features = ["colored"] }
|
||||
thiserror = "1.0.30"
|
||||
env_logger = "0.9.0"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
debug = true
|
|
@ -50,7 +50,7 @@ Generally, here's the status of each platform:
|
|||
| 1st class global state | ✅ | redux/recoil/mobx on top of context |
|
||||
| Runs natively | ✅ | runs as a portable binary w/o a runtime (Node) |
|
||||
| Subtree Memoization | ✅ | skip diffing static element subtrees |
|
||||
| High-efficiency templates | 🛠 | rsx! calls are translated to templates on the DOM's side |
|
||||
| High-efficiency templates | ✅ | rsx! calls are translated to templates on the DOM's side |
|
||||
| Compile-time correct | ✅ | Throw errors on invalid template layouts |
|
||||
| Heuristic Engine | ✅ | track component memory usage to minimize future allocations |
|
||||
| Fine-grained reactivity | 👀 | Skip diffing for fine-grain updates |
|
||||
|
@ -66,7 +66,7 @@ These Features are planned for the future of Dioxus:
|
|||
### Core
|
||||
- [x] Release of Dioxus Core
|
||||
- [x] Upgrade documentation to include more theory and be more comprehensive
|
||||
- [ ] Support for HTML-side templates for lightning-fast dom manipulation
|
||||
- [x] Support for HTML-side templates for lightning-fast dom manipulation
|
||||
- [ ] Support for multiple renderers for same virtualdom (subtrees)
|
||||
- [ ] Support for ThreadSafe (Send + Sync)
|
||||
- [ ] Support for Portals
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
fmt::{Result, Write},
|
||||
};
|
||||
|
||||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed};
|
||||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, IfmtInput};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
|
||||
|
@ -72,8 +72,8 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn write_text(&mut self, text: &syn::LitStr) -> Result {
|
||||
write!(self.buf, "\"{}\"", text.value())
|
||||
pub fn write_text(&mut self, text: &IfmtInput) -> Result {
|
||||
write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value())
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
|
@ -155,13 +155,13 @@ impl Buffer {
|
|||
|
||||
total += match &attr.attr {
|
||||
ElementAttr::AttrText { value, name } => {
|
||||
value.value().len() + name.span().line_length() + 3
|
||||
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
value.span().line_length() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
value.value().len() + name.value().len() + 3
|
||||
value.source.as_ref().unwrap().value().len() + name.value().len() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
name.value().len() + value.span().line_length() + 3
|
||||
|
|
|
@ -167,7 +167,12 @@ impl Buffer {
|
|||
write!(self.buf, "{}: {}", name, out)?;
|
||||
}
|
||||
ContentField::Formatted(s) => {
|
||||
write!(self.buf, "{}: \"{}\"", name, s.value())?;
|
||||
write!(
|
||||
self.buf,
|
||||
"{}: \"{}\"",
|
||||
name,
|
||||
s.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
}
|
||||
ContentField::OnHandlerRaw(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
|
@ -209,7 +214,7 @@ impl Buffer {
|
|||
let attr_len = fields
|
||||
.iter()
|
||||
.map(|field| match &field.content {
|
||||
ContentField::Formatted(s) => s.value().len() ,
|
||||
ContentField::Formatted(s) => s.source.as_ref().unwrap().value().len() ,
|
||||
ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
|
||||
let formatted = prettyplease::unparse_expr(exp);
|
||||
let len = if formatted.contains('\n') {
|
||||
|
|
|
@ -132,7 +132,7 @@ impl Buffer {
|
|||
fn write_attributes(
|
||||
&mut self,
|
||||
attributes: &[ElementAttrNamed],
|
||||
key: &Option<syn::LitStr>,
|
||||
key: &Option<IfmtInput>,
|
||||
sameline: bool,
|
||||
) -> Result {
|
||||
let mut attr_iter = attributes.iter().peekable();
|
||||
|
@ -141,7 +141,11 @@ impl Buffer {
|
|||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
}
|
||||
write!(self.buf, "key: \"{}\"", key.value())?;
|
||||
write!(
|
||||
self.buf,
|
||||
"key: \"{}\"",
|
||||
key.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
if !attributes.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
if sameline {
|
||||
|
@ -178,7 +182,11 @@ impl Buffer {
|
|||
fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
|
||||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
write!(self.buf, "{name}: \"{value}\"", value = value.value())?;
|
||||
write!(
|
||||
self.buf,
|
||||
"{name}: \"{value}\"",
|
||||
value = value.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
|
@ -190,7 +198,7 @@ impl Buffer {
|
|||
self.buf,
|
||||
"\"{name}\": \"{value}\"",
|
||||
name = name.value(),
|
||||
value = value.value()
|
||||
value = value.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -272,7 +280,7 @@ impl Buffer {
|
|||
}
|
||||
|
||||
match children {
|
||||
[BodyNode::Text(ref text)] => Some(text.value().len()),
|
||||
[BodyNode::Text(ref text)] => Some(text.source.as_ref().unwrap().value().len()),
|
||||
[BodyNode::Component(ref comp)] => {
|
||||
let attr_len = self.field_len(&comp.fields, &comp.manual_props);
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ proc-macro2 = { version = "1.0" }
|
|||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
dioxus-rsx = { path = "../rsx" }
|
||||
dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
|
||||
|
||||
# testing
|
||||
[dev-dependencies]
|
||||
|
@ -28,4 +27,3 @@ trybuild = "1.0"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
hot-reload = ["dioxus-rsx-interpreter"]
|
|
@ -34,40 +34,21 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
|||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn rsx(s: TokenStream) -> TokenStream {
|
||||
#[cfg(feature = "hot-reload")]
|
||||
let rsx_text = s.to_string();
|
||||
match syn::parse::<rsx::CallBody>(s) {
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
Ok(body) => body.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A version of the rsx! macro that does not use templates. Used for testing diffing
|
||||
#[proc_macro]
|
||||
pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
|
||||
match syn::parse::<rsx::CallBody>(s) {
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
Ok(body) => {
|
||||
#[cfg(feature = "hot-reload")]
|
||||
{
|
||||
use dioxus_rsx_interpreter::captuered_context::CapturedContextBuilder;
|
||||
|
||||
match CapturedContextBuilder::from_call_body(body) {
|
||||
Ok(captured) => {
|
||||
let lazy = quote::quote! {
|
||||
LazyNodes::new(move |__cx|{
|
||||
let code_location = get_line_num!();
|
||||
let captured = #captured;
|
||||
let text = #rsx_text;
|
||||
|
||||
resolve_scope(code_location, text, captured, __cx)
|
||||
})
|
||||
};
|
||||
if let Some(cx) = captured.custom_context {
|
||||
quote::quote! {
|
||||
#cx.render(#lazy)
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
lazy.into()
|
||||
}
|
||||
}
|
||||
Err(err) => err.into_compile_error().into(),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "hot-reload"))]
|
||||
body.to_token_stream().into()
|
||||
let mut tokens = proc_macro2::TokenStream::new();
|
||||
body.to_tokens_without_template(&mut tokens);
|
||||
tokens.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,4 +48,5 @@ backtrace = "0.3"
|
|||
[features]
|
||||
default = []
|
||||
serialize = ["serde"]
|
||||
debug_vdom = []
|
||||
debug_vdom = []
|
||||
hot-reload = []
|
|
@ -233,3 +233,204 @@ impl<'a> AttributeValue<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A owned attribute value.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize"),
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum OwnedAttributeValue {
|
||||
Text(String),
|
||||
Float32(f32),
|
||||
Float64(f64),
|
||||
Int32(i32),
|
||||
Int64(i64),
|
||||
Uint32(u32),
|
||||
Uint64(u64),
|
||||
Bool(bool),
|
||||
|
||||
Vec3Float(f32, f32, f32),
|
||||
Vec3Int(i32, i32, i32),
|
||||
Vec3Uint(u32, u32, u32),
|
||||
|
||||
Vec4Float(f32, f32, f32, f32),
|
||||
Vec4Int(i32, i32, i32, i32),
|
||||
Vec4Uint(u32, u32, u32, u32),
|
||||
|
||||
Bytes(Vec<u8>),
|
||||
// TODO: support other types
|
||||
// Any(ArbitraryAttributeValue<'a>),
|
||||
}
|
||||
|
||||
impl PartialEq<AttributeValue<'_>> for OwnedAttributeValue {
|
||||
fn eq(&self, other: &AttributeValue<'_>) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Text(l0), AttributeValue::Text(r0)) => l0 == r0,
|
||||
(Self::Float32(l0), AttributeValue::Float32(r0)) => l0 == r0,
|
||||
(Self::Float64(l0), AttributeValue::Float64(r0)) => l0 == r0,
|
||||
(Self::Int32(l0), AttributeValue::Int32(r0)) => l0 == r0,
|
||||
(Self::Int64(l0), AttributeValue::Int64(r0)) => l0 == r0,
|
||||
(Self::Uint32(l0), AttributeValue::Uint32(r0)) => l0 == r0,
|
||||
(Self::Uint64(l0), AttributeValue::Uint64(r0)) => l0 == r0,
|
||||
(Self::Bool(l0), AttributeValue::Bool(r0)) => l0 == r0,
|
||||
(Self::Vec3Float(l0, l1, l2), AttributeValue::Vec3Float(r0, r1, r2)) => {
|
||||
l0 == r0 && l1 == r1 && l2 == r2
|
||||
}
|
||||
(Self::Vec3Int(l0, l1, l2), AttributeValue::Vec3Int(r0, r1, r2)) => {
|
||||
l0 == r0 && l1 == r1 && l2 == r2
|
||||
}
|
||||
(Self::Vec3Uint(l0, l1, l2), AttributeValue::Vec3Uint(r0, r1, r2)) => {
|
||||
l0 == r0 && l1 == r1 && l2 == r2
|
||||
}
|
||||
(Self::Vec4Float(l0, l1, l2, l3), AttributeValue::Vec4Float(r0, r1, r2, r3)) => {
|
||||
l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
|
||||
}
|
||||
(Self::Vec4Int(l0, l1, l2, l3), AttributeValue::Vec4Int(r0, r1, r2, r3)) => {
|
||||
l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
|
||||
}
|
||||
(Self::Vec4Uint(l0, l1, l2, l3), AttributeValue::Vec4Uint(r0, r1, r2, r3)) => {
|
||||
l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3
|
||||
}
|
||||
(Self::Bytes(l0), AttributeValue::Bytes(r0)) => l0 == r0,
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AttributeValue<'a>> for OwnedAttributeValue {
|
||||
fn from(attr: AttributeValue<'a>) -> Self {
|
||||
match attr {
|
||||
AttributeValue::Text(t) => OwnedAttributeValue::Text(t.to_owned()),
|
||||
AttributeValue::Float32(f) => OwnedAttributeValue::Float32(f),
|
||||
AttributeValue::Float64(f) => OwnedAttributeValue::Float64(f),
|
||||
AttributeValue::Int32(i) => OwnedAttributeValue::Int32(i),
|
||||
AttributeValue::Int64(i) => OwnedAttributeValue::Int64(i),
|
||||
AttributeValue::Uint32(u) => OwnedAttributeValue::Uint32(u),
|
||||
AttributeValue::Uint64(u) => OwnedAttributeValue::Uint64(u),
|
||||
AttributeValue::Bool(b) => OwnedAttributeValue::Bool(b),
|
||||
AttributeValue::Vec3Float(f1, f2, f3) => OwnedAttributeValue::Vec3Float(f1, f2, f3),
|
||||
AttributeValue::Vec3Int(f1, f2, f3) => OwnedAttributeValue::Vec3Int(f1, f2, f3),
|
||||
AttributeValue::Vec3Uint(f1, f2, f3) => OwnedAttributeValue::Vec3Uint(f1, f2, f3),
|
||||
AttributeValue::Vec4Float(f1, f2, f3, f4) => {
|
||||
OwnedAttributeValue::Vec4Float(f1, f2, f3, f4)
|
||||
}
|
||||
AttributeValue::Vec4Int(f1, f2, f3, f4) => OwnedAttributeValue::Vec4Int(f1, f2, f3, f4),
|
||||
AttributeValue::Vec4Uint(f1, f2, f3, f4) => {
|
||||
OwnedAttributeValue::Vec4Uint(f1, f2, f3, f4)
|
||||
}
|
||||
AttributeValue::Bytes(b) => OwnedAttributeValue::Bytes(b.to_owned()),
|
||||
AttributeValue::Any(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo
|
||||
#[allow(missing_docs)]
|
||||
impl OwnedAttributeValue {
|
||||
pub fn as_text(&self) -> Option<&str> {
|
||||
match self {
|
||||
OwnedAttributeValue::Text(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float32(&self) -> Option<f32> {
|
||||
match self {
|
||||
OwnedAttributeValue::Float32(f) => Some(*f),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float64(&self) -> Option<f64> {
|
||||
match self {
|
||||
OwnedAttributeValue::Float64(f) => Some(*f),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_int32(&self) -> Option<i32> {
|
||||
match self {
|
||||
OwnedAttributeValue::Int32(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_int64(&self) -> Option<i64> {
|
||||
match self {
|
||||
OwnedAttributeValue::Int64(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_uint32(&self) -> Option<u32> {
|
||||
match self {
|
||||
OwnedAttributeValue::Uint32(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_uint64(&self) -> Option<u64> {
|
||||
match self {
|
||||
OwnedAttributeValue::Uint64(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match self {
|
||||
OwnedAttributeValue::Bool(b) => Some(*b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
|
||||
match self {
|
||||
OwnedAttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
|
||||
match self {
|
||||
OwnedAttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
|
||||
match self {
|
||||
OwnedAttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
|
||||
match self {
|
||||
OwnedAttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
|
||||
match self {
|
||||
OwnedAttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
|
||||
match self {
|
||||
OwnedAttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> Option<&[u8]> {
|
||||
match self {
|
||||
OwnedAttributeValue::Bytes(b) => Some(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,10 +91,19 @@
|
|||
//! More info on how to improve this diffing algorithm:
|
||||
//! - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
|
||||
|
||||
use crate::innerlude::{
|
||||
AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, VNode,
|
||||
VPlaceholder, VText,
|
||||
use crate::{
|
||||
dynamic_template_context::TemplateContext,
|
||||
innerlude::{
|
||||
AnyProps, ElementId, GlobalNodeId, Mutations, RendererTemplateId, ScopeArena, ScopeId,
|
||||
VComponent, VElement, VFragment, VNode, VPlaceholder, VText,
|
||||
},
|
||||
template::{
|
||||
Template, TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId,
|
||||
TemplateNodeType, TemplateValue, TextTemplateSegment, VTemplateRef,
|
||||
},
|
||||
Attribute, TemplateAttributeValue,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
|
@ -102,7 +111,7 @@ pub(crate) struct DiffState<'bump> {
|
|||
pub(crate) scopes: &'bump ScopeArena,
|
||||
pub(crate) mutations: Mutations<'bump>,
|
||||
pub(crate) force_diff: bool,
|
||||
pub(crate) element_stack: SmallVec<[ElementId; 10]>,
|
||||
pub(crate) element_stack: SmallVec<[GlobalNodeId; 10]>,
|
||||
pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
|
||||
}
|
||||
|
||||
|
@ -133,7 +142,7 @@ impl<'b> DiffState<'b> {
|
|||
}
|
||||
|
||||
pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
|
||||
use VNode::{Component, Element, Fragment, Placeholder, Text};
|
||||
use VNode::{Component, Element, Fragment, Placeholder, TemplateRef, Text};
|
||||
match (old_node, new_node) {
|
||||
(Text(old), Text(new)) => {
|
||||
self.diff_text_nodes(old, new, old_node, new_node);
|
||||
|
@ -148,16 +157,20 @@ impl<'b> DiffState<'b> {
|
|||
}
|
||||
|
||||
(Component(old), Component(new)) => {
|
||||
self.diff_component_nodes(old_node, new_node, *old, *new);
|
||||
self.diff_component_nodes(*old, *new, old_node, new_node);
|
||||
}
|
||||
|
||||
(Fragment(old), Fragment(new)) => {
|
||||
self.diff_fragment_nodes(old, new);
|
||||
}
|
||||
|
||||
(TemplateRef(old), TemplateRef(new)) => {
|
||||
self.diff_template_ref_nodes(old, new, old_node, new_node);
|
||||
}
|
||||
|
||||
(
|
||||
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
|
||||
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
|
||||
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_),
|
||||
Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_),
|
||||
) => self.replace_node(old_node, new_node),
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +182,7 @@ impl<'b> DiffState<'b> {
|
|||
VNode::Element(element) => self.create_element_node(element, node),
|
||||
VNode::Fragment(frag) => self.create_fragment_node(frag),
|
||||
VNode::Component(component) => self.create_component_node(*component),
|
||||
VNode::TemplateRef(temp) => self.create_template_ref_node(*temp, node),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,19 +218,21 @@ impl<'b> DiffState<'b> {
|
|||
|
||||
dom_id.set(Some(real_id));
|
||||
|
||||
self.element_stack.push(real_id);
|
||||
self.element_stack.push(GlobalNodeId::VNodeId(real_id));
|
||||
{
|
||||
self.mutations.create_element(tag_name, *namespace, real_id);
|
||||
|
||||
let cur_scope_id = self.current_scope();
|
||||
|
||||
for listener in listeners.iter() {
|
||||
listener.mounted_node.set(Some(real_id));
|
||||
listener
|
||||
.mounted_node
|
||||
.set(Some(GlobalNodeId::VNodeId(real_id)));
|
||||
self.mutations.new_event_listener(listener, cur_scope_id);
|
||||
}
|
||||
|
||||
for attr in attributes.iter() {
|
||||
self.mutations.set_attribute(attr, real_id.as_u64());
|
||||
self.mutations.set_attribute(attr, real_id);
|
||||
}
|
||||
|
||||
if !children.is_empty() {
|
||||
|
@ -249,7 +265,6 @@ impl<'b> DiffState<'b> {
|
|||
props,
|
||||
Some(parent_idx),
|
||||
self.element_stack.last().copied().unwrap(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -286,6 +301,37 @@ impl<'b> DiffState<'b> {
|
|||
created
|
||||
}
|
||||
|
||||
pub(crate) fn create_template_ref_node(
|
||||
&mut self,
|
||||
new: &'b VTemplateRef<'b>,
|
||||
node: &'b VNode<'b>,
|
||||
) -> usize {
|
||||
let (id, created) = {
|
||||
let mut resolver = self.scopes.template_resolver.borrow_mut();
|
||||
resolver.get_or_create_client_id(&new.template_id)
|
||||
};
|
||||
|
||||
let template = {
|
||||
let templates = self.scopes.templates.borrow();
|
||||
templates.get(&new.template_id).unwrap().clone()
|
||||
};
|
||||
let template = template.borrow();
|
||||
|
||||
if created {
|
||||
self.register_template(&template, id);
|
||||
}
|
||||
|
||||
let real_id = self.scopes.reserve_node(node);
|
||||
|
||||
new.id.set(Some(real_id));
|
||||
|
||||
self.mutations.create_template_ref(real_id, id.into());
|
||||
|
||||
new.hydrate(&template, self);
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
pub(crate) fn diff_text_nodes(
|
||||
&mut self,
|
||||
old: &'b VText<'b>,
|
||||
|
@ -304,7 +350,7 @@ impl<'b> DiffState<'b> {
|
|||
};
|
||||
|
||||
if old.text != new.text {
|
||||
self.mutations.set_text(new.text, root.as_u64());
|
||||
self.mutations.set_text(new.text, root);
|
||||
}
|
||||
|
||||
self.scopes.update_node(new_node, root);
|
||||
|
@ -377,16 +423,18 @@ impl<'b> DiffState<'b> {
|
|||
// TODO: take a more efficient path than this
|
||||
if old.attributes.len() == new.attributes.len() {
|
||||
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
|
||||
if old_attr.value != new_attr.value || new_attr.is_volatile {
|
||||
self.mutations.set_attribute(new_attr, root.as_u64());
|
||||
if !old_attr.is_static && old_attr.value != new_attr.value
|
||||
|| new_attr.attribute.volatile
|
||||
{
|
||||
self.mutations.set_attribute(new_attr, root);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for attribute in old.attributes {
|
||||
self.mutations.remove_attribute(attribute, root.as_u64());
|
||||
self.mutations.remove_attribute(attribute, root);
|
||||
}
|
||||
for attribute in new.attributes {
|
||||
self.mutations.set_attribute(attribute, root.as_u64());
|
||||
self.mutations.set_attribute(attribute, root);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,18 +452,16 @@ impl<'b> DiffState<'b> {
|
|||
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
|
||||
new_l.mounted_node.set(old_l.mounted_node.get());
|
||||
if old_l.event != new_l.event {
|
||||
self.mutations
|
||||
.remove_event_listener(old_l.event, root.as_u64());
|
||||
self.mutations.remove_event_listener(old_l.event, root);
|
||||
self.mutations.new_event_listener(new_l, cur_scope_id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for listener in old.listeners {
|
||||
self.mutations
|
||||
.remove_event_listener(listener.event, root.as_u64());
|
||||
self.mutations.remove_event_listener(listener.event, root);
|
||||
}
|
||||
for listener in new.listeners {
|
||||
listener.mounted_node.set(Some(root));
|
||||
listener.mounted_node.set(Some(GlobalNodeId::VNodeId(root)));
|
||||
self.mutations.new_event_listener(listener, cur_scope_id);
|
||||
}
|
||||
}
|
||||
|
@ -434,10 +480,10 @@ impl<'b> DiffState<'b> {
|
|||
|
||||
fn diff_component_nodes(
|
||||
&mut self,
|
||||
old_node: &'b VNode<'b>,
|
||||
new_node: &'b VNode<'b>,
|
||||
old: &'b VComponent<'b>,
|
||||
new: &'b VComponent<'b>,
|
||||
old_node: &'b VNode<'b>,
|
||||
new_node: &'b VNode<'b>,
|
||||
) {
|
||||
let scope_addr = old
|
||||
.scope
|
||||
|
@ -526,6 +572,228 @@ impl<'b> DiffState<'b> {
|
|||
self.diff_children(old.children, new.children);
|
||||
}
|
||||
|
||||
fn diff_template_ref_nodes(
|
||||
&mut self,
|
||||
old: &'b VTemplateRef<'b>,
|
||||
new: &'b VTemplateRef<'b>,
|
||||
old_node: &'b VNode<'b>,
|
||||
new_node: &'b VNode<'b>,
|
||||
) {
|
||||
if std::ptr::eq(old, new) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the templates are different, just rebuild it
|
||||
if old.template_id != new.template_id
|
||||
|| self
|
||||
.scopes
|
||||
.template_resolver
|
||||
.borrow()
|
||||
.is_dirty(&new.template_id)
|
||||
{
|
||||
self.replace_node(old_node, new_node);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the node is comming back not assigned, that means it was borrowed but removed
|
||||
let root = match old.id.get() {
|
||||
Some(id) => id,
|
||||
None => self.scopes.reserve_node(new_node),
|
||||
};
|
||||
|
||||
self.scopes.update_node(new_node, root);
|
||||
|
||||
new.id.set(Some(root));
|
||||
|
||||
self.element_stack.push(GlobalNodeId::VNodeId(root));
|
||||
|
||||
self.mutations.enter_template_ref(root);
|
||||
|
||||
let scope_bump = &self.current_scope_bump();
|
||||
|
||||
let template = {
|
||||
let templates = self.scopes.templates.borrow();
|
||||
templates.get(&new.template_id).unwrap().clone()
|
||||
};
|
||||
let template = template.borrow();
|
||||
|
||||
fn diff_attributes<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>(
|
||||
nodes: &Nodes,
|
||||
ctx: (
|
||||
&mut Mutations<'b>,
|
||||
&'b Bump,
|
||||
&VTemplateRef<'b>,
|
||||
&Template,
|
||||
usize,
|
||||
),
|
||||
) where
|
||||
Nodes: AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
|
||||
Attributes: AsRef<[TemplateAttribute<V>]>,
|
||||
V: TemplateValue,
|
||||
Children: AsRef<[TemplateNodeId]>,
|
||||
Listeners: AsRef<[usize]>,
|
||||
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
|
||||
Text: AsRef<str>,
|
||||
{
|
||||
let (mutations, scope_bump, new, template, idx) = ctx;
|
||||
for (node_id, attr_idx) in template.get_dynamic_nodes_for_attribute_index(idx) {
|
||||
if let TemplateNodeType::Element(el) = &nodes.as_ref()[node_id.0].node_type {
|
||||
let TemplateElement { attributes, .. } = el;
|
||||
let attr = &attributes.as_ref()[*attr_idx];
|
||||
let attribute = Attribute {
|
||||
attribute: attr.attribute,
|
||||
value: new.dynamic_context.resolve_attribute(idx).clone(),
|
||||
is_static: false,
|
||||
};
|
||||
mutations.set_attribute(scope_bump.alloc(attribute), *node_id);
|
||||
} else {
|
||||
panic!("expected element node");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// diff dynamic attributes
|
||||
for (idx, (old_attr, new_attr)) in old
|
||||
.dynamic_context
|
||||
.attributes
|
||||
.iter()
|
||||
.zip(new.dynamic_context.attributes.iter())
|
||||
.enumerate()
|
||||
{
|
||||
if old_attr != new_attr {
|
||||
template.with_nodes(
|
||||
diff_attributes,
|
||||
diff_attributes,
|
||||
(&mut self.mutations, scope_bump, new, &template, idx),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_attribute<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
|
||||
node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
|
||||
ctx: (&mut Mutations<'b>, &'b Bump, &VTemplateRef<'b>, usize),
|
||||
) where
|
||||
Attributes: AsRef<[TemplateAttribute<V>]>,
|
||||
V: TemplateValue,
|
||||
Children: AsRef<[TemplateNodeId]>,
|
||||
Listeners: AsRef<[usize]>,
|
||||
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
|
||||
Text: AsRef<str>,
|
||||
{
|
||||
let (mutations, scope_bump, new, template_attr_idx) = ctx;
|
||||
if let TemplateNodeType::Element(el) = &node.node_type {
|
||||
let TemplateElement { attributes, .. } = el;
|
||||
let attr = &attributes.as_ref()[template_attr_idx];
|
||||
let value = match &attr.value {
|
||||
TemplateAttributeValue::Dynamic(idx) => {
|
||||
new.dynamic_context.resolve_attribute(*idx).clone()
|
||||
}
|
||||
TemplateAttributeValue::Static(value) => value.allocate(scope_bump),
|
||||
};
|
||||
let attribute = Attribute {
|
||||
attribute: attr.attribute,
|
||||
value,
|
||||
is_static: false,
|
||||
};
|
||||
mutations.set_attribute(scope_bump.alloc(attribute), node.id);
|
||||
} else {
|
||||
panic!("expected element node");
|
||||
}
|
||||
}
|
||||
|
||||
// set all volatile attributes
|
||||
for (id, idx) in template.volatile_attributes() {
|
||||
template.with_node(
|
||||
id,
|
||||
set_attribute,
|
||||
set_attribute,
|
||||
(&mut self.mutations, scope_bump, new, idx),
|
||||
)
|
||||
}
|
||||
|
||||
fn diff_dynamic_node<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
|
||||
node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
|
||||
ctx: (&mut DiffState<'b>, &'b VNode<'b>, &'b VNode<'b>, ElementId),
|
||||
) where
|
||||
Attributes: AsRef<[TemplateAttribute<V>]>,
|
||||
V: TemplateValue,
|
||||
Children: AsRef<[TemplateNodeId]>,
|
||||
Listeners: AsRef<[usize]>,
|
||||
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
|
||||
Text: AsRef<str>,
|
||||
{
|
||||
let (diff, old_node, new_node, root) = ctx;
|
||||
if let TemplateNodeType::Element { .. } = node.node_type {
|
||||
diff.element_stack.push(GlobalNodeId::VNodeId(root));
|
||||
diff.diff_node(old_node, new_node);
|
||||
diff.element_stack.pop();
|
||||
} else {
|
||||
diff.diff_node(old_node, new_node);
|
||||
}
|
||||
}
|
||||
|
||||
// diff dynmaic nodes
|
||||
for (idx, (old_node, new_node)) in old
|
||||
.dynamic_context
|
||||
.nodes
|
||||
.iter()
|
||||
.zip(new.dynamic_context.nodes.iter())
|
||||
.enumerate()
|
||||
{
|
||||
if let Some(id) = template.get_dynamic_nodes_for_node_index(idx) {
|
||||
template.with_node(
|
||||
id,
|
||||
diff_dynamic_node,
|
||||
diff_dynamic_node,
|
||||
(self, old_node, new_node, root),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// diff dynamic text
|
||||
// text nodes could rely on multiple dynamic text parts, so we keep a record of which ones to rerender and send the diffs at the end
|
||||
let mut dirty_text_nodes = FxHashSet::default();
|
||||
for (idx, (old_text, new_text)) in old
|
||||
.dynamic_context
|
||||
.text_segments
|
||||
.iter()
|
||||
.zip(new.dynamic_context.text_segments.iter())
|
||||
.enumerate()
|
||||
{
|
||||
if old_text != new_text {
|
||||
for node_id in template.get_dynamic_nodes_for_text_index(idx) {
|
||||
dirty_text_nodes.insert(*node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
for node_id in dirty_text_nodes {
|
||||
fn diff_text<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
|
||||
node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
|
||||
ctx: (&mut DiffState<'b>, &TemplateContext<'b>),
|
||||
) where
|
||||
Attributes: AsRef<[TemplateAttribute<V>]>,
|
||||
V: TemplateValue,
|
||||
Children: AsRef<[TemplateNodeId]>,
|
||||
Listeners: AsRef<[usize]>,
|
||||
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
|
||||
Text: AsRef<str>,
|
||||
{
|
||||
let (diff, dynamic_context) = ctx;
|
||||
if let TemplateNodeType::Text(text) = &node.node_type {
|
||||
let text = dynamic_context.resolve_text(&text.segments.as_ref());
|
||||
diff.mutations
|
||||
.set_text(diff.current_scope_bump().alloc(text), node.id);
|
||||
} else {
|
||||
panic!("expected text node");
|
||||
}
|
||||
}
|
||||
template.with_node(node_id, diff_text, diff_text, (self, &new.dynamic_context));
|
||||
}
|
||||
|
||||
self.mutations.exit_template_ref();
|
||||
self.element_stack.pop();
|
||||
}
|
||||
|
||||
// Diff the given set of old and new children.
|
||||
//
|
||||
// The parent must be on top of the change list stack when this function is
|
||||
|
@ -970,6 +1238,16 @@ impl<'b> DiffState<'b> {
|
|||
}
|
||||
self.leave_scope();
|
||||
}
|
||||
|
||||
VNode::TemplateRef(t) => {
|
||||
let id = old
|
||||
.try_mounted_id()
|
||||
.unwrap_or_else(|| panic!("broke on {:?}", old));
|
||||
|
||||
self.mutations.replace_with(id, nodes_created as u32);
|
||||
self.remove_nodes(t.dynamic_context.nodes, true);
|
||||
self.scopes.collect_garbage(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -983,7 +1261,7 @@ impl<'b> DiffState<'b> {
|
|||
t.id.set(None);
|
||||
|
||||
if gen_muts {
|
||||
self.mutations.remove(id.as_u64());
|
||||
self.mutations.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -993,14 +1271,14 @@ impl<'b> DiffState<'b> {
|
|||
a.id.set(None);
|
||||
|
||||
if gen_muts {
|
||||
self.mutations.remove(id.as_u64());
|
||||
self.mutations.remove(id);
|
||||
}
|
||||
}
|
||||
VNode::Element(e) => {
|
||||
let id = e.id.get().unwrap();
|
||||
|
||||
if gen_muts {
|
||||
self.mutations.remove(id.as_u64());
|
||||
self.mutations.remove(id);
|
||||
}
|
||||
|
||||
self.scopes.collect_garbage(id);
|
||||
|
@ -1029,6 +1307,19 @@ impl<'b> DiffState<'b> {
|
|||
}
|
||||
self.leave_scope();
|
||||
}
|
||||
|
||||
VNode::TemplateRef(t) => {
|
||||
let id = t.id.get().unwrap();
|
||||
|
||||
if gen_muts {
|
||||
self.mutations.remove(id);
|
||||
}
|
||||
|
||||
self.scopes.collect_garbage(id);
|
||||
t.id.set(None);
|
||||
|
||||
self.remove_nodes(t.dynamic_context.nodes, gen_muts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1058,7 +1349,7 @@ impl<'b> DiffState<'b> {
|
|||
self.mutations.insert_before(first, created as u32);
|
||||
}
|
||||
|
||||
fn current_scope(&self) -> ScopeId {
|
||||
pub fn current_scope(&self) -> ScopeId {
|
||||
self.scope_stack.last().copied().expect("no current scope")
|
||||
}
|
||||
|
||||
|
@ -1082,6 +1373,7 @@ impl<'b> DiffState<'b> {
|
|||
let scope_id = el.scope.get().unwrap();
|
||||
search_node = Some(self.scopes.root_node(scope_id));
|
||||
}
|
||||
VNode::TemplateRef(t) => break t.id.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1098,6 +1390,7 @@ impl<'b> DiffState<'b> {
|
|||
let scope = el.scope.get().expect("element to have a scope assigned");
|
||||
search_node = Some(self.scopes.root_node(scope));
|
||||
}
|
||||
VNode::TemplateRef(t) => break t.id.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1105,7 +1398,7 @@ impl<'b> DiffState<'b> {
|
|||
// recursively push all the nodes of a tree onto the stack and return how many are there
|
||||
fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
|
||||
match node {
|
||||
VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) => {
|
||||
VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) | VNode::TemplateRef(_) => {
|
||||
self.mutations.push_root(node.mounted_id());
|
||||
1
|
||||
}
|
||||
|
@ -1125,4 +1418,18 @@ impl<'b> DiffState<'b> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn current_scope_bump(&self) -> &'b Bump {
|
||||
&self
|
||||
.scopes
|
||||
.get_scope(self.current_scope())
|
||||
.unwrap()
|
||||
.fin_frame()
|
||||
.bump
|
||||
}
|
||||
|
||||
pub fn register_template(&mut self, template: &Template, id: RendererTemplateId) {
|
||||
let bump = &self.scopes.template_bump;
|
||||
template.create(&mut self.mutations, bump, id);
|
||||
}
|
||||
}
|
||||
|
|
195
packages/core/src/dynamic_template_context.rs
Normal file
195
packages/core/src/dynamic_template_context.rs
Normal file
|
@ -0,0 +1,195 @@
|
|||
use std::{marker::PhantomData, ops::Deref};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
template::{TemplateNodeId, TextTemplateSegment},
|
||||
AttributeValue, Listener, VNode,
|
||||
};
|
||||
|
||||
/// A lazily initailized vector
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LazyStaticVec<T: 'static>(pub &'static Lazy<Vec<T>>);
|
||||
|
||||
impl<T: 'static> AsRef<[T]> for LazyStaticVec<T> {
|
||||
fn as_ref(&self) -> &[T] {
|
||||
let v: &Vec<_> = self.0.deref();
|
||||
v.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for LazyStaticVec<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self.0, other.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores what nodes depend on specific dynamic parts of the template to allow the diffing algorithm to jump to that part of the template instead of travering it
|
||||
/// This makes adding constant template nodes add no additional cost to diffing.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DynamicNodeMapping<
|
||||
Nodes,
|
||||
TextOuter,
|
||||
TextInner,
|
||||
AttributesOuter,
|
||||
AttributesInner,
|
||||
Volatile,
|
||||
Listeners,
|
||||
> where
|
||||
Nodes: AsRef<[Option<TemplateNodeId>]>,
|
||||
TextOuter: AsRef<[TextInner]>,
|
||||
TextInner: AsRef<[TemplateNodeId]>,
|
||||
AttributesOuter: AsRef<[AttributesInner]>,
|
||||
AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
|
||||
Volatile: AsRef<[(TemplateNodeId, usize)]>,
|
||||
Listeners: AsRef<[TemplateNodeId]>,
|
||||
{
|
||||
/// The node that depend on each node in the dynamic template
|
||||
pub nodes: Nodes,
|
||||
text_inner: PhantomData<TextInner>,
|
||||
/// The text nodes that depend on each text segment of the dynamic template
|
||||
pub text: TextOuter,
|
||||
/// The attributes along with the attribute index in the template that depend on each attribute of the dynamic template
|
||||
pub attributes: AttributesOuter,
|
||||
attributes_inner: PhantomData<AttributesInner>,
|
||||
/// The attributes that are marked as volatile in the template
|
||||
pub volatile_attributes: Volatile,
|
||||
/// The listeners that depend on each listener of the dynamic template
|
||||
pub nodes_with_listeners: Listeners,
|
||||
}
|
||||
|
||||
impl<Nodes, TextOuter, TextInner, AttributesOuter, AttributesInner, Volatile, Listeners>
|
||||
DynamicNodeMapping<
|
||||
Nodes,
|
||||
TextOuter,
|
||||
TextInner,
|
||||
AttributesOuter,
|
||||
AttributesInner,
|
||||
Volatile,
|
||||
Listeners,
|
||||
>
|
||||
where
|
||||
Nodes: AsRef<[Option<TemplateNodeId>]>,
|
||||
TextOuter: AsRef<[TextInner]>,
|
||||
TextInner: AsRef<[TemplateNodeId]>,
|
||||
AttributesOuter: AsRef<[AttributesInner]>,
|
||||
AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
|
||||
Volatile: AsRef<[(TemplateNodeId, usize)]>,
|
||||
Listeners: AsRef<[TemplateNodeId]>,
|
||||
{
|
||||
/// Creates a new dynamic node mapping
|
||||
pub const fn new(
|
||||
nodes: Nodes,
|
||||
text: TextOuter,
|
||||
attributes: AttributesOuter,
|
||||
volatile_attributes: Volatile,
|
||||
listeners: Listeners,
|
||||
) -> Self {
|
||||
DynamicNodeMapping {
|
||||
nodes,
|
||||
text_inner: PhantomData,
|
||||
text,
|
||||
attributes,
|
||||
attributes_inner: PhantomData,
|
||||
volatile_attributes,
|
||||
nodes_with_listeners: listeners,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn all_dynamic<'a>(&'a self) -> impl Iterator<Item = TemplateNodeId> + 'a {
|
||||
self.nodes
|
||||
.as_ref()
|
||||
.iter()
|
||||
.filter_map(|o| o.as_ref())
|
||||
.chain(
|
||||
self.text
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|ids| ids.as_ref().iter())
|
||||
.flatten(),
|
||||
)
|
||||
.copied()
|
||||
.chain(
|
||||
self.attributes
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|ids| ids.as_ref().iter())
|
||||
.flatten()
|
||||
.map(|dynamic| dynamic.0),
|
||||
)
|
||||
.chain(self.nodes_with_listeners.as_ref().iter().copied())
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic node mapping that is stack allocated
|
||||
pub type StaticDynamicNodeMapping = DynamicNodeMapping<
|
||||
&'static [Option<TemplateNodeId>],
|
||||
&'static [&'static [TemplateNodeId]],
|
||||
&'static [TemplateNodeId],
|
||||
&'static [&'static [(TemplateNodeId, usize)]],
|
||||
&'static [(TemplateNodeId, usize)],
|
||||
// volatile attribute information is available at compile time, but there is no way for the macro to generate it, so we initialize it lazily instead
|
||||
LazyStaticVec<(TemplateNodeId, usize)>,
|
||||
&'static [TemplateNodeId],
|
||||
>;
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
/// A dynamic node mapping that is heap allocated
|
||||
pub type OwnedDynamicNodeMapping = DynamicNodeMapping<
|
||||
Vec<Option<TemplateNodeId>>,
|
||||
Vec<Vec<TemplateNodeId>>,
|
||||
Vec<TemplateNodeId>,
|
||||
Vec<Vec<(TemplateNodeId, usize)>>,
|
||||
Vec<(TemplateNodeId, usize)>,
|
||||
Vec<(TemplateNodeId, usize)>,
|
||||
Vec<TemplateNodeId>,
|
||||
>;
|
||||
|
||||
/// The dynamic parts used to saturate a template durring runtime
|
||||
pub struct TemplateContext<'b> {
|
||||
/// The dynamic nodes
|
||||
pub nodes: &'b [VNode<'b>],
|
||||
/// The dynamic text
|
||||
pub text_segments: &'b [&'b str],
|
||||
/// The dynamic attributes
|
||||
pub attributes: &'b [AttributeValue<'b>],
|
||||
/// The dynamic attributes
|
||||
// The listeners must not change during the lifetime of the context, use a dynamic node if the listeners change
|
||||
pub listeners: &'b [Listener<'b>],
|
||||
/// A optional key for diffing
|
||||
pub key: Option<&'b str>,
|
||||
}
|
||||
|
||||
impl<'b> TemplateContext<'b> {
|
||||
/// Resolve text segments to a string
|
||||
pub fn resolve_text<TextSegments, Text>(&self, text: &TextSegments) -> String
|
||||
where
|
||||
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
|
||||
Text: AsRef<str>,
|
||||
{
|
||||
let mut result = String::new();
|
||||
for seg in text.as_ref() {
|
||||
match seg {
|
||||
TextTemplateSegment::Static(s) => result += s.as_ref(),
|
||||
TextTemplateSegment::Dynamic(idx) => result += self.text_segments[*idx],
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Resolve an attribute value
|
||||
pub fn resolve_attribute(&self, idx: usize) -> &'b AttributeValue<'b> {
|
||||
&self.attributes[idx]
|
||||
}
|
||||
|
||||
/// Resolve a listener
|
||||
pub fn resolve_listener(&self, idx: usize) -> &'b Listener<'b> {
|
||||
&self.listeners[idx]
|
||||
}
|
||||
|
||||
/// Resolve a node
|
||||
pub fn resolve_node(&self, idx: usize) -> &'b VNode<'b> {
|
||||
&self.nodes[idx]
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
//!
|
||||
//! This is all kinda WIP, but the bones are there.
|
||||
|
||||
use crate::{ElementId, ScopeId};
|
||||
use crate::{nodes::GlobalNodeId, ScopeId};
|
||||
use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc};
|
||||
|
||||
pub(crate) struct BubbleState {
|
||||
|
@ -58,7 +58,7 @@ pub struct UserEvent {
|
|||
pub priority: EventPriority,
|
||||
|
||||
/// The optional real node associated with the trigger
|
||||
pub element: Option<ElementId>,
|
||||
pub element: Option<GlobalNodeId>,
|
||||
|
||||
/// The event type IE "onclick" or "onmouseover"
|
||||
pub name: &'static str,
|
||||
|
|
|
@ -4,23 +4,27 @@
|
|||
|
||||
pub(crate) mod arbitrary_value;
|
||||
pub(crate) mod diff;
|
||||
pub(crate) mod dynamic_template_context;
|
||||
pub(crate) mod events;
|
||||
pub(crate) mod lazynodes;
|
||||
pub(crate) mod mutations;
|
||||
pub(crate) mod nodes;
|
||||
pub(crate) mod properties;
|
||||
pub(crate) mod scopes;
|
||||
pub(crate) mod template;
|
||||
pub(crate) mod util;
|
||||
pub(crate) mod virtual_dom;
|
||||
|
||||
pub(crate) mod innerlude {
|
||||
pub use crate::arbitrary_value::*;
|
||||
pub use crate::dynamic_template_context::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
pub use crate::properties::*;
|
||||
pub use crate::scopes::*;
|
||||
pub use crate::template::*;
|
||||
pub use crate::util::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
|
@ -63,20 +67,36 @@ pub(crate) mod innerlude {
|
|||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
AnyEvent, ArbitraryAttributeValue, Attribute, AttributeValue, Component, DioxusElement,
|
||||
DomEdit, Element, ElementId, ElementIdIterator, EventHandler, EventPriority, IntoVNode,
|
||||
LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope, ScopeId,
|
||||
ScopeState, TaskId, UiEvent, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder,
|
||||
VText, VirtualDom,
|
||||
AnyEvent, ArbitraryAttributeValue, Attribute, AttributeDiscription, AttributeValue,
|
||||
CodeLocation, Component, DioxusElement, DomEdit, DynamicNodeMapping, Element, ElementId,
|
||||
ElementIdIterator, EventHandler, EventPriority, GlobalNodeId, IntoVNode, LazyNodes, Listener,
|
||||
Mutations, NodeFactory, OwnedAttributeValue, Properties, RendererTemplateId, SchedulerMsg,
|
||||
Scope, ScopeId, ScopeState, StaticCodeLocation, StaticDynamicNodeMapping, StaticTemplateNode,
|
||||
StaticTemplateNodes, TaskId, Template, TemplateAttribute, TemplateAttributeValue,
|
||||
TemplateContext, TemplateElement, TemplateId, TemplateNode, TemplateNodeId, TemplateNodeType,
|
||||
TemplateValue, TextTemplate, TextTemplateSegment, UiEvent, UserEvent, VComponent, VElement,
|
||||
VFragment, VNode, VPlaceholder, VText, VirtualDom, JS_MAX_INT,
|
||||
};
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub use crate::innerlude::{
|
||||
OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedTemplateNode, OwnedTemplateNodes,
|
||||
SetTemplateMsg,
|
||||
};
|
||||
|
||||
/// The purpose of this module is to alleviate imports of many common types
|
||||
///
|
||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||
pub mod prelude {
|
||||
pub use crate::get_line_num;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub use crate::innerlude::OwnedTemplate;
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, Attributes, Component, DioxusElement, Element, EventHandler, Fragment,
|
||||
LazyNodes, NodeFactory, Properties, Scope, ScopeId, ScopeState, VNode, VirtualDom,
|
||||
fc_to_builder, AttributeDiscription, AttributeValue, Attributes, CodeLocation, Component,
|
||||
DioxusElement, Element, EventHandler, Fragment, LazyNodes, LazyStaticVec, NodeFactory,
|
||||
Properties, Scope, ScopeId, ScopeState, StaticAttributeValue, StaticCodeLocation,
|
||||
StaticDynamicNodeMapping, StaticTemplate, StaticTemplateNodes, Template, TemplateAttribute,
|
||||
TemplateAttributeValue, TemplateContext, TemplateElement, TemplateId, TemplateNode,
|
||||
TemplateNodeId, TemplateNodeType, TextTemplate, TextTemplateSegment, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -85,6 +105,7 @@ pub mod exports {
|
|||
//! Feel free to just add the dependencies in your own Crates.toml
|
||||
pub use bumpalo;
|
||||
pub use futures_channel;
|
||||
pub use once_cell;
|
||||
}
|
||||
|
||||
/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
|
||||
|
|
|
@ -128,6 +128,59 @@ pub enum DomEdit<'bump> {
|
|||
root: u64,
|
||||
},
|
||||
|
||||
/// Create a new purely-text node in a template
|
||||
CreateTextNodeTemplate {
|
||||
/// The ID the new node should have.
|
||||
root: u64,
|
||||
|
||||
/// The textcontent of the noden
|
||||
text: &'bump str,
|
||||
|
||||
/// If the id of the node must be kept in the refrences
|
||||
locally_static: bool,
|
||||
},
|
||||
|
||||
/// Create a new purely-element node in a template
|
||||
CreateElementTemplate {
|
||||
/// The ID the new node should have.
|
||||
root: u64,
|
||||
|
||||
/// The tagname of the node
|
||||
tag: &'bump str,
|
||||
|
||||
/// If the id of the node must be kept in the refrences
|
||||
locally_static: bool,
|
||||
|
||||
/// If any children of this node must be kept in the references
|
||||
fully_static: bool,
|
||||
},
|
||||
|
||||
/// Create a new purely-comment node with a given namespace in a template
|
||||
CreateElementNsTemplate {
|
||||
/// The ID the new node should have.
|
||||
root: u64,
|
||||
|
||||
/// The namespace of the node
|
||||
tag: &'bump str,
|
||||
|
||||
/// The namespace of the node (like `SVG`)
|
||||
ns: &'static str,
|
||||
|
||||
/// If the id of the node must be kept in the refrences
|
||||
locally_static: bool,
|
||||
|
||||
/// If any children of this node must be kept in the references
|
||||
fully_static: bool,
|
||||
},
|
||||
|
||||
/// Create a new placeholder node.
|
||||
/// In most implementations, this will either be a hidden div or a comment node. in a template
|
||||
/// Always both locally and fully static
|
||||
CreatePlaceholderTemplate {
|
||||
/// The ID the new node should have.
|
||||
root: u64,
|
||||
},
|
||||
|
||||
/// Create a new Event Listener.
|
||||
NewEventListener {
|
||||
/// The name of the event to listen for.
|
||||
|
@ -189,6 +242,38 @@ pub enum DomEdit<'bump> {
|
|||
|
||||
/// Manually pop a root node from the stack.
|
||||
PopRoot {},
|
||||
|
||||
/// Enter a TemplateRef tree
|
||||
EnterTemplateRef {
|
||||
/// The ID of the node to enter.
|
||||
root: u64,
|
||||
},
|
||||
|
||||
/// Exit a TemplateRef tree
|
||||
ExitTemplateRef {},
|
||||
|
||||
/// Create a refrence to a template node.
|
||||
CreateTemplateRef {
|
||||
/// The ID of the new template refrence.
|
||||
id: u64,
|
||||
|
||||
/// The ID of the template the node is refrencing.
|
||||
template_id: u64,
|
||||
},
|
||||
|
||||
/// Create a new templete.
|
||||
/// IMPORTANT: When adding nodes to a templete, id's will reset to zero, so they must be allocated on a different stack.
|
||||
/// It is recommended to use Cow<NativeNode>.
|
||||
CreateTemplate {
|
||||
/// The ID of the new template.
|
||||
id: u64,
|
||||
},
|
||||
|
||||
/// Finish a templete
|
||||
FinishTemplate {
|
||||
/// The number of root nodes in the template
|
||||
len: u32,
|
||||
},
|
||||
}
|
||||
|
||||
use fxhash::FxHashSet;
|
||||
|
@ -204,8 +289,8 @@ impl<'a> Mutations<'a> {
|
|||
}
|
||||
|
||||
// Navigation
|
||||
pub(crate) fn push_root(&mut self, root: ElementId) {
|
||||
let id = root.as_u64();
|
||||
pub(crate) fn push_root(&mut self, root: impl Into<u64>) {
|
||||
let id = root.into();
|
||||
self.edits.push(PushRoot { root: id });
|
||||
}
|
||||
|
||||
|
@ -214,18 +299,18 @@ impl<'a> Mutations<'a> {
|
|||
self.edits.push(PopRoot {});
|
||||
}
|
||||
|
||||
pub(crate) fn replace_with(&mut self, root: ElementId, m: u32) {
|
||||
let root = root.as_u64();
|
||||
pub(crate) fn replace_with(&mut self, root: impl Into<u64>, m: u32) {
|
||||
let root = root.into();
|
||||
self.edits.push(ReplaceWith { m, root });
|
||||
}
|
||||
|
||||
pub(crate) fn insert_after(&mut self, root: ElementId, n: u32) {
|
||||
let root = root.as_u64();
|
||||
pub(crate) fn insert_after(&mut self, root: impl Into<u64>, n: u32) {
|
||||
let root = root.into();
|
||||
self.edits.push(InsertAfter { n, root });
|
||||
}
|
||||
|
||||
pub(crate) fn insert_before(&mut self, root: ElementId, n: u32) {
|
||||
let root = root.as_u64();
|
||||
pub(crate) fn insert_before(&mut self, root: impl Into<u64>, n: u32) {
|
||||
let root = root.into();
|
||||
self.edits.push(InsertBefore { n, root });
|
||||
}
|
||||
|
||||
|
@ -234,13 +319,13 @@ impl<'a> Mutations<'a> {
|
|||
}
|
||||
|
||||
// Remove Nodes from the dom
|
||||
pub(crate) fn remove(&mut self, id: u64) {
|
||||
self.edits.push(Remove { root: id });
|
||||
pub(crate) fn remove(&mut self, id: impl Into<u64>) {
|
||||
self.edits.push(Remove { root: id.into() });
|
||||
}
|
||||
|
||||
// Create
|
||||
pub(crate) fn create_text_node(&mut self, text: &'a str, id: ElementId) {
|
||||
let id = id.as_u64();
|
||||
pub(crate) fn create_text_node(&mut self, text: &'a str, id: impl Into<u64>) {
|
||||
let id = id.into();
|
||||
self.edits.push(CreateTextNode { text, root: id });
|
||||
}
|
||||
|
||||
|
@ -248,20 +333,68 @@ impl<'a> Mutations<'a> {
|
|||
&mut self,
|
||||
tag: &'static str,
|
||||
ns: Option<&'static str>,
|
||||
id: ElementId,
|
||||
id: impl Into<u64>,
|
||||
) {
|
||||
let id = id.as_u64();
|
||||
let id = id.into();
|
||||
match ns {
|
||||
Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }),
|
||||
None => self.edits.push(CreateElement { root: id, tag }),
|
||||
}
|
||||
}
|
||||
|
||||
// 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: ElementId) {
|
||||
let id = id.as_u64();
|
||||
pub(crate) fn create_placeholder(&mut self, id: impl Into<u64>) {
|
||||
let id = id.into();
|
||||
self.edits.push(CreatePlaceholder { root: id });
|
||||
}
|
||||
|
||||
// Create
|
||||
pub(crate) fn create_text_node_template(
|
||||
&mut self,
|
||||
text: &'a str,
|
||||
id: impl Into<u64>,
|
||||
locally_static: bool,
|
||||
) {
|
||||
let id = id.into();
|
||||
self.edits.push(CreateTextNodeTemplate {
|
||||
text,
|
||||
root: id,
|
||||
locally_static,
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn create_element_template(
|
||||
&mut self,
|
||||
tag: &'static str,
|
||||
ns: Option<&'static str>,
|
||||
id: impl Into<u64>,
|
||||
locally_static: bool,
|
||||
fully_static: bool,
|
||||
) {
|
||||
let id = id.into();
|
||||
match ns {
|
||||
Some(ns) => self.edits.push(CreateElementNsTemplate {
|
||||
root: id,
|
||||
ns,
|
||||
tag,
|
||||
locally_static,
|
||||
fully_static,
|
||||
}),
|
||||
None => self.edits.push(CreateElementTemplate {
|
||||
root: id,
|
||||
tag,
|
||||
locally_static,
|
||||
fully_static,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
|
||||
pub(crate) fn create_placeholder_template(&mut self, id: impl Into<u64>) {
|
||||
let id = id.into();
|
||||
self.edits.push(CreatePlaceholderTemplate { root: id });
|
||||
}
|
||||
|
||||
// events
|
||||
pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
|
||||
let Listener {
|
||||
|
@ -270,7 +403,13 @@ impl<'a> Mutations<'a> {
|
|||
..
|
||||
} = listener;
|
||||
|
||||
let element_id = mounted_node.get().unwrap().as_u64();
|
||||
let element_id = match mounted_node.get().unwrap() {
|
||||
GlobalNodeId::TemplateId {
|
||||
template_ref_id: _,
|
||||
template_node_id,
|
||||
} => template_node_id.into(),
|
||||
GlobalNodeId::VNodeId(id) => id.into(),
|
||||
};
|
||||
|
||||
self.edits.push(NewEventListener {
|
||||
scope,
|
||||
|
@ -278,39 +417,41 @@ impl<'a> Mutations<'a> {
|
|||
root: element_id,
|
||||
});
|
||||
}
|
||||
pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: u64) {
|
||||
self.edits.push(RemoveEventListener { event, root });
|
||||
|
||||
pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: impl Into<u64>) {
|
||||
self.edits.push(RemoveEventListener {
|
||||
event,
|
||||
root: root.into(),
|
||||
});
|
||||
}
|
||||
|
||||
// modify
|
||||
pub(crate) fn set_text(&mut self, text: &'a str, root: u64) {
|
||||
pub(crate) fn set_text(&mut self, text: &'a str, root: impl Into<u64>) {
|
||||
let root = root.into();
|
||||
self.edits.push(SetText { text, root });
|
||||
}
|
||||
|
||||
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) {
|
||||
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: impl Into<u64>) {
|
||||
let root = root.into();
|
||||
let Attribute {
|
||||
name,
|
||||
value,
|
||||
namespace,
|
||||
..
|
||||
value, attribute, ..
|
||||
} = attribute;
|
||||
|
||||
self.edits.push(SetAttribute {
|
||||
field: name,
|
||||
field: attribute.name,
|
||||
value: value.clone(),
|
||||
ns: *namespace,
|
||||
ns: attribute.namespace,
|
||||
root,
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: u64) {
|
||||
let Attribute {
|
||||
name, namespace, ..
|
||||
} = attribute;
|
||||
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: impl Into<u64>) {
|
||||
let root = root.into();
|
||||
let Attribute { attribute, .. } = attribute;
|
||||
|
||||
self.edits.push(RemoveAttribute {
|
||||
name,
|
||||
ns: *namespace,
|
||||
name: attribute.name,
|
||||
ns: attribute.namespace,
|
||||
root,
|
||||
});
|
||||
}
|
||||
|
@ -318,6 +459,33 @@ impl<'a> Mutations<'a> {
|
|||
pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
|
||||
self.dirty_scopes.insert(scope);
|
||||
}
|
||||
|
||||
pub(crate) fn create_templete(&mut self, id: impl Into<u64>) {
|
||||
self.edits.push(CreateTemplate { id: id.into() });
|
||||
}
|
||||
|
||||
pub(crate) fn finish_templete(&mut self, len: u32) {
|
||||
self.edits.push(FinishTemplate { len });
|
||||
}
|
||||
|
||||
pub(crate) fn create_template_ref(&mut self, id: impl Into<u64>, template_id: u64) {
|
||||
self.edits.push(CreateTemplateRef {
|
||||
id: id.into(),
|
||||
template_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn enter_template_ref(&mut self, id: impl Into<u64>) {
|
||||
self.edits.push(EnterTemplateRef { root: id.into() });
|
||||
}
|
||||
|
||||
pub(crate) fn exit_template_ref(&mut self) {
|
||||
if let Some(&DomEdit::EnterTemplateRef { .. }) = self.edits.last() {
|
||||
self.edits.pop();
|
||||
} else {
|
||||
self.edits.push(ExitTemplateRef {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// refs are only assigned once
|
||||
|
|
|
@ -3,16 +3,70 @@
|
|||
//! 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 quick.
|
||||
use crate::{
|
||||
innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
|
||||
dynamic_template_context::TemplateContext,
|
||||
innerlude::{
|
||||
AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState, Template,
|
||||
TemplateId,
|
||||
},
|
||||
lazynodes::LazyNodes,
|
||||
template::{TemplateNodeId, VTemplateRef},
|
||||
AnyEvent, Component,
|
||||
};
|
||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
fmt::{Arguments, Debug, Formatter},
|
||||
num::ParseIntError,
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// The ID of a node in the vdom that is either standalone or in a template
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serialize", serde(untagged))]
|
||||
pub enum GlobalNodeId {
|
||||
/// The ID of a node and the template that contains it
|
||||
TemplateId {
|
||||
/// The template that contains the node
|
||||
template_ref_id: ElementId,
|
||||
/// The ID of the node in the template
|
||||
template_node_id: TemplateNodeId,
|
||||
},
|
||||
/// The ID of a regular node
|
||||
VNodeId(ElementId),
|
||||
}
|
||||
|
||||
impl Into<GlobalNodeId> for ElementId {
|
||||
fn into(self) -> GlobalNodeId {
|
||||
GlobalNodeId::VNodeId(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<ElementId> for GlobalNodeId {
|
||||
fn eq(&self, other: &ElementId) -> bool {
|
||||
match self {
|
||||
GlobalNodeId::TemplateId { .. } => false,
|
||||
GlobalNodeId::VNodeId(id) => id == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GlobalNodeId {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Some((tmpl_id, node_id)) = s.split_once(',') {
|
||||
Ok(GlobalNodeId::TemplateId {
|
||||
template_ref_id: ElementId(tmpl_id.parse()?),
|
||||
template_node_id: TemplateNodeId(node_id.parse()?),
|
||||
})
|
||||
} else {
|
||||
Ok(GlobalNodeId::VNodeId(ElementId(s.parse()?)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
|
||||
///
|
||||
/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
|
||||
|
@ -112,6 +166,9 @@ pub enum VNode<'src> {
|
|||
/// }
|
||||
/// ```
|
||||
Placeholder(&'src VPlaceholder),
|
||||
|
||||
/// Templetes ase generated by the rsx macro to eleminate diffing static nodes.
|
||||
TemplateRef(&'src VTemplateRef<'src>),
|
||||
}
|
||||
|
||||
impl<'src> VNode<'src> {
|
||||
|
@ -123,6 +180,7 @@ impl<'src> VNode<'src> {
|
|||
VNode::Fragment(f) => f.key,
|
||||
VNode::Text(_t) => None,
|
||||
VNode::Placeholder(_f) => None,
|
||||
VNode::TemplateRef(t) => t.dynamic_context.key,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,6 +201,7 @@ impl<'src> VNode<'src> {
|
|||
VNode::Placeholder(el) => el.id.get(),
|
||||
VNode::Fragment(_) => None,
|
||||
VNode::Component(_) => None,
|
||||
VNode::TemplateRef(t) => t.id.get(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +213,7 @@ impl<'src> VNode<'src> {
|
|||
VNode::Component(c) => VNode::Component(c),
|
||||
VNode::Placeholder(a) => VNode::Placeholder(a),
|
||||
VNode::Fragment(f) => VNode::Fragment(f),
|
||||
VNode::TemplateRef(t) => VNode::TemplateRef(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +249,11 @@ impl Debug for VNode<'_> {
|
|||
.field("key", &comp.key)
|
||||
.field("scope", &comp.scope)
|
||||
.finish(),
|
||||
VNode::TemplateRef(temp) => s
|
||||
.debug_struct("VNode::TemplateRef")
|
||||
.field("template_id", &temp.template_id)
|
||||
.field("id", &temp.id)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,6 +263,8 @@ impl Debug for VNode<'_> {
|
|||
/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||
/// unmounted, then the `ElementId` will be reused for a new component.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serialize", serde(transparent))]
|
||||
pub struct ElementId(pub usize);
|
||||
impl std::fmt::Display for ElementId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
@ -206,13 +273,19 @@ impl std::fmt::Display for ElementId {
|
|||
}
|
||||
|
||||
impl ElementId {
|
||||
/// Convertt the ElementId to a `u64`.
|
||||
pub fn as_u64(self) -> u64 {
|
||||
/// Convert the ElementId to a `u64`.
|
||||
pub fn as_u64(&self) -> u64 {
|
||||
(*self).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u64> for ElementId {
|
||||
fn into(self) -> u64 {
|
||||
self.0 as u64
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_cell() -> Cell<Option<ElementId>> {
|
||||
fn empty_cell<T>() -> Cell<Option<T>> {
|
||||
Cell::new(None)
|
||||
}
|
||||
|
||||
|
@ -267,7 +340,7 @@ pub struct VElement<'a> {
|
|||
/// The parent of the Element (if any).
|
||||
///
|
||||
/// Used when bubbling events
|
||||
pub parent: Cell<Option<ElementId>>,
|
||||
pub parent: Cell<Option<GlobalNodeId>>,
|
||||
|
||||
/// The Listeners of the VElement.
|
||||
pub listeners: &'a [Listener<'a>],
|
||||
|
@ -330,30 +403,52 @@ pub trait DioxusElement {
|
|||
}
|
||||
}
|
||||
|
||||
type StaticStr = &'static str;
|
||||
|
||||
/// A discription of the attribute
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
|
||||
derive(serde::Deserialize)
|
||||
)]
|
||||
pub struct AttributeDiscription {
|
||||
/// The name of the attribute.
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
|
||||
serde(deserialize_with = "crate::util::deserialize_static_leaky")
|
||||
)]
|
||||
pub name: StaticStr,
|
||||
|
||||
/// The namespace of the attribute.
|
||||
///
|
||||
/// Doesn't exist in the html spec.
|
||||
/// Used in Dioxus to denote "style" tags and other attribute groups.
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", any(feature = "hot-reload", debug_assertions)),
|
||||
serde(deserialize_with = "crate::util::deserialize_static_leaky_ns")
|
||||
)]
|
||||
pub namespace: Option<StaticStr>,
|
||||
|
||||
/// An indication of we should always try and set the attribute.
|
||||
/// Used in controlled components to ensure changes are propagated.
|
||||
pub volatile: bool,
|
||||
}
|
||||
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or
|
||||
/// `href="https://example.com"`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Attribute<'a> {
|
||||
/// The name of the attribute.
|
||||
pub name: &'static str,
|
||||
|
||||
/// The value of the attribute.
|
||||
pub value: AttributeValue<'a>,
|
||||
/// The discription of the attribute.
|
||||
pub attribute: AttributeDiscription,
|
||||
|
||||
/// An indication if this attribute can be ignored during diffing
|
||||
///
|
||||
/// Usually only when there are no strings to be formatted (so the value is &'static str)
|
||||
pub is_static: bool,
|
||||
|
||||
/// An indication of we should always try and set the attribute.
|
||||
/// Used in controlled components to ensure changes are propagated.
|
||||
pub is_volatile: bool,
|
||||
|
||||
/// The namespace of the attribute.
|
||||
///
|
||||
/// Doesn't exist in the html spec.
|
||||
/// Used in Dioxus to denote "style" tags and other attribute groups.
|
||||
pub namespace: Option<&'static str>,
|
||||
/// The value of the attribute.
|
||||
pub value: AttributeValue<'a>,
|
||||
}
|
||||
|
||||
/// An event listener.
|
||||
|
@ -361,7 +456,7 @@ pub struct Attribute<'a> {
|
|||
pub struct Listener<'bump> {
|
||||
/// The ID of the node that this listener is mounted to
|
||||
/// Used to generate the event listener's ID on the DOM
|
||||
pub mounted_node: Cell<Option<ElementId>>,
|
||||
pub mounted_node: Cell<Option<GlobalNodeId>>,
|
||||
|
||||
/// The type of event to listen for.
|
||||
///
|
||||
|
@ -599,6 +694,20 @@ impl<'a> NodeFactory<'a> {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Create a new [`Attribute`] from a attribute discrimination and a value
|
||||
pub fn attr_disciption(
|
||||
&self,
|
||||
discription: AttributeDiscription,
|
||||
val: Arguments,
|
||||
) -> Attribute<'a> {
|
||||
let (value, is_static) = self.raw_text(val);
|
||||
Attribute {
|
||||
attribute: discription,
|
||||
is_static,
|
||||
value: AttributeValue::Text(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Attribute`]
|
||||
pub fn attr(
|
||||
&self,
|
||||
|
@ -609,11 +718,13 @@ impl<'a> NodeFactory<'a> {
|
|||
) -> Attribute<'a> {
|
||||
let (value, is_static) = self.raw_text(val);
|
||||
Attribute {
|
||||
name,
|
||||
value: AttributeValue::Text(value),
|
||||
attribute: AttributeDiscription {
|
||||
name,
|
||||
namespace,
|
||||
volatile: is_volatile,
|
||||
},
|
||||
is_static,
|
||||
namespace,
|
||||
is_volatile,
|
||||
value: AttributeValue::Text(value),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -627,11 +738,13 @@ impl<'a> NodeFactory<'a> {
|
|||
is_static: bool,
|
||||
) -> Attribute<'a> {
|
||||
Attribute {
|
||||
name,
|
||||
value,
|
||||
attribute: AttributeDiscription {
|
||||
name,
|
||||
namespace,
|
||||
volatile: is_volatile,
|
||||
},
|
||||
is_static,
|
||||
namespace,
|
||||
is_volatile,
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -743,6 +856,27 @@ impl<'a> NodeFactory<'a> {
|
|||
let callback = RefCell::new(Some(caller));
|
||||
EventHandler { callback }
|
||||
}
|
||||
|
||||
/// Create a refrence to a template
|
||||
pub fn template_ref(
|
||||
&self,
|
||||
id: TemplateId,
|
||||
template: Template,
|
||||
dynamic_context: TemplateContext<'a>,
|
||||
) -> VNode<'a> {
|
||||
let borrow_ref = self.scope.templates.borrow();
|
||||
// We only create the template if it doesn't already exist to allow for hot reloading
|
||||
if !borrow_ref.contains_key(&id) {
|
||||
drop(borrow_ref);
|
||||
let mut borrow_mut = self.scope.templates.borrow_mut();
|
||||
borrow_mut.insert(id.clone(), Rc::new(RefCell::new(template)));
|
||||
}
|
||||
VNode::TemplateRef(self.bump.alloc(VTemplateRef {
|
||||
id: empty_cell(),
|
||||
dynamic_context,
|
||||
template_id: id,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for NodeFactory<'_> {
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
use crate::{innerlude::*, unsafe_utils::extend_vnode};
|
||||
use crate::{
|
||||
dynamic_template_context::TemplateContext,
|
||||
innerlude::*,
|
||||
template::{
|
||||
TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId, TemplateNodeType,
|
||||
TemplateValue, TextTemplateSegment,
|
||||
},
|
||||
unsafe_utils::extend_vnode,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
use fxhash::FxHashMap;
|
||||
use slab::Slab;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::Borrow,
|
||||
cell::{Cell, RefCell},
|
||||
cell::{Cell, Ref, RefCell},
|
||||
collections::{HashMap, HashSet},
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
|
@ -35,6 +42,10 @@ pub(crate) struct ScopeArena {
|
|||
pub free_scopes: RefCell<Vec<*mut ScopeState>>,
|
||||
pub nodes: RefCell<Slab<*const VNode<'static>>>,
|
||||
pub tasks: Rc<TaskQueue>,
|
||||
pub template_resolver: RefCell<TemplateResolver>,
|
||||
pub templates: Rc<RefCell<FxHashMap<TemplateId, Rc<RefCell<Template>>>>>,
|
||||
// this is used to store intermidiate artifacts of creating templates, so that the lifetime aligns with Mutations<'bump>.
|
||||
pub template_bump: Bump,
|
||||
}
|
||||
|
||||
impl ScopeArena {
|
||||
|
@ -74,6 +85,9 @@ impl ScopeArena {
|
|||
gen: Cell::new(0),
|
||||
sender,
|
||||
}),
|
||||
template_resolver: RefCell::new(TemplateResolver::default()),
|
||||
templates: Rc::new(RefCell::new(FxHashMap::default())),
|
||||
template_bump: Bump::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,8 +107,7 @@ impl ScopeArena {
|
|||
fc_ptr: ComponentPtr,
|
||||
vcomp: Box<dyn AnyProps>,
|
||||
parent_scope: Option<ScopeId>,
|
||||
container: ElementId,
|
||||
subtree: u32,
|
||||
container: GlobalNodeId,
|
||||
) -> ScopeId {
|
||||
// Increment the ScopeId system. ScopeIDs are never reused
|
||||
let new_scope_id = ScopeId(self.scope_gen.get());
|
||||
|
@ -124,7 +137,6 @@ impl ScopeArena {
|
|||
scope.height = height;
|
||||
scope.fnptr = fc_ptr;
|
||||
scope.props.get_mut().replace(vcomp);
|
||||
scope.subtree.set(subtree);
|
||||
scope.frames[0].reset();
|
||||
scope.frames[1].reset();
|
||||
scope.shared_contexts.get_mut().clear();
|
||||
|
@ -155,10 +167,6 @@ impl ScopeArena {
|
|||
props: RefCell::new(Some(vcomp)),
|
||||
frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
|
||||
|
||||
// todo: subtrees
|
||||
subtree: Cell::new(0),
|
||||
is_subtree_root: Cell::default(),
|
||||
|
||||
generation: 0.into(),
|
||||
|
||||
tasks: self.tasks.clone(),
|
||||
|
@ -172,6 +180,8 @@ impl ScopeArena {
|
|||
hook_arena: Bump::new(),
|
||||
hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
|
||||
hook_idx: Cell::default(),
|
||||
|
||||
templates: self.templates.clone(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -314,44 +324,140 @@ impl ScopeArena {
|
|||
scope.cycle_frame();
|
||||
}
|
||||
|
||||
pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: ElementId) {
|
||||
pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: GlobalNodeId) {
|
||||
let nodes = self.nodes.borrow();
|
||||
let mut cur_el = Some(element);
|
||||
|
||||
let state = Rc::new(BubbleState::new());
|
||||
|
||||
while let Some(id) = cur_el.take() {
|
||||
if let Some(el) = nodes.get(id.0) {
|
||||
let real_el = unsafe { &**el };
|
||||
|
||||
if let VNode::Element(real_el) = real_el {
|
||||
for listener in real_el.listeners.borrow().iter() {
|
||||
if listener.event == event.name {
|
||||
if state.canceled.get() {
|
||||
// stop bubbling if canceled
|
||||
return;
|
||||
}
|
||||
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
if let Some(cb) = cb.as_mut() {
|
||||
// todo: arcs are pretty heavy to clone
|
||||
// we really want to convert arc to rc
|
||||
// unfortunately, the SchedulerMsg must be send/sync to be sent across threads
|
||||
// we could convert arc to rc internally or something
|
||||
(cb)(AnyEvent {
|
||||
bubble_state: state.clone(),
|
||||
data: event.data.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if !event.bubbles {
|
||||
return;
|
||||
}
|
||||
if state.canceled.get() {
|
||||
// stop bubbling if canceled
|
||||
return;
|
||||
}
|
||||
match id {
|
||||
GlobalNodeId::TemplateId {
|
||||
template_ref_id,
|
||||
template_node_id,
|
||||
} => {
|
||||
log::trace!(
|
||||
"looking for listener in {:?} in node {:?}",
|
||||
template_ref_id,
|
||||
template_node_id
|
||||
);
|
||||
if let Some(template) = nodes.get(template_ref_id.0) {
|
||||
let template = unsafe { &**template };
|
||||
if let VNode::TemplateRef(template_ref) = template {
|
||||
let templates = self.templates.borrow();
|
||||
let template = templates.get(&template_ref.template_id).unwrap();
|
||||
cur_el = template.borrow().with_node(
|
||||
template_node_id,
|
||||
bubble_template,
|
||||
bubble_template,
|
||||
(
|
||||
&nodes,
|
||||
&template_ref.dynamic_context,
|
||||
&event,
|
||||
&state,
|
||||
template_ref_id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
cur_el = real_el.parent.get();
|
||||
}
|
||||
GlobalNodeId::VNodeId(id) => {
|
||||
if let Some(el) = nodes.get(id.0) {
|
||||
let real_el = unsafe { &**el };
|
||||
log::trace!("looking for listener on {:?}", real_el);
|
||||
|
||||
if let VNode::Element(real_el) = real_el {
|
||||
for listener in real_el.listeners.iter() {
|
||||
if listener.event == event.name {
|
||||
log::trace!("calling listener {:?}", listener.event);
|
||||
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
if let Some(cb) = cb.as_mut() {
|
||||
// todo: arcs are pretty heavy to clone
|
||||
// we really want to convert arc to rc
|
||||
// unfortunately, the SchedulerMsg must be send/sync to be sent across threads
|
||||
// we could convert arc to rc internally or something
|
||||
(cb)(AnyEvent {
|
||||
bubble_state: state.clone(),
|
||||
data: event.data.clone(),
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cur_el = real_el.parent.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !event.bubbles {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text>(
|
||||
node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
|
||||
ctx: (
|
||||
&Ref<Slab<*const VNode>>,
|
||||
&TemplateContext<'b>,
|
||||
&UserEvent,
|
||||
&Rc<BubbleState>,
|
||||
ElementId,
|
||||
),
|
||||
) -> Option<GlobalNodeId>
|
||||
where
|
||||
Attributes: AsRef<[TemplateAttribute<V>]>,
|
||||
V: TemplateValue,
|
||||
Children: AsRef<[TemplateNodeId]>,
|
||||
Listeners: AsRef<[usize]>,
|
||||
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
|
||||
Text: AsRef<str>,
|
||||
{
|
||||
let (vnodes, dynamic_context, event, state, template_ref_id) = ctx;
|
||||
if let TemplateNodeType::Element(el) = &node.node_type {
|
||||
let TemplateElement { listeners, .. } = el;
|
||||
for listener_idx in listeners.as_ref() {
|
||||
let listener = dynamic_context.resolve_listener(*listener_idx);
|
||||
if listener.event == event.name {
|
||||
log::trace!("calling listener {:?}", listener.event);
|
||||
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
if let Some(cb) = cb.as_mut() {
|
||||
// todo: arcs are pretty heavy to clone
|
||||
// we really want to convert arc to rc
|
||||
// unfortunately, the SchedulerMsg must be send/sync to be sent across threads
|
||||
// we could convert arc to rc internally or something
|
||||
(cb)(AnyEvent {
|
||||
bubble_state: state.clone(),
|
||||
data: event.data.clone(),
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(id) = el.parent {
|
||||
Some(GlobalNodeId::TemplateId {
|
||||
template_ref_id,
|
||||
template_node_id: id,
|
||||
})
|
||||
} else {
|
||||
vnodes.get(template_ref_id.0).and_then(|el| {
|
||||
let real_el = unsafe { &**el };
|
||||
if let VNode::Element(real_el) = real_el {
|
||||
real_el.parent.get()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -463,14 +569,10 @@ pub struct TaskId {
|
|||
/// use case they might have.
|
||||
pub struct ScopeState {
|
||||
pub(crate) parent_scope: Option<*mut ScopeState>,
|
||||
pub(crate) container: ElementId,
|
||||
pub(crate) container: GlobalNodeId,
|
||||
pub(crate) our_arena_idx: ScopeId,
|
||||
pub(crate) height: u32,
|
||||
pub(crate) fnptr: ComponentPtr,
|
||||
|
||||
// todo: subtrees
|
||||
pub(crate) is_subtree_root: Cell<bool>,
|
||||
pub(crate) subtree: Cell<u32>,
|
||||
pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
|
||||
|
||||
// nodes, items
|
||||
|
@ -486,6 +588,9 @@ pub struct ScopeState {
|
|||
// shared state -> todo: move this out of scopestate
|
||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
||||
pub(crate) tasks: Rc<TaskQueue>,
|
||||
|
||||
// templates
|
||||
pub(crate) templates: Rc<RefCell<FxHashMap<TemplateId, Rc<RefCell<Template>>>>>,
|
||||
}
|
||||
|
||||
pub struct SelfReferentialItems<'a> {
|
||||
|
@ -495,52 +600,6 @@ pub struct SelfReferentialItems<'a> {
|
|||
|
||||
// Public methods exposed to libraries and components
|
||||
impl ScopeState {
|
||||
/// Get the subtree ID that this scope belongs to.
|
||||
///
|
||||
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
||||
/// the mutations to the correct window/portal/subtree.
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
||||
/// dom.rebuild();
|
||||
///
|
||||
/// let base = dom.base_scope();
|
||||
///
|
||||
/// assert_eq!(base.subtree(), 0);
|
||||
/// ```
|
||||
///
|
||||
/// todo: enable
|
||||
pub(crate) fn _subtree(&self) -> u32 {
|
||||
self.subtree.get()
|
||||
}
|
||||
|
||||
/// Create a new subtree with this scope as the root of the subtree.
|
||||
///
|
||||
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
||||
/// the mutations to the correct window/portal/subtree.
|
||||
///
|
||||
/// This method
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// render!(div { "Subtree {id}"})
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// todo: enable subtree
|
||||
pub(crate) fn _create_subtree(&self) -> Option<u32> {
|
||||
if self.is_subtree_root.get() {
|
||||
None
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the height of this Scope - IE the number of scopes above it.
|
||||
///
|
||||
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
|
||||
|
@ -903,8 +962,6 @@ impl ScopeState {
|
|||
self.hook_idx.set(0);
|
||||
self.parent_scope = None;
|
||||
self.generation.set(0);
|
||||
self.is_subtree_root.set(false);
|
||||
self.subtree.set(0);
|
||||
|
||||
// next: shared context data
|
||||
self.shared_contexts.get_mut().clear();
|
||||
|
|
1051
packages/core/src/template.rs
Normal file
1051
packages/core/src/template.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -34,7 +34,10 @@ impl<'a> Iterator for ElementIdIterator<'a> {
|
|||
if let Some((count, node)) = self.stack.last_mut() {
|
||||
match node {
|
||||
// We can only exit our looping when we get "real" nodes
|
||||
VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
|
||||
VNode::Element(_)
|
||||
| VNode::Text(_)
|
||||
| VNode::Placeholder(_)
|
||||
| VNode::TemplateRef(_) => {
|
||||
// We've recursed INTO an element/text
|
||||
// We need to recurse *out* of it and move forward to the next
|
||||
// println!("Found element! Returning it!");
|
||||
|
@ -81,3 +84,44 @@ impl<'a> Iterator for ElementIdIterator<'a> {
|
|||
returned_node
|
||||
}
|
||||
}
|
||||
|
||||
/// This intentionally leaks once per element name to allow more flexability when hot reloding templetes
|
||||
#[cfg(all(any(feature = "hot-reload", debug_assertions), feature = "serde"))]
|
||||
mod leaky {
|
||||
use std::sync::Mutex;
|
||||
|
||||
use fxhash::FxHashSet;
|
||||
use once_cell::sync::Lazy;
|
||||
static STATIC_CACHE: Lazy<Mutex<FxHashSet<&'static str>>> =
|
||||
Lazy::new(|| Mutex::new(FxHashSet::default()));
|
||||
|
||||
pub fn deserialize_static_leaky<'de, D>(d: D) -> Result<&'static str, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
let s = <&str>::deserialize(d)?;
|
||||
Ok(if let Some(stat) = STATIC_CACHE.lock().unwrap().get(s) {
|
||||
*stat
|
||||
} else {
|
||||
Box::leak(s.into())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize_static_leaky_ns<'de, D>(d: D) -> Result<Option<&'static str>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
Ok(<Option<&str>>::deserialize(d)?.map(|s| {
|
||||
if let Some(stat) = STATIC_CACHE.lock().unwrap().get(s) {
|
||||
*stat
|
||||
} else {
|
||||
Box::leak(s.into())
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(any(feature = "hot-reload", debug_assertions), feature = "serde"))]
|
||||
pub use leaky::*;
|
||||
|
|
|
@ -128,6 +128,10 @@ pub enum SchedulerMsg {
|
|||
/// Mark all components as dirty and update them
|
||||
DirtyAll,
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
/// Mark a template as dirty, used for hot reloading
|
||||
SetTemplate(Box<SetTemplateMsg>),
|
||||
|
||||
/// New tasks from components that should be polled when the next poll is ready
|
||||
NewTask(ScopeId),
|
||||
}
|
||||
|
@ -226,8 +230,7 @@ impl VirtualDom {
|
|||
render_fn: root,
|
||||
}),
|
||||
None,
|
||||
ElementId(0),
|
||||
0,
|
||||
GlobalNodeId::VNodeId(ElementId(0)),
|
||||
);
|
||||
|
||||
Self {
|
||||
|
@ -398,6 +401,25 @@ impl VirtualDom {
|
|||
self.dirty_scopes.insert(*id);
|
||||
}
|
||||
}
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
SchedulerMsg::SetTemplate(msg) => {
|
||||
let SetTemplateMsg(id, tmpl) = *msg;
|
||||
if self
|
||||
.scopes
|
||||
.templates
|
||||
.borrow_mut()
|
||||
.insert(
|
||||
id.clone(),
|
||||
std::rc::Rc::new(std::cell::RefCell::new(Template::Owned(tmpl))),
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
self.scopes.template_resolver.borrow_mut().mark_dirty(&id)
|
||||
}
|
||||
|
||||
// mark any scopes that used the template as dirty
|
||||
self.process_message(SchedulerMsg::DirtyAll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,6 +471,7 @@ impl VirtualDom {
|
|||
#[allow(unused)]
|
||||
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
||||
let mut committed_mutations = vec![];
|
||||
self.scopes.template_bump.reset();
|
||||
|
||||
while !self.dirty_scopes.is_empty() {
|
||||
let scopes = &self.scopes;
|
||||
|
@ -524,11 +547,13 @@ impl VirtualDom {
|
|||
/// ```
|
||||
pub fn rebuild(&mut self) -> Mutations {
|
||||
let scope_id = ScopeId(0);
|
||||
let mut diff_state = DiffState::new(&self.scopes);
|
||||
|
||||
let mut diff_state = DiffState::new(&self.scopes);
|
||||
self.scopes.run_scope(scope_id);
|
||||
|
||||
diff_state.element_stack.push(ElementId(0));
|
||||
diff_state
|
||||
.element_stack
|
||||
.push(GlobalNodeId::VNodeId(ElementId(0)));
|
||||
diff_state.scope_stack.push(scope_id);
|
||||
|
||||
let node = self.scopes.fin_head(scope_id);
|
||||
|
@ -625,7 +650,9 @@ impl VirtualDom {
|
|||
/// ```
|
||||
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
||||
let mut machine = DiffState::new(&self.scopes);
|
||||
machine.element_stack.push(ElementId(0));
|
||||
machine
|
||||
.element_stack
|
||||
.push(GlobalNodeId::VNodeId(ElementId(0)));
|
||||
machine.scope_stack.push(ScopeId(0));
|
||||
machine.diff_node(old, new);
|
||||
|
||||
|
@ -648,7 +675,9 @@ impl VirtualDom {
|
|||
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
|
||||
let mut machine = DiffState::new(&self.scopes);
|
||||
machine.scope_stack.push(ScopeId(0));
|
||||
machine.element_stack.push(ElementId(0));
|
||||
machine
|
||||
.element_stack
|
||||
.push(GlobalNodeId::VNodeId(ElementId(0)));
|
||||
let node = self.render_vnodes(nodes);
|
||||
let created = machine.create_node(node);
|
||||
machine.mutations.append_children(created as u32);
|
||||
|
@ -677,17 +706,32 @@ impl VirtualDom {
|
|||
|
||||
let mut create = DiffState::new(&self.scopes);
|
||||
create.scope_stack.push(ScopeId(0));
|
||||
create.element_stack.push(ElementId(0));
|
||||
create
|
||||
.element_stack
|
||||
.push(GlobalNodeId::VNodeId(ElementId(0)));
|
||||
let created = create.create_node(old);
|
||||
create.mutations.append_children(created as u32);
|
||||
|
||||
let mut edit = DiffState::new(&self.scopes);
|
||||
edit.scope_stack.push(ScopeId(0));
|
||||
edit.element_stack.push(ElementId(0));
|
||||
edit.element_stack.push(GlobalNodeId::VNodeId(ElementId(0)));
|
||||
edit.diff_node(old, new);
|
||||
|
||||
(create.mutations, edit.mutations)
|
||||
}
|
||||
|
||||
/// Runs a function with the template associated with a given id.
|
||||
pub fn with_template<R>(&self, id: &TemplateId, mut f: impl FnMut(&Template) -> R) -> R {
|
||||
self.scopes
|
||||
.templates
|
||||
.borrow()
|
||||
.get(id)
|
||||
.map(|inner| {
|
||||
let borrow = &*inner;
|
||||
f(&borrow.borrow())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -15,7 +15,6 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1" }
|
||||
dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
|
||||
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
|
@ -33,7 +32,7 @@ webbrowser = "0.7.1"
|
|||
infer = "0.9.0"
|
||||
dunce = "1.0.2"
|
||||
|
||||
interprocess = { version = "1.1.1", optional = true }
|
||||
interprocess = { version = "1.1.1" }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9.3"
|
||||
|
@ -44,7 +43,7 @@ tokio_runtime = ["tokio"]
|
|||
fullscreen = ["wry/fullscreen"]
|
||||
transparent = ["wry/transparent"]
|
||||
tray = ["wry/tray"]
|
||||
hot-reload = ["dioxus-rsx-interpreter", "interprocess"]
|
||||
hot-reload = ["dioxus-core/hot-reload"]
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus-core-macro = { path = "../core-macro" }
|
||||
|
|
|
@ -51,7 +51,7 @@ impl DesktopController {
|
|||
dom.base_scope().provide_context(window_context);
|
||||
|
||||
// allow other proccesses to send the new rsx text to the @dioxusin ipc channel and recieve erros on the @dioxusout channel
|
||||
#[cfg(feature = "hot-reload")]
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
crate::hot_reload::init(&dom);
|
||||
|
||||
let edits = dom.rebuild();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dioxus_core::{ElementId, EventPriority, UserEvent};
|
||||
use dioxus_core::{EventPriority, GlobalNodeId, UserEvent};
|
||||
use dioxus_html::event_bubbles;
|
||||
use dioxus_html::on::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -37,7 +37,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
|
|||
#[derive(Deserialize, Serialize)]
|
||||
struct ImEvent {
|
||||
event: String,
|
||||
mounted_dom_id: u64,
|
||||
mounted_dom_id: GlobalNodeId,
|
||||
contents: serde_json::Value,
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
|||
contents,
|
||||
} = serde_json::from_value(val).unwrap();
|
||||
|
||||
let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
|
||||
let mounted_dom_id = Some(mounted_dom_id);
|
||||
let name = event_name_from_type(&event);
|
||||
|
||||
let event = make_synthetic_event(&event, contents);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use dioxus_core::VirtualDom;
|
||||
use dioxus_rsx_interpreter::{error::Error, ErrorHandler, SetManyRsxMessage, RSX_CONTEXT};
|
||||
use dioxus_core::{SchedulerMsg, SetTemplateMsg, VirtualDom};
|
||||
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::time::Duration;
|
||||
use std::{sync::Arc, sync::Mutex};
|
||||
|
||||
|
@ -15,29 +14,6 @@ pub(crate) fn init(dom: &VirtualDom) {
|
|||
let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
|
||||
Arc::new(Mutex::new(None));
|
||||
let latest_in_connection_handle = latest_in_connection.clone();
|
||||
let latest_out_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
|
||||
Arc::new(Mutex::new(None));
|
||||
let latest_out_connection_handle = latest_out_connection.clone();
|
||||
|
||||
struct DesktopErrorHandler {
|
||||
latest_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>>,
|
||||
}
|
||||
impl ErrorHandler for DesktopErrorHandler {
|
||||
fn handle_error(&self, err: Error) {
|
||||
if let Some(conn) = &mut *self.latest_connection.lock().unwrap() {
|
||||
conn.get_mut()
|
||||
.write_all((serde_json::to_string(&err).unwrap() + "\n").as_bytes())
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RSX_CONTEXT.set_error_handler(DesktopErrorHandler {
|
||||
latest_connection: latest_out_connection_handle,
|
||||
});
|
||||
RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel());
|
||||
|
||||
// connect to processes for incoming data
|
||||
std::thread::spawn(move || {
|
||||
|
@ -48,14 +24,7 @@ pub(crate) fn init(dom: &VirtualDom) {
|
|||
}
|
||||
});
|
||||
|
||||
// connect to processes for outgoing errors
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(listener) = LocalSocketListener::bind("@dioxusout") {
|
||||
for conn in listener.incoming().filter_map(handle_error) {
|
||||
*latest_out_connection.lock().unwrap() = Some(BufReader::new(conn));
|
||||
}
|
||||
}
|
||||
});
|
||||
let mut channel = dom.get_scheduler_channel();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
|
@ -63,8 +32,10 @@ pub(crate) fn init(dom: &VirtualDom) {
|
|||
let mut buf = String::new();
|
||||
match conn.read_line(&mut buf) {
|
||||
Ok(_) => {
|
||||
let msgs: SetManyRsxMessage = serde_json::from_str(&buf).unwrap();
|
||||
RSX_CONTEXT.extend(msgs);
|
||||
let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
|
||||
channel
|
||||
.start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
|
||||
.unwrap();
|
||||
}
|
||||
Err(err) => {
|
||||
if err.kind() != std::io::ErrorKind::WouldBlock {
|
||||
|
|
|
@ -8,7 +8,7 @@ mod controller;
|
|||
mod desktop_context;
|
||||
mod escape;
|
||||
mod events;
|
||||
#[cfg(feature = "hot-reload")]
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod hot_reload;
|
||||
mod protocol;
|
||||
|
||||
|
|
|
@ -17,12 +17,6 @@ dioxus-html = { path = "../html", version = "^0.2.1", optional = true }
|
|||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1", optional = true }
|
||||
dioxus-hooks = { path = "../hooks", version = "^0.2.1", optional = true }
|
||||
dioxus-rsx = { path = "../rsx", optional = true }
|
||||
dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
|
||||
|
||||
|
||||
# dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true }
|
||||
# dioxus-native-core = { path = "./packages/native-core", version = "^0.2.0", optional = true }
|
||||
# dioxus-native-core-macro = { path = "./packages/native-core-macro", version = "^0.2.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["macro", "hooks", "html"]
|
||||
|
@ -30,8 +24,7 @@ macro = ["dioxus-core-macro", "dioxus-rsx"]
|
|||
html = ["dioxus-html"]
|
||||
hooks = ["dioxus-hooks"]
|
||||
hot-reload = [
|
||||
"dioxus-core-macro/hot-reload",
|
||||
"dioxus-rsx-interpreter",
|
||||
"dioxus-core/hot-reload",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -33,10 +33,4 @@ pub mod prelude {
|
|||
|
||||
#[cfg(feature = "html")]
|
||||
pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
|
||||
#[cfg(feature = "hot-reload")]
|
||||
pub use dioxus_rsx_interpreter::{
|
||||
captuered_context::{CapturedContext, FormattedArg, IfmtArgs},
|
||||
get_line_num, resolve_scope, CodeLocation, RsxContext,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -30,12 +30,29 @@ fn test_original_diff() {
|
|||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
CreateElement { root: 1, tag: "div" },
|
||||
CreateElement { root: 2, tag: "div" },
|
||||
CreateTextNode { root: 3, text: "Hello, world!" },
|
||||
AppendChildren { many: 1 },
|
||||
CreateTemplate { id: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370496,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
CreateTextNodeTemplate {
|
||||
root: 4503599627370497,
|
||||
text: "Hello, world!",
|
||||
locally_static: true
|
||||
},
|
||||
AppendChildren { many: 1 },
|
||||
AppendChildren { many: 1 },
|
||||
FinishTemplate { len: 1 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 },
|
||||
AppendChildren { many: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -66,18 +83,49 @@ fn create() {
|
|||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
CreateElement { root: 1, tag: "div" },
|
||||
CreateElement { root: 2, tag: "div" },
|
||||
CreateTextNode { root: 3, text: "Hello, world!" },
|
||||
CreateElement { root: 4, tag: "div" },
|
||||
CreateElement { root: 5, tag: "div" },
|
||||
CreateTextNode { root: 6, text: "hello" },
|
||||
CreateTextNode { root: 7, text: "world" },
|
||||
AppendChildren { many: 2 },
|
||||
CreateTemplate { id: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: false
|
||||
},
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370496,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: false
|
||||
},
|
||||
CreateTextNodeTemplate {
|
||||
root: 4503599627370497,
|
||||
text: "Hello, world!",
|
||||
locally_static: true
|
||||
},
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370498,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: false
|
||||
},
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370499,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: false
|
||||
},
|
||||
CreatePlaceholderTemplate { root: 4503599627370500 },
|
||||
AppendChildren { many: 1 },
|
||||
AppendChildren { many: 1 },
|
||||
AppendChildren { many: 2 },
|
||||
AppendChildren { many: 1 },
|
||||
AppendChildren { many: 1 },
|
||||
FinishTemplate { len: 1 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 },
|
||||
EnterTemplateRef { root: 1 },
|
||||
CreateTextNode { root: 2, text: "hello" },
|
||||
CreateTextNode { root: 3, text: "world" },
|
||||
ReplaceWith { root: 4503599627370500, m: 2 },
|
||||
ExitTemplateRef {},
|
||||
AppendChildren { many: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -99,16 +147,20 @@ fn create_list() {
|
|||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
CreateElement { root: 1, tag: "div" },
|
||||
CreateTextNode { root: 2, text: "hello" },
|
||||
CreateTemplate { id: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true },
|
||||
AppendChildren { many: 1 },
|
||||
CreateElement { root: 3, tag: "div" },
|
||||
CreateTextNode { root: 4, text: "hello" },
|
||||
AppendChildren { many: 1 },
|
||||
CreateElement { root: 5, tag: "div" },
|
||||
CreateTextNode { root: 6, text: "hello" },
|
||||
AppendChildren { many: 1 },
|
||||
AppendChildren { many: 3 },
|
||||
FinishTemplate { len: 1 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 },
|
||||
CreateTemplateRef { id: 2, template_id: 0 },
|
||||
CreateTemplateRef { id: 3, template_id: 0 },
|
||||
AppendChildren { many: 3 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -131,11 +183,38 @@ fn create_simple() {
|
|||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
CreateElement { root: 1, tag: "div" },
|
||||
CreateElement { root: 2, tag: "div" },
|
||||
CreateElement { root: 3, tag: "div" },
|
||||
CreateElement { root: 4, tag: "div" },
|
||||
AppendChildren { many: 4 },
|
||||
CreateTemplate { id: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
AppendChildren { many: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370496,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
AppendChildren { many: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370497,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
AppendChildren { many: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370498,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
AppendChildren { many: 0 },
|
||||
FinishTemplate { len: 4 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 },
|
||||
AppendChildren { many: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -168,25 +247,50 @@ fn create_components() {
|
|||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
CreateElement { root: 1, tag: "h1" },
|
||||
CreateElement { root: 2, tag: "div" },
|
||||
CreateTextNode { root: 3, text: "abc1" },
|
||||
CreateTemplate { id: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "h1",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
AppendChildren { many: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370496,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: false
|
||||
},
|
||||
CreatePlaceholderTemplate { root: 4503599627370497 },
|
||||
AppendChildren { many: 1 },
|
||||
CreateElement { root: 4, tag: "p" },
|
||||
CreateElement { root: 5, tag: "h1" },
|
||||
CreateElement { root: 6, tag: "div" },
|
||||
CreateTextNode { root: 7, text: "abc2" },
|
||||
AppendChildren { many: 1 },
|
||||
CreateElement { root: 8, tag: "p" },
|
||||
CreateElement { root: 9, tag: "h1" },
|
||||
CreateElement { root: 10, tag: "div" },
|
||||
CreateTextNode { root: 11, text: "abc3" },
|
||||
AppendChildren { many: 1 },
|
||||
CreateElement { root: 12, tag: "p" },
|
||||
AppendChildren { many: 9 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370498,
|
||||
tag: "p",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
AppendChildren { many: 0 },
|
||||
FinishTemplate { len: 3 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 },
|
||||
EnterTemplateRef { root: 1 },
|
||||
CreateTextNode { root: 2, text: "abc1" },
|
||||
ReplaceWith { root: 4503599627370497, m: 1 },
|
||||
ExitTemplateRef {},
|
||||
CreateTemplateRef { id: 3, template_id: 0 },
|
||||
EnterTemplateRef { root: 3 },
|
||||
CreateTextNode { root: 4, text: "abc2" },
|
||||
ReplaceWith { root: 4503599627370497, m: 1 },
|
||||
ExitTemplateRef {},
|
||||
CreateTemplateRef { id: 5, template_id: 0 },
|
||||
EnterTemplateRef { root: 5 },
|
||||
CreateTextNode { root: 6, text: "abc3" },
|
||||
ReplaceWith { root: 4503599627370497, m: 1 },
|
||||
ExitTemplateRef {},
|
||||
AppendChildren { many: 3 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anchors() {
|
||||
static App: Component = |cx| {
|
||||
|
@ -201,11 +305,19 @@ fn anchors() {
|
|||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
CreateElement { root: 1, tag: "div" },
|
||||
CreateTextNode { root: 2, text: "hello" },
|
||||
CreateTemplate { id: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true },
|
||||
AppendChildren { many: 1 },
|
||||
CreatePlaceholder { root: 3 },
|
||||
AppendChildren { many: 2 },
|
||||
FinishTemplate { len: 1 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 },
|
||||
CreatePlaceholder { root: 2 },
|
||||
AppendChildren { many: 2 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
//!
|
||||
//! It does not validated that component lifecycles work properly. This is done in another test file.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::{core_macro::rsx_without_templates, prelude::*};
|
||||
|
||||
fn new_dom() -> VirtualDom {
|
||||
VirtualDom::new(|cx| render!("hi"))
|
||||
VirtualDom::new(|cx| cx.render(rsx_without_templates!("hi")))
|
||||
}
|
||||
|
||||
use dioxus_core::DomEdit::*;
|
||||
|
@ -19,8 +19,8 @@ use dioxus_core::DomEdit::*;
|
|||
fn html_and_rsx_generate_the_same_output() {
|
||||
let dom = new_dom();
|
||||
let (create, change) = dom.diff_lazynodes(
|
||||
rsx! ( div { "Hello world" } ),
|
||||
rsx! ( div { "Goodbye world" } ),
|
||||
rsx_without_templates! ( div { "Hello world" } ),
|
||||
rsx_without_templates! ( div { "Goodbye world" } ),
|
||||
);
|
||||
assert_eq!(
|
||||
create.edits,
|
||||
|
@ -40,7 +40,7 @@ fn html_and_rsx_generate_the_same_output() {
|
|||
fn fragments_create_properly() {
|
||||
let dom = new_dom();
|
||||
|
||||
let create = dom.create_vnodes(rsx! {
|
||||
let create = dom.create_vnodes(rsx_without_templates! {
|
||||
div { "Hello a" }
|
||||
div { "Hello b" }
|
||||
div { "Hello c" }
|
||||
|
@ -68,8 +68,8 @@ fn fragments_create_properly() {
|
|||
fn empty_fragments_create_anchors() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
|
||||
let right = rsx!({ (0..1).map(|_f| rsx! { div {}}) });
|
||||
let left = rsx_without_templates!({ (0..0).map(|_f| rsx_without_templates! { div {}}) });
|
||||
let right = rsx_without_templates!({ (0..1).map(|_f| rsx_without_templates! { div {}}) });
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
|
||||
|
@ -91,8 +91,8 @@ fn empty_fragments_create_anchors() {
|
|||
fn empty_fragments_create_many_anchors() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
|
||||
let right = rsx!({ (0..5).map(|_f| rsx! { div {}}) });
|
||||
let left = rsx_without_templates!({ (0..0).map(|_f| rsx_without_templates! { div {}}) });
|
||||
let right = rsx_without_templates!({ (0..5).map(|_f| rsx_without_templates! { div {}}) });
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
assert_eq!(
|
||||
|
@ -119,10 +119,10 @@ fn empty_fragments_create_many_anchors() {
|
|||
fn empty_fragments_create_anchors_with_many_children() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({ (0..0).map(|_| rsx! { div {} }) });
|
||||
let right = rsx!({
|
||||
let left = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
|
||||
let right = rsx_without_templates!({
|
||||
(0..3).map(|f| {
|
||||
rsx! { div { "hello: {f}" }}
|
||||
rsx_without_templates! { div { "hello: {f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -154,12 +154,12 @@ fn empty_fragments_create_anchors_with_many_children() {
|
|||
fn many_items_become_fragment() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
(0..2).map(|_| {
|
||||
rsx! { div { "hello" }}
|
||||
rsx_without_templates! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
let right = rsx!({ (0..0).map(|_| rsx! { div {} }) });
|
||||
let right = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) });
|
||||
|
||||
let (create, change) = dom.diff_lazynodes(left, right);
|
||||
assert_eq!(
|
||||
|
@ -190,14 +190,14 @@ fn many_items_become_fragment() {
|
|||
fn two_equal_fragments_are_equal() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
(0..2).map(|_| {
|
||||
rsx! { div { "hello" }}
|
||||
rsx_without_templates! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
(0..2).map(|_| {
|
||||
rsx! { div { "hello" }}
|
||||
rsx_without_templates! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -210,12 +210,12 @@ fn two_equal_fragments_are_equal() {
|
|||
fn two_fragments_with_differrent_elements_are_differet() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!(
|
||||
{ (0..2).map(|_| rsx! { div { }} ) }
|
||||
let left = rsx_without_templates!(
|
||||
{ (0..2).map(|_| rsx_without_templates! { div { }} ) }
|
||||
p {}
|
||||
);
|
||||
let right = rsx!(
|
||||
{ (0..5).map(|_| rsx! (h1 { }) ) }
|
||||
let right = rsx_without_templates!(
|
||||
{ (0..5).map(|_| rsx_without_templates! (h1 { }) ) }
|
||||
p {}
|
||||
);
|
||||
|
||||
|
@ -243,12 +243,12 @@ fn two_fragments_with_differrent_elements_are_differet() {
|
|||
fn two_fragments_with_differrent_elements_are_differet_shorter() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!(
|
||||
{(0..5).map(|f| {rsx! { div { }}})}
|
||||
let left = rsx_without_templates!(
|
||||
{(0..5).map(|f| {rsx_without_templates! { div { }}})}
|
||||
p {}
|
||||
);
|
||||
let right = rsx!(
|
||||
{(0..2).map(|f| {rsx! { h1 { }}})}
|
||||
let right = rsx_without_templates!(
|
||||
{(0..2).map(|f| {rsx_without_templates! { h1 { }}})}
|
||||
p {}
|
||||
);
|
||||
|
||||
|
@ -287,12 +287,12 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
|
|||
fn two_fragments_with_same_elements_are_differet() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!(
|
||||
{(0..2).map(|f| {rsx! { div { }}})}
|
||||
let left = rsx_without_templates!(
|
||||
{(0..2).map(|f| rsx_without_templates! { div { }})}
|
||||
p {}
|
||||
);
|
||||
let right = rsx!(
|
||||
{(0..5).map(|f| {rsx! { div { }}})}
|
||||
let right = rsx_without_templates!(
|
||||
{(0..5).map(|f| rsx_without_templates! { div { }})}
|
||||
p {}
|
||||
);
|
||||
|
||||
|
@ -322,12 +322,12 @@ fn two_fragments_with_same_elements_are_differet() {
|
|||
fn keyed_diffing_order() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!(
|
||||
{(0..5).map(|f| {rsx! { div { key: "{f}" }}})}
|
||||
let left = rsx_without_templates!(
|
||||
{(0..5).map(|f| {rsx_without_templates! { div { key: "{f}" }}})}
|
||||
p {"e"}
|
||||
);
|
||||
let right = rsx!(
|
||||
{(0..2).map(|f| rsx! { div { key: "{f}" }})}
|
||||
let right = rsx_without_templates!(
|
||||
{(0..2).map(|f| rsx_without_templates! { div { key: "{f}" }})}
|
||||
p {"e"}
|
||||
);
|
||||
|
||||
|
@ -343,15 +343,15 @@ fn keyed_diffing_order() {
|
|||
fn keyed_diffing_out_of_order() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -368,15 +368,15 @@ fn keyed_diffing_out_of_order() {
|
|||
fn keyed_diffing_out_of_order_adds() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -395,15 +395,15 @@ fn keyed_diffing_out_of_order_adds() {
|
|||
fn keyed_diffing_out_of_order_adds_2() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -423,15 +423,15 @@ fn keyed_diffing_out_of_order_adds_2() {
|
|||
fn keyed_diffing_out_of_order_adds_3() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -451,15 +451,15 @@ fn keyed_diffing_out_of_order_adds_3() {
|
|||
fn keyed_diffing_out_of_order_adds_4() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -479,15 +479,15 @@ fn keyed_diffing_out_of_order_adds_4() {
|
|||
fn keyed_diffing_out_of_order_adds_5() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -502,15 +502,15 @@ fn keyed_diffing_out_of_order_adds_5() {
|
|||
fn keyed_diffing_additions() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -529,15 +529,15 @@ fn keyed_diffing_additions() {
|
|||
fn keyed_diffing_additions_and_moves_on_ends() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -561,15 +561,15 @@ fn keyed_diffing_additions_and_moves_on_ends() {
|
|||
fn keyed_diffing_additions_and_moves_in_middle() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -598,15 +598,15 @@ fn keyed_diffing_additions_and_moves_in_middle() {
|
|||
fn controlled_keyed_diffing_out_of_order() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[4, 5, 6, 7].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[0, 5, 9, 6, 4].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -636,15 +636,15 @@ fn controlled_keyed_diffing_out_of_order() {
|
|||
fn controlled_keyed_diffing_out_of_order_max_test() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
[0, 1, 2, 3, 4].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
[3, 0, 1, 10, 2].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
rsx_without_templates! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -668,15 +668,15 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
|
|||
fn remove_list() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
(0..10).rev().take(5).map(|i| {
|
||||
rsx! { Fragment { key: "{i}", "{i}" }}
|
||||
rsx_without_templates! { Fragment { key: "{i}", "{i}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
(0..10).rev().take(2).map(|i| {
|
||||
rsx! { Fragment { key: "{i}", "{i}" }}
|
||||
rsx_without_templates! { Fragment { key: "{i}", "{i}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -702,15 +702,15 @@ fn remove_list() {
|
|||
fn remove_list_nokeyed() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
let left = rsx_without_templates!({
|
||||
(0..10).rev().take(5).map(|i| {
|
||||
rsx! { Fragment { "{i}" }}
|
||||
rsx_without_templates! { Fragment { "{i}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let right = rsx!({
|
||||
let right = rsx_without_templates!({
|
||||
(0..10).rev().take(2).map(|i| {
|
||||
rsx! { Fragment { "{i}" }}
|
||||
rsx_without_templates! { Fragment { "{i}" }}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -732,10 +732,10 @@ fn add_nested_elements() {
|
|||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{}
|
||||
},
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
div{}
|
||||
}
|
||||
|
@ -758,10 +758,10 @@ fn add_listeners() {
|
|||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{}
|
||||
},
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
onkeydown: |_| {},
|
||||
|
@ -783,13 +783,13 @@ fn remove_listeners() {
|
|||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
onkeydown: |_| {},
|
||||
}
|
||||
},
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{}
|
||||
},
|
||||
);
|
||||
|
@ -808,12 +808,12 @@ fn diff_listeners() {
|
|||
let vdom = new_dom();
|
||||
|
||||
let (_create, change) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
onkeydown: |_| {},
|
||||
}
|
||||
},
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
onkeyup: |_| {},
|
||||
}
|
||||
|
|
|
@ -37,27 +37,37 @@ fn test_early_abort() {
|
|||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreateElement { tag: "div", root: 1 },
|
||||
CreateTextNode { text: "Hello, world!", root: 2 },
|
||||
AppendChildren { many: 1 },
|
||||
CreateTemplate { id: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "div",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
CreateTextNodeTemplate {
|
||||
root: 4503599627370496,
|
||||
text: "Hello, world!",
|
||||
locally_static: true
|
||||
},
|
||||
AppendChildren { many: 1 },
|
||||
FinishTemplate { len: 1 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 },
|
||||
AppendChildren { many: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
let edits = dom.hard_diff(ScopeId(0));
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[CreatePlaceholder { root: 3 }, ReplaceWith { root: 1, m: 1 },],
|
||||
[CreatePlaceholder { root: 2 }, ReplaceWith { root: 1, m: 1 },],
|
||||
);
|
||||
|
||||
let edits = dom.hard_diff(ScopeId(0));
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreateElement { tag: "div", root: 1 }, // keys get reused
|
||||
CreateTextNode { text: "Hello, world!", root: 2 }, // keys get reused
|
||||
AppendChildren { many: 1 },
|
||||
ReplaceWith { root: 3, m: 1 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 }, // gets reused
|
||||
ReplaceWith { root: 2, m: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
//! Tests for the lifecycle of components.
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::{core_macro::rsx_without_templates, prelude::*};
|
||||
use dioxus_core::DomEdit::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
@ -16,7 +16,7 @@ fn manual_diffing() {
|
|||
|
||||
static App: Component<AppProps> = |cx| {
|
||||
let val = cx.props.value.lock().unwrap();
|
||||
cx.render(rsx! { div { "{val}" } })
|
||||
cx.render(rsx_without_templates! { div { "{val}" } })
|
||||
};
|
||||
|
||||
let value = Arc::new(Mutex::new("Hello"));
|
||||
|
@ -38,7 +38,7 @@ fn events_generate() {
|
|||
|
||||
let inner = match *count {
|
||||
0 => {
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div {
|
||||
onclick: move |_| *count += 1,
|
||||
div {
|
||||
|
@ -81,21 +81,21 @@ fn components_generate() {
|
|||
*render_phase += 1;
|
||||
|
||||
cx.render(match *render_phase {
|
||||
1 => rsx!("Text0"),
|
||||
2 => rsx!(div {}),
|
||||
3 => rsx!("Text2"),
|
||||
4 => rsx!(Child {}),
|
||||
5 => rsx!({ None as Option<()> }),
|
||||
6 => rsx!("text 3"),
|
||||
7 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
|
||||
8 => rsx!(Child {}),
|
||||
1 => rsx_without_templates!("Text0"),
|
||||
2 => rsx_without_templates!(div {}),
|
||||
3 => rsx_without_templates!("Text2"),
|
||||
4 => rsx_without_templates!(Child {}),
|
||||
5 => rsx_without_templates!({ None as Option<()> }),
|
||||
6 => rsx_without_templates!("text 3"),
|
||||
7 => rsx_without_templates!({ (0..2).map(|f| rsx_without_templates!("text {f}")) }),
|
||||
8 => rsx_without_templates!(Child {}),
|
||||
_ => todo!(),
|
||||
})
|
||||
};
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
println!("Running child");
|
||||
cx.render(rsx! {
|
||||
cx.render(rsx_without_templates! {
|
||||
h1 {}
|
||||
})
|
||||
}
|
||||
|
@ -175,53 +175,53 @@ fn component_swap() {
|
|||
*render_phase += 1;
|
||||
|
||||
cx.render(match *render_phase {
|
||||
0 => rsx!(
|
||||
0 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Dashboard {}
|
||||
}
|
||||
),
|
||||
1 => rsx!(
|
||||
1 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Results {}
|
||||
}
|
||||
),
|
||||
2 => rsx!(
|
||||
2 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Dashboard {}
|
||||
}
|
||||
),
|
||||
3 => rsx!(
|
||||
3 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Results {}
|
||||
}
|
||||
),
|
||||
4 => rsx!(
|
||||
4 => rsx_without_templates!(
|
||||
div {
|
||||
NavBar {}
|
||||
Dashboard {}
|
||||
}
|
||||
),
|
||||
_ => rsx!("blah"),
|
||||
_ => rsx_without_templates!("blah"),
|
||||
})
|
||||
};
|
||||
|
||||
static NavBar: Component = |cx| {
|
||||
println!("running navbar");
|
||||
cx.render(rsx! {
|
||||
cx.render(rsx_without_templates! {
|
||||
h1 {
|
||||
"NavBar"
|
||||
{(0..3).map(|f| rsx!(NavLink {}))}
|
||||
{(0..3).map(|f| rsx_without_templates!(NavLink {}))}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
static NavLink: Component = |cx| {
|
||||
println!("running navlink");
|
||||
cx.render(rsx! {
|
||||
cx.render(rsx_without_templates! {
|
||||
h1 {
|
||||
"NavLink"
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ fn component_swap() {
|
|||
|
||||
static Dashboard: Component = |cx| {
|
||||
println!("running dashboard");
|
||||
cx.render(rsx! {
|
||||
cx.render(rsx_without_templates! {
|
||||
div {
|
||||
"dashboard"
|
||||
}
|
||||
|
@ -239,7 +239,7 @@ fn component_swap() {
|
|||
|
||||
static Results: Component = |cx| {
|
||||
println!("running results");
|
||||
cx.render(rsx! {
|
||||
cx.render(rsx_without_templates! {
|
||||
div {
|
||||
"results"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(unused, non_upper_case_globals, non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::{core_macro::rsx_without_templates, prelude::*};
|
||||
use dioxus_core::{DomEdit, Mutations, SchedulerMsg, ScopeId};
|
||||
use std::rc::Rc;
|
||||
use DomEdit::*;
|
||||
|
@ -11,12 +11,12 @@ fn shared_state_test() {
|
|||
|
||||
static App: Component = |cx| {
|
||||
cx.provide_context(Rc::new(MySharedState("world!")));
|
||||
cx.render(rsx!(Child {}))
|
||||
cx.render(rsx_without_templates!(Child {}))
|
||||
};
|
||||
|
||||
static Child: Component = |cx| {
|
||||
let shared = cx.consume_context::<Rc<MySharedState>>()?;
|
||||
cx.render(rsx!("Hello, {shared.0}"))
|
||||
cx.render(rsx_without_templates!("Hello, {shared.0}"))
|
||||
};
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
@ -42,13 +42,13 @@ fn swap_test() {
|
|||
cx.provide_context(Rc::new(MySharedState("world!")));
|
||||
|
||||
let child = match *val % 2 {
|
||||
0 => rsx!(
|
||||
0 => rsx_without_templates!(
|
||||
Child1 {
|
||||
Child1 { }
|
||||
Child2 { }
|
||||
}
|
||||
),
|
||||
_ => rsx!(
|
||||
_ => rsx_without_templates!(
|
||||
Child2 {
|
||||
Child2 { }
|
||||
Child2 { }
|
||||
|
@ -56,7 +56,7 @@ fn swap_test() {
|
|||
),
|
||||
};
|
||||
|
||||
cx.render(rsx!(
|
||||
cx.render(rsx_without_templates!(
|
||||
Router {
|
||||
div { child }
|
||||
}
|
||||
|
@ -65,14 +65,14 @@ fn swap_test() {
|
|||
|
||||
#[inline_props]
|
||||
fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> {
|
||||
cx.render(rsx!(div { children }))
|
||||
cx.render(rsx_without_templates!(div { children }))
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
|
||||
println!("Child1: {}", shared.0);
|
||||
cx.render(rsx! {
|
||||
cx.render(rsx_without_templates! {
|
||||
div {
|
||||
"{shared.0}",
|
||||
children
|
||||
|
@ -84,7 +84,7 @@ fn swap_test() {
|
|||
fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
|
||||
println!("Child2: {}", shared.0);
|
||||
cx.render(rsx! {
|
||||
cx.render(rsx_without_templates! {
|
||||
h1 {
|
||||
"{shared.0}",
|
||||
children
|
||||
|
|
|
@ -63,14 +63,38 @@ fn conditional_rendering() {
|
|||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
CreateElement { root: 1, tag: "h1" },
|
||||
CreateTextNode { root: 2, text: "hello" },
|
||||
CreateTemplate { id: 0 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "h1",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true },
|
||||
AppendChildren { many: 1 },
|
||||
CreateElement { root: 3, tag: "span" },
|
||||
CreateTextNode { root: 4, text: "a" },
|
||||
CreatePlaceholderTemplate { root: 4503599627370497 },
|
||||
CreatePlaceholderTemplate { root: 4503599627370498 },
|
||||
FinishTemplate { len: 3 },
|
||||
CreateTemplateRef { id: 1, template_id: 0 },
|
||||
EnterTemplateRef { root: 1 },
|
||||
CreateTemplate { id: 1 },
|
||||
CreateElementTemplate {
|
||||
root: 4503599627370495,
|
||||
tag: "span",
|
||||
locally_static: true,
|
||||
fully_static: true
|
||||
},
|
||||
CreateTextNodeTemplate { root: 4503599627370496, text: "a", locally_static: true },
|
||||
AppendChildren { many: 1 },
|
||||
CreatePlaceholder { root: 5 },
|
||||
AppendChildren { many: 3 },
|
||||
FinishTemplate { len: 1 },
|
||||
CreateTemplateRef { id: 2, template_id: 1 },
|
||||
ReplaceWith { root: 4503599627370497, m: 1 },
|
||||
ExitTemplateRef {},
|
||||
EnterTemplateRef { root: 1 },
|
||||
CreatePlaceholder { root: 3 },
|
||||
ReplaceWith { root: 4503599627370498, m: 1 },
|
||||
ExitTemplateRef {},
|
||||
AppendChildren { many: 1 }
|
||||
]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{GlobalAttributes, SvgAttributes};
|
||||
use dioxus_core::*;
|
||||
use std::fmt::Arguments;
|
||||
|
||||
macro_rules! builder_constructors {
|
||||
(
|
||||
|
@ -28,10 +27,12 @@ macro_rules! builder_constructors {
|
|||
|
||||
impl $name {
|
||||
$(
|
||||
$(#[$attr_method])*
|
||||
pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr(stringify!($fil), val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const $fil: AttributeDiscription = AttributeDiscription{
|
||||
name: stringify!($fil),
|
||||
namespace: None,
|
||||
volatile: false
|
||||
};
|
||||
)*
|
||||
}
|
||||
)*
|
||||
|
@ -57,9 +58,13 @@ macro_rules! builder_constructors {
|
|||
|
||||
impl $name {
|
||||
$(
|
||||
pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr(stringify!($fil), val, Some(stringify!($namespace)), false)
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const $fil: AttributeDiscription = AttributeDiscription{
|
||||
name: stringify!($fil),
|
||||
namespace: Some(stringify!($namespace)),
|
||||
volatile: false
|
||||
};
|
||||
)*
|
||||
}
|
||||
)*
|
||||
|
@ -1061,13 +1066,19 @@ impl input {
|
|||
/// - `time`
|
||||
/// - `url`
|
||||
/// - `week`
|
||||
pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("type", val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const r#type: AttributeDiscription = AttributeDiscription {
|
||||
name: "type",
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
};
|
||||
|
||||
pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("value", val, None, true)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const value: AttributeDiscription = AttributeDiscription {
|
||||
name: "value",
|
||||
namespace: None,
|
||||
volatile: true,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1077,41 +1088,63 @@ volatile attributes
|
|||
impl script {
|
||||
// r#async: Bool,
|
||||
// r#type: String, // TODO could be an enum
|
||||
pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("type", val, None, false)
|
||||
}
|
||||
pub fn r#script<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("script", val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const r#type: AttributeDiscription = AttributeDiscription {
|
||||
name: "type",
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
};
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const r#script: AttributeDiscription = AttributeDiscription {
|
||||
name: "script",
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
};
|
||||
}
|
||||
|
||||
impl button {
|
||||
pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("type", val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const r#type: AttributeDiscription = AttributeDiscription {
|
||||
name: "type",
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
};
|
||||
}
|
||||
|
||||
impl select {
|
||||
pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("value", val, None, true)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const value: AttributeDiscription = AttributeDiscription {
|
||||
name: "value",
|
||||
namespace: None,
|
||||
volatile: true,
|
||||
};
|
||||
}
|
||||
|
||||
impl option {
|
||||
pub fn selected<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("selected", val, None, true)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const selected: AttributeDiscription = AttributeDiscription {
|
||||
name: "selected",
|
||||
namespace: None,
|
||||
volatile: true,
|
||||
};
|
||||
}
|
||||
|
||||
impl textarea {
|
||||
pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("value", val, None, true)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const value: AttributeDiscription = AttributeDiscription {
|
||||
name: "value",
|
||||
namespace: None,
|
||||
volatile: true,
|
||||
};
|
||||
}
|
||||
impl label {
|
||||
pub fn r#for<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("for", val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const r#for: AttributeDiscription = AttributeDiscription {
|
||||
name: "for",
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
};
|
||||
}
|
||||
|
||||
builder_constructors! {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use dioxus_core::*;
|
||||
use std::fmt::Arguments;
|
||||
|
||||
macro_rules! no_namespace_trait_methods {
|
||||
(
|
||||
|
@ -9,10 +8,12 @@ macro_rules! no_namespace_trait_methods {
|
|||
)*
|
||||
) => {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr(stringify!($name), val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
const $name: AttributeDiscription = AttributeDiscription{
|
||||
name: stringify!($name),
|
||||
namespace: None,
|
||||
volatile: false
|
||||
};
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
@ -24,11 +25,12 @@ macro_rules! style_trait_methods {
|
|||
)*
|
||||
) => {
|
||||
$(
|
||||
#[inline]
|
||||
$(#[$attr])*
|
||||
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr($lit, val, Some("style"), false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
const $name: AttributeDiscription = AttributeDiscription{
|
||||
name: $lit,
|
||||
namespace: Some("style"),
|
||||
volatile: false
|
||||
};
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
@ -40,10 +42,12 @@ macro_rules! aria_trait_methods {
|
|||
)*
|
||||
) => {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr($lit, val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
const $name: AttributeDiscription = AttributeDiscription{
|
||||
name: $lit,
|
||||
namespace: None,
|
||||
volatile: false
|
||||
};
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
@ -53,9 +57,12 @@ pub trait GlobalAttributes {
|
|||
///
|
||||
/// For more information, see the MDN docs:
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
|
||||
fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("dioxus-prevent-default", val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
const prevent_default: AttributeDiscription = AttributeDiscription {
|
||||
name: "dioxus-prevent-default",
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
};
|
||||
|
||||
no_namespace_trait_methods! {
|
||||
accesskey;
|
||||
|
@ -597,9 +604,12 @@ pub trait SvgAttributes {
|
|||
///
|
||||
/// For more information, see the MDN docs:
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
|
||||
fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
|
||||
cx.attr("dioxus-prevent-default", val, None, false)
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
const prevent_default: AttributeDiscription = AttributeDiscription {
|
||||
name: "dioxus-prevent-default",
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
};
|
||||
aria_trait_methods! {
|
||||
accent_height: "accent-height",
|
||||
accumulate: "accumulate",
|
||||
|
|
|
@ -73,4 +73,44 @@ extern "C" {
|
|||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str, ns: Option<&str>);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn CreateTemplateRef(this: &Interpreter, id: u64, template_id: u64);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn CreateTemplate(this: &Interpreter, id: u64);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn FinishTemplate(this: &Interpreter, len: u32);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn EnterTemplateRef(this: &Interpreter, id: u64);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn ExitTemplateRef(this: &Interpreter);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn CreateElementTemplate(
|
||||
this: &Interpreter,
|
||||
tag: &str,
|
||||
root: u64,
|
||||
locally_static: bool,
|
||||
fully_static: bool,
|
||||
);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn CreateElementNsTemplate(
|
||||
this: &Interpreter,
|
||||
tag: &str,
|
||||
id: u64,
|
||||
ns: &str,
|
||||
locally_static: bool,
|
||||
fully_static: bool,
|
||||
);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn CreateTextNodeTemplate(this: &Interpreter, text: &str, root: u64, locally_static: bool);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn CreatePlaceholderTemplate(this: &Interpreter, root: u64);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
// id > Number.MAX_SAFE_INTEGER/2 in template ref
|
||||
// id <= Number.MAX_SAFE_INTEGER/2 in global nodes
|
||||
const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2);
|
||||
|
||||
export function main() {
|
||||
let root = window.document.getElementById("main");
|
||||
if (root != null) {
|
||||
|
@ -6,6 +10,123 @@ export function main() {
|
|||
}
|
||||
}
|
||||
|
||||
class TemplateRef {
|
||||
constructor(fragment, dynamicNodePaths, roots, id) {
|
||||
this.fragment = fragment;
|
||||
this.dynamicNodePaths = dynamicNodePaths;
|
||||
this.roots = roots;
|
||||
this.id = id;
|
||||
this.placed = false;
|
||||
this.nodes = [];
|
||||
}
|
||||
|
||||
build(id) {
|
||||
if (!this.nodes[id]) {
|
||||
let current = this.fragment;
|
||||
const path = this.dynamicNodePaths[id];
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const idx = path[i];
|
||||
current = current.firstChild;
|
||||
for (let i2 = 0; i2 < idx; i2++) {
|
||||
current = current.nextSibling;
|
||||
}
|
||||
}
|
||||
this.nodes[id] = current;
|
||||
}
|
||||
}
|
||||
|
||||
get(id) {
|
||||
this.build(id);
|
||||
return this.nodes[id];
|
||||
}
|
||||
|
||||
parent() {
|
||||
return this.roots[0].parentNode;
|
||||
}
|
||||
|
||||
first() {
|
||||
return this.roots[0];
|
||||
}
|
||||
|
||||
last() {
|
||||
return this.roots[this.roots.length - 1];
|
||||
}
|
||||
|
||||
move() {
|
||||
// move the root nodes into a new template
|
||||
this.fragment = new DocumentFragment();
|
||||
for (let n of this.roots) {
|
||||
this.fragment.appendChild(n);
|
||||
}
|
||||
}
|
||||
|
||||
getFragment() {
|
||||
if (!this.placed) {
|
||||
this.placed = true;
|
||||
}
|
||||
else {
|
||||
this.move();
|
||||
}
|
||||
return this.fragment;
|
||||
}
|
||||
}
|
||||
|
||||
class Template {
|
||||
constructor(template_id, id) {
|
||||
this.nodes = [];
|
||||
this.dynamicNodePaths = [];
|
||||
this.template_id = template_id;
|
||||
this.id = id;
|
||||
this.template = document.createElement("template");
|
||||
this.reconstructingRefrencesIndex = null;
|
||||
}
|
||||
|
||||
finalize(roots) {
|
||||
for (let i = 0; i < roots.length; i++) {
|
||||
let node = roots[i];
|
||||
let path = [i];
|
||||
const is_element = node.nodeType == 1;
|
||||
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
|
||||
if (!locally_static) {
|
||||
this.dynamicNodePaths[node.tmplId] = [...path];
|
||||
}
|
||||
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
|
||||
if (traverse_children) {
|
||||
this.createIds(path, node);
|
||||
}
|
||||
this.template.content.appendChild(node);
|
||||
}
|
||||
document.head.appendChild(this.template);
|
||||
}
|
||||
|
||||
createIds(path, root) {
|
||||
let i = 0;
|
||||
for (let node = root.firstChild; node != null; node = node.nextSibling) {
|
||||
let new_path = [...path, i];
|
||||
const is_element = node.nodeType == 1;
|
||||
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
|
||||
if (!locally_static) {
|
||||
this.dynamicNodePaths[node.tmplId] = [...new_path];
|
||||
}
|
||||
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
|
||||
if (traverse_children) {
|
||||
this.createIds(new_path, node);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
ref(id) {
|
||||
const template = this.template.content.cloneNode(true);
|
||||
let roots = [];
|
||||
this.reconstructingRefrencesIndex = 0;
|
||||
for (let node = template.firstChild; node != null; node = node.nextSibling) {
|
||||
roots.push(node);
|
||||
}
|
||||
return new TemplateRef(template, this.dynamicNodePaths, roots, id);
|
||||
}
|
||||
}
|
||||
|
||||
class ListenerMap {
|
||||
constructor(root) {
|
||||
// bubbling events can listen at the root element
|
||||
|
@ -53,21 +174,18 @@ class ListenerMap {
|
|||
element.removeEventListener(event_name, handler);
|
||||
}
|
||||
}
|
||||
|
||||
removeAllNonBubbling(element) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id];
|
||||
}
|
||||
}
|
||||
|
||||
export class Interpreter {
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.stack = [root];
|
||||
this.templateInProgress = null;
|
||||
this.insideTemplateRef = [];
|
||||
this.listeners = new ListenerMap(root);
|
||||
this.handlers = {};
|
||||
this.lastNodeWasText = false;
|
||||
this.nodes = [root];
|
||||
this.templates = [];
|
||||
}
|
||||
top() {
|
||||
return this.stack[this.stack.length - 1];
|
||||
|
@ -75,11 +193,45 @@ export class Interpreter {
|
|||
pop() {
|
||||
return this.stack.pop();
|
||||
}
|
||||
currentTemplateId() {
|
||||
if (this.insideTemplateRef.length) {
|
||||
return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
getId(id) {
|
||||
if (this.templateInProgress !== null) {
|
||||
return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
|
||||
}
|
||||
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
|
||||
return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
|
||||
}
|
||||
else {
|
||||
return this.nodes[id];
|
||||
}
|
||||
}
|
||||
SetNode(id, node) {
|
||||
this.nodes[id] = node;
|
||||
if (this.templateInProgress !== null) {
|
||||
id -= templateIdLimit;
|
||||
node.tmplId = id;
|
||||
this.templates[this.templateInProgress].nodes[id] = node;
|
||||
}
|
||||
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
|
||||
id -= templateIdLimit;
|
||||
let last = this.insideTemplateRef[this.insideTemplateRef.length - 1];
|
||||
last.childNodes[id] = node;
|
||||
if (last.nodeCache[id]) {
|
||||
last.nodeCache[id] = node;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.nodes[id] = node;
|
||||
}
|
||||
}
|
||||
PushRoot(root) {
|
||||
const node = this.nodes[root];
|
||||
const node = this.getId(root);
|
||||
this.stack.push(node);
|
||||
}
|
||||
PopRoot() {
|
||||
|
@ -89,73 +241,126 @@ export class Interpreter {
|
|||
let root = this.stack[this.stack.length - (1 + many)];
|
||||
let to_add = this.stack.splice(this.stack.length - many);
|
||||
for (let i = 0; i < many; i++) {
|
||||
root.appendChild(to_add[i]);
|
||||
const child = to_add[i];
|
||||
if (child instanceof TemplateRef) {
|
||||
root.appendChild(child.getFragment());
|
||||
}
|
||||
else {
|
||||
root.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
ReplaceWith(root_id, m) {
|
||||
let root = this.nodes[root_id];
|
||||
let els = this.stack.splice(this.stack.length - m);
|
||||
if (is_element_node(root.nodeType)) {
|
||||
this.listeners.removeAllNonBubbling(root);
|
||||
let root = this.getId(root_id);
|
||||
if (root instanceof TemplateRef) {
|
||||
this.InsertBefore(root_id, m);
|
||||
this.Remove(root_id);
|
||||
}
|
||||
else {
|
||||
let els = this.stack.splice(this.stack.length - m).map(function (el) {
|
||||
if (el instanceof TemplateRef) {
|
||||
return el.getFragment();
|
||||
}
|
||||
else {
|
||||
return el;
|
||||
}
|
||||
});
|
||||
root.replaceWith(...els);
|
||||
}
|
||||
root.replaceWith(...els);
|
||||
}
|
||||
InsertAfter(root, n) {
|
||||
let old = this.nodes[root];
|
||||
let new_nodes = this.stack.splice(this.stack.length - n);
|
||||
old.after(...new_nodes);
|
||||
const old = this.getId(root);
|
||||
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
|
||||
if (el instanceof TemplateRef) {
|
||||
return el.getFragment();
|
||||
}
|
||||
else {
|
||||
return el;
|
||||
}
|
||||
});
|
||||
if (old instanceof TemplateRef) {
|
||||
const last = old.last();
|
||||
last.after(...new_nodes);
|
||||
}
|
||||
else {
|
||||
old.after(...new_nodes);
|
||||
}
|
||||
}
|
||||
InsertBefore(root, n) {
|
||||
let old = this.nodes[root];
|
||||
let new_nodes = this.stack.splice(this.stack.length - n);
|
||||
old.before(...new_nodes);
|
||||
const old = this.getId(root);
|
||||
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
|
||||
if (el instanceof TemplateRef) {
|
||||
return el.getFragment();
|
||||
}
|
||||
else {
|
||||
return el;
|
||||
}
|
||||
});
|
||||
if (old instanceof TemplateRef) {
|
||||
const first = old.first();
|
||||
first.before(...new_nodes);
|
||||
}
|
||||
else {
|
||||
old.before(...new_nodes);
|
||||
}
|
||||
}
|
||||
Remove(root) {
|
||||
let node = this.nodes[root];
|
||||
let node = this.getId(root);
|
||||
if (node !== undefined) {
|
||||
if (is_element_node(node)) {
|
||||
this.listeners.removeAllNonBubbling(node);
|
||||
if (node instanceof TemplateRef) {
|
||||
for (let child of node.roots) {
|
||||
child.remove();
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.remove();
|
||||
}
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
CreateTextNode(text, root) {
|
||||
const node = document.createTextNode(text);
|
||||
this.nodes[root] = node;
|
||||
this.stack.push(node);
|
||||
this.SetNode(root, node);
|
||||
}
|
||||
CreateElement(tag, root) {
|
||||
const el = document.createElement(tag);
|
||||
this.nodes[root] = el;
|
||||
this.stack.push(el);
|
||||
this.SetNode(root, el);
|
||||
}
|
||||
CreateElementNs(tag, root, ns) {
|
||||
let el = document.createElementNS(ns, tag);
|
||||
this.stack.push(el);
|
||||
this.nodes[root] = el;
|
||||
this.SetNode(root, el);
|
||||
}
|
||||
CreatePlaceholder(root) {
|
||||
let el = document.createElement("pre");
|
||||
el.hidden = true;
|
||||
this.stack.push(el);
|
||||
this.nodes[root] = el;
|
||||
this.SetNode(root, el);
|
||||
}
|
||||
NewEventListener(event_name, root, handler, bubbles) {
|
||||
const element = this.nodes[root];
|
||||
element.setAttribute("data-dioxus-id", `${root}`);
|
||||
const element = this.getId(root);
|
||||
if (root >= templateIdLimit) {
|
||||
let currentTemplateRefId = this.currentTemplateId();
|
||||
root -= templateIdLimit;
|
||||
element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`);
|
||||
}
|
||||
else {
|
||||
element.setAttribute("data-dioxus-id", `${root}`);
|
||||
}
|
||||
this.listeners.create(event_name, element, handler, bubbles);
|
||||
}
|
||||
RemoveEventListener(root, event_name, bubbles) {
|
||||
const element = this.nodes[root];
|
||||
const element = this.getId(root);
|
||||
element.removeAttribute(`data-dioxus-id`);
|
||||
this.listeners.remove(element, event_name, bubbles);
|
||||
}
|
||||
SetText(root, text) {
|
||||
this.nodes[root].textContent = text;
|
||||
this.getId(root).data = text;
|
||||
}
|
||||
SetAttribute(root, field, value, ns) {
|
||||
const name = field;
|
||||
const node = this.nodes[root];
|
||||
const node = this.getId(root);
|
||||
if (ns === "style") {
|
||||
// @ts-ignore
|
||||
node.style[name] = value;
|
||||
|
@ -189,7 +394,7 @@ export class Interpreter {
|
|||
}
|
||||
RemoveAttribute(root, field, ns) {
|
||||
const name = field;
|
||||
const node = this.nodes[root];
|
||||
const node = this.getId(root);
|
||||
if (ns == "style") {
|
||||
node.style.removeProperty(name);
|
||||
} else if (ns !== null || ns !== undefined) {
|
||||
|
@ -206,52 +411,99 @@ export class Interpreter {
|
|||
node.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
CreateTemplateRef(id, template_id) {
|
||||
const el = this.templates[template_id].ref(id);
|
||||
this.nodes[id] = el;
|
||||
this.stack.push(el);
|
||||
}
|
||||
CreateTemplate(template_id) {
|
||||
this.templateInProgress = template_id;
|
||||
this.templates[template_id] = new Template(template_id, 0);
|
||||
}
|
||||
FinishTemplate(many) {
|
||||
this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many));
|
||||
this.templateInProgress = null;
|
||||
}
|
||||
EnterTemplateRef(id) {
|
||||
this.insideTemplateRef.push(this.nodes[id]);
|
||||
}
|
||||
ExitTemplateRef() {
|
||||
this.insideTemplateRef.pop();
|
||||
}
|
||||
handleEdits(edits) {
|
||||
this.stack.push(this.root);
|
||||
for (let edit of edits) {
|
||||
this.handleEdit(edit);
|
||||
}
|
||||
}
|
||||
CreateElementTemplate(tag, root, locally_static, fully_static) {
|
||||
const el = document.createElement(tag);
|
||||
this.stack.push(el);
|
||||
this.SetNode(root, el);
|
||||
if (!locally_static)
|
||||
el.setAttribute("data-dioxus-dynamic", "true");
|
||||
if (fully_static)
|
||||
el.setAttribute("data-dioxus-fully-static", fully_static);
|
||||
}
|
||||
CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) {
|
||||
const el = document.createElementNS(ns, tag);
|
||||
this.stack.push(el);
|
||||
this.SetNode(root, el);
|
||||
if (!locally_static)
|
||||
el.setAttribute("data-dioxus-dynamic", "true");
|
||||
if (fully_static)
|
||||
el.setAttribute("data-dioxus-fully-static", fully_static);
|
||||
}
|
||||
CreateTextNodeTemplate(text, root, locally_static) {
|
||||
const node = document.createTextNode(text);
|
||||
this.stack.push(node);
|
||||
this.SetNode(root, node);
|
||||
}
|
||||
CreatePlaceholderTemplate(root) {
|
||||
const el = document.createElement("pre");
|
||||
el.setAttribute("data-dioxus-dynamic", "true");
|
||||
el.hidden = true;
|
||||
this.stack.push(el);
|
||||
this.SetNode(root, el);
|
||||
}
|
||||
handleEdit(edit) {
|
||||
switch (edit.type) {
|
||||
case "PushRoot":
|
||||
this.PushRoot(edit.root);
|
||||
this.PushRoot(BigInt(edit.root));
|
||||
break;
|
||||
case "AppendChildren":
|
||||
this.AppendChildren(edit.many);
|
||||
break;
|
||||
case "ReplaceWith":
|
||||
this.ReplaceWith(edit.root, edit.m);
|
||||
this.ReplaceWith(BigInt(edit.root), edit.m);
|
||||
break;
|
||||
case "InsertAfter":
|
||||
this.InsertAfter(edit.root, edit.n);
|
||||
this.InsertAfter(BigInt(edit.root), edit.n);
|
||||
break;
|
||||
case "InsertBefore":
|
||||
this.InsertBefore(edit.root, edit.n);
|
||||
this.InsertBefore(BigInt(edit.root), edit.n);
|
||||
break;
|
||||
case "Remove":
|
||||
this.Remove(edit.root);
|
||||
this.Remove(BigInt(edit.root));
|
||||
break;
|
||||
case "CreateTextNode":
|
||||
this.CreateTextNode(edit.text, edit.root);
|
||||
this.CreateTextNode(edit.text, BigInt(edit.root));
|
||||
break;
|
||||
case "CreateElement":
|
||||
this.CreateElement(edit.tag, edit.root);
|
||||
this.CreateElement(edit.tag, BigInt(edit.root));
|
||||
break;
|
||||
case "CreateElementNs":
|
||||
this.CreateElementNs(edit.tag, edit.root, edit.ns);
|
||||
this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns);
|
||||
break;
|
||||
case "CreatePlaceholder":
|
||||
this.CreatePlaceholder(edit.root);
|
||||
this.CreatePlaceholder(BigInt(edit.root));
|
||||
break;
|
||||
case "RemoveEventListener":
|
||||
this.RemoveEventListener(edit.root, edit.event_name);
|
||||
this.RemoveEventListener(BigInt(edit.root), edit.event_name);
|
||||
break;
|
||||
case "NewEventListener":
|
||||
// this handler is only provided on desktop implementations since this
|
||||
// method is not used by the web implementation
|
||||
let handler = (event) => {
|
||||
|
||||
let target = event.target;
|
||||
if (target != null) {
|
||||
let realId = target.getAttribute(`data-dioxus-id`);
|
||||
|
@ -330,26 +582,66 @@ export class Interpreter {
|
|||
if (realId === null) {
|
||||
return;
|
||||
}
|
||||
if (realId.includes(",")) {
|
||||
realId = realId.split(',');
|
||||
realId = {
|
||||
template_ref_id: parseInt(realId[0]),
|
||||
template_node_id: parseInt(realId[1]),
|
||||
};
|
||||
}
|
||||
else {
|
||||
realId = parseInt(realId);
|
||||
}
|
||||
window.ipc.postMessage(
|
||||
serializeIpcMessage("user_event", {
|
||||
event: edit.event_name,
|
||||
mounted_dom_id: parseInt(realId),
|
||||
mounted_dom_id: realId,
|
||||
contents: contents,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name));
|
||||
this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name));
|
||||
|
||||
break;
|
||||
case "SetText":
|
||||
this.SetText(edit.root, edit.text);
|
||||
this.SetText(BigInt(edit.root), edit.text);
|
||||
break;
|
||||
case "SetAttribute":
|
||||
this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
|
||||
this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns);
|
||||
break;
|
||||
case "RemoveAttribute":
|
||||
this.RemoveAttribute(edit.root, edit.name, edit.ns);
|
||||
this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns);
|
||||
break;
|
||||
case "PopRoot":
|
||||
this.PopRoot();
|
||||
break;
|
||||
case "CreateTemplateRef":
|
||||
this.CreateTemplateRef(BigInt(edit.id), edit.template_id);
|
||||
break;
|
||||
case "CreateTemplate":
|
||||
this.CreateTemplate(BigInt(edit.id));
|
||||
break;
|
||||
case "FinishTemplate":
|
||||
this.FinishTemplate(edit.len);
|
||||
break;
|
||||
case "EnterTemplateRef":
|
||||
this.EnterTemplateRef(BigInt(edit.root));
|
||||
break;
|
||||
case "ExitTemplateRef":
|
||||
this.ExitTemplateRef();
|
||||
break;
|
||||
case "CreateElementTemplate":
|
||||
this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static);
|
||||
break;
|
||||
case "CreateElementNsTemplate":
|
||||
this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static);
|
||||
break;
|
||||
case "CreateTextNodeTemplate":
|
||||
this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static);
|
||||
break;
|
||||
case "CreatePlaceholderTemplate":
|
||||
this.CreatePlaceholderTemplate(BigInt(edit.root));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dioxus_core::{ElementId, EventPriority, UserEvent};
|
||||
use dioxus_core::GlobalNodeId;
|
||||
use dioxus_core::{EventPriority, UserEvent};
|
||||
use dioxus_html::event_bubbles;
|
||||
use dioxus_html::on::*;
|
||||
|
||||
|
@ -25,7 +26,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
|
|||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct ImEvent {
|
||||
event: String,
|
||||
mounted_dom_id: u64,
|
||||
mounted_dom_id: GlobalNodeId,
|
||||
contents: serde_json::Value,
|
||||
}
|
||||
|
||||
|
@ -36,7 +37,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
|||
contents,
|
||||
} = serde_json::from_value(val).unwrap();
|
||||
|
||||
let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
|
||||
let mounted_dom_id = Some(mounted_dom_id);
|
||||
|
||||
let name = event_name_from_type(&event);
|
||||
let event = make_synthetic_event(&event, contents);
|
||||
|
|
|
@ -35,14 +35,186 @@ class IPC {
|
|||
}
|
||||
}
|
||||
|
||||
// id > Number.MAX_SAFE_INTEGER/2 in template ref
|
||||
// id <= Number.MAX_SAFE_INTEGER/2 in global nodes
|
||||
const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2);
|
||||
|
||||
class TemplateRef {
|
||||
constructor(fragment, dynamicNodePaths, roots, id) {
|
||||
this.fragment = fragment;
|
||||
this.dynamicNodePaths = dynamicNodePaths;
|
||||
this.roots = roots;
|
||||
this.id = id;
|
||||
this.placed = false;
|
||||
this.nodes = [];
|
||||
}
|
||||
|
||||
build(id) {
|
||||
if (!this.nodes[id]) {
|
||||
let current = this.fragment;
|
||||
const path = this.dynamicNodePaths[id];
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const idx = path[i];
|
||||
current = current.firstChild;
|
||||
for (let i2 = 0; i2 < idx; i2++) {
|
||||
current = current.nextSibling;
|
||||
}
|
||||
}
|
||||
this.nodes[id] = current;
|
||||
}
|
||||
}
|
||||
|
||||
get(id) {
|
||||
this.build(id);
|
||||
return this.nodes[id];
|
||||
}
|
||||
|
||||
parent() {
|
||||
return this.roots[0].parentNode;
|
||||
}
|
||||
|
||||
first() {
|
||||
return this.roots[0];
|
||||
}
|
||||
|
||||
last() {
|
||||
return this.roots[this.roots.length - 1];
|
||||
}
|
||||
|
||||
move() {
|
||||
// move the root nodes into a new template
|
||||
this.fragment = new DocumentFragment();
|
||||
for (let n of this.roots) {
|
||||
this.fragment.appendChild(n);
|
||||
}
|
||||
}
|
||||
|
||||
getFragment() {
|
||||
if (!this.placed) {
|
||||
this.placed = true;
|
||||
}
|
||||
else {
|
||||
this.move();
|
||||
}
|
||||
return this.fragment;
|
||||
}
|
||||
}
|
||||
|
||||
class Template {
|
||||
constructor(template_id, id) {
|
||||
this.nodes = [];
|
||||
this.dynamicNodePaths = [];
|
||||
this.template_id = template_id;
|
||||
this.id = id;
|
||||
this.template = document.createElement("template");
|
||||
this.reconstructingRefrencesIndex = null;
|
||||
}
|
||||
|
||||
finalize(roots) {
|
||||
for (let i = 0; i < roots.length; i++) {
|
||||
let node = roots[i];
|
||||
let path = [i];
|
||||
const is_element = node.nodeType == 1;
|
||||
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
|
||||
if (!locally_static) {
|
||||
this.dynamicNodePaths[node.tmplId] = [...path];
|
||||
}
|
||||
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
|
||||
if (traverse_children) {
|
||||
this.createIds(path, node);
|
||||
}
|
||||
this.template.content.appendChild(node);
|
||||
}
|
||||
document.head.appendChild(this.template);
|
||||
}
|
||||
|
||||
createIds(path, root) {
|
||||
let i = 0;
|
||||
for (let node = root.firstChild; node != null; node = node.nextSibling) {
|
||||
let new_path = [...path, i];
|
||||
const is_element = node.nodeType == 1;
|
||||
const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic");
|
||||
if (!locally_static) {
|
||||
this.dynamicNodePaths[node.tmplId] = [...new_path];
|
||||
}
|
||||
const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static");
|
||||
if (traverse_children) {
|
||||
this.createIds(new_path, node);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
ref(id) {
|
||||
const template = this.template.content.cloneNode(true);
|
||||
let roots = [];
|
||||
this.reconstructingRefrencesIndex = 0;
|
||||
for (let node = template.firstChild; node != null; node = node.nextSibling) {
|
||||
roots.push(node);
|
||||
}
|
||||
return new TemplateRef(template, this.dynamicNodePaths, roots, id);
|
||||
}
|
||||
}
|
||||
|
||||
class ListenerMap {
|
||||
constructor(root) {
|
||||
// bubbling events can listen at the root element
|
||||
this.global = {};
|
||||
// non bubbling events listen at the element the listener was created at
|
||||
this.local = {};
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
create(event_name, element, handler, bubbles) {
|
||||
if (bubbles) {
|
||||
if (this.global[event_name] === undefined) {
|
||||
this.global[event_name] = {};
|
||||
this.global[event_name].active = 1;
|
||||
this.global[event_name].callback = handler;
|
||||
this.root.addEventListener(event_name, handler);
|
||||
} else {
|
||||
this.global[event_name].active++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
if (!this.local[id]) {
|
||||
this.local[id] = {};
|
||||
}
|
||||
this.local[id][event_name] = handler;
|
||||
element.addEventListener(event_name, handler);
|
||||
}
|
||||
}
|
||||
|
||||
remove(element, event_name, bubbles) {
|
||||
if (bubbles) {
|
||||
this.global[event_name].active--;
|
||||
if (this.global[event_name].active === 0) {
|
||||
this.root.removeEventListener(event_name, this.global[event_name].callback);
|
||||
delete this.global[event_name];
|
||||
}
|
||||
}
|
||||
else {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id][event_name];
|
||||
if (this.local[id].length === 0) {
|
||||
delete this.local[id];
|
||||
}
|
||||
element.removeEventListener(event_name, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Interpreter {
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.stack = [root];
|
||||
this.listeners = {};
|
||||
this.templateInProgress = null;
|
||||
this.insideTemplateRef = [];
|
||||
this.listeners = new ListenerMap(root);
|
||||
this.handlers = {};
|
||||
this.lastNodeWasText = false;
|
||||
this.nodes = [root];
|
||||
this.templates = [];
|
||||
}
|
||||
top() {
|
||||
return this.stack[this.stack.length - 1];
|
||||
|
@ -50,88 +222,174 @@ class Interpreter {
|
|||
pop() {
|
||||
return this.stack.pop();
|
||||
}
|
||||
currentTemplateId() {
|
||||
if (this.insideTemplateRef.length) {
|
||||
return this.insideTemplateRef[this.insideTemplateRef.length - 1].id;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
getId(id) {
|
||||
if (this.templateInProgress !== null) {
|
||||
return this.templates[this.templateInProgress].nodes[id - templateIdLimit];
|
||||
}
|
||||
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
|
||||
return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit);
|
||||
}
|
||||
else {
|
||||
return this.nodes[id];
|
||||
}
|
||||
}
|
||||
SetNode(id, node) {
|
||||
if (this.templateInProgress !== null) {
|
||||
id -= templateIdLimit;
|
||||
node.tmplId = id;
|
||||
this.templates[this.templateInProgress].nodes[id] = node;
|
||||
}
|
||||
else if (this.insideTemplateRef.length && id >= templateIdLimit) {
|
||||
id -= templateIdLimit;
|
||||
let last = this.insideTemplateRef[this.insideTemplateRef.length - 1];
|
||||
last.childNodes[id] = node;
|
||||
if (last.nodeCache[id]) {
|
||||
last.nodeCache[id] = node;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.nodes[id] = node;
|
||||
}
|
||||
}
|
||||
PushRoot(root) {
|
||||
const node = this.nodes[root];
|
||||
const node = this.getId(root);
|
||||
this.stack.push(node);
|
||||
}
|
||||
PopRoot() {
|
||||
this.stack.pop();
|
||||
}
|
||||
AppendChildren(many) {
|
||||
let root = this.stack[this.stack.length - (1 + many)];
|
||||
let to_add = this.stack.splice(this.stack.length - many);
|
||||
for (let i = 0; i < many; i++) {
|
||||
root.appendChild(to_add[i]);
|
||||
const child = to_add[i];
|
||||
if (child instanceof TemplateRef) {
|
||||
root.appendChild(child.getFragment());
|
||||
}
|
||||
else {
|
||||
root.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
ReplaceWith(root_id, m) {
|
||||
let root = this.nodes[root_id];
|
||||
let els = this.stack.splice(this.stack.length - m);
|
||||
root.replaceWith(...els);
|
||||
let root = this.getId(root_id);
|
||||
if (root instanceof TemplateRef) {
|
||||
this.InsertBefore(root_id, m);
|
||||
this.Remove(root_id);
|
||||
}
|
||||
else {
|
||||
let els = this.stack.splice(this.stack.length - m).map(function (el) {
|
||||
if (el instanceof TemplateRef) {
|
||||
return el.getFragment();
|
||||
}
|
||||
else {
|
||||
return el;
|
||||
}
|
||||
});
|
||||
root.replaceWith(...els);
|
||||
}
|
||||
}
|
||||
InsertAfter(root, n) {
|
||||
let old = this.nodes[root];
|
||||
let new_nodes = this.stack.splice(this.stack.length - n);
|
||||
old.after(...new_nodes);
|
||||
const old = this.getId(root);
|
||||
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
|
||||
if (el instanceof TemplateRef) {
|
||||
return el.getFragment();
|
||||
}
|
||||
else {
|
||||
return el;
|
||||
}
|
||||
});
|
||||
if (old instanceof TemplateRef) {
|
||||
const last = old.last();
|
||||
last.after(...new_nodes);
|
||||
}
|
||||
else {
|
||||
old.after(...new_nodes);
|
||||
}
|
||||
}
|
||||
InsertBefore(root, n) {
|
||||
let old = this.nodes[root];
|
||||
let new_nodes = this.stack.splice(this.stack.length - n);
|
||||
old.before(...new_nodes);
|
||||
const old = this.getId(root);
|
||||
const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) {
|
||||
if (el instanceof TemplateRef) {
|
||||
return el.getFragment();
|
||||
}
|
||||
else {
|
||||
return el;
|
||||
}
|
||||
});
|
||||
if (old instanceof TemplateRef) {
|
||||
const first = old.first();
|
||||
first.before(...new_nodes);
|
||||
}
|
||||
else {
|
||||
old.before(...new_nodes);
|
||||
}
|
||||
}
|
||||
Remove(root) {
|
||||
let node = this.nodes[root];
|
||||
let node = this.getId(root);
|
||||
if (node !== undefined) {
|
||||
node.remove();
|
||||
if (node instanceof TemplateRef) {
|
||||
for (let child of node.roots) {
|
||||
child.remove();
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateTextNode(text, root) {
|
||||
// todo: make it so the types are okay
|
||||
const node = document.createTextNode(text);
|
||||
this.nodes[root] = node;
|
||||
this.stack.push(node);
|
||||
this.SetNode(root, node);
|
||||
}
|
||||
CreateElement(tag, root) {
|
||||
const el = document.createElement(tag);
|
||||
// el.setAttribute("data-dioxus-id", `${root}`);
|
||||
this.nodes[root] = el;
|
||||
this.stack.push(el);
|
||||
this.SetNode(root, el);
|
||||
}
|
||||
CreateElementNs(tag, root, ns) {
|
||||
let el = document.createElementNS(ns, tag);
|
||||
this.stack.push(el);
|
||||
this.nodes[root] = el;
|
||||
this.SetNode(root, el);
|
||||
}
|
||||
CreatePlaceholder(root) {
|
||||
let el = document.createElement("pre");
|
||||
el.hidden = true;
|
||||
this.stack.push(el);
|
||||
this.nodes[root] = el;
|
||||
this.SetNode(root, el);
|
||||
}
|
||||
NewEventListener(event_name, root, handler) {
|
||||
const element = this.nodes[root];
|
||||
element.setAttribute("data-dioxus-id", `${root}`);
|
||||
if (this.listeners[event_name] === undefined) {
|
||||
this.listeners[event_name] = 0;
|
||||
this.handlers[event_name] = handler;
|
||||
this.root.addEventListener(event_name, handler);
|
||||
} else {
|
||||
this.listeners[event_name]++;
|
||||
NewEventListener(event_name, root, handler, bubbles) {
|
||||
const element = this.getId(root);
|
||||
if (root >= templateIdLimit) {
|
||||
let currentTemplateRefId = this.currentTemplateId();
|
||||
root -= templateIdLimit;
|
||||
element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`);
|
||||
}
|
||||
else {
|
||||
element.setAttribute("data-dioxus-id", `${root}`);
|
||||
}
|
||||
this.listeners.create(event_name, element, handler, bubbles);
|
||||
}
|
||||
RemoveEventListener(root, event_name) {
|
||||
const element = this.nodes[root];
|
||||
RemoveEventListener(root, event_name, bubbles) {
|
||||
const element = this.getId(root);
|
||||
element.removeAttribute(`data-dioxus-id`);
|
||||
this.listeners[event_name]--;
|
||||
if (this.listeners[event_name] === 0) {
|
||||
this.root.removeEventListener(event_name, this.handlers[event_name]);
|
||||
delete this.listeners[event_name];
|
||||
delete this.handlers[event_name];
|
||||
}
|
||||
this.listeners.remove(element, event_name, bubbles);
|
||||
}
|
||||
SetText(root, text) {
|
||||
this.nodes[root].textContent = text;
|
||||
this.getId(root).data = text;
|
||||
}
|
||||
SetAttribute(root, field, value, ns) {
|
||||
const name = field;
|
||||
const node = this.nodes[root];
|
||||
const node = this.getId(root);
|
||||
if (ns === "style") {
|
||||
// @ts-ignore
|
||||
node.style[name] = value;
|
||||
|
@ -163,10 +421,14 @@ class Interpreter {
|
|||
}
|
||||
}
|
||||
}
|
||||
RemoveAttribute(root, name) {
|
||||
const node = this.nodes[root];
|
||||
|
||||
if (name === "value") {
|
||||
RemoveAttribute(root, field, ns) {
|
||||
const name = field;
|
||||
const node = this.getId(root);
|
||||
if (ns == "style") {
|
||||
node.style.removeProperty(name);
|
||||
} else if (ns !== null || ns !== undefined) {
|
||||
node.removeAttributeNS(ns, name);
|
||||
} else if (name === "value") {
|
||||
node.value = "";
|
||||
} else if (name === "checked") {
|
||||
node.checked = false;
|
||||
|
@ -178,46 +440,94 @@ class Interpreter {
|
|||
node.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
CreateTemplateRef(id, template_id) {
|
||||
const el = this.templates[template_id].ref(id);
|
||||
this.nodes[id] = el;
|
||||
this.stack.push(el);
|
||||
}
|
||||
CreateTemplate(template_id) {
|
||||
this.templateInProgress = template_id;
|
||||
this.templates[template_id] = new Template(template_id, 0);
|
||||
}
|
||||
FinishTemplate(many) {
|
||||
this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many));
|
||||
this.templateInProgress = null;
|
||||
}
|
||||
EnterTemplateRef(id) {
|
||||
this.insideTemplateRef.push(this.nodes[id]);
|
||||
}
|
||||
ExitTemplateRef() {
|
||||
this.insideTemplateRef.pop();
|
||||
}
|
||||
handleEdits(edits) {
|
||||
this.stack.push(this.root);
|
||||
for (let edit of edits) {
|
||||
this.handleEdit(edit);
|
||||
}
|
||||
}
|
||||
CreateElementTemplate(tag, root, locally_static, fully_static) {
|
||||
const el = document.createElement(tag);
|
||||
this.stack.push(el);
|
||||
this.SetNode(root, el);
|
||||
if (!locally_static)
|
||||
el.setAttribute("data-dioxus-dynamic", "true");
|
||||
if (fully_static)
|
||||
el.setAttribute("data-dioxus-fully-static", fully_static);
|
||||
}
|
||||
CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) {
|
||||
const el = document.createElementNS(ns, tag);
|
||||
this.stack.push(el);
|
||||
this.SetNode(root, el);
|
||||
if (!locally_static)
|
||||
el.setAttribute("data-dioxus-dynamic", "true");
|
||||
if (fully_static)
|
||||
el.setAttribute("data-dioxus-fully-static", fully_static);
|
||||
}
|
||||
CreateTextNodeTemplate(text, root, locally_static) {
|
||||
const node = document.createTextNode(text);
|
||||
this.stack.push(node);
|
||||
this.SetNode(root, node);
|
||||
}
|
||||
CreatePlaceholderTemplate(root) {
|
||||
const el = document.createElement("pre");
|
||||
el.setAttribute("data-dioxus-dynamic", "true");
|
||||
el.hidden = true;
|
||||
this.stack.push(el);
|
||||
this.SetNode(root, el);
|
||||
}
|
||||
handleEdit(edit) {
|
||||
switch (edit.type) {
|
||||
case "PushRoot":
|
||||
this.PushRoot(edit.root);
|
||||
this.PushRoot(BigInt(edit.root));
|
||||
break;
|
||||
case "AppendChildren":
|
||||
this.AppendChildren(edit.many);
|
||||
break;
|
||||
case "ReplaceWith":
|
||||
this.ReplaceWith(edit.root, edit.m);
|
||||
this.ReplaceWith(BigInt(edit.root), edit.m);
|
||||
break;
|
||||
case "InsertAfter":
|
||||
this.InsertAfter(edit.root, edit.n);
|
||||
this.InsertAfter(BigInt(edit.root), edit.n);
|
||||
break;
|
||||
case "InsertBefore":
|
||||
this.InsertBefore(edit.root, edit.n);
|
||||
this.InsertBefore(BigInt(edit.root), edit.n);
|
||||
break;
|
||||
case "Remove":
|
||||
this.Remove(edit.root);
|
||||
this.Remove(BigInt(edit.root));
|
||||
break;
|
||||
case "CreateTextNode":
|
||||
this.CreateTextNode(edit.text, edit.root);
|
||||
this.CreateTextNode(edit.text, BigInt(edit.root));
|
||||
break;
|
||||
case "CreateElement":
|
||||
this.CreateElement(edit.tag, edit.root);
|
||||
this.CreateElement(edit.tag, BigInt(edit.root));
|
||||
break;
|
||||
case "CreateElementNs":
|
||||
this.CreateElementNs(edit.tag, edit.root, edit.ns);
|
||||
this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns);
|
||||
break;
|
||||
case "CreatePlaceholder":
|
||||
this.CreatePlaceholder(edit.root);
|
||||
this.CreatePlaceholder(BigInt(edit.root));
|
||||
break;
|
||||
case "RemoveEventListener":
|
||||
this.RemoveEventListener(edit.root, edit.event_name);
|
||||
this.RemoveEventListener(BigInt(edit.root), edit.event_name);
|
||||
break;
|
||||
case "NewEventListener":
|
||||
// this handler is only provided on desktop implementations since this
|
||||
|
@ -245,7 +555,7 @@ class Interpreter {
|
|||
}
|
||||
|
||||
// also prevent buttons from submitting
|
||||
if (target.tagName === "BUTTON") {
|
||||
if (target.tagName === "BUTTON" && event.type == "submit") {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
@ -269,11 +579,15 @@ class Interpreter {
|
|||
if (shouldPreventDefault === `on${event.type}`) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.type === "submit") {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (target.tagName === "FORM") {
|
||||
if (
|
||||
target.tagName === "FORM" &&
|
||||
(event.type === "submit" || event.type === "input")
|
||||
) {
|
||||
for (let x = 0; x < target.elements.length; x++) {
|
||||
let element = target.elements[x];
|
||||
let name = element.getAttribute("name");
|
||||
|
@ -281,6 +595,10 @@ class Interpreter {
|
|||
if (element.getAttribute("type") === "checkbox") {
|
||||
// @ts-ignore
|
||||
contents.values[name] = element.checked ? "true" : "false";
|
||||
} else if (element.getAttribute("type") === "radio") {
|
||||
if (element.checked) {
|
||||
contents.values[name] = element.value;
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore
|
||||
contents.values[name] =
|
||||
|
@ -290,28 +608,69 @@ class Interpreter {
|
|||
}
|
||||
}
|
||||
|
||||
if (realId == null) {
|
||||
if (realId === null) {
|
||||
return;
|
||||
}
|
||||
if (realId.includes(",")) {
|
||||
realId = realId.split(',');
|
||||
realId = {
|
||||
template_ref_id: parseInt(realId[0]),
|
||||
template_node_id: parseInt(realId[1]),
|
||||
};
|
||||
}
|
||||
else {
|
||||
realId = parseInt(realId);
|
||||
}
|
||||
window.ipc.send(
|
||||
serializeIpcMessage("user_event", {
|
||||
event: edit.event_name,
|
||||
mounted_dom_id: parseInt(realId),
|
||||
mounted_dom_id: realId,
|
||||
contents: contents,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
this.NewEventListener(edit.event_name, edit.root, handler);
|
||||
this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name));
|
||||
|
||||
break;
|
||||
case "SetText":
|
||||
this.SetText(edit.root, edit.text);
|
||||
this.SetText(BigInt(edit.root), edit.text);
|
||||
break;
|
||||
case "SetAttribute":
|
||||
this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
|
||||
this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns);
|
||||
break;
|
||||
case "RemoveAttribute":
|
||||
this.RemoveAttribute(edit.root, edit.name);
|
||||
this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns);
|
||||
break;
|
||||
case "PopRoot":
|
||||
this.PopRoot();
|
||||
break;
|
||||
case "CreateTemplateRef":
|
||||
this.CreateTemplateRef(BigInt(edit.id), edit.template_id);
|
||||
break;
|
||||
case "CreateTemplate":
|
||||
this.CreateTemplate(BigInt(edit.id));
|
||||
break;
|
||||
case "FinishTemplate":
|
||||
this.FinishTemplate(edit.len);
|
||||
break;
|
||||
case "EnterTemplateRef":
|
||||
this.EnterTemplateRef(BigInt(edit.root));
|
||||
break;
|
||||
case "ExitTemplateRef":
|
||||
this.ExitTemplateRef();
|
||||
break;
|
||||
case "CreateElementTemplate":
|
||||
this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static);
|
||||
break;
|
||||
case "CreateElementNsTemplate":
|
||||
this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static);
|
||||
break;
|
||||
case "CreateTextNodeTemplate":
|
||||
this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static);
|
||||
break;
|
||||
case "CreatePlaceholderTemplate":
|
||||
this.CreatePlaceholderTemplate(BigInt(edit.root));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -346,6 +705,7 @@ function serialize_event(event) {
|
|||
location,
|
||||
repeat,
|
||||
which,
|
||||
code,
|
||||
} = event;
|
||||
return {
|
||||
char_code: charCode,
|
||||
|
@ -358,6 +718,7 @@ function serialize_event(event) {
|
|||
location: location,
|
||||
repeat: repeat,
|
||||
which: which,
|
||||
code,
|
||||
};
|
||||
}
|
||||
case "focus":
|
||||
|
@ -383,9 +744,11 @@ function serialize_event(event) {
|
|||
case "submit": {
|
||||
let target = event.target;
|
||||
let value = target.value ?? target.textContent;
|
||||
|
||||
if (target.type === "checkbox") {
|
||||
value = target.checked ? "true" : "false";
|
||||
}
|
||||
|
||||
return {
|
||||
value: value,
|
||||
values: {},
|
||||
|
@ -394,6 +757,7 @@ function serialize_event(event) {
|
|||
case "click":
|
||||
case "contextmenu":
|
||||
case "doubleclick":
|
||||
case "dblclick":
|
||||
case "drag":
|
||||
case "dragend":
|
||||
case "dragenter":
|
||||
|
@ -613,3 +977,176 @@ const bool_attrs = {
|
|||
selected: true,
|
||||
truespeed: true,
|
||||
};
|
||||
|
||||
function is_element_node(node) {
|
||||
return node.nodeType == 1;
|
||||
}
|
||||
|
||||
function event_bubbles(event) {
|
||||
switch (event) {
|
||||
case "copy":
|
||||
return true;
|
||||
case "cut":
|
||||
return true;
|
||||
case "paste":
|
||||
return true;
|
||||
case "compositionend":
|
||||
return true;
|
||||
case "compositionstart":
|
||||
return true;
|
||||
case "compositionupdate":
|
||||
return true;
|
||||
case "keydown":
|
||||
return true;
|
||||
case "keypress":
|
||||
return true;
|
||||
case "keyup":
|
||||
return true;
|
||||
case "focus":
|
||||
return false;
|
||||
case "focusout":
|
||||
return true;
|
||||
case "focusin":
|
||||
return true;
|
||||
case "blur":
|
||||
return false;
|
||||
case "change":
|
||||
return true;
|
||||
case "input":
|
||||
return true;
|
||||
case "invalid":
|
||||
return true;
|
||||
case "reset":
|
||||
return true;
|
||||
case "submit":
|
||||
return true;
|
||||
case "click":
|
||||
return true;
|
||||
case "contextmenu":
|
||||
return true;
|
||||
case "doubleclick":
|
||||
return true;
|
||||
case "dblclick":
|
||||
return true;
|
||||
case "drag":
|
||||
return true;
|
||||
case "dragend":
|
||||
return true;
|
||||
case "dragenter":
|
||||
return false;
|
||||
case "dragexit":
|
||||
return false;
|
||||
case "dragleave":
|
||||
return true;
|
||||
case "dragover":
|
||||
return true;
|
||||
case "dragstart":
|
||||
return true;
|
||||
case "drop":
|
||||
return true;
|
||||
case "mousedown":
|
||||
return true;
|
||||
case "mouseenter":
|
||||
return false;
|
||||
case "mouseleave":
|
||||
return false;
|
||||
case "mousemove":
|
||||
return true;
|
||||
case "mouseout":
|
||||
return true;
|
||||
case "scroll":
|
||||
return false;
|
||||
case "mouseover":
|
||||
return true;
|
||||
case "mouseup":
|
||||
return true;
|
||||
case "pointerdown":
|
||||
return true;
|
||||
case "pointermove":
|
||||
return true;
|
||||
case "pointerup":
|
||||
return true;
|
||||
case "pointercancel":
|
||||
return true;
|
||||
case "gotpointercapture":
|
||||
return true;
|
||||
case "lostpointercapture":
|
||||
return true;
|
||||
case "pointerenter":
|
||||
return false;
|
||||
case "pointerleave":
|
||||
return false;
|
||||
case "pointerover":
|
||||
return true;
|
||||
case "pointerout":
|
||||
return true;
|
||||
case "select":
|
||||
return true;
|
||||
case "touchcancel":
|
||||
return true;
|
||||
case "touchend":
|
||||
return true;
|
||||
case "touchmove":
|
||||
return true;
|
||||
case "touchstart":
|
||||
return true;
|
||||
case "wheel":
|
||||
return true;
|
||||
case "abort":
|
||||
return false;
|
||||
case "canplay":
|
||||
return false;
|
||||
case "canplaythrough":
|
||||
return false;
|
||||
case "durationchange":
|
||||
return false;
|
||||
case "emptied":
|
||||
return false;
|
||||
case "encrypted":
|
||||
return true;
|
||||
case "ended":
|
||||
return false;
|
||||
case "error":
|
||||
return false;
|
||||
case "loadeddata":
|
||||
return false;
|
||||
case "loadedmetadata":
|
||||
return false;
|
||||
case "loadstart":
|
||||
return false;
|
||||
case "pause":
|
||||
return false;
|
||||
case "play":
|
||||
return false;
|
||||
case "playing":
|
||||
return false;
|
||||
case "progress":
|
||||
return false;
|
||||
case "ratechange":
|
||||
return false;
|
||||
case "seeked":
|
||||
return false;
|
||||
case "seeking":
|
||||
return false;
|
||||
case "stalled":
|
||||
return false;
|
||||
case "suspend":
|
||||
return false;
|
||||
case "timeupdate":
|
||||
return false;
|
||||
case "volumechange":
|
||||
return false;
|
||||
case "waiting":
|
||||
return false;
|
||||
case "animationstart":
|
||||
return true;
|
||||
case "animationend":
|
||||
return true;
|
||||
case "animationiteration":
|
||||
return true;
|
||||
case "transitionend":
|
||||
return true;
|
||||
case "toggle":
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -164,20 +164,20 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|||
|
||||
let gen = quote! {
|
||||
impl State for #type_name {
|
||||
fn update<'a, T: dioxus_native_core::traversable::Traversable<Node = Self, Id = dioxus_core::ElementId>>(
|
||||
dirty: &[(dioxus_core::ElementId, dioxus_native_core::node_ref::NodeMask)],
|
||||
fn update<'a, T: dioxus_native_core::traversable::Traversable<Node = Self, Id = dioxus_core::GlobalNodeId>,T2: dioxus_native_core::traversable::Traversable<Node = dioxus_native_core::real_dom::NodeData, Id = dioxus_core::GlobalNodeId>>(
|
||||
dirty: &[(dioxus_core::GlobalNodeId, dioxus_native_core::node_ref::NodeMask)],
|
||||
state_tree: &'a mut T,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
rdom: &'a T2,
|
||||
ctx: &anymap::AnyMap,
|
||||
) -> fxhash::FxHashSet<dioxus_core::ElementId>{
|
||||
) -> fxhash::FxHashSet<dioxus_core::GlobalNodeId>{
|
||||
#[derive(Eq, PartialEq)]
|
||||
struct HeightOrdering {
|
||||
height: u16,
|
||||
id: dioxus_core::ElementId,
|
||||
id: dioxus_core::GlobalNodeId,
|
||||
}
|
||||
|
||||
impl HeightOrdering {
|
||||
fn new(height: u16, id: dioxus_core::ElementId) -> Self {
|
||||
fn new(height: u16, id: dioxus_core::GlobalNodeId) -> Self {
|
||||
HeightOrdering {
|
||||
height,
|
||||
id,
|
||||
|
@ -185,9 +185,29 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
// not the ordering after height is just for deduplication it can be any ordering as long as it is consistent
|
||||
impl Ord for HeightOrdering {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.height.cmp(&other.height).then(self.id.0.cmp(&other.id.0))
|
||||
self.height.cmp(&other.height).then(match (self.id, other.id){
|
||||
(
|
||||
dioxus_core::GlobalNodeId::TemplateId {
|
||||
template_ref_id,
|
||||
template_node_id,
|
||||
},
|
||||
dioxus_core::GlobalNodeId::TemplateId {
|
||||
template_ref_id: o_template_ref_id,
|
||||
template_node_id: o_template_node_id,
|
||||
},
|
||||
) => template_ref_id
|
||||
.0
|
||||
.cmp(&o_template_ref_id.0)
|
||||
.then(template_node_id.0.cmp(&o_template_node_id.0)),
|
||||
(dioxus_core::GlobalNodeId::TemplateId { .. }, dioxus_core::GlobalNodeId::VNodeId(_)) => std::cmp::Ordering::Less,
|
||||
(dioxus_core::GlobalNodeId::VNodeId(_), dioxus_core::GlobalNodeId::TemplateId { .. }) => {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
(dioxus_core::GlobalNodeId::VNodeId(s_id), dioxus_core::GlobalNodeId::VNodeId(o_id)) => s_id.0.cmp(&o_id.0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,7 +238,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|||
|
||||
let mut dirty_elements = fxhash::FxHashSet::default();
|
||||
// the states of any elements that are dirty
|
||||
let mut states: fxhash::FxHashMap<dioxus_core::ElementId, MembersDirty> = fxhash::FxHashMap::default();
|
||||
let mut states: fxhash::FxHashMap<dioxus_core::GlobalNodeId, MembersDirty> = fxhash::FxHashMap::default();
|
||||
|
||||
for (id, mask) in dirty {
|
||||
let members_dirty = MembersDirty {
|
||||
|
@ -244,7 +264,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|||
<#child_types as dioxus_native_core::state::State>::update(
|
||||
dirty,
|
||||
&mut state_tree.map(|n| &n.#child_members, |n| &mut n.#child_members),
|
||||
vdom,
|
||||
rdom,
|
||||
ctx,
|
||||
)
|
||||
);
|
||||
|
@ -493,7 +513,7 @@ impl<'a> StateStruct<'a> {
|
|||
let mut i = 0;
|
||||
while i < resolution_order.len(){
|
||||
let id = resolution_order[i].id;
|
||||
let vnode = vdom.get_element(id).unwrap();
|
||||
let node = rdom.get(id).unwrap();
|
||||
let members_dirty = states.get_mut(&id).unwrap();
|
||||
let (current_state, parent) = state_tree.get_node_parent_mut(id);
|
||||
let current_state = current_state.unwrap();
|
||||
|
@ -515,7 +535,7 @@ impl<'a> StateStruct<'a> {
|
|||
let mut i = 0;
|
||||
while i < resolution_order.len(){
|
||||
let id = resolution_order[i].id;
|
||||
let vnode = vdom.get_element(id).unwrap();
|
||||
let node = rdom.get(id).unwrap();
|
||||
let members_dirty = states.get_mut(&id).unwrap();
|
||||
let (current_state, children) = state_tree.get_node_children_mut(id);
|
||||
let current_state = current_state.unwrap();
|
||||
|
@ -534,7 +554,7 @@ impl<'a> StateStruct<'a> {
|
|||
let mut i = 0;
|
||||
while i < resolution_order.len(){
|
||||
let id = resolution_order[i];
|
||||
let vnode = vdom.get_element(id).unwrap();
|
||||
let node = rdom.get(id).unwrap();
|
||||
let members_dirty = states.get_mut(&id).unwrap();
|
||||
let current_state = state_tree.get_mut(id).unwrap();
|
||||
if members_dirty.#member && #reduce_member {
|
||||
|
@ -679,8 +699,7 @@ impl<'a> StateMember<'a> {
|
|||
};
|
||||
|
||||
let ty = &self.mem.ty;
|
||||
let node_view =
|
||||
quote!(dioxus_native_core::node_ref::NodeView::new(vnode, #ty::NODE_MASK, vdom));
|
||||
let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK));
|
||||
let dep_idents = self.dep_mems.iter().map(|m| &m.0.ident);
|
||||
match self.dep_kind {
|
||||
DependencyKind::Node => {
|
||||
|
|
|
@ -109,7 +109,7 @@ macro_rules! test_state{
|
|||
let mut dom: RealDom<$s> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
$(
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use dioxus::core as dioxus_core;
|
||||
use dioxus::core::{ElementId, VElement};
|
||||
use dioxus::core::{self as dioxus_core, GlobalNodeId};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::real_dom::RealDom;
|
||||
use dioxus_native_core::state::State;
|
||||
use dioxus_native_core_macro::State;
|
||||
use std::cell::Cell;
|
||||
|
||||
#[derive(State, Default, Clone)]
|
||||
struct Empty {}
|
||||
|
@ -18,44 +16,8 @@ fn remove_node() {
|
|||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div{
|
||||
div{}
|
||||
}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let child_div = VElement {
|
||||
id: Cell::new(Some(ElementId(2))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(1))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
let child_div_el = VNode::Element(&child_div);
|
||||
let root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[child_div_el],
|
||||
};
|
||||
|
||||
assert_eq!(dom.size(), 2);
|
||||
assert!(&dom.contains_node(&VNode::Element(&root_div)));
|
||||
assert_eq!(dom[ElementId(1)].height, 1);
|
||||
assert_eq!(dom[ElementId(2)].height, 2);
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
let mutations = vdom.diff_lazynodes(
|
||||
let (create, edit) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
div{
|
||||
div{}
|
||||
|
@ -65,22 +27,43 @@ fn remove_node() {
|
|||
div{}
|
||||
},
|
||||
);
|
||||
dom.apply_mutations(vec![mutations.1]);
|
||||
|
||||
let new_root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
println!("create: {:#?}", create);
|
||||
println!("edit: {:#?}", edit);
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![create]);
|
||||
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(0),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(1),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
2
|
||||
);
|
||||
|
||||
dom.apply_mutations(vec![edit]);
|
||||
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
|
||||
assert_eq!(dom[ElementId(1)].height, 1);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(2),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(0),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -92,31 +75,7 @@ fn add_node() {
|
|||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
div{}
|
||||
});
|
||||
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
|
||||
let root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert!(&dom.contains_node(&VNode::Element(&root_div)));
|
||||
assert_eq!(dom[ElementId(1)].height, 1);
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
let mutations = vdom.diff_lazynodes(
|
||||
let (create, update) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
div{}
|
||||
},
|
||||
|
@ -126,32 +85,41 @@ fn add_node() {
|
|||
}
|
||||
},
|
||||
);
|
||||
dom.apply_mutations(vec![mutations.1]);
|
||||
|
||||
let child_div = VElement {
|
||||
id: Cell::new(Some(ElementId(2))),
|
||||
key: None,
|
||||
tag: "p",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(1))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
let child_div_el = VNode::Element(&child_div);
|
||||
let new_root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[child_div_el],
|
||||
};
|
||||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
assert_eq!(dom.size(), 2);
|
||||
assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
|
||||
assert_eq!(dom[ElementId(1)].height, 1);
|
||||
assert_eq!(dom[ElementId(2)].height, 2);
|
||||
let _to_update = dom.apply_mutations(vec![create]);
|
||||
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(0),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
1
|
||||
);
|
||||
|
||||
dom.apply_mutations(vec![update]);
|
||||
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(2),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(0),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(2),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(1),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use std::cell::Cell;
|
||||
|
||||
use dioxus::core as dioxus_core;
|
||||
use dioxus::core::{ElementId, VElement, VText};
|
||||
use dioxus::core::{self as dioxus_core, GlobalNodeId};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::real_dom::RealDom;
|
||||
use dioxus_native_core::state::State;
|
||||
|
@ -12,8 +9,6 @@ struct Empty {}
|
|||
|
||||
#[test]
|
||||
fn initial_build_simple() {
|
||||
use std::cell::Cell;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
|
@ -28,19 +23,17 @@ fn initial_build_simple() {
|
|||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[],
|
||||
};
|
||||
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert!(&dom.contains_node(&VNode::Element(&root_div)));
|
||||
assert_eq!(dom[ElementId(1)].height, 1);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(0),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -67,62 +60,59 @@ fn initial_build_with_children() {
|
|||
let mut dom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let _to_update = dom.apply_mutations(vec![mutations]);
|
||||
let first_text = VText {
|
||||
id: Cell::new(Some(ElementId(3))),
|
||||
text: "hello",
|
||||
is_static: true,
|
||||
};
|
||||
let first_text_node = VNode::Text(&first_text);
|
||||
let child_text = VText {
|
||||
id: Cell::new(Some(ElementId(5))),
|
||||
text: "world",
|
||||
is_static: true,
|
||||
};
|
||||
let child_text_node = VNode::Text(&child_text);
|
||||
let child_p_el = VElement {
|
||||
id: Cell::new(Some(ElementId(4))),
|
||||
key: None,
|
||||
tag: "p",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(2))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[child_text_node],
|
||||
};
|
||||
let child_p_node = VNode::Element(&child_p_el);
|
||||
let second_text = VText {
|
||||
id: Cell::new(Some(ElementId(6))),
|
||||
text: "hello world",
|
||||
is_static: true,
|
||||
};
|
||||
let second_text_node = VNode::Text(&second_text);
|
||||
let child_div_el = VElement {
|
||||
id: Cell::new(Some(ElementId(2))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(1))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[first_text_node, child_p_node, second_text_node],
|
||||
};
|
||||
let child_div_node = VNode::Element(&child_div_el);
|
||||
let root_div = VElement {
|
||||
id: Cell::new(Some(ElementId(1))),
|
||||
key: None,
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
parent: Cell::new(Some(ElementId(0))),
|
||||
listeners: &[],
|
||||
attributes: &[],
|
||||
children: &[child_div_node],
|
||||
};
|
||||
assert_eq!(dom.size(), 6);
|
||||
assert!(&dom.contains_node(&VNode::Element(&root_div)));
|
||||
assert_eq!(dom[ElementId(1)].height, 1);
|
||||
assert_eq!(dom[ElementId(2)].height, 2);
|
||||
assert_eq!(dom[ElementId(3)].height, 3);
|
||||
assert_eq!(dom[ElementId(4)].height, 3);
|
||||
assert_eq!(dom[ElementId(5)].height, 4);
|
||||
assert_eq!(dom[ElementId(6)].height, 3);
|
||||
assert_eq!(dom.size(), 1);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(0),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(1),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(2),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
3
|
||||
);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(3),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
3
|
||||
);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(4),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
4
|
||||
);
|
||||
assert_eq!(
|
||||
dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(5),
|
||||
}]
|
||||
.node_data
|
||||
.height,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use dioxus::core as dioxus_core;
|
||||
use dioxus::core_macro::rsx_without_templates;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::{
|
||||
real_dom::{NodeType, RealDom},
|
||||
|
@ -37,68 +38,68 @@ fn traverse() {
|
|||
let mut iter = PersistantElementIter::new();
|
||||
let div_tag = "div".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_type,
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_type,
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
));
|
||||
let text1 = "hello".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_type,
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text1, .. }
|
||||
));
|
||||
let p_tag = "p".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_type,
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
));
|
||||
let text2 = "world".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_type,
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text2, .. }
|
||||
));
|
||||
let text3 = "hello world".to_string();
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_type,
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text3, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.next(&rdom).id()].node_type,
|
||||
&rdom[iter.next(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_type,
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text3, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_type,
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text2, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_type,
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_type,
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text1, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_type,
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_type,
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_type,
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
&rdom[iter.prev(&rdom).id()].node_type,
|
||||
&rdom[iter.prev(&rdom).id()].node_data.node_type,
|
||||
NodeType::Text { text: text3, .. }
|
||||
));
|
||||
}
|
||||
|
@ -112,7 +113,7 @@ fn persist_removes() {
|
|||
}
|
||||
let vdom = VirtualDom::new(Base);
|
||||
let (build, update) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
p{
|
||||
key: "1",
|
||||
|
@ -128,7 +129,7 @@ fn persist_removes() {
|
|||
}
|
||||
}
|
||||
},
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
p{
|
||||
key: "1",
|
||||
|
@ -177,16 +178,19 @@ fn persist_removes() {
|
|||
let p_tag = "p".to_string();
|
||||
let idx = iter1.next(&rdom).id();
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_type,
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
));
|
||||
let text = "hello world".to_string();
|
||||
let idx = iter1.next(&rdom).id();
|
||||
assert!(matches!(&rdom[idx].node_type, NodeType::Text { text, .. }));
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Text { text, .. }
|
||||
));
|
||||
let div_tag = "div".to_string();
|
||||
let idx = iter2.next(&rdom).id();
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_type,
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
));
|
||||
}
|
||||
|
@ -200,7 +204,7 @@ fn persist_instertions_before() {
|
|||
}
|
||||
let vdom = VirtualDom::new(Base);
|
||||
let (build, update) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
p{
|
||||
key: "1",
|
||||
|
@ -212,7 +216,7 @@ fn persist_instertions_before() {
|
|||
}
|
||||
}
|
||||
},
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
p{
|
||||
key: "1",
|
||||
|
@ -252,7 +256,7 @@ fn persist_instertions_before() {
|
|||
let p_tag = "div".to_string();
|
||||
let idx = iter.next(&rdom).id();
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_type,
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
));
|
||||
}
|
||||
|
@ -266,7 +270,7 @@ fn persist_instertions_after() {
|
|||
}
|
||||
let vdom = VirtualDom::new(Base);
|
||||
let (build, update) = vdom.diff_lazynodes(
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
p{
|
||||
key: "1",
|
||||
|
@ -278,7 +282,7 @@ fn persist_instertions_after() {
|
|||
}
|
||||
}
|
||||
},
|
||||
rsx! {
|
||||
rsx_without_templates! {
|
||||
div{
|
||||
p{
|
||||
key: "1",
|
||||
|
@ -318,10 +322,13 @@ fn persist_instertions_after() {
|
|||
let p_tag = "p".to_string();
|
||||
let idx = iter.next(&rdom).id();
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_type,
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
));
|
||||
let text = "hello world".to_string();
|
||||
let idx = iter.next(&rdom).id();
|
||||
assert!(matches!(&rdom[idx].node_type, NodeType::Text { text, .. }));
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Text { text, .. }
|
||||
));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use anymap::AnyMap;
|
||||
use dioxus::core as dioxus_core;
|
||||
use dioxus::core::ElementId;
|
||||
use dioxus::core::{self as dioxus_core, GlobalNodeId};
|
||||
use dioxus::core::{AttributeValue, DomEdit, Mutations};
|
||||
use dioxus::core_macro::rsx_without_templates;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_native_core::node_ref::*;
|
||||
use dioxus_native_core::real_dom::*;
|
||||
|
@ -50,14 +51,13 @@ impl ChildDepState for ChildDepCallCounter {
|
|||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
_: NodeView,
|
||||
_: impl Iterator<Item = &'a Self::DepState>,
|
||||
_: &Self::Ctx,
|
||||
) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
println!("{self:?} {:?}: {} {:?}", node.tag(), node.id(), node.text());
|
||||
self.0 += 1;
|
||||
true
|
||||
}
|
||||
|
@ -142,8 +142,16 @@ impl NodeDepState<()> for NodeStateTester {
|
|||
*self = NodeStateTester(
|
||||
node.tag().map(|s| s.to_string()),
|
||||
node.attributes()
|
||||
.map(|a| (a.name.to_string(), a.value.to_string()))
|
||||
.collect(),
|
||||
.map(|iter| {
|
||||
iter.map(|a| {
|
||||
(
|
||||
a.attribute.name.to_string(),
|
||||
format!("{}", a.value.as_text().unwrap()),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
true
|
||||
}
|
||||
|
@ -185,9 +193,12 @@ fn state_initial() {
|
|||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let mut ctx = AnyMap::new();
|
||||
ctx.insert(42u32);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx);
|
||||
let _to_rerender = dom.update_state(nodes_updated, ctx);
|
||||
|
||||
let root_div = &dom[ElementId(1)];
|
||||
let root_div = &dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(0),
|
||||
}];
|
||||
assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
|
||||
assert_eq!(
|
||||
root_div.state.bubbled.1,
|
||||
|
@ -199,12 +210,18 @@ fn state_initial() {
|
|||
assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
|
||||
assert_eq!(
|
||||
root_div.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("Root".to_string()),
|
||||
None
|
||||
)))
|
||||
);
|
||||
assert_eq!(root_div.state.node.0, Some("div".to_string()));
|
||||
assert_eq!(root_div.state.node.1, vec![]);
|
||||
|
||||
let child_p = &dom[ElementId(2)];
|
||||
let child_p = &dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(1),
|
||||
}];
|
||||
assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
|
||||
assert_eq!(child_p.state.bubbled.1, Vec::new());
|
||||
assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
|
||||
|
@ -212,7 +229,10 @@ fn state_initial() {
|
|||
child_p.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("Root".to_string()),
|
||||
None
|
||||
)))
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_p.state.node.0, Some("p".to_string()));
|
||||
|
@ -221,7 +241,10 @@ fn state_initial() {
|
|||
vec![("color".to_string(), "red".to_string())]
|
||||
);
|
||||
|
||||
let child_h1 = &dom[ElementId(3)];
|
||||
let child_h1 = &dom[GlobalNodeId::TemplateId {
|
||||
template_ref_id: dioxus_core::ElementId(1),
|
||||
template_node_id: dioxus::prelude::TemplateNodeId(2),
|
||||
}];
|
||||
assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
|
||||
assert_eq!(child_h1.state.bubbled.1, Vec::new());
|
||||
assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
|
||||
|
@ -229,7 +252,10 @@ fn state_initial() {
|
|||
child_h1.state.pushed.1,
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("div".to_string()),
|
||||
Some(Box::new(PushedDownStateTester(None, None)))
|
||||
Some(Box::new(PushedDownStateTester(
|
||||
Some("Root".to_string()),
|
||||
None
|
||||
)))
|
||||
)))
|
||||
);
|
||||
assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
|
||||
|
@ -261,7 +287,7 @@ fn state_reduce_parent_called_minimally_on_update() {
|
|||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
let mutations = vdom.create_vnodes(rsx_without_templates! {
|
||||
div {
|
||||
width: "100%",
|
||||
div{
|
||||
|
@ -284,7 +310,7 @@ fn state_reduce_parent_called_minimally_on_update() {
|
|||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
|
||||
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
||||
edits: vec![DomEdit::SetAttribute {
|
||||
root: 1,
|
||||
|
@ -295,7 +321,7 @@ fn state_reduce_parent_called_minimally_on_update() {
|
|||
dirty_scopes: fxhash::FxHashSet::default(),
|
||||
refs: Vec::new(),
|
||||
}]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
assert_eq!(n.state.part2.parent_counter.0, 2);
|
||||
|
@ -307,7 +333,7 @@ fn state_reduce_parent_called_minimally_on_update() {
|
|||
fn state_reduce_child_called_minimally_on_update() {
|
||||
#[allow(non_snake_case)]
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {
|
||||
cx.render(rsx_without_templates!(div {
|
||||
div{
|
||||
div{
|
||||
p{
|
||||
|
@ -324,12 +350,12 @@ fn state_reduce_child_called_minimally_on_update() {
|
|||
"world"
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
let vdom = VirtualDom::new(Base);
|
||||
|
||||
let mutations = vdom.create_vnodes(rsx! {
|
||||
let mutations = vdom.create_vnodes(rsx_without_templates! {
|
||||
div {
|
||||
div{
|
||||
div{
|
||||
|
@ -353,7 +379,7 @@ fn state_reduce_child_called_minimally_on_update() {
|
|||
let mut dom: RealDom<CallCounterState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
|
||||
let nodes_updated = dom.apply_mutations(vec![Mutations {
|
||||
edits: vec![DomEdit::SetAttribute {
|
||||
root: 4,
|
||||
|
@ -364,15 +390,33 @@ fn state_reduce_child_called_minimally_on_update() {
|
|||
dirty_scopes: fxhash::FxHashSet::default(),
|
||||
refs: Vec::new(),
|
||||
}]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
|
||||
|
||||
dom.traverse_depth_first(|n| {
|
||||
println!("{:?}", n);
|
||||
assert_eq!(
|
||||
n.state.part1.child_counter.0,
|
||||
if n.id.0 > 4 { 1 } else { 2 }
|
||||
if let GlobalNodeId::VNodeId(ElementId(id)) = n.node_data.id {
|
||||
if id > 4 {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
n.state.child_counter.0,
|
||||
if let GlobalNodeId::VNodeId(ElementId(id)) = n.node_data.id {
|
||||
if id > 4 {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
);
|
||||
assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -457,7 +501,7 @@ fn dependancies_order_independant() {
|
|||
let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
|
||||
|
||||
let nodes_updated = dom.apply_mutations(vec![mutations]);
|
||||
let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
|
||||
let _to_rerender = dom.update_state(nodes_updated, AnyMap::new());
|
||||
|
||||
let c = CDepCallCounter(1);
|
||||
let b = BDepCallCounter(1, c.clone());
|
||||
|
|
|
@ -1 +1 @@
|
|||
# Dioxus Native-Core: An ECS approach to GUI state and trees
|
||||
# Dioxus Native-Core: An lazy approach to GUI state and trees
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod layout_attributes;
|
|||
pub mod node_ref;
|
||||
pub mod real_dom;
|
||||
pub mod state;
|
||||
pub mod template;
|
||||
#[doc(hidden)]
|
||||
pub mod traversable;
|
||||
pub mod utils;
|
||||
|
|
|
@ -1,37 +1,39 @@
|
|||
use dioxus_core::*;
|
||||
|
||||
use crate::state::union_ordered_iter;
|
||||
use crate::{
|
||||
real_dom::{NodeData, NodeType, OwnedAttributeView},
|
||||
state::union_ordered_iter,
|
||||
};
|
||||
|
||||
/// A view into a [VNode] with limited access.
|
||||
#[derive(Debug)]
|
||||
pub struct NodeView<'a> {
|
||||
inner: &'a VNode<'a>,
|
||||
inner: &'a NodeData,
|
||||
mask: NodeMask,
|
||||
}
|
||||
|
||||
impl<'a> NodeView<'a> {
|
||||
/// Create a new NodeView from a VNode, and mask.
|
||||
pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self {
|
||||
if let VNode::Component(sc) = vnode {
|
||||
let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
|
||||
vnode = scope.root_node();
|
||||
}
|
||||
pub fn new(node: &'a NodeData, view: NodeMask) -> Self {
|
||||
Self {
|
||||
inner: vnode,
|
||||
inner: node,
|
||||
mask: view,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the id of the node
|
||||
pub fn id(&self) -> ElementId {
|
||||
self.inner.mounted_id()
|
||||
pub fn id(&self) -> GlobalNodeId {
|
||||
self.inner.id
|
||||
}
|
||||
|
||||
/// Get the tag of the node if the tag is enabled in the mask
|
||||
pub fn tag(&self) -> Option<&'a str> {
|
||||
self.mask
|
||||
.tag
|
||||
.then(|| self.try_element().map(|el| el.tag))
|
||||
.then(|| match &self.inner.node_type {
|
||||
NodeType::Element { tag, .. } => Some(&**tag),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
|
@ -39,47 +41,47 @@ impl<'a> NodeView<'a> {
|
|||
pub fn namespace(&self) -> Option<&'a str> {
|
||||
self.mask
|
||||
.namespace
|
||||
.then(|| self.try_element().and_then(|el| el.namespace))
|
||||
.then(|| match &self.inner.node_type {
|
||||
NodeType::Element { namespace, .. } => namespace.map(|s| &*s),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Get any attributes that are enabled in the mask
|
||||
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
|
||||
self.try_element()
|
||||
.map(|el| el.attributes)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter(|a| self.mask.attritutes.contains_attribute(a.name))
|
||||
pub fn attributes<'b>(&'b self) -> Option<impl Iterator<Item = OwnedAttributeView<'a>> + 'b> {
|
||||
match &self.inner.node_type {
|
||||
NodeType::Element { attributes, .. } => Some(
|
||||
attributes
|
||||
.iter()
|
||||
.filter(move |(attr, _)| self.mask.attritutes.contains_attribute(&attr.name))
|
||||
.map(|(attr, val)| OwnedAttributeView {
|
||||
attribute: attr,
|
||||
value: val,
|
||||
}),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the text if it is enabled in the mask
|
||||
pub fn text(&self) -> Option<&str> {
|
||||
self.mask
|
||||
.text
|
||||
.then(|| self.try_text().map(|txt| txt.text))
|
||||
.then(|| match &self.inner.node_type {
|
||||
NodeType::Text { text } => Some(&**text),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Get the listeners if it is enabled in the mask
|
||||
pub fn listeners(&self) -> &'a [Listener<'a>] {
|
||||
self.try_element()
|
||||
.map(|el| el.listeners)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Try to get the underlying element.
|
||||
fn try_element(&self) -> Option<&'a VElement<'a>> {
|
||||
if let VNode::Element(el) = &self.inner {
|
||||
Some(el)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get the underlying text node.
|
||||
fn try_text(&self) -> Option<&'a VText<'a>> {
|
||||
if let VNode::Text(txt) = &self.inner {
|
||||
Some(txt)
|
||||
pub fn listeners(&self) -> Option<impl Iterator<Item = &'a str> + '_> {
|
||||
if self.mask.listeners {
|
||||
match &self.inner.node_type {
|
||||
NodeType::Element { listeners, .. } => Some(listeners.iter().map(|l| &**l)),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -100,7 +102,7 @@ impl AttributeMask {
|
|||
/// A empty attribute mask
|
||||
pub const NONE: Self = Self::Static(&[]);
|
||||
|
||||
fn contains_attribute(&self, attr: &'static str) -> bool {
|
||||
fn contains_attribute(&self, attr: &str) -> bool {
|
||||
match self {
|
||||
AttributeMask::All => true,
|
||||
AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
|
||||
|
@ -215,7 +217,8 @@ impl NodeMask {
|
|||
/// A node mask with every part visible.
|
||||
pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
|
||||
.with_text()
|
||||
.with_element();
|
||||
.with_element()
|
||||
.with_listeners();
|
||||
|
||||
/// Check if two masks overlap
|
||||
pub fn overlaps(&self, other: &Self) -> bool {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,11 @@
|
|||
use std::{cmp::Ordering, fmt::Debug};
|
||||
|
||||
use anymap::AnyMap;
|
||||
use dioxus_core::ElementId;
|
||||
use dioxus_core::GlobalNodeId;
|
||||
use fxhash::FxHashSet;
|
||||
|
||||
use crate::node_ref::{NodeMask, NodeView};
|
||||
use crate::real_dom::NodeData;
|
||||
use crate::traversable::Traversable;
|
||||
|
||||
/// Join two sorted iterators
|
||||
|
@ -206,12 +207,16 @@ pub trait NodeDepState<DepState = ()> {
|
|||
/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
|
||||
pub trait State: Default + Clone {
|
||||
#[doc(hidden)]
|
||||
fn update<'a, T: Traversable<Node = Self, Id = ElementId>>(
|
||||
dirty: &[(ElementId, NodeMask)],
|
||||
fn update<
|
||||
'a,
|
||||
T: Traversable<Node = Self, Id = GlobalNodeId>,
|
||||
T2: Traversable<Node = NodeData, Id = GlobalNodeId>,
|
||||
>(
|
||||
dirty: &[(GlobalNodeId, NodeMask)],
|
||||
state_tree: &'a mut T,
|
||||
vdom: &'a dioxus_core::VirtualDom,
|
||||
rdom: &'a T2,
|
||||
ctx: &AnyMap,
|
||||
) -> FxHashSet<ElementId>;
|
||||
) -> FxHashSet<GlobalNodeId>;
|
||||
}
|
||||
|
||||
impl ChildDepState for () {
|
||||
|
|
44
packages/native-core/src/template.rs
Normal file
44
packages/native-core/src/template.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use dioxus_core::{GlobalNodeId, TemplateNodeId};
|
||||
|
||||
use crate::{real_dom::Node, state::State};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NativeTemplate<S: State> {
|
||||
pub(crate) nodes: Vec<Option<Box<Node<S>>>>,
|
||||
pub(crate) roots: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<S: State> NativeTemplate<S> {
|
||||
pub fn insert(&mut self, node: Node<S>) {
|
||||
let id = node.node_data.id;
|
||||
match id {
|
||||
GlobalNodeId::TemplateId {
|
||||
template_node_id: TemplateNodeId(id),
|
||||
..
|
||||
} => {
|
||||
self.nodes.resize(id + 1, None);
|
||||
self.nodes[id] = Some(Box::new(node));
|
||||
}
|
||||
GlobalNodeId::VNodeId(_) => panic!("Cannot insert a VNode into a template"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum TemplateRefOrNode<S: State> {
|
||||
Ref {
|
||||
nodes: Vec<Option<Box<Node<S>>>>,
|
||||
roots: Vec<GlobalNodeId>,
|
||||
parent: Option<GlobalNodeId>,
|
||||
},
|
||||
Node(Node<S>),
|
||||
}
|
||||
|
||||
impl<S: State> TemplateRefOrNode<S> {
|
||||
pub fn parent(&self) -> Option<GlobalNodeId> {
|
||||
match self {
|
||||
TemplateRefOrNode::Ref { parent, .. } => *parent,
|
||||
TemplateRefOrNode::Node(node) => node.node_data.parent,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,16 +2,16 @@ use crate::{
|
|||
real_dom::{NodeType, RealDom},
|
||||
state::State,
|
||||
};
|
||||
use dioxus_core::{DomEdit, ElementId, Mutations};
|
||||
use dioxus_core::{DomEdit, ElementId, GlobalNodeId, Mutations};
|
||||
|
||||
pub enum ElementProduced {
|
||||
/// The iterator produced an element by progressing to the next node in a depth first order.
|
||||
Progressed(ElementId),
|
||||
Progressed(GlobalNodeId),
|
||||
/// The iterator reached the end of the tree and looped back to the root
|
||||
Looped(ElementId),
|
||||
Looped(GlobalNodeId),
|
||||
}
|
||||
impl ElementProduced {
|
||||
pub fn id(&self) -> ElementId {
|
||||
pub fn id(&self) -> GlobalNodeId {
|
||||
match self {
|
||||
ElementProduced::Progressed(id) => *id,
|
||||
ElementProduced::Looped(id) => *id,
|
||||
|
@ -50,13 +50,16 @@ impl NodePosition {
|
|||
/// The iterator loops around when it reaches the end or the beginning.
|
||||
pub struct PersistantElementIter {
|
||||
// stack of elements and fragments
|
||||
stack: smallvec::SmallVec<[(ElementId, NodePosition); 5]>,
|
||||
stack: smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>,
|
||||
}
|
||||
|
||||
impl Default for PersistantElementIter {
|
||||
fn default() -> Self {
|
||||
PersistantElementIter {
|
||||
stack: smallvec::smallvec![(ElementId(0), NodePosition::AtNode)],
|
||||
stack: smallvec::smallvec![(
|
||||
GlobalNodeId::VNodeId(dioxus_core::ElementId(0)),
|
||||
NodePosition::AtNode
|
||||
)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,8 +77,10 @@ impl PersistantElementIter {
|
|||
.edits
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
// nodes within templates will never be removed
|
||||
if let DomEdit::Remove { root } = e {
|
||||
Some(*root)
|
||||
let id = rdom.decode_id(*root);
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -85,7 +90,7 @@ impl PersistantElementIter {
|
|||
if let Some(r) = self
|
||||
.stack
|
||||
.iter()
|
||||
.position(|(el_id, _)| ids_removed.iter().any(|id| el_id.as_u64() == *id))
|
||||
.position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id))
|
||||
{
|
||||
self.stack.truncate(r);
|
||||
changed = true;
|
||||
|
@ -93,33 +98,24 @@ impl PersistantElementIter {
|
|||
// if a child is removed or inserted before or at the current element, update the child index
|
||||
for (el_id, child_idx) in self.stack.iter_mut() {
|
||||
if let NodePosition::InChild(child_idx) = child_idx {
|
||||
if let NodeType::Element { children, .. } = &rdom[*el_id].node_type {
|
||||
if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type {
|
||||
for m in &mutations.edits {
|
||||
match m {
|
||||
DomEdit::Remove { root } => {
|
||||
if children
|
||||
.iter()
|
||||
.take(*child_idx + 1)
|
||||
.any(|c| c.as_u64() == *root)
|
||||
{
|
||||
let id = rdom.decode_id(*root);
|
||||
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
|
||||
*child_idx -= 1;
|
||||
}
|
||||
}
|
||||
DomEdit::InsertBefore { root, n } => {
|
||||
if children
|
||||
.iter()
|
||||
.take(*child_idx + 1)
|
||||
.any(|c| c.as_u64() == *root)
|
||||
{
|
||||
let id = rdom.decode_id(*root);
|
||||
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
|
||||
*child_idx += *n as usize;
|
||||
}
|
||||
}
|
||||
DomEdit::InsertAfter { root, n } => {
|
||||
if children
|
||||
.iter()
|
||||
.take(*child_idx)
|
||||
.any(|c| c.as_u64() == *root)
|
||||
{
|
||||
let id = rdom.decode_id(*root);
|
||||
if children.iter().take(*child_idx).any(|c| *c == id) {
|
||||
*child_idx += *n as usize;
|
||||
}
|
||||
}
|
||||
|
@ -135,14 +131,14 @@ impl PersistantElementIter {
|
|||
/// get the next element
|
||||
pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
|
||||
if self.stack.is_empty() {
|
||||
let id = ElementId(0);
|
||||
let id = GlobalNodeId::VNodeId(ElementId(0));
|
||||
let new = (id, NodePosition::AtNode);
|
||||
self.stack.push(new);
|
||||
ElementProduced::Looped(id)
|
||||
} else {
|
||||
let (last, o_child_idx) = self.stack.last_mut().unwrap();
|
||||
let node = &rdom[*last];
|
||||
match &node.node_type {
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Element { children, .. } => {
|
||||
*o_child_idx = o_child_idx.map(|i| i + 1);
|
||||
// if we have children, go to the next child
|
||||
|
@ -152,7 +148,7 @@ impl PersistantElementIter {
|
|||
self.next(rdom)
|
||||
} else {
|
||||
let id = children[child_idx];
|
||||
if let NodeType::Element { .. } = &rdom[id].node_type {
|
||||
if let NodeType::Element { .. } = &rdom[id].node_data.node_type {
|
||||
self.stack.push((id, NodePosition::AtNode));
|
||||
}
|
||||
ElementProduced::Progressed(id)
|
||||
|
@ -171,11 +167,11 @@ impl PersistantElementIter {
|
|||
pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
|
||||
// recursively add the last child element to the stack
|
||||
fn push_back<S: State>(
|
||||
stack: &mut smallvec::SmallVec<[(ElementId, NodePosition); 5]>,
|
||||
new_node: ElementId,
|
||||
stack: &mut smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>,
|
||||
new_node: GlobalNodeId,
|
||||
rdom: &RealDom<S>,
|
||||
) -> ElementId {
|
||||
match &rdom[new_node].node_type {
|
||||
) -> GlobalNodeId {
|
||||
match &rdom[new_node].node_data.node_type {
|
||||
NodeType::Element { children, .. } => {
|
||||
if children.is_empty() {
|
||||
new_node
|
||||
|
@ -188,12 +184,12 @@ impl PersistantElementIter {
|
|||
}
|
||||
}
|
||||
if self.stack.is_empty() {
|
||||
let new_node = ElementId(0);
|
||||
let new_node = GlobalNodeId::VNodeId(ElementId(0));
|
||||
ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
|
||||
} else {
|
||||
let (last, o_child_idx) = self.stack.last_mut().unwrap();
|
||||
let node = &rdom[*last];
|
||||
match &node.node_type {
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Element { children, .. } => {
|
||||
// if we have children, go to the next child
|
||||
if let NodePosition::InChild(0) = o_child_idx {
|
||||
|
@ -227,7 +223,7 @@ impl PersistantElementIter {
|
|||
}
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> ElementId {
|
||||
fn pop(&mut self) -> GlobalNodeId {
|
||||
self.stack.pop().unwrap().0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,11 @@ license = "MIT/Apache-2.0"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { version = "1.0" }
|
||||
proc-macro2 = { version = "1.0", features = ["span-locations"] }
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
quote = { version = "1.0" }
|
||||
dioxus-core = { path = "../core", features = ["serialize", "hot-reload"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
hot-reload = []
|
|
@ -1,32 +1,32 @@
|
|||
use crate::elements::*;
|
||||
|
||||
// map the rsx name of the attribute to the html name of the attribute and the namespace that contains it
|
||||
// map the rsx name of the attribute to the html name of the attribute, the namespace that contains it, and if the attribute is volitile
|
||||
pub fn attrbute_to_static_str(
|
||||
attr: &str,
|
||||
element: &'static str,
|
||||
namespace: Option<&'static str>,
|
||||
) -> Option<(&'static str, Option<&'static str>)> {
|
||||
) -> Option<(&'static str, Option<&'static str>, bool)> {
|
||||
if namespace == Some("http://www.w3.org/2000/svg") {
|
||||
svg::MAPPED_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, None))
|
||||
.map(|(_, b)| (*b, None, false))
|
||||
} else {
|
||||
NO_NAMESPACE_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|&a| *a == attr)
|
||||
.map(|a| (*a, None))
|
||||
.map(|a| (*a, None, false))
|
||||
.or_else(|| {
|
||||
STYLE_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, Some("style")))
|
||||
.map(|(_, b)| (*b, Some("style"), false))
|
||||
})
|
||||
.or_else(|| {
|
||||
MAPPED_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, None))
|
||||
.map(|(_, b)| (*b, None, false))
|
||||
})
|
||||
}
|
||||
.or_else(|| {
|
||||
|
@ -37,8 +37,8 @@ pub fn attrbute_to_static_str(
|
|||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, None))
|
||||
.find(|(a, _, _)| *a == attr)
|
||||
.map(|(_, b, volitile)| (*b, None, *volitile))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
|
@ -46,14 +46,24 @@ pub fn attrbute_to_static_str(
|
|||
.or_else(|| {
|
||||
ELEMENTS_WITH_NAMESPACE.iter().find_map(|(el, ns, attrs)| {
|
||||
(element == *el && namespace == Some(*ns))
|
||||
.then(|| attrs.iter().find(|a| **a == attr).map(|a| (*a, None)))
|
||||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|a| **a == attr)
|
||||
.map(|a| (*a, None, false))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
ELEMENTS_WITHOUT_NAMESPACE.iter().find_map(|(el, attrs)| {
|
||||
(element == *el)
|
||||
.then(|| attrs.iter().find(|a| **a == attr).map(|a| (*a, None)))
|
||||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|a| **a == attr)
|
||||
.map(|a| (*a, None, false))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
})
|
||||
|
@ -628,7 +638,6 @@ mapped_trait_methods! {
|
|||
}
|
||||
|
||||
pub mod svg {
|
||||
|
||||
mapped_trait_methods! {
|
||||
accent_height: "accent-height",
|
||||
accumulate: "accumulate",
|
|
@ -23,7 +23,7 @@ use syn::{
|
|||
Token,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct Component {
|
||||
pub name: syn::Path,
|
||||
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
|
||||
|
@ -174,7 +174,11 @@ impl ToTokens for Component {
|
|||
let key_token = match has_key {
|
||||
Some(field) => {
|
||||
let inners = &field.content;
|
||||
quote! { Some(format_args_f!(#inners)) }
|
||||
if let ContentField::Formatted(ifmt) = inners {
|
||||
quote! { Some(#ifmt) }
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
None => quote! { None },
|
||||
};
|
||||
|
@ -193,16 +197,16 @@ impl ToTokens for Component {
|
|||
}
|
||||
|
||||
// the struct's fields info
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct ComponentField {
|
||||
pub name: Ident,
|
||||
pub content: ContentField,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum ContentField {
|
||||
ManExpr(Expr),
|
||||
Formatted(LitStr),
|
||||
Formatted(IfmtInput),
|
||||
OnHandlerRaw(Expr),
|
||||
}
|
||||
|
||||
|
@ -211,7 +215,7 @@ impl ToTokens for ContentField {
|
|||
match self {
|
||||
ContentField::ManExpr(e) => e.to_tokens(tokens),
|
||||
ContentField::Formatted(s) => tokens.append_all(quote! {
|
||||
__cx.raw_text(format_args_f!(#s)).0
|
||||
__cx.raw_text(#s).0
|
||||
}),
|
||||
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
|
||||
__cx.event_handler(#e)
|
||||
|
@ -231,7 +235,7 @@ impl Parse for ComponentField {
|
|||
}
|
||||
|
||||
if name == "key" {
|
||||
let content = ContentField::ManExpr(input.parse()?);
|
||||
let content = ContentField::Formatted(input.parse()?);
|
||||
return Ok(Self { name, content });
|
||||
}
|
||||
if input.peek(LitStr) {
|
||||
|
|
|
@ -10,10 +10,10 @@ use syn::{
|
|||
// =======================================
|
||||
// Parse the VNode::Element type
|
||||
// =======================================
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct Element {
|
||||
pub name: Ident,
|
||||
pub key: Option<LitStr>,
|
||||
pub key: Option<IfmtInput>,
|
||||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub _is_static: bool,
|
||||
|
@ -46,7 +46,7 @@ impl Parse for Element {
|
|||
content.parse::<Token![:]>()?;
|
||||
|
||||
if content.peek(LitStr) && content.peek2(Token![,]) {
|
||||
let value = content.parse::<LitStr>()?;
|
||||
let value = content.parse()?;
|
||||
attributes.push(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr::CustomAttrText { name, value },
|
||||
|
@ -164,7 +164,7 @@ impl ToTokens for Element {
|
|||
let children = &self.children;
|
||||
|
||||
let key = match &self.key {
|
||||
Some(ty) => quote! { Some(format_args_f!(#ty)) },
|
||||
Some(ty) => quote! { Some(#ty) },
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
|
@ -190,16 +190,16 @@ impl ToTokens for Element {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum ElementAttr {
|
||||
/// attribute: "valuee {}"
|
||||
AttrText { name: Ident, value: LitStr },
|
||||
AttrText { name: Ident, value: IfmtInput },
|
||||
|
||||
/// attribute: true,
|
||||
AttrExpression { name: Ident, value: Expr },
|
||||
|
||||
/// "attribute": "value {}"
|
||||
CustomAttrText { name: LitStr, value: LitStr },
|
||||
CustomAttrText { name: LitStr, value: IfmtInput },
|
||||
|
||||
/// "attribute": true,
|
||||
CustomAttrExpression { name: LitStr, value: Expr },
|
||||
|
@ -231,7 +231,7 @@ impl ElementAttr {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub struct ElementAttrNamed {
|
||||
pub el_name: Ident,
|
||||
pub attr: ElementAttr,
|
||||
|
@ -244,29 +244,24 @@ impl ToTokens for ElementAttrNamed {
|
|||
tokens.append_all(match attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
quote! {
|
||||
dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
|
||||
__cx.attr_disciption( dioxus_elements::#el_name::#name, #value)
|
||||
}
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
quote! {
|
||||
dioxus_elements::#el_name.#name(__cx, #value)
|
||||
__cx.attr_disciption( dioxus_elements::#el_name::#name, #value)
|
||||
}
|
||||
}
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
quote! {
|
||||
__cx.attr( #name, format_args_f!(#value), None, false )
|
||||
__cx.attr( #name, #value, None, false )
|
||||
}
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
quote! {
|
||||
__cx.attr( #name, format_args_f!(#value), None, false )
|
||||
__cx.attr( #name, #value, None, false )
|
||||
}
|
||||
}
|
||||
// ElementAttr::EventClosure { name, closure } => {
|
||||
// quote! {
|
||||
// dioxus_elements::on::#name(__cx, #closure)
|
||||
// }
|
||||
// }
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
quote! {
|
||||
dioxus_elements::on::#name(__cx, #tokens)
|
||||
|
|
|
@ -65,14 +65,22 @@ macro_rules! builder_constructors {
|
|||
];
|
||||
};
|
||||
}
|
||||
pub const ELEMENTS_WITH_MAPPED_ATTRIBUTES: &[(&str, &[(&str, &str)])] = &[
|
||||
("script", &[("r#type", "type"), ("r#script", "script")]),
|
||||
("button", &[("r#type", "type")]),
|
||||
("select", &[("value", "value")]),
|
||||
("option", &[("selected", "selected")]),
|
||||
("textarea", &[("value", "value")]),
|
||||
("label", &[("r#for", "for")]),
|
||||
("input", &[("r#type", "type"), ("value", "value")]),
|
||||
|
||||
/// All attributes that are tied to a specific element that either have a different name, or are volitile
|
||||
pub const ELEMENTS_WITH_MAPPED_ATTRIBUTES: &[(&str, &[(&str, &str, bool)])] = &[
|
||||
(
|
||||
"script",
|
||||
&[("r#type", "type", false), ("r#script", "script", false)],
|
||||
),
|
||||
("button", &[("r#type", "type", false)]),
|
||||
("select", &[("value", "value", true)]),
|
||||
("option", &[("selected", "selected", true)]),
|
||||
("textarea", &[("value", "value", true)]),
|
||||
("label", &[("r#for", "for", false)]),
|
||||
(
|
||||
"input",
|
||||
&[("r#type", "type", false), ("value", "value", true)],
|
||||
),
|
||||
];
|
||||
|
||||
// Organized in the same order as
|
|
@ -1,9 +1,8 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use dioxus_core::OwnedCodeLocation;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::CodeLocation;
|
||||
|
||||
/// An error produced when interperting the rsx
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Error {
|
||||
|
@ -23,11 +22,11 @@ pub enum RecompileReason {
|
|||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ParseError {
|
||||
pub message: String,
|
||||
pub location: CodeLocation,
|
||||
pub location: OwnedCodeLocation,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn new(error: syn::Error, mut location: CodeLocation) -> Self {
|
||||
pub fn new(error: syn::Error, mut location: OwnedCodeLocation) -> Self {
|
||||
let message = error.to_string();
|
||||
let syn_call_site = error.span().start();
|
||||
location.line += syn_call_site.line as u32;
|
|
@ -2,87 +2,37 @@ use std::{collections::HashSet, str::FromStr};
|
|||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
|
||||
use quote::{quote, ToTokens};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
*,
|
||||
};
|
||||
|
||||
pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
|
||||
// build format_literal
|
||||
let mut format_literal = String::new();
|
||||
let mut expr_counter = 0;
|
||||
for segment in input.segments.iter() {
|
||||
match segment {
|
||||
Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
|
||||
Segment::Formatted {
|
||||
format_args,
|
||||
segment,
|
||||
} => {
|
||||
format_literal += "{";
|
||||
match segment {
|
||||
FormattedSegment::Expr(_) => {
|
||||
format_literal += &expr_counter.to_string();
|
||||
expr_counter += 1;
|
||||
}
|
||||
FormattedSegment::Ident(ident) => {
|
||||
format_literal += &ident.to_string();
|
||||
}
|
||||
}
|
||||
format_literal += ":";
|
||||
format_literal += format_args;
|
||||
format_literal += "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let positional_args = input.segments.iter().filter_map(|seg| {
|
||||
if let Segment::Formatted {
|
||||
segment: FormattedSegment::Expr(expr),
|
||||
..
|
||||
} = seg
|
||||
{
|
||||
Some(expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// remove duplicate idents
|
||||
let named_args_idents: HashSet<_> = input
|
||||
.segments
|
||||
.iter()
|
||||
.filter_map(|seg| {
|
||||
if let Segment::Formatted {
|
||||
segment: FormattedSegment::Ident(ident),
|
||||
..
|
||||
} = seg
|
||||
{
|
||||
Some(ident)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let named_args = named_args_idents
|
||||
.iter()
|
||||
.map(|ident| quote!(#ident = #ident));
|
||||
|
||||
Ok(quote! {
|
||||
format_args!(
|
||||
#format_literal
|
||||
#(, #positional_args)*
|
||||
#(, #named_args)*
|
||||
)
|
||||
})
|
||||
Ok(input.into_token_stream())
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct IfmtInput {
|
||||
pub source: Option<LitStr>,
|
||||
pub segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
impl IfmtInput {
|
||||
pub fn to_static(&self) -> Option<String> {
|
||||
self.segments
|
||||
.iter()
|
||||
.try_fold(String::new(), |acc, segment| {
|
||||
if let Segment::Literal(seg) = segment {
|
||||
Some(acc + seg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IfmtInput {
|
||||
type Err = syn::Error;
|
||||
|
||||
|
@ -96,7 +46,9 @@ impl FromStr for IfmtInput {
|
|||
current_literal.push(c);
|
||||
continue;
|
||||
}
|
||||
segments.push(Segment::Literal(current_literal));
|
||||
if !current_literal.is_empty() {
|
||||
segments.push(Segment::Literal(current_literal));
|
||||
}
|
||||
current_literal = String::new();
|
||||
let mut current_captured = String::new();
|
||||
while let Some(c) = chars.next() {
|
||||
|
@ -104,10 +56,10 @@ impl FromStr for IfmtInput {
|
|||
let mut current_format_args = String::new();
|
||||
for c in chars.by_ref() {
|
||||
if c == '}' {
|
||||
segments.push(Segment::Formatted {
|
||||
segments.push(Segment::Formatted(FormattedSegment {
|
||||
format_args: current_format_args,
|
||||
segment: FormattedSegment::parse(¤t_captured)?,
|
||||
});
|
||||
segment: FormattedSegmentType::parse(¤t_captured)?,
|
||||
}));
|
||||
break;
|
||||
}
|
||||
current_format_args.push(c);
|
||||
|
@ -115,10 +67,10 @@ impl FromStr for IfmtInput {
|
|||
break;
|
||||
}
|
||||
if c == '}' {
|
||||
segments.push(Segment::Formatted {
|
||||
segments.push(Segment::Formatted(FormattedSegment {
|
||||
format_args: String::new(),
|
||||
segment: FormattedSegment::parse(¤t_captured)?,
|
||||
});
|
||||
segment: FormattedSegmentType::parse(¤t_captured)?,
|
||||
}));
|
||||
break;
|
||||
}
|
||||
current_captured.push(c);
|
||||
|
@ -138,27 +90,117 @@ impl FromStr for IfmtInput {
|
|||
current_literal.push(c);
|
||||
}
|
||||
}
|
||||
segments.push(Segment::Literal(current_literal));
|
||||
Ok(Self { segments })
|
||||
if !current_literal.is_empty() {
|
||||
segments.push(Segment::Literal(current_literal));
|
||||
}
|
||||
Ok(Self {
|
||||
segments,
|
||||
source: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Segment {
|
||||
Literal(String),
|
||||
Formatted {
|
||||
format_args: String,
|
||||
segment: FormattedSegment,
|
||||
},
|
||||
impl ToTokens for IfmtInput {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
// build format_literal
|
||||
let mut format_literal = String::new();
|
||||
let mut expr_counter = 0;
|
||||
for segment in self.segments.iter() {
|
||||
match segment {
|
||||
Segment::Literal(s) => format_literal += &s.replace('{', "{{").replace('}', "}}"),
|
||||
Segment::Formatted(FormattedSegment {
|
||||
format_args,
|
||||
segment,
|
||||
}) => {
|
||||
format_literal += "{";
|
||||
match segment {
|
||||
FormattedSegmentType::Expr(_) => {
|
||||
format_literal += &expr_counter.to_string();
|
||||
expr_counter += 1;
|
||||
}
|
||||
FormattedSegmentType::Ident(ident) => {
|
||||
format_literal += &ident.to_string();
|
||||
}
|
||||
}
|
||||
format_literal += ":";
|
||||
format_literal += format_args;
|
||||
format_literal += "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let positional_args = self.segments.iter().filter_map(|seg| {
|
||||
if let Segment::Formatted(FormattedSegment {
|
||||
segment: FormattedSegmentType::Expr(expr),
|
||||
..
|
||||
}) = seg
|
||||
{
|
||||
Some(expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// remove duplicate idents
|
||||
let named_args_idents: HashSet<_> = self
|
||||
.segments
|
||||
.iter()
|
||||
.filter_map(|seg| {
|
||||
if let Segment::Formatted(FormattedSegment {
|
||||
segment: FormattedSegmentType::Ident(ident),
|
||||
..
|
||||
}) = seg
|
||||
{
|
||||
Some(ident)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let named_args = named_args_idents
|
||||
.iter()
|
||||
.map(|ident| quote!(#ident = #ident));
|
||||
|
||||
quote! {
|
||||
format_args!(
|
||||
#format_literal
|
||||
#(, #positional_args)*
|
||||
#(, #named_args)*
|
||||
)
|
||||
}
|
||||
.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FormattedSegment {
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum Segment {
|
||||
Literal(String),
|
||||
Formatted(FormattedSegment),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct FormattedSegment {
|
||||
format_args: String,
|
||||
segment: FormattedSegmentType,
|
||||
}
|
||||
|
||||
impl ToTokens for FormattedSegment {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let (fmt, seg) = (&self.format_args, &self.segment);
|
||||
let fmt = format!("{{0:{}}}", fmt);
|
||||
tokens.append_all(quote! {
|
||||
format_args!(#fmt, #seg)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum FormattedSegmentType {
|
||||
Expr(Box<Expr>),
|
||||
Ident(Ident),
|
||||
}
|
||||
|
||||
impl FormattedSegment {
|
||||
impl FormattedSegmentType {
|
||||
fn parse(input: &str) -> Result<Self> {
|
||||
if let Ok(ident) = parse_str::<Ident>(input) {
|
||||
if ident == input {
|
||||
|
@ -176,7 +218,7 @@ impl FormattedSegment {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToTokens for FormattedSegment {
|
||||
impl ToTokens for FormattedSegmentType {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Self::Expr(expr) => expr.to_tokens(tokens),
|
||||
|
@ -189,6 +231,8 @@ impl Parse for IfmtInput {
|
|||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let input: LitStr = input.parse()?;
|
||||
let input_str = input.value();
|
||||
IfmtInput::from_str(&input_str)
|
||||
let mut ifmt = IfmtInput::from_str(&input_str)?;
|
||||
ifmt.source = Some(input);
|
||||
Ok(ifmt)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,17 +13,25 @@
|
|||
|
||||
#[macro_use]
|
||||
mod errors;
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod attributes;
|
||||
mod component;
|
||||
mod element;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod elements;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod error;
|
||||
mod ifmt;
|
||||
mod node;
|
||||
mod template;
|
||||
|
||||
// Re-export the namespaces into each other
|
||||
pub use component::*;
|
||||
pub use element::*;
|
||||
pub use ifmt::*;
|
||||
pub use node::*;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub use template::{try_parse_template, DynamicTemplateContextBuilder};
|
||||
|
||||
// imports
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
@ -32,6 +40,7 @@ use syn::{
|
|||
parse::{Parse, ParseStream},
|
||||
Result, Token,
|
||||
};
|
||||
use template::TemplateBuilder;
|
||||
|
||||
pub struct CallBody {
|
||||
pub roots: Vec<BodyNode>,
|
||||
|
@ -58,12 +67,17 @@ impl Parse for CallBody {
|
|||
/// Serialize the same way, regardless of flavor
|
||||
impl ToTokens for CallBody {
|
||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||
let inner = if self.roots.len() == 1 {
|
||||
let inner = &self.roots[0];
|
||||
quote! { #inner }
|
||||
let template = TemplateBuilder::from_roots(self.roots.clone());
|
||||
let inner = if let Some(template) = template {
|
||||
quote! { #template }
|
||||
} else {
|
||||
let childs = &self.roots;
|
||||
quote! { __cx.fragment_root([ #(#childs),* ]) }
|
||||
let children = &self.roots;
|
||||
if children.len() == 1 {
|
||||
let inner = &self.roots[0];
|
||||
quote! { #inner }
|
||||
} else {
|
||||
quote! { __cx.fragment_root([ #(#children),* ]) }
|
||||
}
|
||||
};
|
||||
|
||||
// Otherwise we just build the LazyNode wrapper
|
||||
|
@ -75,3 +89,22 @@ impl ToTokens for CallBody {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CallBody {
|
||||
pub fn to_tokens_without_template(&self, out_tokens: &mut TokenStream2) {
|
||||
let children = &self.roots;
|
||||
let inner = if children.len() == 1 {
|
||||
let inner = &self.roots[0];
|
||||
quote! { #inner }
|
||||
} else {
|
||||
quote! { __cx.fragment_root([ #(#children),* ]) }
|
||||
};
|
||||
|
||||
out_tokens.append_all(quote! {
|
||||
LazyNodes::new(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
#inner
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@ Parse
|
|||
-> "text {with_args}"
|
||||
-> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr
|
||||
*/
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum BodyNode {
|
||||
Element(Element),
|
||||
Component(Component),
|
||||
Text(LitStr),
|
||||
Text(IfmtInput),
|
||||
RawExpr(Expr),
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ impl BodyNode {
|
|||
match self {
|
||||
BodyNode::Element(el) => el.name.span(),
|
||||
BodyNode::Component(component) => component.name.span(),
|
||||
BodyNode::Text(text) => text.span(),
|
||||
BodyNode::Text(text) => text.source.span(),
|
||||
BodyNode::RawExpr(exp) => exp.span(),
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ impl ToTokens for BodyNode {
|
|||
BodyNode::Element(el) => el.to_tokens(tokens),
|
||||
BodyNode::Component(comp) => comp.to_tokens(tokens),
|
||||
BodyNode::Text(txt) => tokens.append_all(quote! {
|
||||
__cx.text(format_args_f!(#txt))
|
||||
__cx.text(#txt)
|
||||
}),
|
||||
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
|
||||
__cx.fragment_from_iter(#exp)
|
||||
|
|
917
packages/rsx/src/template.rs
Normal file
917
packages/rsx/src/template.rs
Normal file
|
@ -0,0 +1,917 @@
|
|||
use dioxus_core::{
|
||||
OwnedAttributeValue, TemplateAttributeValue, TemplateNodeId, TextTemplate, TextTemplateSegment,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::TokenStreamExt;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{Expr, Ident, LitStr};
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub fn try_parse_template(
|
||||
rsx: &str,
|
||||
location: OwnedCodeLocation,
|
||||
previous_template: Option<DynamicTemplateContextBuilder>,
|
||||
) -> Result<(OwnedTemplate, DynamicTemplateContextBuilder), Error> {
|
||||
use crate::CallBody;
|
||||
|
||||
let call_body: CallBody =
|
||||
syn::parse_str(rsx).map_err(|e| Error::ParseError(ParseError::new(e, location.clone())))?;
|
||||
let mut template_builder = TemplateBuilder::from_roots_always(call_body.roots);
|
||||
if let Some(prev) = previous_template {
|
||||
template_builder = template_builder
|
||||
.try_switch_dynamic_context(prev)
|
||||
.ok_or_else(|| {
|
||||
Error::RecompileRequiredError(RecompileReason::CapturedVariable(
|
||||
"dynamic context updated".to_string(),
|
||||
))
|
||||
})?;
|
||||
}
|
||||
let dyn_ctx = template_builder.dynamic_context.clone();
|
||||
Ok((template_builder.try_into_owned(&location)?, dyn_ctx))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
use hot_reload_imports::*;
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod hot_reload_imports {
|
||||
pub use crate::{
|
||||
attributes::attrbute_to_static_str,
|
||||
elements::element_to_static_str,
|
||||
error::{Error, ParseError, RecompileReason},
|
||||
};
|
||||
pub use dioxus_core::prelude::OwnedTemplate;
|
||||
pub use dioxus_core::{
|
||||
AttributeDiscription, OwnedAttributeValue, OwnedCodeLocation, OwnedDynamicNodeMapping,
|
||||
OwnedTemplateNode, Template, TemplateAttribute, TemplateAttributeValue, TemplateElement,
|
||||
TemplateNodeId, TemplateNodeType, TextTemplate, TextTemplateSegment,
|
||||
};
|
||||
pub use std::collections::HashMap;
|
||||
}
|
||||
use crate::{BodyNode, ElementAttr, FormattedSegment, Segment};
|
||||
|
||||
struct TemplateElementBuilder {
|
||||
tag: Ident,
|
||||
attributes: Vec<TemplateAttributeBuilder>,
|
||||
children: Vec<TemplateNodeId>,
|
||||
listeners: Vec<usize>,
|
||||
parent: Option<TemplateNodeId>,
|
||||
}
|
||||
|
||||
impl TemplateElementBuilder {
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn try_into_owned(
|
||||
self,
|
||||
location: &OwnedCodeLocation,
|
||||
) -> Result<
|
||||
TemplateElement<
|
||||
Vec<TemplateAttribute<OwnedAttributeValue>>,
|
||||
OwnedAttributeValue,
|
||||
Vec<TemplateNodeId>,
|
||||
Vec<usize>,
|
||||
>,
|
||||
Error,
|
||||
> {
|
||||
let Self {
|
||||
tag,
|
||||
attributes,
|
||||
children,
|
||||
listeners,
|
||||
parent,
|
||||
} = self;
|
||||
let (element_tag, element_ns) =
|
||||
element_to_static_str(&tag.to_string()).ok_or_else(|| {
|
||||
Error::ParseError(ParseError::new(
|
||||
syn::Error::new(tag.span(), format!("unknown element: {}", tag)),
|
||||
location.clone(),
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut owned_attributes = Vec::new();
|
||||
for a in attributes {
|
||||
owned_attributes.push(a.try_into_owned(location, element_tag, element_ns)?);
|
||||
}
|
||||
|
||||
Ok(TemplateElement::new(
|
||||
element_tag,
|
||||
element_ns,
|
||||
owned_attributes,
|
||||
children,
|
||||
listeners,
|
||||
parent,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TemplateElementBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
tag,
|
||||
attributes,
|
||||
children,
|
||||
listeners,
|
||||
parent,
|
||||
} = self;
|
||||
let children = children.iter().map(|id| {
|
||||
let raw = id.0;
|
||||
quote! {TemplateNodeId(#raw)}
|
||||
});
|
||||
let parent = match parent {
|
||||
Some(id) => {
|
||||
let raw = id.0;
|
||||
quote! {Some(TemplateNodeId(#raw))}
|
||||
}
|
||||
None => quote! {None},
|
||||
};
|
||||
tokens.append_all(quote! {
|
||||
TemplateElement::new(
|
||||
dioxus_elements::#tag::TAG_NAME,
|
||||
dioxus_elements::#tag::NAME_SPACE,
|
||||
&[#(#attributes),*],
|
||||
&[#(#children),*],
|
||||
&[#(#listeners),*],
|
||||
#parent,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum AttributeName {
|
||||
Ident(Ident),
|
||||
Str(LitStr),
|
||||
}
|
||||
|
||||
struct TemplateAttributeBuilder {
|
||||
element_tag: Ident,
|
||||
name: AttributeName,
|
||||
value: TemplateAttributeValue<OwnedAttributeValue>,
|
||||
}
|
||||
|
||||
impl TemplateAttributeBuilder {
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn try_into_owned(
|
||||
self,
|
||||
location: &OwnedCodeLocation,
|
||||
element_tag: &'static str,
|
||||
element_ns: Option<&'static str>,
|
||||
) -> Result<TemplateAttribute<OwnedAttributeValue>, Error> {
|
||||
let Self { name, value, .. } = self;
|
||||
let (name, span, literal) = match name {
|
||||
AttributeName::Ident(name) => (name.to_string(), name.span(), false),
|
||||
AttributeName::Str(name) => (name.value(), name.span(), true),
|
||||
};
|
||||
let (name, namespace, volatile) = attrbute_to_static_str(&name, element_tag, element_ns)
|
||||
.ok_or_else(|| {
|
||||
if literal {
|
||||
// literals will be captured when a full recompile is triggered
|
||||
Error::RecompileRequiredError(RecompileReason::CapturedAttribute(
|
||||
name.to_string(),
|
||||
))
|
||||
} else {
|
||||
Error::ParseError(ParseError::new(
|
||||
syn::Error::new(span, format!("unknown attribute: {}", name)),
|
||||
location.clone(),
|
||||
))
|
||||
}
|
||||
})?;
|
||||
let attribute = AttributeDiscription {
|
||||
name,
|
||||
namespace,
|
||||
volatile,
|
||||
};
|
||||
Ok(TemplateAttribute { value, attribute })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TemplateAttributeBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
element_tag,
|
||||
name,
|
||||
value,
|
||||
} = self;
|
||||
let value = match value {
|
||||
TemplateAttributeValue::Static(val) => {
|
||||
let val = match val {
|
||||
OwnedAttributeValue::Text(txt) => quote! {StaticAttributeValue::Text(#txt)},
|
||||
OwnedAttributeValue::Float32(f) => quote! {StaticAttributeValue::Float32(#f)},
|
||||
OwnedAttributeValue::Float64(f) => quote! {StaticAttributeValue::Float64(#f)},
|
||||
OwnedAttributeValue::Int32(i) => quote! {StaticAttributeValue::Int32(#i)},
|
||||
OwnedAttributeValue::Int64(i) => quote! {StaticAttributeValue::Int64(#i)},
|
||||
OwnedAttributeValue::Uint32(u) => quote! {StaticAttributeValue::Uint32(#u)},
|
||||
OwnedAttributeValue::Uint64(u) => quote! {StaticAttributeValue::Uint64(#u)},
|
||||
OwnedAttributeValue::Bool(b) => quote! {StaticAttributeValue::Bool(#b)},
|
||||
OwnedAttributeValue::Vec3Float(f1, f2, f3) => {
|
||||
quote! {StaticAttributeValue::Vec3Float(#f1, #f2, #f3)}
|
||||
}
|
||||
OwnedAttributeValue::Vec3Int(f1, f2, f3) => {
|
||||
quote! {StaticAttributeValue::Vec3Int(#f1, #f2, #f3)}
|
||||
}
|
||||
OwnedAttributeValue::Vec3Uint(f1, f2, f3) => {
|
||||
quote! {StaticAttributeValue::Vec3Uint(#f1, #f2, #f3)}
|
||||
}
|
||||
OwnedAttributeValue::Vec4Float(f1, f2, f3, f4) => {
|
||||
quote! {StaticAttributeValue::Vec4Float(#f1, #f2, #f3, #f4)}
|
||||
}
|
||||
OwnedAttributeValue::Vec4Int(f1, f2, f3, f4) => {
|
||||
quote! {StaticAttributeValue::Vec4Int(#f1, #f2, #f3, #f4)}
|
||||
}
|
||||
OwnedAttributeValue::Vec4Uint(f1, f2, f3, f4) => {
|
||||
quote! {StaticAttributeValue::Vec4Uint(#f1, #f2, #f3, #f4)}
|
||||
}
|
||||
OwnedAttributeValue::Bytes(b) => {
|
||||
quote! {StaticAttributeValue::Bytes(&[#(#b),*])}
|
||||
}
|
||||
};
|
||||
quote! {TemplateAttributeValue::Static(#val)}
|
||||
}
|
||||
TemplateAttributeValue::Dynamic(idx) => quote! {TemplateAttributeValue::Dynamic(#idx)},
|
||||
};
|
||||
match name {
|
||||
AttributeName::Ident(name) => tokens.append_all(quote! {
|
||||
TemplateAttribute{
|
||||
attribute: dioxus_elements::#element_tag::#name,
|
||||
value: #value,
|
||||
}
|
||||
}),
|
||||
AttributeName::Str(lit) => tokens.append_all(quote! {
|
||||
TemplateAttribute{
|
||||
attribute: dioxus::prelude::AttributeDiscription{
|
||||
name: #lit,
|
||||
namespace: None,
|
||||
volatile: false
|
||||
},
|
||||
value: #value,
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TemplateNodeTypeBuilder {
|
||||
Element(TemplateElementBuilder),
|
||||
Text(TextTemplate<Vec<TextTemplateSegment<String>>, String>),
|
||||
DynamicNode(usize),
|
||||
}
|
||||
|
||||
impl TemplateNodeTypeBuilder {
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn try_into_owned(
|
||||
self,
|
||||
location: &OwnedCodeLocation,
|
||||
) -> Result<
|
||||
TemplateNodeType<
|
||||
Vec<TemplateAttribute<OwnedAttributeValue>>,
|
||||
OwnedAttributeValue,
|
||||
Vec<TemplateNodeId>,
|
||||
Vec<usize>,
|
||||
Vec<TextTemplateSegment<String>>,
|
||||
String,
|
||||
>,
|
||||
Error,
|
||||
> {
|
||||
match self {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
Ok(TemplateNodeType::Element(el.try_into_owned(location)?))
|
||||
}
|
||||
TemplateNodeTypeBuilder::Text(txt) => Ok(TemplateNodeType::Text(txt)),
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => Ok(TemplateNodeType::DynamicNode(idx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TemplateNodeTypeBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
TemplateNodeTypeBuilder::Element(el) => tokens.append_all(quote! {
|
||||
TemplateNodeType::Element(#el)
|
||||
}),
|
||||
TemplateNodeTypeBuilder::Text(txt) => {
|
||||
let segments = txt.segments.iter().map(|seg| match seg {
|
||||
TextTemplateSegment::Static(s) => quote!(TextTemplateSegment::Static(#s)),
|
||||
TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)),
|
||||
});
|
||||
tokens.append_all(quote! {
|
||||
TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*]))
|
||||
});
|
||||
}
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! {
|
||||
TemplateNodeType::DynamicNode(#idx)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TemplateNodeBuilder {
|
||||
id: TemplateNodeId,
|
||||
node_type: TemplateNodeTypeBuilder,
|
||||
}
|
||||
|
||||
impl TemplateNodeBuilder {
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNode, Error> {
|
||||
let TemplateNodeBuilder { id, node_type } = self;
|
||||
let node_type = node_type.try_into_owned(location)?;
|
||||
Ok(OwnedTemplateNode {
|
||||
id,
|
||||
node_type,
|
||||
locally_static: false,
|
||||
fully_static: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_fully_static(&self, nodes: &Vec<TemplateNodeBuilder>) -> bool {
|
||||
self.is_locally_static()
|
||||
&& match &self.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => el
|
||||
.children
|
||||
.iter()
|
||||
.all(|child| nodes[child.0].is_fully_static(nodes)),
|
||||
TemplateNodeTypeBuilder::Text(_) => true,
|
||||
TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_locally_static(&self) -> bool {
|
||||
match &self.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
el.attributes.iter().all(|attr| match &attr.value {
|
||||
TemplateAttributeValue::Static(_) => true,
|
||||
TemplateAttributeValue::Dynamic(_) => false,
|
||||
}) && el.listeners.is_empty()
|
||||
}
|
||||
TemplateNodeTypeBuilder::Text(txt) => txt.segments.iter().all(|seg| match seg {
|
||||
TextTemplateSegment::Static(_) => true,
|
||||
TextTemplateSegment::Dynamic(_) => false,
|
||||
}),
|
||||
TemplateNodeTypeBuilder::DynamicNode(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_tokens(&self, tokens: &mut TokenStream, nodes: &Vec<TemplateNodeBuilder>) {
|
||||
let Self { id, node_type } = self;
|
||||
let raw_id = id.0;
|
||||
let fully_static = self.is_fully_static(nodes);
|
||||
let locally_static = self.is_locally_static();
|
||||
|
||||
tokens.append_all(quote! {
|
||||
TemplateNode {
|
||||
id: TemplateNodeId(#raw_id),
|
||||
node_type: #node_type,
|
||||
locally_static: #locally_static,
|
||||
fully_static: #fully_static,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TemplateBuilder {
|
||||
nodes: Vec<TemplateNodeBuilder>,
|
||||
root_nodes: Vec<TemplateNodeId>,
|
||||
dynamic_context: DynamicTemplateContextBuilder,
|
||||
}
|
||||
|
||||
impl TemplateBuilder {
|
||||
/// Create a template builder from nodes if it would improve performance to do so.
|
||||
pub fn from_roots(roots: Vec<BodyNode>) -> Option<Self> {
|
||||
let mut builder = Self::default();
|
||||
|
||||
for root in roots {
|
||||
let id = builder.build_node(root, None);
|
||||
builder.root_nodes.push(id);
|
||||
}
|
||||
|
||||
// only build a template if there is at least one static node
|
||||
if builder.nodes.iter().all(|r| {
|
||||
if let TemplateNodeTypeBuilder::DynamicNode(_) = &r.node_type {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
None
|
||||
} else {
|
||||
Some(builder)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a template builder from nodes regardless of performance.
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
fn from_roots_always(roots: Vec<BodyNode>) -> Self {
|
||||
let mut builder = Self::default();
|
||||
|
||||
for root in roots {
|
||||
let id = builder.build_node(root, None);
|
||||
builder.root_nodes.push(id);
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
fn build_node(&mut self, node: BodyNode, parent: Option<TemplateNodeId>) -> TemplateNodeId {
|
||||
let id = TemplateNodeId(self.nodes.len());
|
||||
match node {
|
||||
BodyNode::Element(el) => {
|
||||
let mut attributes = Vec::new();
|
||||
let mut listeners = Vec::new();
|
||||
for attr in el.attributes {
|
||||
match attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
if let Some(static_value) = value.to_static() {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Ident(name),
|
||||
value: TemplateAttributeValue::Static(
|
||||
OwnedAttributeValue::Text(static_value),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Ident(name),
|
||||
value: TemplateAttributeValue::Dynamic(
|
||||
self.dynamic_context.add_attr(quote!(AttributeValue::Text(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
|
||||
.into_bump_str()
|
||||
))),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
if let Some(static_value) = value.to_static() {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Str(name),
|
||||
value: TemplateAttributeValue::Static(
|
||||
OwnedAttributeValue::Text(static_value),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Str(name),
|
||||
value: TemplateAttributeValue::Dynamic(
|
||||
self.dynamic_context.add_attr(quote!(AttributeValue::Text(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
|
||||
.into_bump_str()
|
||||
))),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Ident(name),
|
||||
value: TemplateAttributeValue::Dynamic(
|
||||
self.dynamic_context.add_attr(quote!(AttributeValue::Text(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
|
||||
.into_bump_str()
|
||||
))),
|
||||
),
|
||||
})
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
attributes.push(TemplateAttributeBuilder {
|
||||
element_tag: el.name.clone(),
|
||||
name: AttributeName::Str(name),
|
||||
value: TemplateAttributeValue::Dynamic(
|
||||
self.dynamic_context.add_attr(quote!(AttributeValue::Text(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #value)
|
||||
.into_bump_str()
|
||||
))),
|
||||
),
|
||||
})
|
||||
}
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
listeners.push(self.dynamic_context.add_listener(name, tokens))
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(key) = el.key {
|
||||
self.dynamic_context.add_key(quote!(
|
||||
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #key)
|
||||
.into_bump_str()
|
||||
));
|
||||
}
|
||||
self.nodes.push(TemplateNodeBuilder {
|
||||
id,
|
||||
node_type: TemplateNodeTypeBuilder::Element(TemplateElementBuilder {
|
||||
tag: el.name,
|
||||
attributes,
|
||||
children: Vec::new(),
|
||||
listeners,
|
||||
parent,
|
||||
}),
|
||||
});
|
||||
|
||||
let children: Vec<_> = el
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|child| self.build_node(child, Some(id)))
|
||||
.collect();
|
||||
let parent = &mut self.nodes[id.0];
|
||||
if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type {
|
||||
element.children = children;
|
||||
}
|
||||
}
|
||||
|
||||
BodyNode::Component(comp) => {
|
||||
self.nodes.push(TemplateNodeBuilder {
|
||||
id,
|
||||
node_type: TemplateNodeTypeBuilder::DynamicNode(
|
||||
self.dynamic_context.add_node(BodyNode::Component(comp)),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
BodyNode::Text(txt) => {
|
||||
let mut segments = Vec::new();
|
||||
|
||||
for segment in txt.segments {
|
||||
segments.push(match segment {
|
||||
Segment::Literal(lit) => TextTemplateSegment::Static(lit),
|
||||
Segment::Formatted(fmted) => {
|
||||
TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.nodes.push(TemplateNodeBuilder {
|
||||
id,
|
||||
node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments)),
|
||||
});
|
||||
}
|
||||
|
||||
BodyNode::RawExpr(expr) => {
|
||||
self.nodes.push(TemplateNodeBuilder {
|
||||
id,
|
||||
node_type: TemplateNodeTypeBuilder::DynamicNode(
|
||||
self.dynamic_context.add_node(BodyNode::RawExpr(expr)),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub fn try_switch_dynamic_context(
|
||||
mut self,
|
||||
dynamic_context: DynamicTemplateContextBuilder,
|
||||
) -> Option<Self> {
|
||||
let attribute_mapping: HashMap<String, usize> = dynamic_context
|
||||
.attributes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ts)| (ts.to_string(), i))
|
||||
.collect();
|
||||
let text_mapping: HashMap<String, usize> = dynamic_context
|
||||
.text
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ts)| (ts.to_token_stream().to_string(), i))
|
||||
.collect();
|
||||
let listener_mapping: HashMap<(String, Expr), usize> = dynamic_context
|
||||
.listeners
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ts)| (ts.clone(), i))
|
||||
.collect();
|
||||
let node_mapping: HashMap<String, usize> = dynamic_context
|
||||
.nodes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ts)| (ts.to_token_stream().to_string(), i))
|
||||
.collect();
|
||||
|
||||
for node in &mut self.nodes {
|
||||
match &mut node.node_type {
|
||||
TemplateNodeTypeBuilder::Element(element) => {
|
||||
for listener in &mut element.listeners {
|
||||
*listener =
|
||||
*listener_mapping.get(&self.dynamic_context.listeners[*listener])?;
|
||||
}
|
||||
for attribute in &mut element.attributes {
|
||||
if let TemplateAttributeValue::Dynamic(idx) = &mut attribute.value {
|
||||
*idx = *attribute_mapping
|
||||
.get(&self.dynamic_context.attributes[*idx].to_string())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateNodeTypeBuilder::Text(txt) => {
|
||||
for seg in &mut txt.segments {
|
||||
if let TextTemplateSegment::Dynamic(idx) = seg {
|
||||
*idx = *text_mapping.get(
|
||||
&self.dynamic_context.text[*idx]
|
||||
.to_token_stream()
|
||||
.to_string(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => {
|
||||
*idx = *node_mapping.get(
|
||||
&self.dynamic_context.nodes[*idx]
|
||||
.to_token_stream()
|
||||
.to_string(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dynamic_context = dynamic_context;
|
||||
|
||||
Some(self)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplate, Error> {
|
||||
let mut nodes = Vec::new();
|
||||
let dynamic_mapping = self.dynamic_mapping(&nodes);
|
||||
for node in self.nodes {
|
||||
nodes.push(node.try_into_owned(location)?);
|
||||
}
|
||||
|
||||
Ok(OwnedTemplate {
|
||||
nodes,
|
||||
root_nodes: self.root_nodes,
|
||||
dynamic_mapping,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
pub fn dynamic_mapping(
|
||||
&self,
|
||||
resolved_nodes: &Vec<OwnedTemplateNode>,
|
||||
) -> OwnedDynamicNodeMapping {
|
||||
let dynamic_context = &self.dynamic_context;
|
||||
let mut node_mapping = vec![None; dynamic_context.nodes.len()];
|
||||
let nodes = &self.nodes;
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => node_mapping[*idx] = Some(n.id),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Text(txt) => {
|
||||
for seg in &txt.segments {
|
||||
match seg {
|
||||
TextTemplateSegment::Static(_) => (),
|
||||
TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
for (i, attr) in el.attributes.iter().enumerate() {
|
||||
match attr.value {
|
||||
TemplateAttributeValue::Static(_) => (),
|
||||
TemplateAttributeValue::Dynamic(idx) => {
|
||||
attribute_mapping[idx].push((n.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut listener_mapping = Vec::new();
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
if !el.listeners.is_empty() {
|
||||
listener_mapping.push(n.id);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let mut volatile_mapping = Vec::new();
|
||||
for n in resolved_nodes {
|
||||
if let TemplateNodeType::Element(el) = &n.node_type {
|
||||
for (i, attr) in el.attributes.iter().enumerate() {
|
||||
if attr.attribute.volatile {
|
||||
volatile_mapping.push((n.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OwnedDynamicNodeMapping::new(
|
||||
node_mapping,
|
||||
text_mapping,
|
||||
attribute_mapping,
|
||||
volatile_mapping,
|
||||
listener_mapping,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for TemplateBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let Self {
|
||||
nodes,
|
||||
root_nodes,
|
||||
dynamic_context,
|
||||
} = self;
|
||||
|
||||
let mut node_mapping = vec![None; dynamic_context.nodes.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::DynamicNode(idx) => node_mapping[*idx] = Some(n.id),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Text(txt) => {
|
||||
for seg in &txt.segments {
|
||||
match seg {
|
||||
TextTemplateSegment::Static(_) => (),
|
||||
TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()];
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
for (i, attr) in el.attributes.iter().enumerate() {
|
||||
match attr.value {
|
||||
TemplateAttributeValue::Static(_) => (),
|
||||
TemplateAttributeValue::Dynamic(idx) => {
|
||||
attribute_mapping[idx].push((n.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut listener_mapping = Vec::new();
|
||||
for n in nodes {
|
||||
match &n.node_type {
|
||||
TemplateNodeTypeBuilder::Element(el) => {
|
||||
if !el.listeners.is_empty() {
|
||||
listener_mapping.push(n.id);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let root_nodes = root_nodes.iter().map(|id| {
|
||||
let raw = id.0;
|
||||
quote! { TemplateNodeId(#raw) }
|
||||
});
|
||||
let node_mapping_quoted = node_mapping.iter().map(|op| match op {
|
||||
Some(id) => {
|
||||
let raw_id = id.0;
|
||||
quote! {Some(TemplateNodeId(#raw_id))}
|
||||
}
|
||||
None => quote! {None},
|
||||
});
|
||||
let text_mapping_quoted = text_mapping.iter().map(|inner| {
|
||||
let raw = inner.iter().map(|id| id.0);
|
||||
quote! {&[#(TemplateNodeId(#raw)),*]}
|
||||
});
|
||||
let attribute_mapping_quoted = attribute_mapping.iter().map(|inner| {
|
||||
let raw = inner.iter().map(|(id, _)| id.0);
|
||||
let indecies = inner.iter().map(|(_, idx)| idx);
|
||||
quote! {&[#((TemplateNodeId(#raw), #indecies)),*]}
|
||||
});
|
||||
let listener_mapping_quoted = listener_mapping.iter().map(|id| {
|
||||
let raw = id.0;
|
||||
quote! {TemplateNodeId(#raw)}
|
||||
});
|
||||
let mut nodes_quoted = TokenStream::new();
|
||||
for n in nodes {
|
||||
n.to_tokens(&mut nodes_quoted, nodes);
|
||||
quote! {,}.to_tokens(&mut nodes_quoted);
|
||||
}
|
||||
|
||||
let quoted = quote! {
|
||||
{
|
||||
const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted];
|
||||
const __TEXT_MAPPING: &'static [&'static [dioxus::prelude::TemplateNodeId]] = &[#(#text_mapping_quoted),*];
|
||||
const __ATTRIBUTE_MAPPING: &'static [&'static [(dioxus::prelude::TemplateNodeId, usize)]] = &[#(#attribute_mapping_quoted),*];
|
||||
const __ROOT_NODES: &'static [dioxus::prelude::TemplateNodeId] = &[#(#root_nodes),*];
|
||||
const __NODE_MAPPING: &'static [Option<dioxus::prelude::TemplateNodeId>] = &[#(#node_mapping_quoted),*];
|
||||
const __NODES_WITH_LISTENERS: &'static [dioxus::prelude::TemplateNodeId] = &[#(#listener_mapping_quoted),*];
|
||||
static __VOLITALE_MAPPING_INNER: dioxus::core::exports::once_cell::sync::Lazy<Vec<(dioxus::prelude::TemplateNodeId, usize)>> = dioxus::core::exports::once_cell::sync::Lazy::new(||{
|
||||
// check each property to see if it is volatile
|
||||
let mut volatile = Vec::new();
|
||||
for n in __NODES {
|
||||
if let TemplateNodeType::Element(el) = &n.node_type {
|
||||
for (i, attr) in el.attributes.iter().enumerate() {
|
||||
if attr.attribute.volatile {
|
||||
volatile.push((n.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
volatile
|
||||
});
|
||||
static __VOLITALE_MAPPING: &'static dioxus::core::exports::once_cell::sync::Lazy<Vec<(dioxus::prelude::TemplateNodeId, usize)>> = &__VOLITALE_MAPPING_INNER;
|
||||
static __STATIC_VOLITALE_MAPPING: dioxus::prelude::LazyStaticVec<(dioxus::prelude::TemplateNodeId, usize)> = LazyStaticVec(__VOLITALE_MAPPING);
|
||||
static __TEMPLATE: dioxus::prelude::Template = Template::Static(&StaticTemplate {
|
||||
nodes: __NODES,
|
||||
root_nodes: __ROOT_NODES,
|
||||
dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS),
|
||||
});
|
||||
|
||||
let __bump = __cx.bump();
|
||||
__cx.template_ref(dioxus::prelude::TemplateId(get_line_num!()), __TEMPLATE.clone(), #dynamic_context)
|
||||
}
|
||||
};
|
||||
|
||||
tokens.append_all(quoted)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct DynamicTemplateContextBuilder {
|
||||
nodes: Vec<BodyNode>,
|
||||
text: Vec<FormattedSegment>,
|
||||
attributes: Vec<TokenStream>,
|
||||
listeners: Vec<(String, Expr)>,
|
||||
key: Option<TokenStream>,
|
||||
}
|
||||
|
||||
impl DynamicTemplateContextBuilder {
|
||||
fn add_node(&mut self, node: BodyNode) -> usize {
|
||||
let node_id = self.nodes.len();
|
||||
|
||||
self.nodes.push(node);
|
||||
|
||||
node_id
|
||||
}
|
||||
|
||||
fn add_text(&mut self, text: FormattedSegment) -> usize {
|
||||
let text_id = self.text.len();
|
||||
|
||||
self.text.push(text);
|
||||
|
||||
text_id
|
||||
}
|
||||
|
||||
fn add_attr(&mut self, attr: TokenStream) -> usize {
|
||||
let attr_id = self.attributes.len();
|
||||
|
||||
self.attributes.push(attr);
|
||||
|
||||
attr_id
|
||||
}
|
||||
|
||||
fn add_listener(&mut self, name: Ident, listener: Expr) -> usize {
|
||||
let listener_id = self.listeners.len();
|
||||
|
||||
self.listeners.push((name.to_string(), listener));
|
||||
|
||||
listener_id
|
||||
}
|
||||
|
||||
fn add_key(&mut self, key: TokenStream) {
|
||||
self.key = Some(key);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for DynamicTemplateContextBuilder {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let nodes = &self.nodes;
|
||||
let text = &self.text;
|
||||
let attributes = &self.attributes;
|
||||
let listeners_names = self
|
||||
.listeners
|
||||
.iter()
|
||||
.map(|(n, _)| syn::parse_str::<Ident>(n).expect(n));
|
||||
let listeners_exprs = self.listeners.iter().map(|(_, e)| e);
|
||||
let key = match &self.key {
|
||||
Some(k) => quote!(Some(#k)),
|
||||
None => quote!(None),
|
||||
};
|
||||
tokens.append_all(quote! {
|
||||
TemplateContext {
|
||||
nodes: __cx.bump().alloc([#(#nodes),*]),
|
||||
text_segments: __cx.bump().alloc([#(&*dioxus::core::exports::bumpalo::format!(in __bump, "{}", #text).into_bump_str()),*]),
|
||||
attributes: __cx.bump().alloc([#(#attributes),*]),
|
||||
listeners: __cx.bump().alloc([#(dioxus_elements::on::#listeners_names(__cx, #listeners_exprs)),*]),
|
||||
key: #key,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
[package]
|
||||
name = "dioxus-rsx-interpreter"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0", features = ["extra-traits"] }
|
||||
proc-macro2 = { version = "1.0.39", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
dioxus-rsx = { path = "../rsx", default-features = false }
|
||||
dioxus-ssr = { path = "../ssr" }
|
||||
dioxus-core = { path = "../core" }
|
||||
dioxus-html = { path = "../html" }
|
||||
dioxus-hooks = { path = "../hooks"}
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus-core-macro = { path = "../core-macro" }
|
||||
bumpalo = { version = "3.6", features = ["collections", "boxed"] }
|
||||
dioxus = { path = "../dioxus", features = ["hot-reload"]}
|
|
@ -1,209 +0,0 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use dioxus_core::{Listener, VNode};
|
||||
use dioxus_rsx::{
|
||||
BodyNode, CallBody, Component, ElementAttr, ElementAttrNamed, IfmtInput, Segment,
|
||||
};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{Expr, Ident, LitStr, Result};
|
||||
|
||||
use crate::{attributes::attrbute_to_static_str, CodeLocation};
|
||||
#[derive(Default)]
|
||||
pub struct CapturedContextBuilder {
|
||||
pub ifmted: Vec<IfmtInput>,
|
||||
pub components: Vec<Component>,
|
||||
pub iterators: Vec<BodyNode>,
|
||||
pub captured_expressions: Vec<Expr>,
|
||||
pub listeners: Vec<ElementAttrNamed>,
|
||||
pub custom_context: Option<Ident>,
|
||||
pub custom_attributes: HashSet<LitStr>,
|
||||
}
|
||||
|
||||
impl CapturedContextBuilder {
|
||||
pub fn extend(&mut self, other: CapturedContextBuilder) {
|
||||
self.ifmted.extend(other.ifmted);
|
||||
self.components.extend(other.components);
|
||||
self.iterators.extend(other.iterators);
|
||||
self.listeners.extend(other.listeners);
|
||||
self.captured_expressions.extend(other.captured_expressions);
|
||||
self.custom_attributes.extend(other.custom_attributes);
|
||||
}
|
||||
|
||||
pub fn from_call_body(body: CallBody) -> Result<Self> {
|
||||
let mut new = Self::default();
|
||||
for node in body.roots {
|
||||
new.extend(Self::find_captured(node)?);
|
||||
}
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
fn find_captured(node: BodyNode) -> Result<Self> {
|
||||
let mut captured = CapturedContextBuilder::default();
|
||||
match node {
|
||||
BodyNode::Element(el) => {
|
||||
for attr in el.attributes {
|
||||
match attr.attr {
|
||||
ElementAttr::AttrText { value, .. } => {
|
||||
let value_tokens = value.to_token_stream();
|
||||
let formated: IfmtInput = syn::parse2(value_tokens)?;
|
||||
captured.ifmted.push(formated);
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
captured.custom_attributes.insert(name);
|
||||
let value_tokens = value.to_token_stream();
|
||||
let formated: IfmtInput = syn::parse2(value_tokens)?;
|
||||
captured.ifmted.push(formated);
|
||||
}
|
||||
ElementAttr::AttrExpression { name: _, value } => {
|
||||
captured.captured_expressions.push(value);
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
captured.custom_attributes.insert(name);
|
||||
captured.captured_expressions.push(value);
|
||||
}
|
||||
ElementAttr::EventTokens { .. } => captured.listeners.push(attr),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(key) = el.key {
|
||||
let value_tokens = key.to_token_stream();
|
||||
let formated: IfmtInput = syn::parse2(value_tokens)?;
|
||||
captured.ifmted.push(formated);
|
||||
}
|
||||
|
||||
for child in el.children {
|
||||
captured.extend(Self::find_captured(child)?);
|
||||
}
|
||||
}
|
||||
BodyNode::Component(comp) => {
|
||||
captured.components.push(comp);
|
||||
}
|
||||
BodyNode::Text(t) => {
|
||||
let tokens = t.to_token_stream();
|
||||
let formated: IfmtInput = syn::parse2(tokens).unwrap();
|
||||
captured.ifmted.push(formated);
|
||||
}
|
||||
BodyNode::RawExpr(_) => captured.iterators.push(node),
|
||||
}
|
||||
Ok(captured)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for CapturedContextBuilder {
|
||||
fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
|
||||
let CapturedContextBuilder {
|
||||
ifmted,
|
||||
components,
|
||||
iterators,
|
||||
captured_expressions,
|
||||
listeners,
|
||||
custom_context: _,
|
||||
custom_attributes,
|
||||
} = self;
|
||||
let listeners_str = listeners
|
||||
.iter()
|
||||
.map(|comp| comp.to_token_stream().to_string());
|
||||
let compontents_str = components
|
||||
.iter()
|
||||
.map(|comp| comp.to_token_stream().to_string());
|
||||
let iterators_str = iterators.iter().map(|node| match node {
|
||||
BodyNode::RawExpr(expr) => expr.to_token_stream().to_string(),
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let captured: Vec<_> = ifmted
|
||||
.iter()
|
||||
.flat_map(|input| input.segments.iter())
|
||||
.filter_map(|seg| match seg {
|
||||
Segment::Formatted {
|
||||
format_args,
|
||||
segment,
|
||||
} => {
|
||||
let expr = segment.to_token_stream();
|
||||
let as_string = expr.to_string();
|
||||
let format_expr = if format_args.is_empty() {
|
||||
"{".to_string() + format_args + "}"
|
||||
} else {
|
||||
"{".to_string() + ":" + format_args + "}"
|
||||
};
|
||||
Some(quote! {
|
||||
FormattedArg{
|
||||
expr: #as_string,
|
||||
format_args: #format_args,
|
||||
result: format!(#format_expr, #expr)
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let captured_attr_expressions_text = captured_expressions
|
||||
.iter()
|
||||
.map(|e| format!("{}", e.to_token_stream()));
|
||||
let custom_attributes_iter = custom_attributes.iter();
|
||||
tokens.append_all(quote! {
|
||||
CapturedContext {
|
||||
captured: IfmtArgs{
|
||||
named_args: vec![#(#captured),*]
|
||||
},
|
||||
components: vec![#((#compontents_str, #components)),*],
|
||||
iterators: vec![#((#iterators_str, #iterators)),*],
|
||||
expressions: vec![#((#captured_attr_expressions_text, #captured_expressions.to_string())),*],
|
||||
listeners: vec![#((#listeners_str, #listeners)),*],
|
||||
custom_attributes: &[#(#custom_attributes_iter),*],
|
||||
location: code_location.clone()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CapturedContext<'a> {
|
||||
// map of the variable name to the formated value
|
||||
pub captured: IfmtArgs,
|
||||
// map of the attribute name and element path to the formated value
|
||||
// pub captured_attribute_values: IfmtArgs,
|
||||
// the only thing we can update in component is the children
|
||||
pub components: Vec<(&'static str, VNode<'a>)>,
|
||||
// we can't reasonably interpert iterators, so they are staticly inserted
|
||||
pub iterators: Vec<(&'static str, VNode<'a>)>,
|
||||
// map expression to the value resulting from the expression
|
||||
pub expressions: Vec<(&'static str, String)>,
|
||||
// map listener code to the resulting listener
|
||||
pub listeners: Vec<(&'static str, Listener<'a>)>,
|
||||
// used to map custom attrbutes form &'a str to &'static str
|
||||
pub custom_attributes: &'static [&'static str],
|
||||
// used to provide better error messages
|
||||
pub location: CodeLocation,
|
||||
}
|
||||
|
||||
impl<'a> CapturedContext<'a> {
|
||||
pub fn attrbute_to_static_str(
|
||||
&self,
|
||||
attr: &str,
|
||||
tag: &'static str,
|
||||
ns: Option<&'static str>,
|
||||
literal: bool,
|
||||
) -> Option<(&'static str, Option<&'static str>)> {
|
||||
if let Some(attr) = attrbute_to_static_str(attr, tag, ns) {
|
||||
Some(attr)
|
||||
} else if literal {
|
||||
self.custom_attributes
|
||||
.iter()
|
||||
.find(|attribute| attr == **attribute)
|
||||
.map(|attribute| (*attribute, None))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IfmtArgs {
|
||||
// All expressions that have been resolved
|
||||
pub named_args: Vec<FormattedArg>,
|
||||
}
|
||||
|
||||
/// A formated segment that has been resolved
|
||||
pub struct FormattedArg {
|
||||
pub expr: &'static str,
|
||||
pub format_args: &'static str,
|
||||
pub result: String,
|
||||
}
|
|
@ -1,277 +0,0 @@
|
|||
use dioxus_core::{Attribute, AttributeValue, NodeFactory, VNode};
|
||||
use dioxus_rsx::{BodyNode, CallBody, ElementAttr, IfmtInput, Segment};
|
||||
use quote::ToTokens;
|
||||
use quote::__private::Span;
|
||||
use std::str::FromStr;
|
||||
use syn::{parse2, parse_str, Expr};
|
||||
|
||||
use crate::captuered_context::{CapturedContext, IfmtArgs};
|
||||
use crate::elements::element_to_static_str;
|
||||
use crate::error::{Error, ParseError, RecompileReason};
|
||||
|
||||
fn resolve_ifmt(ifmt: &IfmtInput, captured: &IfmtArgs) -> Result<String, Error> {
|
||||
let mut result = String::new();
|
||||
for seg in &ifmt.segments {
|
||||
match seg {
|
||||
Segment::Formatted {
|
||||
segment,
|
||||
format_args,
|
||||
} => {
|
||||
let expr = segment.to_token_stream();
|
||||
let expr: Expr = parse2(expr).unwrap();
|
||||
let search = captured.named_args.iter().find(|fmted| {
|
||||
parse_str::<Expr>(fmted.expr).unwrap() == expr
|
||||
&& fmted.format_args == format_args
|
||||
});
|
||||
match search {
|
||||
Some(formatted) => {
|
||||
result.push_str(&formatted.result);
|
||||
}
|
||||
None => {
|
||||
let expr_str = segment.to_token_stream().to_string();
|
||||
return Err(Error::RecompileRequiredError(
|
||||
RecompileReason::CapturedExpression(format!(
|
||||
"could not resolve {{{}:{}}}",
|
||||
expr_str, format_args
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Segment::Literal(lit) => result.push_str(lit),
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn build<'a>(
|
||||
rsx: CallBody,
|
||||
mut ctx: CapturedContext<'a>,
|
||||
factory: &NodeFactory<'a>,
|
||||
) -> Result<VNode<'a>, Error> {
|
||||
let children_built = factory.bump().alloc(Vec::new());
|
||||
for child in rsx.roots {
|
||||
children_built.push(build_node(child, &mut ctx, factory)?);
|
||||
}
|
||||
|
||||
if children_built.len() == 1 {
|
||||
Ok(children_built.pop().unwrap())
|
||||
} else {
|
||||
Ok(factory.fragment_from_iter(children_built.iter()))
|
||||
}
|
||||
}
|
||||
|
||||
fn build_node<'a>(
|
||||
node: BodyNode,
|
||||
ctx: &mut CapturedContext<'a>,
|
||||
factory: &NodeFactory<'a>,
|
||||
) -> Result<VNode<'a>, Error> {
|
||||
let bump = factory.bump();
|
||||
match node {
|
||||
BodyNode::Text(text) => {
|
||||
let ifmt = IfmtInput::from_str(&text.value())
|
||||
.map_err(|err| Error::ParseError(ParseError::new(err, ctx.location.clone())))?;
|
||||
let text = bump.alloc(resolve_ifmt(&ifmt, &ctx.captured)?);
|
||||
Ok(factory.text(format_args!("{}", text)))
|
||||
}
|
||||
BodyNode::Element(el) => {
|
||||
let attributes: &mut Vec<Attribute> = bump.alloc(Vec::new());
|
||||
let tag = &el.name.to_string();
|
||||
if let Some((tag, ns)) = element_to_static_str(tag) {
|
||||
for attr in &el.attributes {
|
||||
match &attr.attr {
|
||||
ElementAttr::AttrText { .. } | ElementAttr::CustomAttrText { .. } => {
|
||||
let (name, value, span, literal): (String, IfmtInput, Span, bool) =
|
||||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } => (
|
||||
name.to_string(),
|
||||
IfmtInput::from_str(&value.value()).map_err(|err| {
|
||||
Error::ParseError(ParseError::new(
|
||||
err,
|
||||
ctx.location.clone(),
|
||||
))
|
||||
})?,
|
||||
name.span(),
|
||||
false,
|
||||
),
|
||||
ElementAttr::CustomAttrText { name, value } => (
|
||||
name.value(),
|
||||
IfmtInput::from_str(&value.value()).map_err(|err| {
|
||||
Error::ParseError(ParseError::new(
|
||||
err,
|
||||
ctx.location.clone(),
|
||||
))
|
||||
})?,
|
||||
name.span(),
|
||||
true,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some((name, namespace)) =
|
||||
ctx.attrbute_to_static_str(&name, tag, ns, literal)
|
||||
{
|
||||
let value = bump.alloc(resolve_ifmt(&value, &ctx.captured)?);
|
||||
attributes.push(Attribute {
|
||||
name,
|
||||
value: AttributeValue::Text(value),
|
||||
is_static: true,
|
||||
is_volatile: false,
|
||||
namespace,
|
||||
});
|
||||
} else if literal {
|
||||
// literals will be captured when a full recompile is triggered
|
||||
return Err(Error::RecompileRequiredError(
|
||||
RecompileReason::CapturedAttribute(name.to_string()),
|
||||
));
|
||||
} else {
|
||||
return Err(Error::ParseError(ParseError::new(
|
||||
syn::Error::new(span, format!("unknown attribute: {}", name)),
|
||||
ctx.location.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
ElementAttr::AttrExpression { .. }
|
||||
| ElementAttr::CustomAttrExpression { .. } => {
|
||||
let (name, value, span, literal) = match &attr.attr {
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
(name.to_string(), value, name.span(), false)
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
(name.value(), value, name.span(), true)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if let Some((_, resulting_value)) = ctx
|
||||
.expressions
|
||||
.iter()
|
||||
.find(|(n, _)| parse_str::<Expr>(*n).unwrap() == *value)
|
||||
{
|
||||
if let Some((name, namespace)) =
|
||||
ctx.attrbute_to_static_str(&name, tag, ns, literal)
|
||||
{
|
||||
let value = bump.alloc(resulting_value.clone());
|
||||
attributes.push(Attribute {
|
||||
name,
|
||||
value: AttributeValue::Text(value),
|
||||
is_static: true,
|
||||
is_volatile: false,
|
||||
namespace,
|
||||
});
|
||||
} else if literal {
|
||||
// literals will be captured when a full recompile is triggered
|
||||
return Err(Error::RecompileRequiredError(
|
||||
RecompileReason::CapturedAttribute(name.to_string()),
|
||||
));
|
||||
} else {
|
||||
return Err(Error::ParseError(ParseError::new(
|
||||
syn::Error::new(
|
||||
span,
|
||||
format!("unknown attribute: {}", name),
|
||||
),
|
||||
ctx.location.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let children = bump.alloc(Vec::new());
|
||||
for child in el.children {
|
||||
let node = build_node(child, ctx, factory)?;
|
||||
children.push(node);
|
||||
}
|
||||
let listeners = bump.alloc(Vec::new());
|
||||
for attr in el.attributes {
|
||||
if let ElementAttr::EventTokens { .. } = attr.attr {
|
||||
let expr: Expr = parse2(attr.to_token_stream()).map_err(|err| {
|
||||
Error::ParseError(ParseError::new(err, ctx.location.clone()))
|
||||
})?;
|
||||
if let Some(idx) = ctx.listeners.iter().position(|(code, _)| {
|
||||
if let Ok(parsed) = parse_str::<Expr>(*code) {
|
||||
parsed == expr
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
let (_, listener) = ctx.listeners.remove(idx);
|
||||
listeners.push(listener)
|
||||
} else {
|
||||
return Err(Error::RecompileRequiredError(
|
||||
RecompileReason::CapturedListener(
|
||||
expr.to_token_stream().to_string(),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
match el.key {
|
||||
None => Ok(factory.raw_element(
|
||||
tag,
|
||||
ns,
|
||||
listeners,
|
||||
attributes.as_slice(),
|
||||
children.as_slice(),
|
||||
None,
|
||||
)),
|
||||
Some(lit) => {
|
||||
let ifmt: IfmtInput = lit.value().parse().map_err(|err| {
|
||||
Error::ParseError(ParseError::new(err, ctx.location.clone()))
|
||||
})?;
|
||||
let key = bump.alloc(resolve_ifmt(&ifmt, &ctx.captured)?);
|
||||
|
||||
Ok(factory.raw_element(
|
||||
tag,
|
||||
ns,
|
||||
listeners,
|
||||
attributes.as_slice(),
|
||||
children.as_slice(),
|
||||
Some(format_args!("{}", key)),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::ParseError(ParseError::new(
|
||||
syn::Error::new(el.name.span(), format!("unknown element: {}", tag)),
|
||||
ctx.location.clone(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
BodyNode::Component(comp) => {
|
||||
let expr: Expr = parse2(comp.to_token_stream())
|
||||
.map_err(|err| Error::ParseError(ParseError::new(err, ctx.location.clone())))?;
|
||||
if let Some(idx) = ctx.components.iter().position(|(code, _)| {
|
||||
if let Ok(parsed) = parse_str::<Expr>(*code) {
|
||||
parsed == expr
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
let (_, vnode) = ctx.components.remove(idx);
|
||||
Ok(vnode)
|
||||
} else {
|
||||
Err(Error::RecompileRequiredError(
|
||||
RecompileReason::CapturedComponent(comp.name.to_token_stream().to_string()),
|
||||
))
|
||||
}
|
||||
}
|
||||
BodyNode::RawExpr(iterator) => {
|
||||
if let Some(idx) = ctx.iterators.iter().position(|(code, _)| {
|
||||
if let Ok(parsed) = parse_str::<Expr>(*code) {
|
||||
parsed == iterator
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
let (_, vnode) = ctx.iterators.remove(idx);
|
||||
Ok(vnode)
|
||||
} else {
|
||||
Err(Error::RecompileRequiredError(
|
||||
RecompileReason::CapturedExpression(iterator.to_token_stream().to_string()),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,183 +0,0 @@
|
|||
use captuered_context::CapturedContext;
|
||||
use dioxus_core::{NodeFactory, SchedulerMsg, VNode};
|
||||
use dioxus_hooks::UnboundedSender;
|
||||
use error::{Error, ParseError};
|
||||
use interperter::build;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{RwLock, RwLockReadGuard};
|
||||
use syn::parse_str;
|
||||
|
||||
mod attributes;
|
||||
pub mod captuered_context;
|
||||
mod elements;
|
||||
pub mod error;
|
||||
mod interperter;
|
||||
|
||||
lazy_static! {
|
||||
/// This a a global store of the current rsx text for each call to rsx
|
||||
// Global mutable data is genrally not great, but it allows users to not worry about passing down the text RsxContex every time they switch to hot reloading.
|
||||
pub static ref RSX_CONTEXT: RsxContext = RsxContext::new(RsxData::default());
|
||||
}
|
||||
|
||||
// the location of the code relative to the current crate based on [std::panic::Location]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CodeLocation {
|
||||
pub crate_path: String,
|
||||
pub file_path: String,
|
||||
pub line: u32,
|
||||
pub column: u32,
|
||||
}
|
||||
|
||||
/// Get the resolved rsx given the origional rsx, a captured context of dynamic components, and a factory to build the resulting node
|
||||
#[cfg_attr(
|
||||
not(debug_assertions),
|
||||
deprecated(
|
||||
note = "The hot reload feature is enabled in release mode. This feature should be disabled for production builds."
|
||||
)
|
||||
)]
|
||||
pub fn resolve_scope<'a>(
|
||||
location: CodeLocation,
|
||||
rsx: &'static str,
|
||||
captured: CapturedContext<'a>,
|
||||
factory: NodeFactory<'a>,
|
||||
) -> VNode<'a> {
|
||||
let rsx_text_index = &*RSX_CONTEXT;
|
||||
// only the insert the rsx text once
|
||||
if !rsx_text_index.read().hm.contains_key(&location) {
|
||||
rsx_text_index.insert(location.clone(), rsx.to_string());
|
||||
}
|
||||
if let Some(text) = {
|
||||
let read = rsx_text_index.read();
|
||||
// clone prevents deadlock on nested rsx calls
|
||||
read.hm.get(&location).cloned()
|
||||
} {
|
||||
match interpert_rsx(factory, &text, captured) {
|
||||
Ok(vnode) => vnode,
|
||||
Err(err) => {
|
||||
rsx_text_index.report_error(err);
|
||||
factory.text(format_args!(""))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("rsx: line number {:?} not found in rsx index", location);
|
||||
}
|
||||
}
|
||||
|
||||
fn interpert_rsx<'a>(
|
||||
factory: dioxus_core::NodeFactory<'a>,
|
||||
text: &str,
|
||||
context: captuered_context::CapturedContext<'a>,
|
||||
) -> Result<VNode<'a>, Error> {
|
||||
build(
|
||||
parse_str(text)
|
||||
.map_err(|err| Error::ParseError(ParseError::new(err, context.location.clone())))?,
|
||||
context,
|
||||
&factory,
|
||||
)
|
||||
}
|
||||
|
||||
/// get the code location of the code that called this function
|
||||
#[macro_export]
|
||||
macro_rules! get_line_num {
|
||||
() => {{
|
||||
let line = line!();
|
||||
let column = column!();
|
||||
let file_path = file!().to_string();
|
||||
let crate_path = env!("CARGO_MANIFEST_DIR").to_string();
|
||||
|
||||
CodeLocation {
|
||||
crate_path,
|
||||
file_path,
|
||||
line: line,
|
||||
column: column,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// A handle to the rsx context with interior mutability
|
||||
#[derive(Debug)]
|
||||
pub struct RsxContext {
|
||||
data: RwLock<RsxData>,
|
||||
}
|
||||
|
||||
/// A store of the text for the rsx macro for each call to rsx
|
||||
#[derive(Default)]
|
||||
pub struct RsxData {
|
||||
pub hm: HashMap<CodeLocation, String>,
|
||||
pub error_handler: Option<Box<dyn ErrorHandler>>,
|
||||
pub scheduler_channel: Option<UnboundedSender<SchedulerMsg>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RsxData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RsxData").field("hm", &self.hm).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl RsxContext {
|
||||
pub fn new(data: RsxData) -> Self {
|
||||
Self {
|
||||
data: RwLock::new(data),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the text for an rsx call at some location
|
||||
pub fn insert(&self, loc: CodeLocation, text: String) {
|
||||
let mut write = self.data.write().unwrap();
|
||||
write.hm.insert(loc, text);
|
||||
if let Some(channel) = &mut write.scheduler_channel {
|
||||
channel.unbounded_send(SchedulerMsg::DirtyAll).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the text for many rsx calls
|
||||
pub fn extend(&self, msg: SetManyRsxMessage) {
|
||||
let mut write = self.data.write().unwrap();
|
||||
for rsx in msg.0 {
|
||||
write.hm.insert(rsx.location, rsx.new_text);
|
||||
}
|
||||
if let Some(channel) = &mut write.scheduler_channel {
|
||||
channel.unbounded_send(SchedulerMsg::DirtyAll).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&self) -> RwLockReadGuard<RsxData> {
|
||||
self.data.read().unwrap()
|
||||
}
|
||||
|
||||
fn report_error(&self, error: Error) {
|
||||
if let Some(handler) = &self.data.write().unwrap().error_handler {
|
||||
handler.handle_error(error)
|
||||
} else {
|
||||
panic!("no error handler set for this platform...\n{}", error);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the handler for errors interperting the rsx
|
||||
pub fn set_error_handler(&self, handler: impl ErrorHandler + 'static) {
|
||||
self.data.write().unwrap().error_handler = Some(Box::new(handler));
|
||||
}
|
||||
|
||||
/// Provide the scduler channel from [dioxus_code::VirtualDom::get_scheduler_channel].
|
||||
/// The channel allows the interpreter to force re-rendering of the dom when the rsx is changed.
|
||||
pub fn provide_scheduler_channel(&self, channel: UnboundedSender<SchedulerMsg>) {
|
||||
self.data.write().unwrap().scheduler_channel = Some(channel)
|
||||
}
|
||||
}
|
||||
|
||||
/// A error handler for errors reported by the rsx interperter
|
||||
pub trait ErrorHandler: Send + Sync {
|
||||
fn handle_error(&self, err: Error);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct SetRsxMessage {
|
||||
pub location: CodeLocation,
|
||||
pub new_text: String,
|
||||
}
|
||||
|
||||
/// Set many rsx texts at once to avoid duplicate errors
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct SetManyRsxMessage(pub Vec<SetRsxMessage>);
|
|
@ -1,407 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn render_basic() {
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
let dom = VirtualDom::new(Base);
|
||||
let static_vnodes = rsx!(div{"hello world"});
|
||||
let location = CodeLocation {
|
||||
file_path: String::new(),
|
||||
crate_path: String::new(),
|
||||
line: 0,
|
||||
column: 0,
|
||||
};
|
||||
let empty_context = CapturedContext {
|
||||
captured: IfmtArgs {
|
||||
named_args: Vec::new(),
|
||||
},
|
||||
components: Vec::new(),
|
||||
iterators: Vec::new(),
|
||||
expressions: Vec::new(),
|
||||
listeners: Vec::new(),
|
||||
location: location.clone(),
|
||||
custom_attributes: &[],
|
||||
};
|
||||
let interperted_vnodes = LazyNodes::new(|factory| {
|
||||
dioxus_rsx_interpreter::resolve_scope(
|
||||
location,
|
||||
"div{\"hello world\"}",
|
||||
empty_context,
|
||||
factory,
|
||||
)
|
||||
});
|
||||
|
||||
let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
|
||||
let static_vnodes = dom.render_vnodes(static_vnodes);
|
||||
assert!(check_eq(interperted_vnodes, static_vnodes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn render_nested() {
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
let dom = VirtualDom::new(Base);
|
||||
let static_vnodes = rsx! {
|
||||
div {
|
||||
p { "hello world" }
|
||||
div {
|
||||
p { "hello world" }
|
||||
}
|
||||
}
|
||||
};
|
||||
let location = CodeLocation {
|
||||
file_path: String::new(),
|
||||
crate_path: String::new(),
|
||||
line: 1,
|
||||
column: 0,
|
||||
};
|
||||
let empty_context = CapturedContext {
|
||||
captured: IfmtArgs {
|
||||
named_args: Vec::new(),
|
||||
},
|
||||
components: Vec::new(),
|
||||
iterators: Vec::new(),
|
||||
expressions: Vec::new(),
|
||||
listeners: Vec::new(),
|
||||
location: location.clone(),
|
||||
custom_attributes: &[],
|
||||
};
|
||||
let interperted_vnodes = LazyNodes::new(|factory| {
|
||||
dioxus_rsx_interpreter::resolve_scope(
|
||||
location,
|
||||
r#"div {
|
||||
p { "hello world" }
|
||||
div {
|
||||
p { "hello world" }
|
||||
}
|
||||
}"#,
|
||||
empty_context,
|
||||
factory,
|
||||
)
|
||||
});
|
||||
|
||||
let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
|
||||
let static_vnodes = dom.render_vnodes(static_vnodes);
|
||||
assert!(check_eq(interperted_vnodes, static_vnodes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn render_custom_attribute() {
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
let dom = VirtualDom::new(Base);
|
||||
let static_vnodes = rsx! {
|
||||
div {
|
||||
"data-test-1": 0,
|
||||
"data-test-2": "1",
|
||||
}
|
||||
};
|
||||
let location = CodeLocation {
|
||||
file_path: String::new(),
|
||||
crate_path: String::new(),
|
||||
line: 2,
|
||||
column: 0,
|
||||
};
|
||||
let empty_context = CapturedContext {
|
||||
captured: IfmtArgs {
|
||||
named_args: Vec::new(),
|
||||
},
|
||||
components: Vec::new(),
|
||||
iterators: Vec::new(),
|
||||
expressions: vec![("0", "0".to_string())],
|
||||
listeners: Vec::new(),
|
||||
location: location.clone(),
|
||||
custom_attributes: &["data-test-1", "data-test-2"],
|
||||
};
|
||||
let interperted_vnodes = LazyNodes::new(|factory| {
|
||||
dioxus_rsx_interpreter::resolve_scope(
|
||||
location,
|
||||
r#"div {
|
||||
"data-test-1": 0,
|
||||
"data-test-2": "1",
|
||||
}"#,
|
||||
empty_context,
|
||||
factory,
|
||||
)
|
||||
});
|
||||
|
||||
let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
|
||||
let static_vnodes = dom.render_vnodes(static_vnodes);
|
||||
assert!(check_eq(interperted_vnodes, static_vnodes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn render_component() {
|
||||
fn Comp(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
let dom = VirtualDom::new(Base);
|
||||
let static_vnodes = rsx! {
|
||||
div {
|
||||
Comp {}
|
||||
}
|
||||
};
|
||||
let location = CodeLocation {
|
||||
file_path: String::new(),
|
||||
crate_path: String::new(),
|
||||
line: 3,
|
||||
column: 0,
|
||||
};
|
||||
|
||||
let interperted_vnodes = LazyNodes::new(|factory| {
|
||||
let context = CapturedContext {
|
||||
captured: IfmtArgs {
|
||||
named_args: Vec::new(),
|
||||
},
|
||||
components: vec![(
|
||||
r#"__cx.component(Comp, fc_to_builder(Comp).build(), None, "Comp")"#,
|
||||
factory.component(Comp, (), None, "Comp"),
|
||||
)],
|
||||
iterators: Vec::new(),
|
||||
expressions: Vec::new(),
|
||||
listeners: Vec::new(),
|
||||
location: location.clone(),
|
||||
custom_attributes: &[],
|
||||
};
|
||||
dioxus_rsx_interpreter::resolve_scope(
|
||||
location,
|
||||
r#"div {
|
||||
Comp {}
|
||||
}"#,
|
||||
context,
|
||||
factory,
|
||||
)
|
||||
});
|
||||
|
||||
let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
|
||||
let static_vnodes = dom.render_vnodes(static_vnodes);
|
||||
println!("{:#?}", interperted_vnodes);
|
||||
println!("{:#?}", static_vnodes);
|
||||
assert!(check_eq(interperted_vnodes, static_vnodes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn render_iterator() {
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
let dom = VirtualDom::new(Base);
|
||||
let iter = (0..10).map(|i| dom.render_vnodes(rsx! {"{i}"}));
|
||||
let static_vnodes = rsx! {
|
||||
div {
|
||||
iter
|
||||
}
|
||||
};
|
||||
let location = CodeLocation {
|
||||
file_path: String::new(),
|
||||
crate_path: String::new(),
|
||||
line: 4,
|
||||
column: 0,
|
||||
};
|
||||
|
||||
let interperted_vnodes = LazyNodes::new(|factory| {
|
||||
let context = CapturedContext {
|
||||
captured: IfmtArgs {
|
||||
named_args: Vec::new(),
|
||||
},
|
||||
components: Vec::new(),
|
||||
iterators: vec![(
|
||||
r#"
|
||||
(0..10).map(|i| dom.render_vnodes(rsx!{"{i}"}))"#,
|
||||
factory.fragment_from_iter((0..10).map(|i| factory.text(format_args!("{i}")))),
|
||||
)],
|
||||
expressions: Vec::new(),
|
||||
listeners: Vec::new(),
|
||||
location: location.clone(),
|
||||
custom_attributes: &[],
|
||||
};
|
||||
dioxus_rsx_interpreter::resolve_scope(
|
||||
location,
|
||||
r#"div {
|
||||
(0..10).map(|i| dom.render_vnodes(rsx!{"{i}"}))
|
||||
}"#,
|
||||
context,
|
||||
factory,
|
||||
)
|
||||
});
|
||||
|
||||
let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
|
||||
let static_vnodes = dom.render_vnodes(static_vnodes);
|
||||
println!("{:#?}", interperted_vnodes);
|
||||
println!("{:#?}", static_vnodes);
|
||||
assert!(check_eq(interperted_vnodes, static_vnodes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn render_captured_variable() {
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
let dom = VirtualDom::new(Base);
|
||||
|
||||
let x = 10;
|
||||
let static_vnodes = rsx! {
|
||||
div {
|
||||
"{x}"
|
||||
}
|
||||
};
|
||||
let location = CodeLocation {
|
||||
file_path: String::new(),
|
||||
crate_path: String::new(),
|
||||
line: 5,
|
||||
column: 0,
|
||||
};
|
||||
|
||||
let interperted_vnodes = LazyNodes::new(|factory| {
|
||||
let context = CapturedContext {
|
||||
captured: IfmtArgs {
|
||||
named_args: vec![FormattedArg {
|
||||
expr: "x",
|
||||
format_args: "",
|
||||
result: x.to_string(),
|
||||
}],
|
||||
},
|
||||
components: Vec::new(),
|
||||
iterators: Vec::new(),
|
||||
expressions: Vec::new(),
|
||||
listeners: Vec::new(),
|
||||
location: location.clone(),
|
||||
custom_attributes: &[],
|
||||
};
|
||||
dioxus_rsx_interpreter::resolve_scope(
|
||||
location,
|
||||
r#"div {
|
||||
"{x}"
|
||||
}"#,
|
||||
context,
|
||||
factory,
|
||||
)
|
||||
});
|
||||
|
||||
let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
|
||||
let static_vnodes = dom.render_vnodes(static_vnodes);
|
||||
println!("{:#?}", interperted_vnodes);
|
||||
println!("{:#?}", static_vnodes);
|
||||
assert!(check_eq(interperted_vnodes, static_vnodes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn render_listener() {
|
||||
fn Base(cx: Scope) -> Element {
|
||||
render!(div {})
|
||||
}
|
||||
|
||||
let dom = VirtualDom::new(Base);
|
||||
let static_vnodes = rsx! {
|
||||
div {
|
||||
onclick: |_| println!("clicked")
|
||||
}
|
||||
};
|
||||
let location = CodeLocation {
|
||||
file_path: String::new(),
|
||||
crate_path: String::new(),
|
||||
line: 6,
|
||||
column: 0,
|
||||
};
|
||||
|
||||
let interperted_vnodes = LazyNodes::new(|factory| {
|
||||
let f = |_| println!("clicked");
|
||||
let f = factory.bump().alloc(f);
|
||||
let context = CapturedContext {
|
||||
captured: IfmtArgs {
|
||||
named_args: Vec::new(),
|
||||
},
|
||||
components: Vec::new(),
|
||||
iterators: Vec::new(),
|
||||
expressions: Vec::new(),
|
||||
listeners: vec![(
|
||||
r#"dioxus_elements::on::onclick(__cx, |_| println!("clicked"))"#,
|
||||
dioxus_elements::on::onclick(factory, f),
|
||||
)],
|
||||
location: location.clone(),
|
||||
custom_attributes: &[],
|
||||
};
|
||||
dioxus_rsx_interpreter::resolve_scope(
|
||||
location,
|
||||
r#"div {
|
||||
onclick: |_| println!("clicked")
|
||||
}"#,
|
||||
context,
|
||||
factory,
|
||||
)
|
||||
});
|
||||
|
||||
let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
|
||||
let static_vnodes = dom.render_vnodes(static_vnodes);
|
||||
println!("{:#?}", interperted_vnodes);
|
||||
println!("{:#?}", static_vnodes);
|
||||
assert!(check_eq(interperted_vnodes, static_vnodes));
|
||||
}
|
||||
|
||||
fn check_eq<'a>(a: &'a VNode<'a>, b: &'a VNode<'a>) -> bool {
|
||||
match (a, b) {
|
||||
(VNode::Text(t_a), VNode::Text(t_b)) => t_a.text == t_b.text,
|
||||
(VNode::Element(e_a), VNode::Element(e_b)) => {
|
||||
e_a.attributes
|
||||
.iter()
|
||||
.zip(e_b.attributes.iter())
|
||||
.all(|(a, b)| {
|
||||
a.is_static == b.is_static
|
||||
&& a.is_volatile == b.is_volatile
|
||||
&& a.name == b.name
|
||||
&& a.value == b.value
|
||||
&& a.namespace == b.namespace
|
||||
})
|
||||
&& e_a
|
||||
.children
|
||||
.iter()
|
||||
.zip(e_b.children.iter())
|
||||
.all(|(a, b)| check_eq(a, b))
|
||||
&& e_a.key == e_b.key
|
||||
&& e_a.tag == e_b.tag
|
||||
&& e_a.namespace == e_b.namespace
|
||||
&& e_a
|
||||
.listeners
|
||||
.iter()
|
||||
.zip(e_b.listeners.iter())
|
||||
.all(|(a, b)| a.event == b.event)
|
||||
}
|
||||
(VNode::Fragment(f_a), VNode::Fragment(f_b)) => {
|
||||
f_a.key == f_b.key
|
||||
&& f_a
|
||||
.children
|
||||
.iter()
|
||||
.zip(f_b.children.iter())
|
||||
.all(|(a, b)| check_eq(a, b))
|
||||
}
|
||||
(VNode::Component(c_a), VNode::Component(c_b)) => {
|
||||
c_a.can_memoize == c_b.can_memoize
|
||||
&& c_a.key == c_b.key
|
||||
&& c_a.fn_name == c_b.fn_name
|
||||
&& c_a.user_fc == c_b.user_fc
|
||||
}
|
||||
(VNode::Placeholder(_), VNode::Placeholder(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::{Display, Formatter, Write};
|
||||
|
||||
use dioxus_core::exports::bumpalo;
|
||||
use dioxus_core::IntoVNode;
|
||||
use dioxus_core::*;
|
||||
|
||||
|
@ -32,7 +33,8 @@ impl SsrRenderer {
|
|||
TextRenderer {
|
||||
cfg: self.cfg.clone(),
|
||||
root: &root,
|
||||
vdom: None
|
||||
vdom: Some(&self.vdom),
|
||||
bump: bumpalo::Bump::new(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -57,14 +59,18 @@ pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
|
|||
|
||||
let root = f.into_vnode(NodeFactory::new(scope));
|
||||
|
||||
format!(
|
||||
"{:}",
|
||||
TextRenderer {
|
||||
cfg: SsrConfig::default(),
|
||||
root: &root,
|
||||
vdom: None
|
||||
}
|
||||
)
|
||||
let vdom = Some(&vdom);
|
||||
|
||||
let ssr_renderer = TextRenderer {
|
||||
cfg: SsrConfig::default(),
|
||||
root: &root,
|
||||
vdom,
|
||||
bump: bumpalo::Bump::new(),
|
||||
};
|
||||
let r = ssr_renderer.to_string();
|
||||
drop(ssr_renderer);
|
||||
drop(vdom);
|
||||
r
|
||||
}
|
||||
|
||||
pub fn render_vdom(dom: &VirtualDom) -> String {
|
||||
|
@ -91,7 +97,8 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
|||
TextRenderer {
|
||||
cfg: SsrConfig::default(),
|
||||
root: vdom.get_scope(scope).unwrap().root_node(),
|
||||
vdom: Some(vdom)
|
||||
vdom: Some(vdom),
|
||||
bump: bumpalo::Bump::new()
|
||||
}
|
||||
))
|
||||
}
|
||||
|
@ -114,32 +121,36 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
|||
/// let output = format!("{}", renderer);
|
||||
/// assert_eq!(output, "<div>hello world</div>");
|
||||
/// ```
|
||||
pub struct TextRenderer<'a, 'b> {
|
||||
vdom: Option<&'a VirtualDom>,
|
||||
pub struct TextRenderer<'a, 'b, 'c> {
|
||||
vdom: Option<&'c VirtualDom>,
|
||||
root: &'b VNode<'a>,
|
||||
cfg: SsrConfig,
|
||||
bump: bumpalo::Bump,
|
||||
}
|
||||
|
||||
impl Display for TextRenderer<'_, '_> {
|
||||
impl<'a: 'c, 'c> Display for TextRenderer<'a, '_, 'c> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let mut last_node_was_text = false;
|
||||
self.html_render(self.root, f, 0, &mut last_node_was_text)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TextRenderer<'a, '_> {
|
||||
impl<'a> TextRenderer<'a, '_, 'a> {
|
||||
pub fn from_vdom(vdom: &'a VirtualDom, cfg: SsrConfig) -> Self {
|
||||
Self {
|
||||
cfg,
|
||||
root: vdom.base_scope().root_node(),
|
||||
vdom: Some(vdom),
|
||||
bump: bumpalo::Bump::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> {
|
||||
fn html_render(
|
||||
&self,
|
||||
node: &VNode,
|
||||
f: &mut std::fmt::Formatter,
|
||||
f: &mut impl Write,
|
||||
il: u16,
|
||||
last_node_was_text: &mut bool,
|
||||
) -> std::fmt::Result {
|
||||
|
@ -180,66 +191,7 @@ impl<'a> TextRenderer<'a, '_> {
|
|||
|
||||
write!(f, "<{}", el.tag)?;
|
||||
|
||||
let mut inner_html = None;
|
||||
let mut attr_iter = el.attributes.iter().peekable();
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
match attr.namespace {
|
||||
None => match attr.name {
|
||||
"dangerous_inner_html" => {
|
||||
inner_html = Some(attr.value.as_text().unwrap())
|
||||
}
|
||||
"allowfullscreen"
|
||||
| "allowpaymentrequest"
|
||||
| "async"
|
||||
| "autofocus"
|
||||
| "autoplay"
|
||||
| "checked"
|
||||
| "controls"
|
||||
| "default"
|
||||
| "defer"
|
||||
| "disabled"
|
||||
| "formnovalidate"
|
||||
| "hidden"
|
||||
| "ismap"
|
||||
| "itemscope"
|
||||
| "loop"
|
||||
| "multiple"
|
||||
| "muted"
|
||||
| "nomodule"
|
||||
| "novalidate"
|
||||
| "open"
|
||||
| "playsinline"
|
||||
| "readonly"
|
||||
| "required"
|
||||
| "reversed"
|
||||
| "selected"
|
||||
| "truespeed" => {
|
||||
if attr.value.is_truthy() {
|
||||
write!(f, " {}=\"{}\"", attr.name, attr.value)?
|
||||
}
|
||||
}
|
||||
_ => write!(f, " {}=\"{}\"", attr.name, attr.value)?,
|
||||
},
|
||||
|
||||
Some(ns) => {
|
||||
// write the opening tag
|
||||
write!(f, " {}=\"", ns)?;
|
||||
let mut cur_ns_el = attr;
|
||||
'ns_parse: loop {
|
||||
write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?;
|
||||
match attr_iter.peek() {
|
||||
Some(next_attr) if next_attr.namespace == Some(ns) => {
|
||||
cur_ns_el = attr_iter.next().unwrap();
|
||||
}
|
||||
_ => break 'ns_parse,
|
||||
}
|
||||
}
|
||||
// write the closing tag
|
||||
write!(f, "\"")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let inner_html = render_attributes(el.attributes.iter(), f)?;
|
||||
|
||||
match self.cfg.newline {
|
||||
true => writeln!(f, ">")?,
|
||||
|
@ -283,9 +235,329 @@ impl<'a> TextRenderer<'a, '_> {
|
|||
} else {
|
||||
}
|
||||
}
|
||||
VNode::TemplateRef(tmpl) => {
|
||||
if let Some(vdom) = self.vdom {
|
||||
let template_id = &tmpl.template_id;
|
||||
let dynamic_context = &tmpl.dynamic_context;
|
||||
vdom.with_template(template_id, move |tmpl| {
|
||||
match tmpl {
|
||||
Template::Static(s) => {
|
||||
for r in s.root_nodes {
|
||||
self.render_template_node(
|
||||
&s.nodes,
|
||||
&s.nodes[r.0],
|
||||
dynamic_context,
|
||||
&s.dynamic_mapping,
|
||||
f,
|
||||
last_node_was_text,
|
||||
il,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Template::Owned(o) => {
|
||||
for r in &o.root_nodes {
|
||||
self.render_template_node(
|
||||
&o.nodes,
|
||||
&o.nodes[r.0],
|
||||
dynamic_context,
|
||||
&o.dynamic_mapping,
|
||||
f,
|
||||
last_node_was_text,
|
||||
il,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
})?
|
||||
} else {
|
||||
panic!("Cannot render template without vdom");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_template_node<
|
||||
TemplateNodes,
|
||||
Attributes,
|
||||
V,
|
||||
Children,
|
||||
Listeners,
|
||||
TextSegments,
|
||||
Text,
|
||||
Nodes,
|
||||
TextOuter,
|
||||
TextInner,
|
||||
AttributesOuter,
|
||||
AttributesInner,
|
||||
Volatile,
|
||||
Listeners2,
|
||||
>(
|
||||
&self,
|
||||
template_nodes: &TemplateNodes,
|
||||
node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
|
||||
dynamic_context: &TemplateContext,
|
||||
dynamic_node_mapping: &DynamicNodeMapping<
|
||||
Nodes,
|
||||
TextOuter,
|
||||
TextInner,
|
||||
AttributesOuter,
|
||||
AttributesInner,
|
||||
Volatile,
|
||||
Listeners2,
|
||||
>,
|
||||
f: &mut impl Write,
|
||||
last_node_was_text: &mut bool,
|
||||
il: u16,
|
||||
) -> std::fmt::Result
|
||||
where
|
||||
TemplateNodes:
|
||||
AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
|
||||
Attributes: AsRef<[TemplateAttribute<V>]>,
|
||||
AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
|
||||
AttributesOuter: AsRef<[AttributesInner]>,
|
||||
Children: AsRef<[TemplateNodeId]>,
|
||||
Listeners: AsRef<[usize]>,
|
||||
Listeners2: AsRef<[TemplateNodeId]>,
|
||||
Nodes: AsRef<[Option<TemplateNodeId>]>,
|
||||
Text: AsRef<str>,
|
||||
TextInner: AsRef<[TemplateNodeId]>,
|
||||
TextOuter: AsRef<[TextInner]>,
|
||||
TextSegments: AsRef<[TextTemplateSegment<Text>]>,
|
||||
V: TemplateValue,
|
||||
Volatile: AsRef<[(TemplateNodeId, usize)]>,
|
||||
{
|
||||
match &node.node_type {
|
||||
TemplateNodeType::Element(el) => {
|
||||
*last_node_was_text = false;
|
||||
|
||||
if self.cfg.indent {
|
||||
for _ in 0..il {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, "<{}", el.tag)?;
|
||||
|
||||
let mut inner_html = None;
|
||||
|
||||
let mut attr_iter = el.attributes.as_ref().into_iter().peekable();
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
match attr.attribute.namespace {
|
||||
None => {
|
||||
if attr.attribute.name == "dangerous_inner_html" {
|
||||
inner_html = {
|
||||
let text = match &attr.value {
|
||||
TemplateAttributeValue::Static(val) => {
|
||||
val.allocate(&self.bump).as_text().unwrap()
|
||||
}
|
||||
TemplateAttributeValue::Dynamic(idx) => dynamic_context
|
||||
.resolve_attribute(*idx)
|
||||
.as_text()
|
||||
.unwrap(),
|
||||
};
|
||||
Some(text)
|
||||
}
|
||||
} else if is_boolean_attribute(attr.attribute.name) {
|
||||
match &attr.value {
|
||||
TemplateAttributeValue::Static(val) => {
|
||||
let val = val.allocate(&self.bump);
|
||||
if val.is_truthy() {
|
||||
write!(f, " {}=\"{}\"", attr.attribute.name, val)?
|
||||
}
|
||||
}
|
||||
TemplateAttributeValue::Dynamic(idx) => {
|
||||
let val = dynamic_context.resolve_attribute(*idx);
|
||||
if val.is_truthy() {
|
||||
write!(f, " {}=\"{}\"", attr.attribute.name, val)?
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match &attr.value {
|
||||
TemplateAttributeValue::Static(val) => {
|
||||
let val = val.allocate(&self.bump);
|
||||
write!(f, " {}=\"{}\"", attr.attribute.name, val)?
|
||||
}
|
||||
TemplateAttributeValue::Dynamic(idx) => {
|
||||
let val = dynamic_context.resolve_attribute(*idx);
|
||||
write!(f, " {}=\"{}\"", attr.attribute.name, val)?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(ns) => {
|
||||
// write the opening tag
|
||||
write!(f, " {}=\"", ns)?;
|
||||
let mut cur_ns_el = attr;
|
||||
loop {
|
||||
match &attr.value {
|
||||
TemplateAttributeValue::Static(val) => {
|
||||
let val = val.allocate(&self.bump);
|
||||
write!(f, "{}:{};", cur_ns_el.attribute.name, val)?;
|
||||
}
|
||||
TemplateAttributeValue::Dynamic(idx) => {
|
||||
let val = dynamic_context.resolve_attribute(*idx);
|
||||
write!(f, "{}:{};", cur_ns_el.attribute.name, val)?;
|
||||
}
|
||||
}
|
||||
match attr_iter.peek() {
|
||||
Some(next_attr)
|
||||
if next_attr.attribute.namespace == Some(ns) =>
|
||||
{
|
||||
cur_ns_el = attr_iter.next().unwrap();
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
// write the closing tag
|
||||
write!(f, "\"")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.cfg.newline {
|
||||
true => writeln!(f, ">")?,
|
||||
false => write!(f, ">")?,
|
||||
}
|
||||
|
||||
if let Some(inner_html) = inner_html {
|
||||
write!(f, "{}", inner_html)?;
|
||||
} else {
|
||||
let mut last_node_was_text = false;
|
||||
for child in el.children.as_ref() {
|
||||
self.render_template_node(
|
||||
template_nodes,
|
||||
&template_nodes.as_ref()[child.0],
|
||||
dynamic_context,
|
||||
dynamic_node_mapping,
|
||||
f,
|
||||
&mut last_node_was_text,
|
||||
il + 1,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if self.cfg.newline {
|
||||
writeln!(f)?;
|
||||
}
|
||||
if self.cfg.indent {
|
||||
for _ in 0..il {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, "</{}>", el.tag)?;
|
||||
if self.cfg.newline {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
TemplateNodeType::Text(txt) => {
|
||||
if *last_node_was_text {
|
||||
write!(f, "<!--spacer-->")?;
|
||||
}
|
||||
|
||||
if self.cfg.indent {
|
||||
for _ in 0..il {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
}
|
||||
|
||||
*last_node_was_text = true;
|
||||
|
||||
let text = dynamic_context.resolve_text(&txt.segments);
|
||||
|
||||
write!(f, "{}", text)?
|
||||
}
|
||||
TemplateNodeType::DynamicNode(idx) => {
|
||||
let node = dynamic_context.resolve_node(*idx);
|
||||
self.html_render(node, f, il, last_node_was_text)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn render_attributes<'a, 'b: 'a, I>(
|
||||
attrs: I,
|
||||
f: &mut impl Write,
|
||||
) -> Result<Option<&'b str>, std::fmt::Error>
|
||||
where
|
||||
I: Iterator<Item = &'a Attribute<'b>>,
|
||||
{
|
||||
let mut inner_html = None;
|
||||
let mut attr_iter = attrs.peekable();
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
match attr.attribute.namespace {
|
||||
None => {
|
||||
if attr.attribute.name == "dangerous_inner_html" {
|
||||
inner_html = Some(attr.value.as_text().unwrap())
|
||||
} else {
|
||||
if is_boolean_attribute(attr.attribute.name) {
|
||||
if !attr.value.is_truthy() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
write!(f, " {}=\"{}\"", attr.attribute.name, attr.value)?
|
||||
}
|
||||
}
|
||||
Some(ns) => {
|
||||
// write the opening tag
|
||||
write!(f, " {}=\"", ns)?;
|
||||
let mut cur_ns_el = attr;
|
||||
loop {
|
||||
write!(f, "{}:{};", cur_ns_el.attribute.name, cur_ns_el.value)?;
|
||||
match attr_iter.peek() {
|
||||
Some(next_attr) if next_attr.attribute.namespace == Some(ns) => {
|
||||
cur_ns_el = attr_iter.next().unwrap();
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
// write the closing tag
|
||||
write!(f, "\"")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(inner_html)
|
||||
}
|
||||
|
||||
fn is_boolean_attribute(attribute: &'static str) -> bool {
|
||||
if let "allowfullscreen"
|
||||
| "allowpaymentrequest"
|
||||
| "async"
|
||||
| "autofocus"
|
||||
| "autoplay"
|
||||
| "checked"
|
||||
| "controls"
|
||||
| "default"
|
||||
| "defer"
|
||||
| "disabled"
|
||||
| "formnovalidate"
|
||||
| "hidden"
|
||||
| "ismap"
|
||||
| "itemscope"
|
||||
| "loop"
|
||||
| "multiple"
|
||||
| "muted"
|
||||
| "nomodule"
|
||||
| "novalidate"
|
||||
| "open"
|
||||
| "playsinline"
|
||||
| "readonly"
|
||||
| "required"
|
||||
| "reversed"
|
||||
| "selected"
|
||||
| "truespeed" = attribute
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{node::PreventDefault, Dom};
|
||||
|
||||
use dioxus_core::ElementId;
|
||||
use dioxus_core::GlobalNodeId;
|
||||
use dioxus_native_core::utils::{ElementProduced, PersistantElementIter};
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
|
||||
|
@ -69,7 +69,10 @@ impl NodeDepState<()> for Focus {
|
|||
|
||||
fn reduce(&mut self, node: NodeView<'_>, _sibling: (), _: &Self::Ctx) -> bool {
|
||||
let new = Focus {
|
||||
level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") {
|
||||
level: if let Some(a) = node
|
||||
.attributes()
|
||||
.and_then(|mut a| a.find(|a| a.attribute.name == "tabindex"))
|
||||
{
|
||||
if let Some(index) = a
|
||||
.value
|
||||
.as_int32()
|
||||
|
@ -87,8 +90,12 @@ impl NodeDepState<()> for Focus {
|
|||
}
|
||||
} else if node
|
||||
.listeners()
|
||||
.iter()
|
||||
.any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok())
|
||||
.map(|mut listeners| {
|
||||
listeners
|
||||
.any(|l| FOCUS_EVENTS.binary_search(&l).is_ok())
|
||||
.then(|| ())
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
FocusLevel::Focusable
|
||||
} else {
|
||||
|
@ -110,7 +117,7 @@ const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]);
|
|||
#[derive(Default)]
|
||||
pub(crate) struct FocusState {
|
||||
pub(crate) focus_iter: PersistantElementIter,
|
||||
pub(crate) last_focused_id: Option<ElementId>,
|
||||
pub(crate) last_focused_id: Option<GlobalNodeId>,
|
||||
pub(crate) focus_level: FocusLevel,
|
||||
pub(crate) dirty: bool,
|
||||
}
|
||||
|
@ -224,9 +231,9 @@ impl FocusState {
|
|||
|
||||
pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
|
||||
fn remove_children(
|
||||
to_prune: &mut [&mut Option<ElementId>],
|
||||
to_prune: &mut [&mut Option<GlobalNodeId>],
|
||||
rdom: &Dom,
|
||||
removed: ElementId,
|
||||
removed: GlobalNodeId,
|
||||
) {
|
||||
for opt in to_prune.iter_mut() {
|
||||
if let Some(id) = opt {
|
||||
|
@ -235,7 +242,7 @@ impl FocusState {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let NodeType::Element { children, .. } = &rdom[removed].node_type {
|
||||
if let NodeType::Element { children, .. } = &rdom[removed].node_data.node_type {
|
||||
for child in children {
|
||||
remove_children(to_prune, rdom, *child);
|
||||
}
|
||||
|
@ -249,19 +256,19 @@ impl FocusState {
|
|||
dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
|
||||
&mut [&mut self.last_focused_id],
|
||||
rdom,
|
||||
ElementId(*root as usize),
|
||||
rdom.decode_id(*root),
|
||||
),
|
||||
dioxus_core::DomEdit::Remove { root } => remove_children(
|
||||
&mut [&mut self.last_focused_id],
|
||||
rdom,
|
||||
ElementId(*root as usize),
|
||||
rdom.decode_id(*root),
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: ElementId) {
|
||||
pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: GlobalNodeId) {
|
||||
if let Some(old) = self.last_focused_id.replace(id) {
|
||||
rdom[old].state.focused = false;
|
||||
}
|
||||
|
|
|
@ -243,23 +243,24 @@ impl InnerInputState {
|
|||
fn try_create_event(
|
||||
name: &'static str,
|
||||
data: Arc<dyn Any + Send + Sync>,
|
||||
will_bubble: &mut FxHashSet<ElementId>,
|
||||
will_bubble: &mut FxHashSet<GlobalNodeId>,
|
||||
resolved_events: &mut Vec<UserEvent>,
|
||||
node: &Node,
|
||||
dom: &Dom,
|
||||
) {
|
||||
// only trigger event if the event was not triggered already by a child
|
||||
if will_bubble.insert(node.id) {
|
||||
let mut parent = node.parent;
|
||||
let id = node.node_data.id;
|
||||
if will_bubble.insert(id) {
|
||||
let mut parent = node.node_data.parent;
|
||||
while let Some(parent_id) = parent {
|
||||
will_bubble.insert(parent_id);
|
||||
parent = dom[parent_id].parent;
|
||||
parent = dom[parent_id].node_data.parent;
|
||||
}
|
||||
resolved_events.push(UserEvent {
|
||||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name,
|
||||
element: Some(node.id),
|
||||
element: Some(id),
|
||||
data,
|
||||
bubbles: event_bubbles(name),
|
||||
})
|
||||
|
@ -546,7 +547,7 @@ impl InnerInputState {
|
|||
let currently_contains = layout_contains_point(node_layout, new_pos);
|
||||
|
||||
if currently_contains && node.state.focus.level.focusable() {
|
||||
focus_id = Some(node.id);
|
||||
focus_id = Some(node.node_data.id);
|
||||
}
|
||||
});
|
||||
if let Some(id) = focus_id {
|
||||
|
@ -565,7 +566,7 @@ fn get_abs_layout(node: &Node, dom: &Dom, taffy: &Taffy) -> Layout {
|
|||
let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap();
|
||||
let mut current = node;
|
||||
|
||||
while let Some(parent_id) = current.parent {
|
||||
while let Some(parent_id) = current.node_data.parent {
|
||||
let parent = &dom[parent_id];
|
||||
current = parent;
|
||||
let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
|
||||
|
@ -664,7 +665,7 @@ impl RinkInputHandler {
|
|||
scope_id: None,
|
||||
priority: EventPriority::Medium,
|
||||
name: event,
|
||||
element: Some(node.id),
|
||||
element: Some(node.node_data.id),
|
||||
data: data.clone(),
|
||||
bubbles: event_bubbles(event),
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::rc::Rc;
|
|||
use dioxus_core::*;
|
||||
use dioxus_native_core::layout_attributes::apply_layout_attributes;
|
||||
use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
|
||||
use dioxus_native_core::real_dom::OwnedAttributeView;
|
||||
use dioxus_native_core::state::ChildDepState;
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
use taffy::prelude::*;
|
||||
|
@ -84,10 +85,17 @@ impl ChildDepState for TaffyLayout {
|
|||
}
|
||||
} else {
|
||||
// gather up all the styles from the attribute list
|
||||
for Attribute { name, value, .. } in node.attributes() {
|
||||
assert!(SORTED_LAYOUT_ATTRS.binary_search(name).is_ok());
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_layout_attributes(name, text, &mut style);
|
||||
if let Some(attributes) = node.attributes() {
|
||||
for OwnedAttributeView {
|
||||
attribute, value, ..
|
||||
} in attributes
|
||||
{
|
||||
assert!(SORTED_LAYOUT_ATTRS
|
||||
.binary_search(&attribute.name.as_ref())
|
||||
.is_ok());
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_layout_attributes(&&attribute.name, text, &mut style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
|||
let to_update = rdom.apply_mutations(vec![mutations]);
|
||||
let mut any_map = AnyMap::new();
|
||||
any_map.insert(taffy.clone());
|
||||
let _to_rerender = rdom.update_state(&dom, to_update, any_map);
|
||||
let _to_rerender = rdom.update_state(to_update, any_map);
|
||||
}
|
||||
|
||||
render_vdom(
|
||||
|
@ -133,8 +133,10 @@ fn render_vdom(
|
|||
terminal.clear().unwrap();
|
||||
}
|
||||
|
||||
let mut to_rerender: fxhash::FxHashSet<ElementId> =
|
||||
vec![ElementId(0)].into_iter().collect();
|
||||
let mut to_rerender: fxhash::FxHashSet<GlobalNodeId> =
|
||||
vec![GlobalNodeId::VNodeId(ElementId(0))]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut updated = true;
|
||||
|
||||
loop {
|
||||
|
@ -250,7 +252,7 @@ fn render_vdom(
|
|||
// update the style and layout
|
||||
let mut any_map = AnyMap::new();
|
||||
any_map.insert(taffy.clone());
|
||||
to_rerender = rdom.update_state(vdom, to_update, any_map);
|
||||
to_rerender = rdom.update_state(to_update, any_map);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,11 +61,11 @@ impl NodeDepState<()> for PreventDefault {
|
|||
_sibling: (),
|
||||
_ctx: &Self::Ctx,
|
||||
) -> bool {
|
||||
let new = match node
|
||||
.attributes()
|
||||
.find(|a| a.name == "dioxus-prevent-default")
|
||||
.and_then(|a| a.value.as_text())
|
||||
{
|
||||
let new = match node.attributes().and_then(|mut attrs| {
|
||||
attrs
|
||||
.find(|a| a.attribute.name == "dioxus-prevent-default")
|
||||
.and_then(|a| a.value.as_text())
|
||||
}) {
|
||||
Some("onfocus") => PreventDefault::Focus,
|
||||
Some("onkeypress") => PreventDefault::KeyPress,
|
||||
Some("onkeyrelease") => PreventDefault::KeyRelease,
|
||||
|
|
|
@ -25,7 +25,7 @@ pub(crate) fn render_vnode(
|
|||
) {
|
||||
use dioxus_native_core::real_dom::NodeType;
|
||||
|
||||
if let NodeType::Placeholder = &node.node_type {
|
||||
if let NodeType::Placeholder = &node.node_data.node_type {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ pub(crate) fn render_vnode(
|
|||
let Point { x, y } = location;
|
||||
let Size { width, height } = size;
|
||||
|
||||
match &node.node_type {
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Text { text } => {
|
||||
#[derive(Default)]
|
||||
struct Label<'a> {
|
||||
|
|
|
@ -29,10 +29,10 @@
|
|||
- [ ] pub aspect_ratio: Number,
|
||||
*/
|
||||
|
||||
use dioxus_core::Attribute;
|
||||
use dioxus_native_core::{
|
||||
layout_attributes::parse_value,
|
||||
node_ref::{AttributeMask, NodeMask, NodeView},
|
||||
real_dom::OwnedAttributeView,
|
||||
state::ParentDepState,
|
||||
};
|
||||
use dioxus_native_core_macro::sorted_str_slice;
|
||||
|
@ -79,9 +79,14 @@ impl ParentDepState for StyleModifier {
|
|||
}
|
||||
|
||||
// gather up all the styles from the attribute list
|
||||
for Attribute { name, value, .. } in node.attributes() {
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_style_attributes(name, text, &mut new);
|
||||
if let Some(attrs) = node.attributes() {
|
||||
for OwnedAttributeView {
|
||||
attribute, value, ..
|
||||
} in attrs
|
||||
{
|
||||
if let Some(text) = value.as_text() {
|
||||
apply_style_attributes(&attribute.name, text, &mut new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
|
|||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
|
||||
"web"
|
||||
] }
|
||||
dioxus-rsx-interpreter = { path = "../rsx_interpreter", version = "^0.1.0", optional = true }
|
||||
|
||||
js-sys = "0.3.56"
|
||||
wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
|
||||
|
@ -31,7 +30,7 @@ futures-util = "0.3.19"
|
|||
smallstr = "0.2.0"
|
||||
serde-wasm-bindgen = "0.4.2"
|
||||
futures-channel = "0.3.21"
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde_json = { version = "1.0" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.56"
|
||||
|
@ -72,12 +71,17 @@ features = [
|
|||
"SvgAnimatedString",
|
||||
"HtmlOptionElement",
|
||||
"IdleDeadline",
|
||||
"WebSocket",
|
||||
"Location",
|
||||
"MessageEvent",
|
||||
"console",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["panic_hook"]
|
||||
panic_hook = ["console_error_panic_hook"]
|
||||
hot-reload = ["dioxus-rsx-interpreter", "web-sys/WebSocket", "web-sys/Location", "web-sys/MessageEvent", "web-sys/console", "serde_json"]
|
||||
hot-reload = ["dioxus/hot-reload"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { path = "../dioxus" }
|
||||
|
|
|
@ -24,6 +24,7 @@ fn app(cx: Scope) -> Element {
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn Bapp(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//! - tests to ensure dyn_into works for various event types.
|
||||
//! - Partial delegation?>
|
||||
|
||||
use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
|
||||
use dioxus_core::{DomEdit, SchedulerMsg, UserEvent};
|
||||
use dioxus_html::event_bubbles;
|
||||
use dioxus_interpreter_js::Interpreter;
|
||||
use js_sys::Function;
|
||||
|
@ -43,7 +43,7 @@ impl WebsysDom {
|
|||
break Ok(UserEvent {
|
||||
name: event_name_from_typ(&typ),
|
||||
data: virtual_event_from_websys_event(event.clone(), target.clone()),
|
||||
element: Some(ElementId(id)),
|
||||
element: Some(id),
|
||||
scope_id: None,
|
||||
priority: dioxus_core::EventPriority::Medium,
|
||||
bubbles: event.bubbles(),
|
||||
|
@ -157,6 +157,45 @@ impl WebsysDom {
|
|||
let value = serde_wasm_bindgen::to_value(&value).unwrap();
|
||||
self.interpreter.SetAttribute(root, field, value, ns)
|
||||
}
|
||||
DomEdit::CreateTemplateRef { id, template_id } => {
|
||||
self.interpreter.CreateTemplateRef(id, template_id)
|
||||
}
|
||||
DomEdit::CreateTemplate { id } => self.interpreter.CreateTemplate(id),
|
||||
DomEdit::FinishTemplate { len } => self.interpreter.FinishTemplate(len),
|
||||
DomEdit::EnterTemplateRef { root } => self.interpreter.EnterTemplateRef(root),
|
||||
DomEdit::ExitTemplateRef {} => self.interpreter.ExitTemplateRef(),
|
||||
DomEdit::CreateTextNodeTemplate {
|
||||
root,
|
||||
text,
|
||||
locally_static,
|
||||
} => self
|
||||
.interpreter
|
||||
.CreateTextNodeTemplate(text, root, locally_static),
|
||||
DomEdit::CreateElementTemplate {
|
||||
root,
|
||||
tag,
|
||||
locally_static,
|
||||
fully_static,
|
||||
} => {
|
||||
self.interpreter
|
||||
.CreateElementTemplate(tag, root, locally_static, fully_static)
|
||||
}
|
||||
DomEdit::CreateElementNsTemplate {
|
||||
root,
|
||||
tag,
|
||||
ns,
|
||||
locally_static,
|
||||
fully_static,
|
||||
} => self.interpreter.CreateElementNsTemplate(
|
||||
tag,
|
||||
root,
|
||||
ns,
|
||||
locally_static,
|
||||
fully_static,
|
||||
),
|
||||
DomEdit::CreatePlaceholderTemplate { root } => {
|
||||
self.interpreter.CreatePlaceholderTemplate(root)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use dioxus_core::SchedulerMsg;
|
||||
use dioxus_core::SetTemplateMsg;
|
||||
use dioxus_core::VirtualDom;
|
||||
use dioxus_rsx_interpreter::error::Error;
|
||||
use dioxus_rsx_interpreter::{ErrorHandler, SetManyRsxMessage, RSX_CONTEXT};
|
||||
use futures_channel::mpsc::unbounded;
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
use futures_util::StreamExt;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{console, MessageEvent, WebSocket};
|
||||
use web_sys::{MessageEvent, WebSocket};
|
||||
|
||||
pub(crate) fn init(dom: &VirtualDom) {
|
||||
let window = web_sys::window().unwrap();
|
||||
|
@ -23,46 +20,18 @@ pub(crate) fn init(dom: &VirtualDom) {
|
|||
);
|
||||
|
||||
let ws = WebSocket::new(&url).unwrap();
|
||||
let mut channel = dom.get_scheduler_channel();
|
||||
|
||||
// change the rsx when new data is received
|
||||
let cl = Closure::wrap(Box::new(|e: MessageEvent| {
|
||||
let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
|
||||
if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
|
||||
let msgs: SetManyRsxMessage = serde_json::from_str(&format!("{text}")).unwrap();
|
||||
RSX_CONTEXT.extend(msgs);
|
||||
let msg: SetTemplateMsg = serde_json::from_str(&format!("{text}")).unwrap();
|
||||
channel
|
||||
.start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
|
||||
.unwrap();
|
||||
}
|
||||
}) as Box<dyn FnMut(MessageEvent)>);
|
||||
|
||||
ws.set_onmessage(Some(cl.as_ref().unchecked_ref()));
|
||||
cl.forget();
|
||||
|
||||
let (error_channel_sender, mut error_channel_receiver) = unbounded();
|
||||
|
||||
struct WebErrorHandler {
|
||||
sender: UnboundedSender<Error>,
|
||||
}
|
||||
|
||||
impl ErrorHandler for WebErrorHandler {
|
||||
fn handle_error(&self, err: dioxus_rsx_interpreter::error::Error) {
|
||||
self.sender.unbounded_send(err).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
RSX_CONTEXT.set_error_handler(WebErrorHandler {
|
||||
sender: error_channel_sender,
|
||||
});
|
||||
|
||||
RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel());
|
||||
|
||||
// forward stream to the websocket
|
||||
dom.base_scope().spawn_forever(async move {
|
||||
while let Some(err) = error_channel_receiver.next().await {
|
||||
if ws.ready_state() == WebSocket::OPEN {
|
||||
ws.send_with_str(serde_json::to_string(&err).unwrap().as_str())
|
||||
.unwrap();
|
||||
} else {
|
||||
console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into());
|
||||
panic!("{}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -61,15 +61,14 @@ pub use crate::util::use_eval;
|
|||
use dioxus_core::prelude::Component;
|
||||
use dioxus_core::SchedulerMsg;
|
||||
use dioxus_core::VirtualDom;
|
||||
use futures_util::FutureExt;
|
||||
|
||||
mod cache;
|
||||
mod cfg;
|
||||
mod dom;
|
||||
#[cfg(feature = "hot-reload")]
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
mod hot_reload;
|
||||
mod rehydrate;
|
||||
mod ric_raf;
|
||||
// mod ric_raf;
|
||||
mod util;
|
||||
|
||||
/// Launch the VirtualDOM given a root component and a configuration.
|
||||
|
@ -169,7 +168,7 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
|
|||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
#[cfg(feature = "hot-reload")]
|
||||
#[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
hot_reload::init(&dom);
|
||||
|
||||
for s in crate::cache::BUILTIN_INTERNED_STRINGS {
|
||||
|
@ -214,7 +213,7 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
|
|||
websys_dom.apply_edits(edits.edits);
|
||||
}
|
||||
|
||||
let mut work_loop = ric_raf::RafLoop::new();
|
||||
// let mut work_loop = ric_raf::RafLoop::new();
|
||||
|
||||
loop {
|
||||
log::trace!("waiting for work");
|
||||
|
@ -225,13 +224,14 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
|
|||
log::trace!("working..");
|
||||
|
||||
// wait for the mainthread to schedule us in
|
||||
let mut deadline = work_loop.wait_for_idle_time().await;
|
||||
// let mut deadline = work_loop.wait_for_idle_time().await;
|
||||
|
||||
// run the virtualdom work phase until the frame deadline is reached
|
||||
let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
|
||||
// let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
|
||||
let mutations = dom.work_with_deadline(|| false);
|
||||
|
||||
// wait for the animation frame to fire so we can apply our changes
|
||||
work_loop.wait_for_raf().await;
|
||||
// work_loop.wait_for_raf().await;
|
||||
|
||||
for edit in mutations {
|
||||
// actually apply our changes during the animation frame
|
||||
|
|
|
@ -108,12 +108,30 @@ impl WebsysDom {
|
|||
}
|
||||
|
||||
for listener in vel.listeners {
|
||||
self.interpreter.NewEventListener(
|
||||
listener.event,
|
||||
listener.mounted_node.get().unwrap().as_u64(),
|
||||
self.handler.as_ref().unchecked_ref(),
|
||||
event_bubbles(listener.event),
|
||||
);
|
||||
let global_id = listener.mounted_node.get().unwrap();
|
||||
match global_id {
|
||||
dioxus_core::GlobalNodeId::TemplateId {
|
||||
template_ref_id,
|
||||
template_node_id: id,
|
||||
} => {
|
||||
self.interpreter.EnterTemplateRef(template_ref_id.into());
|
||||
self.interpreter.NewEventListener(
|
||||
listener.event,
|
||||
id.into(),
|
||||
self.handler.as_ref().unchecked_ref(),
|
||||
event_bubbles(listener.event),
|
||||
);
|
||||
self.interpreter.ExitTemplateRef();
|
||||
}
|
||||
dioxus_core::GlobalNodeId::VNodeId(id) => {
|
||||
self.interpreter.NewEventListener(
|
||||
listener.event,
|
||||
id.into(),
|
||||
self.handler.as_ref().unchecked_ref(),
|
||||
event_bubbles(listener.event),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !vel.listeners.is_empty() {
|
||||
|
@ -165,6 +183,7 @@ impl WebsysDom {
|
|||
let node = scope.root_node();
|
||||
self.rehydrate_single(nodes, place, dom, node, last_node_was_text)?;
|
||||
}
|
||||
VNode::TemplateRef(_) => todo!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue