diff --git a/docs/guide/src/en/interactivity/dynamic_rendering.md b/docs/guide/src/en/interactivity/dynamic_rendering.md index 290871e9c..5c7ba1314 100644 --- a/docs/guide/src/en/interactivity/dynamic_rendering.md +++ b/docs/guide/src/en/interactivity/dynamic_rendering.md @@ -64,7 +64,7 @@ Example: suppose you have a list of comments you want to render. Then, you can r ### Inline for loops -Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter, `.map`, and `rsx`, you can use a `for` loop with a body of rsx code: +Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter`, `.map`, and `rsx`, you can use a `for` loop with a body of rsx code: ```rust {{#include ../../../examples/rendering_lists.rs:render_list_for_loop}} diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index 26c47f54b..fea5a4a3f 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -8,6 +8,8 @@ use std::fmt::{Result, Write}; use dioxus_rsx::IfmtInput; +use crate::write_ifmt; + /// The output buffer that tracks indent and string #[derive(Debug, Default)] pub struct Buffer { @@ -48,7 +50,7 @@ impl Buffer { } pub fn write_text(&mut self, text: &IfmtInput) -> Result { - write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value()) + write_ifmt(text, &mut self.buf) } } diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index 89ebe2229..d5d92b79a 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -1,4 +1,4 @@ -use crate::{writer::Location, Writer}; +use crate::{ifmt_to_string, writer::Location, Writer}; use dioxus_rsx::*; use quote::ToTokens; use std::fmt::{Result, Write}; @@ -170,9 +170,9 @@ impl Writer<'_> { ContentField::Formatted(s) => { write!( self.out, - "{}: \"{}\"", + "{}: {}", name, - s.source.as_ref().unwrap().value() + s.source.as_ref().unwrap().to_token_stream() )?; } ContentField::OnHandlerRaw(exp) => { @@ -215,7 +215,7 @@ impl Writer<'_> { let attr_len = fields .iter() .map(|field| match &field.content { - ContentField::Formatted(s) => s.source.as_ref().unwrap().value().len() , + ContentField::Formatted(s) => ifmt_to_string(s).len() , ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => { let formatted = prettyplease::unparse_expr(exp); let len = if formatted.contains('\n') { diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 5bb7d42fc..50a991897 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -1,6 +1,7 @@ -use crate::Writer; +use crate::{ifmt_to_string, Writer}; use dioxus_rsx::*; use proc_macro2::Span; +use quote::ToTokens; use std::{ fmt::Result, fmt::{self, Write}, @@ -175,11 +176,7 @@ impl Writer<'_> { if !sameline { self.out.indented_tabbed_line()?; } - write!( - self.out, - "key: \"{}\"", - key.source.as_ref().unwrap().value() - )?; + write!(self.out, "key: {}", ifmt_to_string(key))?; if !attributes.is_empty() { write!(self.out, ",")?; if sameline { @@ -216,11 +213,7 @@ impl Writer<'_> { fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result { match &attr.attr { ElementAttr::AttrText { name, value } => { - write!( - self.out, - "{name}: \"{value}\"", - value = value.source.as_ref().unwrap().value() - )?; + write!(self.out, "{name}: {value}", value = ifmt_to_string(value))?; } ElementAttr::AttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); @@ -230,15 +223,15 @@ impl Writer<'_> { ElementAttr::CustomAttrText { name, value } => { write!( self.out, - "\"{name}\": \"{value}\"", - name = name.value(), - value = value.source.as_ref().unwrap().value() + "{name}: {value}", + name = name.to_token_stream(), + value = ifmt_to_string(value) )?; } ElementAttr::CustomAttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); - write!(self.out, "\"{}\": {}", name.value(), out)?; + write!(self.out, "{}: {}", name.to_token_stream(), out)?; } ElementAttr::EventTokens { name, tokens } => { @@ -315,7 +308,7 @@ impl Writer<'_> { } match children { - [BodyNode::Text(ref text)] => Some(text.source.as_ref().unwrap().value().len()), + [BodyNode::Text(ref text)] => Some(ifmt_to_string(text).len()), [BodyNode::Component(ref comp)] => { let attr_len = self.field_len(&comp.fields, &comp.manual_props); @@ -338,7 +331,7 @@ impl Writer<'_> { if el.children.len() == 1 { if let BodyNode::Text(ref text) = el.children[0] { - let value = text.source.as_ref().unwrap().value(); + let value = ifmt_to_string(text); if value.len() + el.name.to_string().len() + attr_len < 80 { return Some(value.len() + el.name.to_string().len() + attr_len); @@ -356,7 +349,7 @@ impl Writer<'_> { match item { BodyNode::Component(_) | BodyNode::Element(_) => return None, BodyNode::Text(text) => { - total_count += text.source.as_ref().unwrap().value().len() + total_count += ifmt_to_string(text).len(); } BodyNode::RawExpr(expr) => match get_expr_length(expr) { Some(len) => total_count += len, diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 12bf00585..0d2426a0b 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -1,7 +1,10 @@ +use std::fmt::{Display, Write}; + use crate::writer::*; use collect_macros::byte_offset; -use dioxus_rsx::{BodyNode, CallBody}; +use dioxus_rsx::{BodyNode, CallBody, IfmtInput}; use proc_macro2::LineColumn; +use quote::ToTokens; use syn::{ExprMacro, MacroDelimiter}; mod buffer; @@ -130,8 +133,6 @@ pub fn write_block_out(body: CallBody) -> Option { } fn write_body(buf: &mut Writer, body: &CallBody) { - use std::fmt::Write; - if buf.is_short_children(&body.roots).is_some() { // write all the indents with spaces and commas between for idx in 0..body.roots.len() - 1 { @@ -208,3 +209,24 @@ pub fn apply_formats(input: &str, blocks: Vec) -> String { out } + +struct DisplayIfmt<'a>(&'a IfmtInput); + +impl Display for DisplayIfmt<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let inner_tokens = self.0.source.as_ref().unwrap().to_token_stream(); + inner_tokens.fmt(f) + } +} + +pub(crate) fn ifmt_to_string(input: &IfmtInput) -> String { + let mut buf = String::new(); + let display = DisplayIfmt(input); + write!(&mut buf, "{}", display).unwrap(); + buf +} + +pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::fmt::Result { + let display = DisplayIfmt(input); + write!(writable, "{}", display) +} diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index 6a0e38d82..59f135592 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -8,6 +8,7 @@ use std::{ use syn::{spanned::Spanned, Expr, ExprIf}; use crate::buffer::Buffer; +use crate::ifmt_to_string; #[derive(Debug)] pub struct Writer<'a> { @@ -147,16 +148,16 @@ impl<'a> Writer<'a> { total += match &attr.attr { ElementAttr::AttrText { value, name } => { - value.source.as_ref().unwrap().value().len() + name.span().line_length() + 6 + ifmt_to_string(value).len() + name.span().line_length() + 6 } ElementAttr::AttrExpression { name, value } => { value.span().line_length() + name.span().line_length() + 6 } ElementAttr::CustomAttrText { value, name } => { - value.source.as_ref().unwrap().value().len() + name.value().len() + 6 + ifmt_to_string(value).len() + name.to_token_stream().to_string().len() + 6 } ElementAttr::CustomAttrExpression { name, value } => { - name.value().len() + value.span().line_length() + 6 + name.to_token_stream().to_string().len() + value.span().line_length() + 6 } ElementAttr::EventTokens { tokens, name } => { let location = Location::new(tokens.span().start()); diff --git a/packages/autofmt/tests/samples.rs b/packages/autofmt/tests/samples.rs index 19e636926..8145b0e8e 100644 --- a/packages/autofmt/tests/samples.rs +++ b/packages/autofmt/tests/samples.rs @@ -4,8 +4,8 @@ macro_rules! twoway { // doc attrs $( #[doc = $doc:expr] )* - $name:ident - ),* + $name:ident, + )* ) => { $( $( #[doc = $doc] )* @@ -14,6 +14,9 @@ macro_rules! twoway { let src = include_str!(concat!("./samples/", stringify!($name), ".rsx")); let formatted = dioxus_autofmt::fmt_file(src); let out = dioxus_autofmt::apply_formats(src, formatted); + // normalize line endings + let out = out.replace("\r", ""); + let src = src.replace("\r", ""); pretty_assertions::assert_eq!(&src, &out); } )* @@ -21,24 +24,25 @@ macro_rules! twoway { } twoway![ - simple, - comments, attributes, - manual_props, + collapse_expr, + comments, + commentshard, complex, + emoji, + ifchain_forloop, + immediate_expr, + key, + long_exprs, + long, + manual_props, + messy_indent, + multirsx, + raw_strings, + reallylong, + simple, + t2, tiny, tinynoopt, - long, - key, - multirsx, - commentshard, - emoji, - messy_indent, - long_exprs, - ifchain_forloop, - t2, - reallylong, - immediate_expr, - collapse_expr, - trailing_expr + trailing_expr, ]; diff --git a/packages/autofmt/tests/samples/complex.rsx b/packages/autofmt/tests/samples/complex.rsx index 598d08a82..1bf29a767 100644 --- a/packages/autofmt/tests/samples/complex.rsx +++ b/packages/autofmt/tests/samples/complex.rsx @@ -13,7 +13,9 @@ rsx! { // Complex nesting with handlers li { - Link { class: "flex items-center pl-3 py-3 pr-4 {active_class} rounded", to: "{to}", + Link { + class: "flex items-center pl-3 py-3 pr-4 {active_class} rounded", + to: "{to}", span { class: "inline-block mr-3", icons::icon_0 {} } span { "{name}" } children.is_some().then(|| rsx! { diff --git a/packages/autofmt/tests/samples/raw_strings.rsx b/packages/autofmt/tests/samples/raw_strings.rsx new file mode 100644 index 000000000..8a4d0c38f --- /dev/null +++ b/packages/autofmt/tests/samples/raw_strings.rsx @@ -0,0 +1,12 @@ +rsx! { + // Raw text strings + button { r#"Click me"#, r##"Click me"##, r######"Click me"######, r#"dynamic {1}"# } + + // Raw attribute strings + div { + width: r#"10px"#, + height: r##"{10}px"##, + "raw-attr": r###"raw-attr"###, + "raw-attr2": r###"{100}"### + } +} diff --git a/packages/autofmt/tests/wrong.rs b/packages/autofmt/tests/wrong.rs index acf930b40..5a0fb8a87 100644 --- a/packages/autofmt/tests/wrong.rs +++ b/packages/autofmt/tests/wrong.rs @@ -6,6 +6,11 @@ macro_rules! twoway { let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx")); let formatted = dioxus_autofmt::fmt_file(src_wrong); let out = dioxus_autofmt::apply_formats(src_wrong, formatted); + + // normalize line endings + let out = out.replace("\r", ""); + let src_right = src_right.replace("\r", ""); + pretty_assertions::assert_eq!(&src_right, &out); } }; diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 56a2d05de..a8b913440 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -7,7 +7,7 @@ use crate::{ nodes::{DynamicNode, VNode}, scopes::ScopeId, virtual_dom::VirtualDom, - Attribute, AttributeValue, TemplateNode, + Attribute, TemplateNode, }; use rustc_hash::{FxHashMap, FxHashSet}; @@ -125,10 +125,8 @@ impl<'b> VirtualDom { .mounted_element .set(left_attr.mounted_element.get()); - // We want to make sure anything listener that gets pulled is valid - if let AttributeValue::Listener(_) = right_attr.value { - self.update_template(left_attr.mounted_element.get(), right_template); - } + // We want to make sure anything that gets pulled is valid + self.update_template(left_attr.mounted_element.get(), right_template); // If the attributes are different (or volatile), we need to update them if left_attr.value != right_attr.value || left_attr.volatile { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index da5526b32..fe4d3e35b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -389,64 +389,70 @@ impl VirtualDom { // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent. while let Some(el_ref) = parent_path { // safety: we maintain references of all vnodes in the element slab - let template = unsafe { el_ref.template.unwrap().as_ref() }; - let node_template = template.template.get(); - let target_path = el_ref.path; + if let Some(template) = el_ref.template { + let template = unsafe { template.as_ref() }; + let node_template = template.template.get(); + let target_path = el_ref.path; - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { - let this_path = node_template.attr_paths[idx]; + for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + let this_path = node_template.attr_paths[idx]; - // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one - if attr.name.trim_start_matches("on") == name - && target_path.is_decendant(&this_path) - { - listeners.push(&attr.value); + // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one + if attr.name.trim_start_matches("on") == name + && target_path.is_decendant(&this_path) + { + listeners.push(&attr.value); - // Break if this is the exact target element. - // This means we won't call two listeners with the same name on the same element. This should be - // documented, or be rejected from the rsx! macro outright - if target_path == this_path { - break; + // Break if this is the exact target element. + // This means we won't call two listeners with the same name on the same element. This should be + // documented, or be rejected from the rsx! macro outright + if target_path == this_path { + break; + } } } - } - // Now that we've accumulated all the parent attributes for the target element, call them in reverse order - // We check the bubble state between each call to see if the event has been stopped from bubbling - for listener in listeners.drain(..).rev() { - if let AttributeValue::Listener(listener) = listener { - if let Some(cb) = listener.borrow_mut().as_deref_mut() { - cb(uievent.clone()); - } + // Now that we've accumulated all the parent attributes for the target element, call them in reverse order + // We check the bubble state between each call to see if the event has been stopped from bubbling + for listener in listeners.drain(..).rev() { + if let AttributeValue::Listener(listener) = listener { + if let Some(cb) = listener.borrow_mut().as_deref_mut() { + cb(uievent.clone()); + } - if !uievent.propagates.get() { - return; + if !uievent.propagates.get() { + return; + } } } - } - parent_path = template.parent.and_then(|id| self.elements.get(id.0)); + parent_path = dbg!(template.parent).and_then(|id| self.elements.get(id.0)); + } else { + break; + } } } else { // Otherwise, we just call the listener on the target element if let Some(el_ref) = parent_path { // safety: we maintain references of all vnodes in the element slab - let template = unsafe { el_ref.template.unwrap().as_ref() }; - let node_template = template.template.get(); - let target_path = el_ref.path; + if let Some(template) = el_ref.template { + let template = unsafe { template.as_ref() }; + let node_template = template.template.get(); + let target_path = el_ref.path; - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { - let this_path = node_template.attr_paths[idx]; + for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + let this_path = node_template.attr_paths[idx]; - // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one - // Only call the listener if this is the exact target element. - if attr.name.trim_start_matches("on") == name && target_path == this_path { - if let AttributeValue::Listener(listener) = &attr.value { - if let Some(cb) = listener.borrow_mut().as_deref_mut() { - cb(uievent.clone()); + // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one + // Only call the listener if this is the exact target element. + if attr.name.trim_start_matches("on") == name && target_path == this_path { + if let AttributeValue::Listener(listener) = &attr.value { + if let Some(cb) = listener.borrow_mut().as_deref_mut() { + cb(uievent.clone()); + } + + break; } - - break; } } } diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index c338a50cc..6be5d08b4 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -1,4 +1,4 @@ -use dioxus_interpreter_js::INTERPRETER_JS; +use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS}; use std::{ borrow::Cow, path::{Path, PathBuf}, @@ -37,7 +37,7 @@ fn module_loader(root_name: &str) -> String { ); format!( r#" - diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index bdea9fa56..2a90ee9a6 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -14,7 +14,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } dioxus-html = { path = "../html", version = "^0.3.0", features = ["wasm-bind"] } dioxus-interpreter-js = { path = "../interpreter", version = "^0.3.0", features = [ - "sledgehammer" + "sledgehammer", + "minimal_bindings", ] } js-sys = "0.3.56" diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 1647b419a..b9fb6b942 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -11,13 +11,13 @@ use dioxus_core::{ BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode, }; use dioxus_html::{event_bubbles, CompositionData, FileEngine, FormData, MountedData}; -use dioxus_interpreter_js::{get_node, save_template, Channel}; +use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel}; use futures_channel::mpsc; use js_sys::Array; use rustc_hash::FxHashMap; use std::{any::Any, rc::Rc, sync::Arc}; -use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast}; -use web_sys::{console, Document, Element, Event, HtmlElement}; +use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue}; +use web_sys::{console, Document, Element, Event}; use crate::{file_engine::WebFileEngine, Config}; @@ -135,14 +135,12 @@ impl WebsysDom { namespace, } = attr { - match namespace { - Some(ns) if *ns == "style" => { - el.dyn_ref::() - .map(|f| f.style().set_property(name, value)); - } - Some(ns) => el.set_attribute_ns(Some(ns), name, value).unwrap(), - None => el.set_attribute(name, value).unwrap(), - } + minimal_bindings::setAttributeInner( + el.clone().into(), + name, + JsValue::from_str(value), + *namespace, + ); } } for child in *children {