* adhox: Fix Clippy lints in const-serialize etc. Fix the clippy lints that were preventing building: * const-serialize/src/lib.rs * core/src/nodes.rs * fullstack/src/serve_config.rs * adhoc: Change the way to satisfy the lint for 1.85 The lint for pointer comparison is only in 1.85. The solution uses a mechanism that was only stabilized with 1.85. Instead of using that solution, will just allow the lint on this function for now. Once the MSRV of Dioxus is updated to 1.85 or above, this can be changed to use the recommeneded solution. * adhoc: Fix the 'cargo make test' tests There were a number of tests failing to compile and/or run. Fixed those that could be meaningfully fixed. Those that were incomplete examples that could not be fixed are marked as 'ignore'. * adhoc: GitIgnore test results in playwright tests * fix wry rollback --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
1233 lines
36 KiB
Rust
1233 lines
36 KiB
Rust
use dioxus_core_types::DioxusFormattable;
|
||
|
||
use crate::innerlude::VProps;
|
||
use crate::prelude::RenderError;
|
||
use crate::{any_props::BoxedAnyProps, innerlude::ScopeState};
|
||
use crate::{arena::ElementId, Element, Event};
|
||
use crate::{
|
||
innerlude::{ElementRef, EventHandler, MountId},
|
||
properties::ComponentFunction,
|
||
};
|
||
use crate::{Properties, ScopeId, VirtualDom};
|
||
use std::ops::Deref;
|
||
use std::rc::Rc;
|
||
use std::vec;
|
||
use std::{
|
||
any::{Any, TypeId},
|
||
cell::Cell,
|
||
fmt::{Arguments, Debug},
|
||
};
|
||
|
||
/// The information about the
|
||
#[derive(Debug)]
|
||
pub(crate) struct VNodeMount {
|
||
/// The parent of this node
|
||
pub parent: Option<ElementRef>,
|
||
|
||
/// A back link to the original node
|
||
pub node: VNode,
|
||
|
||
/// The IDs for the roots of this template - to be used when moving the template around and removing it from
|
||
/// the actual Dom
|
||
pub root_ids: Box<[ElementId]>,
|
||
|
||
/// The element in the DOM that each attribute is mounted to
|
||
pub(crate) mounted_attributes: Box<[ElementId]>,
|
||
|
||
/// For components: This is the ScopeId the component is mounted to
|
||
/// For other dynamic nodes: This is element in the DOM that each dynamic node is mounted to
|
||
pub(crate) mounted_dynamic_nodes: Box<[usize]>,
|
||
}
|
||
|
||
/// A reference to a template along with any context needed to hydrate it
|
||
///
|
||
/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
|
||
/// static parts of the template.
|
||
#[derive(Debug)]
|
||
pub struct VNodeInner {
|
||
/// The key given to the root of this template.
|
||
///
|
||
/// In fragments, this is the key of the first child. In other cases, it is the key of the root.
|
||
pub key: Option<String>,
|
||
|
||
/// The static nodes and static descriptor of the template
|
||
pub template: Template,
|
||
|
||
/// The dynamic nodes in the template
|
||
pub dynamic_nodes: Box<[DynamicNode]>,
|
||
|
||
/// The dynamic attribute slots in the template
|
||
///
|
||
/// This is a list of positions in the template where dynamic attributes can be inserted.
|
||
///
|
||
/// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
|
||
///
|
||
/// For example:
|
||
/// ```rust
|
||
/// # use dioxus::prelude::*;
|
||
/// let class = "my-class";
|
||
/// let attrs = vec![];
|
||
/// let color = "red";
|
||
///
|
||
/// rsx! {
|
||
/// div {
|
||
/// class: "{class}",
|
||
/// ..attrs,
|
||
/// p {
|
||
/// color: "{color}",
|
||
/// }
|
||
/// }
|
||
/// };
|
||
/// ```
|
||
///
|
||
/// Would be represented as:
|
||
/// ```text
|
||
/// [
|
||
/// [class, every attribute in attrs sorted by name], // Slot 0 in the template
|
||
/// [color], // Slot 1 in the template
|
||
/// ]
|
||
/// ```
|
||
pub dynamic_attrs: Box<[Box<[Attribute]>]>,
|
||
}
|
||
|
||
/// A reference to a template along with any context needed to hydrate it
|
||
///
|
||
/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
|
||
/// static parts of the template.
|
||
#[derive(Debug, Clone)]
|
||
pub struct VNode {
|
||
vnode: Rc<VNodeInner>,
|
||
|
||
/// The mount information for this template
|
||
pub(crate) mount: Cell<MountId>,
|
||
}
|
||
|
||
impl AsRef<VNode> for Element {
|
||
fn as_ref(&self) -> &VNode {
|
||
match self {
|
||
Element::Ok(node) => node,
|
||
Element::Err(RenderError::Aborted(err)) => &err.render,
|
||
Element::Err(RenderError::Suspended(fut)) => &fut.placeholder,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<&Element> for VNode {
|
||
fn from(val: &Element) -> Self {
|
||
AsRef::as_ref(val).clone()
|
||
}
|
||
}
|
||
|
||
impl From<Element> for VNode {
|
||
fn from(val: Element) -> Self {
|
||
match val {
|
||
Element::Ok(node) => node,
|
||
Element::Err(RenderError::Aborted(err)) => err.render,
|
||
Element::Err(RenderError::Suspended(fut)) => fut.placeholder,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A tiny helper trait to get the vnode for a Element
|
||
pub(crate) trait AsVNode {
|
||
/// Get the vnode for this element
|
||
fn as_vnode(&self) -> &VNode;
|
||
|
||
/// Create a deep clone of this VNode
|
||
fn deep_clone(&self) -> Self;
|
||
}
|
||
|
||
impl AsVNode for Element {
|
||
fn as_vnode(&self) -> &VNode {
|
||
AsRef::as_ref(self)
|
||
}
|
||
|
||
fn deep_clone(&self) -> Self {
|
||
match self {
|
||
Ok(node) => Ok(node.deep_clone()),
|
||
Err(RenderError::Aborted(err)) => Err(RenderError::Aborted(err.deep_clone())),
|
||
Err(RenderError::Suspended(fut)) => Err(RenderError::Suspended(fut.deep_clone())),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Default for VNode {
|
||
fn default() -> Self {
|
||
Self::placeholder()
|
||
}
|
||
}
|
||
|
||
impl Drop for VNode {
|
||
fn drop(&mut self) {
|
||
// FIXME:
|
||
// TODO:
|
||
//
|
||
// We have to add this drop *here* because we can't add a drop impl to AttributeValue and
|
||
// keep semver compatibility. Adding a drop impl means you can't destructure the value, which
|
||
// we need to do for enums.
|
||
//
|
||
// if dropping this will drop the last vnode (rc count is 1), then we need to drop the listeners
|
||
// in this template
|
||
if Rc::strong_count(&self.vnode) == 1 {
|
||
for attrs in self.vnode.dynamic_attrs.iter() {
|
||
for attr in attrs.iter() {
|
||
if let AttributeValue::Listener(listener) = &attr.value {
|
||
listener.callback.manually_drop();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl PartialEq for VNode {
|
||
fn eq(&self, other: &Self) -> bool {
|
||
Rc::ptr_eq(&self.vnode, &other.vnode)
|
||
}
|
||
}
|
||
|
||
impl Deref for VNode {
|
||
type Target = VNodeInner;
|
||
|
||
fn deref(&self) -> &Self::Target {
|
||
&self.vnode
|
||
}
|
||
}
|
||
|
||
impl VNode {
|
||
/// Create a template with no nodes that will be skipped over during diffing
|
||
pub fn empty() -> Element {
|
||
Ok(Self::default())
|
||
}
|
||
|
||
/// Create a template with a single placeholder node
|
||
pub fn placeholder() -> Self {
|
||
use std::cell::OnceCell;
|
||
// We can reuse all placeholders across the same thread to save memory
|
||
thread_local! {
|
||
static PLACEHOLDER_VNODE: OnceCell<Rc<VNodeInner>> = const { OnceCell::new() };
|
||
}
|
||
let vnode = PLACEHOLDER_VNODE.with(|cell| {
|
||
cell.get_or_init(move || {
|
||
Rc::new(VNodeInner {
|
||
key: None,
|
||
dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
|
||
dynamic_attrs: Box::new([]),
|
||
template: Template {
|
||
roots: &[TemplateNode::Dynamic { id: 0 }],
|
||
node_paths: &[&[0]],
|
||
attr_paths: &[],
|
||
},
|
||
})
|
||
})
|
||
.clone()
|
||
});
|
||
Self {
|
||
vnode,
|
||
mount: Default::default(),
|
||
}
|
||
}
|
||
|
||
/// Create a new VNode
|
||
pub fn new(
|
||
key: Option<String>,
|
||
template: Template,
|
||
dynamic_nodes: Box<[DynamicNode]>,
|
||
dynamic_attrs: Box<[Box<[Attribute]>]>,
|
||
) -> Self {
|
||
Self {
|
||
vnode: Rc::new(VNodeInner {
|
||
key,
|
||
template,
|
||
dynamic_nodes,
|
||
dynamic_attrs,
|
||
}),
|
||
mount: Default::default(),
|
||
}
|
||
}
|
||
|
||
/// Load a dynamic root at the given index
|
||
///
|
||
/// Returns [`None`] if the root is actually a static node (Element/Text)
|
||
pub fn dynamic_root(&self, idx: usize) -> Option<&DynamicNode> {
|
||
self.template.roots[idx]
|
||
.dynamic_id()
|
||
.map(|id| &self.dynamic_nodes[id])
|
||
}
|
||
|
||
/// Get the mounted id for a dynamic node index
|
||
pub fn mounted_dynamic_node(
|
||
&self,
|
||
dynamic_node_idx: usize,
|
||
dom: &VirtualDom,
|
||
) -> Option<ElementId> {
|
||
let mount = self.mount.get().as_usize()?;
|
||
|
||
match &self.dynamic_nodes[dynamic_node_idx] {
|
||
DynamicNode::Text(_) | DynamicNode::Placeholder(_) => {
|
||
let mounts = dom.runtime.mounts.borrow();
|
||
mounts
|
||
.get(mount)?
|
||
.mounted_dynamic_nodes
|
||
.get(dynamic_node_idx)
|
||
.map(|id| ElementId(*id))
|
||
}
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Get the mounted id for a root node index
|
||
pub fn mounted_root(&self, root_idx: usize, dom: &VirtualDom) -> Option<ElementId> {
|
||
let mount = self.mount.get().as_usize()?;
|
||
|
||
let mounts = dom.runtime.mounts.borrow();
|
||
mounts.get(mount)?.root_ids.get(root_idx).copied()
|
||
}
|
||
|
||
/// Get the mounted id for a dynamic attribute index
|
||
pub fn mounted_dynamic_attribute(
|
||
&self,
|
||
dynamic_attribute_idx: usize,
|
||
dom: &VirtualDom,
|
||
) -> Option<ElementId> {
|
||
let mount = self.mount.get().as_usize()?;
|
||
|
||
let mounts = dom.runtime.mounts.borrow();
|
||
mounts
|
||
.get(mount)?
|
||
.mounted_attributes
|
||
.get(dynamic_attribute_idx)
|
||
.copied()
|
||
}
|
||
|
||
/// Create a deep clone of this VNode
|
||
pub(crate) fn deep_clone(&self) -> Self {
|
||
Self {
|
||
vnode: Rc::new(VNodeInner {
|
||
key: self.vnode.key.clone(),
|
||
template: self.vnode.template,
|
||
dynamic_nodes: self
|
||
.vnode
|
||
.dynamic_nodes
|
||
.iter()
|
||
.map(|node| match node {
|
||
DynamicNode::Fragment(nodes) => DynamicNode::Fragment(
|
||
nodes.iter().map(|node| node.deep_clone()).collect(),
|
||
),
|
||
other => other.clone(),
|
||
})
|
||
.collect(),
|
||
dynamic_attrs: self
|
||
.vnode
|
||
.dynamic_attrs
|
||
.iter()
|
||
.map(|attr| {
|
||
attr.iter()
|
||
.map(|attribute| attribute.deep_clone())
|
||
.collect()
|
||
})
|
||
.collect(),
|
||
}),
|
||
mount: Default::default(),
|
||
}
|
||
}
|
||
}
|
||
|
||
type StaticStr = &'static str;
|
||
type StaticPathArray = &'static [&'static [u8]];
|
||
type StaticTemplateArray = &'static [TemplateNode];
|
||
type StaticTemplateAttributeArray = &'static [TemplateAttribute];
|
||
|
||
/// A static layout of a UI tree that describes a set of dynamic and static nodes.
|
||
///
|
||
/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
|
||
/// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use
|
||
/// its static description of the UI to skip immediately to the dynamic nodes during diffing.
|
||
///
|
||
/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
|
||
/// ways, with the suggested approach being the unique code location (file, line, col, etc).
|
||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||
#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
|
||
pub struct Template {
|
||
/// The list of template nodes that make up the template
|
||
///
|
||
/// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
|
||
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
|
||
pub roots: StaticTemplateArray,
|
||
|
||
/// The paths of each node relative to the root of the template.
|
||
///
|
||
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
|
||
/// topmost element, not the `roots` field.
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_bytes_leaky")
|
||
)]
|
||
pub node_paths: StaticPathArray,
|
||
|
||
/// The paths of each dynamic attribute relative to the root of the template
|
||
///
|
||
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
|
||
/// topmost element, not the `roots` field.
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_bytes_leaky", bound = "")
|
||
)]
|
||
pub attr_paths: StaticPathArray,
|
||
}
|
||
|
||
// Are identical static items merged in the current build. Rust doesn't have a cfg(merge_statics) attribute
|
||
// so we have to check this manually
|
||
#[allow(unpredictable_function_pointer_comparisons)] // This attribute should be removed once MSRV is 1.85 or greater and the below change is made
|
||
fn static_items_merged() -> bool {
|
||
fn a() {}
|
||
fn b() {}
|
||
a as fn() == b as fn()
|
||
// std::ptr::fn_addr_eq(a as fn(), b as fn()) <<<<---- This should replace the a as fn() === b as fn() once the MSRV is 1.85 or greater
|
||
}
|
||
|
||
impl std::hash::Hash for Template {
|
||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||
// If identical static items are merged, we can compare templates by pointer
|
||
if static_items_merged() {
|
||
std::ptr::hash(self.roots as *const _, state);
|
||
std::ptr::hash(self.node_paths as *const _, state);
|
||
std::ptr::hash(self.attr_paths as *const _, state);
|
||
}
|
||
// Otherwise, we hash by value
|
||
else {
|
||
self.roots.hash(state);
|
||
self.node_paths.hash(state);
|
||
self.attr_paths.hash(state);
|
||
}
|
||
}
|
||
}
|
||
|
||
impl PartialEq for Template {
|
||
fn eq(&self, other: &Self) -> bool {
|
||
// If identical static items are merged, we can compare templates by pointer
|
||
if static_items_merged() {
|
||
std::ptr::eq(self.roots as *const _, other.roots as *const _)
|
||
&& std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
|
||
&& std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
|
||
}
|
||
// Otherwise, we compare by value
|
||
else {
|
||
self.roots == other.roots
|
||
&& self.node_paths == other.node_paths
|
||
&& self.attr_paths == other.attr_paths
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(feature = "serialize")]
|
||
pub(crate) fn deserialize_string_leaky<'a, 'de, D>(
|
||
deserializer: D,
|
||
) -> Result<&'static str, D::Error>
|
||
where
|
||
D: serde::Deserializer<'de>,
|
||
{
|
||
use serde::Deserialize;
|
||
|
||
let deserialized = String::deserialize(deserializer)?;
|
||
Ok(&*Box::leak(deserialized.into_boxed_str()))
|
||
}
|
||
|
||
#[cfg(feature = "serialize")]
|
||
fn deserialize_bytes_leaky<'a, 'de, D>(
|
||
deserializer: D,
|
||
) -> Result<&'static [&'static [u8]], D::Error>
|
||
where
|
||
D: serde::Deserializer<'de>,
|
||
{
|
||
use serde::Deserialize;
|
||
|
||
let deserialized = Vec::<Vec<u8>>::deserialize(deserializer)?;
|
||
let deserialized = deserialized
|
||
.into_iter()
|
||
.map(|v| &*Box::leak(v.into_boxed_slice()))
|
||
.collect::<Vec<_>>();
|
||
Ok(&*Box::leak(deserialized.into_boxed_slice()))
|
||
}
|
||
|
||
#[cfg(feature = "serialize")]
|
||
pub(crate) fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'static [T], D::Error>
|
||
where
|
||
T: serde::Deserialize<'de>,
|
||
D: serde::Deserializer<'de>,
|
||
{
|
||
use serde::Deserialize;
|
||
|
||
let deserialized = Box::<[T]>::deserialize(deserializer)?;
|
||
Ok(&*Box::leak(deserialized))
|
||
}
|
||
|
||
#[cfg(feature = "serialize")]
|
||
pub(crate) fn deserialize_option_leaky<'a, 'de, D>(
|
||
deserializer: D,
|
||
) -> Result<Option<&'static str>, D::Error>
|
||
where
|
||
D: serde::Deserializer<'de>,
|
||
{
|
||
use serde::Deserialize;
|
||
|
||
let deserialized = Option::<String>::deserialize(deserializer)?;
|
||
Ok(deserialized.map(|deserialized| &*Box::leak(deserialized.into_boxed_str())))
|
||
}
|
||
|
||
impl Template {
|
||
/// Is this template worth caching at all, since it's completely runtime?
|
||
///
|
||
/// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway.
|
||
pub fn is_completely_dynamic(&self) -> bool {
|
||
use TemplateNode::*;
|
||
self.roots.iter().all(|root| matches!(root, Dynamic { .. }))
|
||
}
|
||
}
|
||
|
||
/// A statically known node in a layout.
|
||
///
|
||
/// This can be created at compile time, saving the VirtualDom time when diffing the tree
|
||
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
derive(serde::Serialize, serde::Deserialize),
|
||
serde(tag = "type")
|
||
)]
|
||
pub enum TemplateNode {
|
||
/// An statically known element in the dom.
|
||
///
|
||
/// In HTML this would be something like `<div id="123"> </div>`
|
||
Element {
|
||
/// The name of the element
|
||
///
|
||
/// IE for a div, it would be the string "div"
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_string_leaky")
|
||
)]
|
||
tag: StaticStr,
|
||
|
||
/// The namespace of the element
|
||
///
|
||
/// In HTML, this would be a valid URI that defines a namespace for all elements below it
|
||
/// SVG is an example of this namespace
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_option_leaky")
|
||
)]
|
||
namespace: Option<StaticStr>,
|
||
|
||
/// A list of possibly dynamic attributes for this element
|
||
///
|
||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_leaky", bound = "")
|
||
)]
|
||
attrs: StaticTemplateAttributeArray,
|
||
|
||
/// A list of template nodes that define another set of template nodes
|
||
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
|
||
children: StaticTemplateArray,
|
||
},
|
||
|
||
/// This template node is just a piece of static text
|
||
Text {
|
||
/// The actual text
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_string_leaky", bound = "")
|
||
)]
|
||
text: StaticStr,
|
||
},
|
||
|
||
/// This template node is unknown, and needs to be created at runtime.
|
||
Dynamic {
|
||
/// The index of the dynamic node in the VNode's dynamic_nodes list
|
||
id: usize,
|
||
},
|
||
}
|
||
|
||
impl TemplateNode {
|
||
/// Try to load the dynamic node at the given index
|
||
pub fn dynamic_id(&self) -> Option<usize> {
|
||
use TemplateNode::*;
|
||
match self {
|
||
Dynamic { id } => Some(*id),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A node created at runtime
|
||
///
|
||
/// This node's index in the DynamicNode list on VNode should match its respective `Dynamic` index
|
||
#[derive(Debug, Clone)]
|
||
pub enum DynamicNode {
|
||
/// A component node
|
||
///
|
||
/// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
|
||
/// assigned scope are dynamic.
|
||
///
|
||
/// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
|
||
/// the render function at runtime
|
||
Component(VComponent),
|
||
|
||
/// A text node
|
||
Text(VText),
|
||
|
||
/// A placeholder
|
||
///
|
||
/// Used by suspense when a node isn't ready and by fragments that don't render anything
|
||
///
|
||
/// In code, this is just an ElementId whose initial value is set to 0 upon creation
|
||
Placeholder(VPlaceholder),
|
||
|
||
/// A list of VNodes.
|
||
///
|
||
/// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
|
||
/// or iterators.
|
||
Fragment(Vec<VNode>),
|
||
}
|
||
|
||
impl DynamicNode {
|
||
/// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`]
|
||
pub fn make_node<'c, I>(into: impl IntoDynNode<I> + 'c) -> DynamicNode {
|
||
into.into_dyn_node()
|
||
}
|
||
}
|
||
|
||
impl Default for DynamicNode {
|
||
fn default() -> Self {
|
||
Self::Placeholder(Default::default())
|
||
}
|
||
}
|
||
|
||
/// An instance of a child component
|
||
pub struct VComponent {
|
||
/// The name of this component
|
||
pub name: &'static str,
|
||
|
||
/// The raw pointer to the render function
|
||
pub(crate) render_fn: usize,
|
||
|
||
/// The props for this component
|
||
pub(crate) props: BoxedAnyProps,
|
||
}
|
||
|
||
impl Clone for VComponent {
|
||
fn clone(&self) -> Self {
|
||
Self {
|
||
name: self.name,
|
||
props: self.props.duplicate(),
|
||
render_fn: self.render_fn,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl VComponent {
|
||
/// Create a new [`VComponent`] variant
|
||
pub fn new<P, M: 'static>(
|
||
component: impl ComponentFunction<P, M>,
|
||
props: P,
|
||
fn_name: &'static str,
|
||
) -> Self
|
||
where
|
||
P: Properties + 'static,
|
||
{
|
||
let render_fn = component.fn_ptr();
|
||
let props = Box::new(VProps::new(
|
||
component,
|
||
<P as Properties>::memoize,
|
||
props,
|
||
fn_name,
|
||
));
|
||
|
||
VComponent {
|
||
render_fn,
|
||
name: fn_name,
|
||
props,
|
||
}
|
||
}
|
||
|
||
/// Get the [`ScopeId`] this node is mounted to if it's mounted
|
||
///
|
||
/// This is useful for rendering nodes outside of the VirtualDom, such as in SSR
|
||
///
|
||
/// Returns [`None`] if the node is not mounted
|
||
pub fn mounted_scope_id(
|
||
&self,
|
||
dynamic_node_index: usize,
|
||
vnode: &VNode,
|
||
dom: &VirtualDom,
|
||
) -> Option<ScopeId> {
|
||
let mount = vnode.mount.get().as_usize()?;
|
||
|
||
let mounts = dom.runtime.mounts.borrow();
|
||
let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
|
||
|
||
Some(ScopeId(scope_id))
|
||
}
|
||
|
||
/// Get the scope this node is mounted to if it's mounted
|
||
///
|
||
/// This is useful for rendering nodes outside of the VirtualDom, such as in SSR
|
||
///
|
||
/// Returns [`None`] if the node is not mounted
|
||
pub fn mounted_scope<'a>(
|
||
&self,
|
||
dynamic_node_index: usize,
|
||
vnode: &VNode,
|
||
dom: &'a VirtualDom,
|
||
) -> Option<&'a ScopeState> {
|
||
let mount = vnode.mount.get().as_usize()?;
|
||
|
||
let mounts = dom.runtime.mounts.borrow();
|
||
let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
|
||
|
||
dom.scopes.get(scope_id)
|
||
}
|
||
}
|
||
|
||
impl std::fmt::Debug for VComponent {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
f.debug_struct("VComponent")
|
||
.field("name", &self.name)
|
||
.finish()
|
||
}
|
||
}
|
||
|
||
/// A text node
|
||
#[derive(Clone, Debug)]
|
||
pub struct VText {
|
||
/// The actual text itself
|
||
pub value: String,
|
||
}
|
||
|
||
impl VText {
|
||
/// Create a new VText
|
||
pub fn new(value: impl ToString) -> Self {
|
||
Self {
|
||
value: value.to_string(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<Arguments<'_>> for VText {
|
||
fn from(args: Arguments) -> Self {
|
||
Self::new(args.to_string())
|
||
}
|
||
}
|
||
|
||
/// A placeholder node, used by suspense and fragments
|
||
#[derive(Clone, Debug, Default)]
|
||
#[non_exhaustive]
|
||
pub struct VPlaceholder {}
|
||
|
||
/// An attribute of the TemplateNode, created at compile time
|
||
#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
derive(serde::Serialize, serde::Deserialize),
|
||
serde(tag = "type")
|
||
)]
|
||
pub enum TemplateAttribute {
|
||
/// This attribute is entirely known at compile time, enabling
|
||
Static {
|
||
/// The name of this attribute.
|
||
///
|
||
/// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_string_leaky", bound = "")
|
||
)]
|
||
name: StaticStr,
|
||
|
||
/// The value of this attribute, known at compile time
|
||
///
|
||
/// Currently this only accepts &str, so values, even if they're known at compile time, are not known
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_string_leaky", bound = "")
|
||
)]
|
||
value: StaticStr,
|
||
|
||
/// The namespace of this attribute. Does not exist in the HTML spec
|
||
#[cfg_attr(
|
||
feature = "serialize",
|
||
serde(deserialize_with = "deserialize_option_leaky", bound = "")
|
||
)]
|
||
namespace: Option<StaticStr>,
|
||
},
|
||
|
||
/// The attribute in this position is actually determined dynamically at runtime
|
||
///
|
||
/// This is the index into the dynamic_attributes field on the container VNode
|
||
Dynamic {
|
||
/// The index
|
||
id: usize,
|
||
},
|
||
}
|
||
|
||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct Attribute {
|
||
/// The name of the attribute.
|
||
pub name: &'static str,
|
||
|
||
/// The value of the attribute
|
||
pub value: AttributeValue,
|
||
|
||
/// 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>,
|
||
|
||
/// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
|
||
pub volatile: bool,
|
||
}
|
||
|
||
impl Attribute {
|
||
/// Create a new [`Attribute`] from a name, value, namespace, and volatile bool
|
||
///
|
||
/// "Volatile" refers to whether or not Dioxus should always override the value. This helps prevent the UI in
|
||
/// some renderers stay in sync with the VirtualDom's understanding of the world
|
||
pub fn new<T>(
|
||
name: &'static str,
|
||
value: impl IntoAttributeValue<T>,
|
||
namespace: Option<&'static str>,
|
||
volatile: bool,
|
||
) -> Attribute {
|
||
Attribute {
|
||
name,
|
||
namespace,
|
||
volatile,
|
||
value: value.into_value(),
|
||
}
|
||
}
|
||
|
||
/// Create a new deep clone of this attribute
|
||
pub(crate) fn deep_clone(&self) -> Self {
|
||
Attribute {
|
||
name: self.name,
|
||
namespace: self.namespace,
|
||
volatile: self.volatile,
|
||
value: match &self.value {
|
||
AttributeValue::Listener(listener) => {
|
||
AttributeValue::Listener(listener.leak_reference().unwrap())
|
||
}
|
||
value => value.clone(),
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
|
||
///
|
||
/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
|
||
/// variant.
|
||
#[derive(Clone)]
|
||
pub enum AttributeValue {
|
||
/// Text attribute
|
||
Text(String),
|
||
|
||
/// A float
|
||
Float(f64),
|
||
|
||
/// Signed integer
|
||
Int(i64),
|
||
|
||
/// Boolean
|
||
Bool(bool),
|
||
|
||
/// A listener, like "onclick"
|
||
Listener(ListenerCb),
|
||
|
||
/// An arbitrary value that implements PartialEq and is static
|
||
Any(Rc<dyn AnyValue>),
|
||
|
||
/// A "none" value, resulting in the removal of an attribute from the dom
|
||
None,
|
||
}
|
||
|
||
impl AttributeValue {
|
||
/// Create a new [`AttributeValue`] with the listener variant from a callback
|
||
///
|
||
/// The callback must be confined to the lifetime of the ScopeState
|
||
pub fn listener<T: 'static>(mut callback: impl FnMut(Event<T>) + 'static) -> AttributeValue {
|
||
// TODO: maybe don't use the copy-variant of EventHandler here?
|
||
// Maybe, create an Owned variant so we are less likely to run into leaks
|
||
AttributeValue::Listener(EventHandler::leak(move |event: Event<dyn Any>| {
|
||
let data = event.data.downcast::<T>().unwrap();
|
||
callback(Event {
|
||
metadata: event.metadata.clone(),
|
||
data,
|
||
});
|
||
}))
|
||
}
|
||
|
||
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
|
||
pub fn any_value<T: AnyValue>(value: T) -> AttributeValue {
|
||
AttributeValue::Any(Rc::new(value))
|
||
}
|
||
}
|
||
|
||
pub type ListenerCb = EventHandler<Event<dyn Any>>;
|
||
|
||
impl std::fmt::Debug for AttributeValue {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
match self {
|
||
Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
|
||
Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
|
||
Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
|
||
Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
|
||
Self::Listener(listener) => f.debug_tuple("Listener").field(listener).finish(),
|
||
Self::Any(_) => f.debug_tuple("Any").finish(),
|
||
Self::None => write!(f, "None"),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl PartialEq for AttributeValue {
|
||
fn eq(&self, other: &Self) -> bool {
|
||
match (self, other) {
|
||
(Self::Text(l0), Self::Text(r0)) => l0 == r0,
|
||
(Self::Float(l0), Self::Float(r0)) => l0 == r0,
|
||
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
|
||
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
|
||
(Self::Listener(l0), Self::Listener(r0)) => l0 == r0,
|
||
(Self::Any(l0), Self::Any(r0)) => l0.as_ref().any_cmp(r0.as_ref()),
|
||
(Self::None, Self::None) => true,
|
||
_ => false,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[doc(hidden)]
|
||
pub trait AnyValue: 'static {
|
||
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
|
||
fn as_any(&self) -> &dyn Any;
|
||
fn type_id(&self) -> TypeId {
|
||
self.as_any().type_id()
|
||
}
|
||
}
|
||
|
||
impl<T: Any + PartialEq + 'static> AnyValue for T {
|
||
fn any_cmp(&self, other: &dyn AnyValue) -> bool {
|
||
if let Some(other) = other.as_any().downcast_ref() {
|
||
self == other
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
fn as_any(&self) -> &dyn Any {
|
||
self
|
||
}
|
||
}
|
||
|
||
/// A trait that allows various items to be converted into a dynamic node for the rsx macro
|
||
pub trait IntoDynNode<A = ()> {
|
||
/// Consume this item and produce a DynamicNode
|
||
fn into_dyn_node(self) -> DynamicNode;
|
||
}
|
||
|
||
impl IntoDynNode for () {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
DynamicNode::default()
|
||
}
|
||
}
|
||
impl IntoDynNode for VNode {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
DynamicNode::Fragment(vec![self])
|
||
}
|
||
}
|
||
impl IntoDynNode for DynamicNode {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
self
|
||
}
|
||
}
|
||
impl<T: IntoDynNode> IntoDynNode for Option<T> {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
match self {
|
||
Some(val) => val.into_dyn_node(),
|
||
None => DynamicNode::default(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoDynNode for &Element {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
match self.as_ref() {
|
||
Ok(val) => val.into_dyn_node(),
|
||
_ => DynamicNode::default(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoDynNode for Element {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
match self {
|
||
Ok(val) => val.into_dyn_node(),
|
||
_ => DynamicNode::default(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoDynNode for &Option<VNode> {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
match self.as_ref() {
|
||
Some(val) => val.clone().into_dyn_node(),
|
||
_ => DynamicNode::default(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoDynNode for &str {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
DynamicNode::Text(VText {
|
||
value: self.to_string(),
|
||
})
|
||
}
|
||
}
|
||
impl IntoDynNode for String {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
DynamicNode::Text(VText { value: self })
|
||
}
|
||
}
|
||
impl IntoDynNode for Arguments<'_> {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
DynamicNode::Text(VText {
|
||
value: self.to_string(),
|
||
})
|
||
}
|
||
}
|
||
impl IntoDynNode for &VNode {
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
DynamicNode::Fragment(vec![self.clone()])
|
||
}
|
||
}
|
||
|
||
pub trait IntoVNode {
|
||
fn into_vnode(self) -> VNode;
|
||
}
|
||
impl IntoVNode for VNode {
|
||
fn into_vnode(self) -> VNode {
|
||
self
|
||
}
|
||
}
|
||
impl IntoVNode for &VNode {
|
||
fn into_vnode(self) -> VNode {
|
||
self.clone()
|
||
}
|
||
}
|
||
impl IntoVNode for Element {
|
||
fn into_vnode(self) -> VNode {
|
||
match self {
|
||
Ok(val) => val.into_vnode(),
|
||
_ => VNode::empty().unwrap(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoVNode for &Element {
|
||
fn into_vnode(self) -> VNode {
|
||
match self {
|
||
Ok(val) => val.into_vnode(),
|
||
_ => VNode::empty().unwrap(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoVNode for Option<VNode> {
|
||
fn into_vnode(self) -> VNode {
|
||
match self {
|
||
Some(val) => val.into_vnode(),
|
||
_ => VNode::empty().unwrap(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoVNode for &Option<VNode> {
|
||
fn into_vnode(self) -> VNode {
|
||
match self.as_ref() {
|
||
Some(val) => val.clone().into_vnode(),
|
||
_ => VNode::empty().unwrap(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoVNode for Option<Element> {
|
||
fn into_vnode(self) -> VNode {
|
||
match self {
|
||
Some(val) => val.into_vnode(),
|
||
_ => VNode::empty().unwrap(),
|
||
}
|
||
}
|
||
}
|
||
impl IntoVNode for &Option<Element> {
|
||
fn into_vnode(self) -> VNode {
|
||
match self.as_ref() {
|
||
Some(val) => val.clone().into_vnode(),
|
||
_ => VNode::empty().unwrap(),
|
||
}
|
||
}
|
||
}
|
||
|
||
// Note that we're using the E as a generic but this is never crafted anyways.
|
||
pub struct FromNodeIterator;
|
||
impl<T, I> IntoDynNode<FromNodeIterator> for T
|
||
where
|
||
T: Iterator<Item = I>,
|
||
I: IntoVNode,
|
||
{
|
||
fn into_dyn_node(self) -> DynamicNode {
|
||
let children: Vec<_> = self.into_iter().map(|node| node.into_vnode()).collect();
|
||
|
||
if children.is_empty() {
|
||
DynamicNode::default()
|
||
} else {
|
||
DynamicNode::Fragment(children)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A value that can be converted into an attribute value
|
||
pub trait IntoAttributeValue<T = ()> {
|
||
/// Convert into an attribute value
|
||
fn into_value(self) -> AttributeValue;
|
||
}
|
||
|
||
impl IntoAttributeValue for AttributeValue {
|
||
fn into_value(self) -> AttributeValue {
|
||
self
|
||
}
|
||
}
|
||
|
||
impl IntoAttributeValue for &str {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Text(self.to_string())
|
||
}
|
||
}
|
||
|
||
impl IntoAttributeValue for String {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Text(self)
|
||
}
|
||
}
|
||
|
||
impl IntoAttributeValue for f32 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Float(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for f64 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Float(self)
|
||
}
|
||
}
|
||
|
||
impl IntoAttributeValue for i8 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for i16 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for i32 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for i64 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for isize {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for i128 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
|
||
impl IntoAttributeValue for u8 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for u16 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for u32 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for u64 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for usize {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
impl IntoAttributeValue for u128 {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Int(self as _)
|
||
}
|
||
}
|
||
|
||
impl IntoAttributeValue for bool {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Bool(self)
|
||
}
|
||
}
|
||
|
||
impl IntoAttributeValue for Arguments<'_> {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Text(self.to_string())
|
||
}
|
||
}
|
||
|
||
impl IntoAttributeValue for Rc<dyn AnyValue> {
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Any(self)
|
||
}
|
||
}
|
||
|
||
impl<T: IntoAttributeValue> IntoAttributeValue for Option<T> {
|
||
fn into_value(self) -> AttributeValue {
|
||
match self {
|
||
Some(val) => val.into_value(),
|
||
None => AttributeValue::None,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub struct AnyFmtMarker;
|
||
impl<T> IntoAttributeValue<AnyFmtMarker> for T
|
||
where
|
||
T: DioxusFormattable,
|
||
{
|
||
fn into_value(self) -> AttributeValue {
|
||
AttributeValue::Text(self.format().to_string())
|
||
}
|
||
}
|
||
|
||
/// A trait for anything that has a dynamic list of attributes
|
||
pub trait HasAttributes {
|
||
/// Push an attribute onto the list of attributes
|
||
fn push_attribute<T>(
|
||
self,
|
||
name: &'static str,
|
||
ns: Option<&'static str>,
|
||
attr: impl IntoAttributeValue<T>,
|
||
volatile: bool,
|
||
) -> Self;
|
||
}
|