Native: Split dioxus-native-dom crate out of dioxus-native (#4366)

Signed-off-by: Nico Burns <nico@nicoburns.com>
This commit is contained in:
Nico Burns authored and GitHub committed 2025-07-07 22:14:59 +01:00
1 parent b252528722
commit a4ebdcb69e
10 files changed
+129 -63

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
[package]
name = "dioxus-native-dom"
version = { workspace = true }
authors = ["Jonathan Kelley", "Nico Burns"]
edition = "2021"
description = "Core headless native renderer for Dioxus based on blitz"
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com/learn/0.6/getting_started"
keywords = ["dom", "ui", "gui", "react"]
[features]
default = ["accessibility", "tracing", "svg", "system-fonts"]
svg = ["blitz-dom/svg"]
accessibility = ["blitz-dom/accessibility"]
tracing = ["dep:tracing", "blitz-dom/tracing"]
system-fonts = ["blitz-dom/system_fonts"]
autofocus = []
[dependencies]
# Blitz dependencies
blitz-dom = { version = "=0.1.0-alpha.5", default-features = false }
blitz-traits = { version = "=0.1.0-alpha.5" }
# DioxusLabs dependencies
dioxus-core = { workspace = true }
dioxus-html = { workspace = true }
# Windowing & Input
keyboard-types = { workspace = true }
# Other dependencies
tracing = { workspace = true, optional = true }
rustc-hash = { workspace = true }
futures-util = { workspace = true }
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
+260
View File
@@ -0,0 +1,260 @@
//! Integration between Dioxus and Blitz
use crate::events::{BlitzKeyboardData, NativeClickData, NativeConverter, NativeFormData};
use crate::mutation_writer::{DioxusState, MutationWriter};
use crate::qual_name;
use crate::NodeId;
use blitz_dom::Attribute;
use blitz_dom::{
net::Resource, BaseDocument, Document, EventDriver, EventHandler, Node, DEFAULT_CSS,
};
use blitz_traits::{
events::{DomEvent, DomEventData, EventState, UiEvent},
net::NetProvider,
shell::{ColorScheme, Viewport},
};
use dioxus_core::{ElementId, Event, VirtualDom};
use dioxus_html::{set_event_converter, PlatformEventData};
use futures_util::{pin_mut, FutureExt};
use std::ops::{Deref, DerefMut};
use std::{any::Any, collections::HashMap, rc::Rc, sync::Arc};
fn wrap_event_data<T: Any>(value: T) -> Rc<dyn Any> {
Rc::new(PlatformEventData::new(Box::new(value)))
}
/// Get the value of the "dioxus-data-id" attribute parsed aa usize
fn get_dioxus_id(node: &Node) -> Option<ElementId> {
node.element_data()?
.attrs
.iter()
.find(|attr| *attr.name.local == *"data-dioxus-id")
.and_then(|attr| attr.value.parse::<usize>().ok())
.map(ElementId)
}
pub struct DioxusDocument {
pub inner: BaseDocument,
pub vdom: VirtualDom,
pub vdom_state: DioxusState,
#[allow(unused)]
pub(crate) html_element_id: NodeId,
#[allow(unused)]
pub(crate) head_element_id: NodeId,
#[allow(unused)]
pub(crate) body_element_id: NodeId,
#[allow(unused)]
pub(crate) main_element_id: NodeId,
}
impl DioxusDocument {
pub fn new(vdom: VirtualDom, net_provider: Option<Arc<dyn NetProvider<Resource>>>) -> Self {
let viewport = Viewport::new(0, 0, 1.0, ColorScheme::Light);
let mut doc = BaseDocument::new(viewport);
// Set net provider
if let Some(net_provider) = net_provider {
doc.set_net_provider(net_provider);
}
// Create some minimal HTML to render the app into.
// Create the html element
let mut mutr = doc.mutate();
let html_element_id = mutr.create_element(qual_name("html", None), vec![]);
mutr.append_children(mutr.doc.root_node().id, &[html_element_id]);
// Create the body element
let head_element_id = mutr.create_element(qual_name("head", None), vec![]);
mutr.append_children(html_element_id, &[head_element_id]);
// Create the body element
let body_element_id = mutr.create_element(qual_name("body", None), vec![]);
mutr.append_children(html_element_id, &[body_element_id]);
// Create another virtual element to hold the root <div id="main"></div> under the html element
let main_attr = blitz_dom::Attribute {
name: qual_name("id", None),
value: "main".to_string(),
};
let main_element_id = mutr.create_element(qual_name("main", None), vec![main_attr]);
mutr.append_children(body_element_id, &[main_element_id]);
drop(mutr);
// Include default and user-specified stylesheets
doc.add_user_agent_stylesheet(DEFAULT_CSS);
let vdom_state = DioxusState::create(main_element_id);
let mut doc = Self {
vdom,
vdom_state,
inner: doc,
html_element_id,
head_element_id,
body_element_id,
main_element_id,
};
doc.inner.set_base_url("dioxus://index.html");
//doc.initial_build();
doc.inner.print_tree();
doc
}
pub fn initial_build(&mut self) {
let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
self.vdom.rebuild(&mut writer);
}
pub fn create_head_element(
&mut self,
name: &str,
attributes: &[(String, String)],
contents: &Option<String>,
) {
let mut mutr = self.inner.mutate();
let attributes = attributes
.iter()
.map(|(name, value)| Attribute {
name: qual_name(name, None),
value: value.clone(),
})
.collect();
let new_elem_id = mutr.create_element(qual_name(name, None), attributes);
mutr.append_children(self.head_element_id, &[new_elem_id]);
if let Some(contents) = contents {
mutr.append_text_to_node(new_elem_id, contents).unwrap();
}
// TODO: set title (we may also wish to have this built-in to the document?)
// if name == "title" {
// let title = mutr.doc.nodes[new_elem_id].text_content();
//
// }
}
}
// Implement DocumentLike and required traits for DioxusDocument
impl Document for DioxusDocument {
fn id(&self) -> usize {
self.inner.id()
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn poll(&mut self, mut cx: std::task::Context) -> bool {
{
let fut = self.vdom.wait_for_work();
pin_mut!(fut);
match fut.poll_unpin(&mut cx) {
std::task::Poll::Ready(_) => {}
std::task::Poll::Pending => return false,
}
}
let mut writer = MutationWriter::new(&mut self.inner, &mut self.vdom_state);
self.vdom.render_immediate(&mut writer);
true
}
fn handle_event(&mut self, event: UiEvent) {
set_event_converter(Box::new(NativeConverter {}));
let handler = DioxusEventHandler {
vdom: &mut self.vdom,
vdom_state: &mut self.vdom_state,
};
let mut driver = EventDriver::new(self.inner.mutate(), handler);
driver.handle_ui_event(event);
}
}
impl Deref for DioxusDocument {
type Target = BaseDocument;
fn deref(&self) -> &BaseDocument {
&self.inner
}
}
impl DerefMut for DioxusDocument {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl From<DioxusDocument> for BaseDocument {
fn from(doc: DioxusDocument) -> BaseDocument {
doc.inner
}
}
pub struct DioxusEventHandler<'v> {
vdom: &'v mut VirtualDom,
#[allow(dead_code, reason = "WIP")]
vdom_state: &'v mut DioxusState,
}
impl EventHandler for DioxusEventHandler<'_> {
fn handle_event(
&mut self,
chain: &[usize],
event: &mut DomEvent,
mutr: &mut blitz_dom::DocumentMutator<'_>,
event_state: &mut EventState,
) {
let event_data = match &event.data {
DomEventData::MouseMove(mevent)
| DomEventData::MouseDown(mevent)
| DomEventData::MouseUp(mevent)
| DomEventData::Click(mevent) => Some(wrap_event_data(NativeClickData(mevent.clone()))),
DomEventData::KeyDown(kevent)
| DomEventData::KeyUp(kevent)
| DomEventData::KeyPress(kevent) => {
Some(wrap_event_data(BlitzKeyboardData(kevent.clone())))
}
DomEventData::Input(data) => Some(wrap_event_data(NativeFormData {
value: data.value.clone(),
values: HashMap::new(),
})),
// TODO: Implement IME handling
DomEventData::Ime(_) => None,
};
let Some(event_data) = event_data else {
return;
};
for &node_id in chain {
// Get dioxus vdom id for node
let dioxus_id = mutr.doc.get_node(node_id).and_then(get_dioxus_id);
let Some(id) = dioxus_id else {
return;
};
// Handle event in vdom
let dx_event = Event::new(event_data.clone(), event.bubbles);
self.vdom
.runtime()
.handle_event(event.name(), dx_event.clone(), id);
// Update event state
if !dx_event.default_action_enabled() {
event_state.prevent_default();
}
if !dx_event.propagates() {
event_state.stop_propagation();
break;
}
}
}
}
+220
View File
@@ -0,0 +1,220 @@
use blitz_traits::events::{BlitzKeyEvent, BlitzMouseButtonEvent, MouseEventButton};
use dioxus_html::{
geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint},
input_data::{MouseButton, MouseButtonSet},
point_interaction::{
InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
},
AnimationData, ClipboardData, CompositionData, DragData, FocusData, FormData, FormValue,
HasFileData, HasFocusData, HasFormData, HasKeyboardData, HasMouseData, HtmlEventConverter,
ImageData, KeyboardData, MediaData, MountedData, MouseData, PlatformEventData, PointerData,
ResizeData, ScrollData, SelectionData, ToggleData, TouchData, TransitionData, VisibleData,
WheelData,
};
use keyboard_types::{Code, Key, Location, Modifiers};
use std::any::Any;
use std::collections::HashMap;
pub struct NativeConverter {}
impl HtmlEventConverter for NativeConverter {
fn convert_form_data(&self, event: &PlatformEventData) -> FormData {
event.downcast::<NativeFormData>().unwrap().clone().into()
}
fn convert_mouse_data(&self, event: &PlatformEventData) -> MouseData {
event.downcast::<NativeClickData>().unwrap().clone().into()
}
fn convert_keyboard_data(&self, event: &PlatformEventData) -> KeyboardData {
event
.downcast::<BlitzKeyboardData>()
.unwrap()
.clone()
.into()
}
fn convert_focus_data(&self, _event: &PlatformEventData) -> FocusData {
NativeFocusData {}.into()
}
fn convert_animation_data(&self, _event: &PlatformEventData) -> AnimationData {
todo!("todo: convert_animation_data in dioxus-native. requires support in blitz")
}
fn convert_clipboard_data(&self, _event: &PlatformEventData) -> ClipboardData {
todo!("todo: convert_clipboard_data in dioxus-native. requires support in blitz")
}
fn convert_composition_data(&self, _event: &PlatformEventData) -> CompositionData {
todo!("todo: convert_composition_data in dioxus-native. requires support in blitz")
}
fn convert_drag_data(&self, _event: &PlatformEventData) -> DragData {
todo!("todo: convert_drag_data in dioxus-native. requires support in blitz")
}
fn convert_image_data(&self, _event: &PlatformEventData) -> ImageData {
todo!("todo: convert_image_data in dioxus-native. requires support in blitz")
}
fn convert_media_data(&self, _event: &PlatformEventData) -> MediaData {
todo!("todo: convert_media_data in dioxus-native. requires support in blitz")
}
fn convert_mounted_data(&self, _event: &PlatformEventData) -> MountedData {
todo!("todo: convert_mounted_data in dioxus-native. requires support in blitz")
}
fn convert_pointer_data(&self, _event: &PlatformEventData) -> PointerData {
todo!("todo: convert_pointer_data in dioxus-native. requires support in blitz")
}
fn convert_scroll_data(&self, _event: &PlatformEventData) -> ScrollData {
todo!("todo: convert_scroll_data in dioxus-native. requires support in blitz")
}
fn convert_selection_data(&self, _event: &PlatformEventData) -> SelectionData {
todo!("todo: convert_selection_data in dioxus-native. requires support in blitz")
}
fn convert_toggle_data(&self, _event: &PlatformEventData) -> ToggleData {
todo!("todo: convert_toggle_data in dioxus-native. requires support in blitz")
}
fn convert_touch_data(&self, _event: &PlatformEventData) -> TouchData {
todo!("todo: convert_touch_data in dioxus-native. requires support in blitz")
}
fn convert_transition_data(&self, _event: &PlatformEventData) -> TransitionData {
todo!("todo: convert_transition_data in dioxus-native. requires support in blitz")
}
fn convert_wheel_data(&self, _event: &PlatformEventData) -> WheelData {
todo!("todo: convert_wheel_data in dioxus-native. requires support in blitz")
}
fn convert_resize_data(&self, _event: &PlatformEventData) -> ResizeData {
todo!("todo: convert_resize_data in dioxus-native. requires support in blitz")
}
fn convert_visible_data(&self, _event: &PlatformEventData) -> VisibleData {
todo!("todo: convert_visible_data in dioxus-native. requires support in blitz")
}
}
#[derive(Clone, Debug)]
pub struct NativeFormData {
pub value: String,
pub values: HashMap<String, FormValue>,
}
impl HasFormData for NativeFormData {
fn as_any(&self) -> &dyn std::any::Any {
self as &dyn std::any::Any
}
fn value(&self) -> String {
self.value.clone()
}
fn values(&self) -> HashMap<String, FormValue> {
self.values.clone()
}
}
impl HasFileData for NativeFormData {}
#[derive(Clone, Debug)]
pub(crate) struct BlitzKeyboardData(pub(crate) BlitzKeyEvent);
impl ModifiersInteraction for BlitzKeyboardData {
fn modifiers(&self) -> Modifiers {
self.0.modifiers
}
}
impl HasKeyboardData for BlitzKeyboardData {
fn key(&self) -> Key {
self.0.key.clone()
}
fn code(&self) -> Code {
self.0.code
}
fn location(&self) -> Location {
self.0.location
}
fn is_auto_repeating(&self) -> bool {
self.0.is_auto_repeating
}
fn is_composing(&self) -> bool {
self.0.is_composing
}
fn as_any(&self) -> &dyn std::any::Any {
self as &dyn Any
}
}
#[derive(Clone)]
pub struct NativeClickData(pub(crate) BlitzMouseButtonEvent);
impl InteractionLocation for NativeClickData {
fn client_coordinates(&self) -> ClientPoint {
ClientPoint::new(self.0.x as _, self.0.y as _)
}
// these require blitz to pass them along, or a dom rect
fn screen_coordinates(&self) -> ScreenPoint {
unimplemented!()
}
fn page_coordinates(&self) -> PagePoint {
unimplemented!()
}
}
impl InteractionElementOffset for NativeClickData {
fn element_coordinates(&self) -> ElementPoint {
todo!()
}
}
impl ModifiersInteraction for NativeClickData {
fn modifiers(&self) -> Modifiers {
self.0.mods
}
}
impl PointerInteraction for NativeClickData {
fn trigger_button(&self) -> Option<MouseButton> {
Some(match self.0.button {
MouseEventButton::Main => MouseButton::Primary,
MouseEventButton::Auxiliary => MouseButton::Auxiliary,
MouseEventButton::Secondary => MouseButton::Secondary,
MouseEventButton::Fourth => MouseButton::Fourth,
MouseEventButton::Fifth => MouseButton::Fifth,
})
}
fn held_buttons(&self) -> MouseButtonSet {
dioxus_html::input_data::decode_mouse_button_set(self.0.buttons.bits() as u16)
}
}
impl HasMouseData for NativeClickData {
fn as_any(&self) -> &dyn std::any::Any {
self as &dyn std::any::Any
}
}
#[derive(Clone)]
pub struct NativeFocusData {}
impl HasFocusData for NativeFocusData {
fn as_any(&self) -> &dyn std::any::Any {
self as &dyn std::any::Any
}
}
+51
View File
@@ -0,0 +1,51 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
//! Core headless native renderer for Dioxus.
//!
//! ## Feature flags
//! - `default`: Enables the features listed below.
//! - `accessibility`: Enables [`accesskit`](https://docs.rs/accesskit/latest/accesskit/) accessibility support.
//! - `hot-reload`: Enables hot-reloading of Dioxus RSX.
//! - `menu`: Enables the [`muda`](https://docs.rs/muda/latest/muda/) menubar.
//! - `tracing`: Enables tracing support.
mod dioxus_document;
mod events;
mod mutation_writer;
pub use dioxus_document::DioxusDocument;
use blitz_dom::{ns, LocalName, Namespace, QualName};
type NodeId = usize;
pub(crate) fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName {
QualName {
prefix: None,
ns: namespace.map(Namespace::from).unwrap_or(ns!(html)),
local: LocalName::from(local_name),
}
}
// Syntax sugar to make tracing calls less noisy in function below
macro_rules! trace {
($pattern:literal) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern);
}};
($pattern:literal, $item1:expr) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern, $item1);
}};
($pattern:literal, $item1:expr, $item2:expr) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern, $item1, $item2);
}};
($pattern:literal, $item1:expr, $item2:expr, $item3:expr) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern, $item1, $item2);
}};
($pattern:literal, $item1:expr, $item2:expr, $item3:expr, $item4:expr) => {{
#[cfg(feature = "tracing")]
tracing::info!($pattern, $item1, $item2, $item3, $item4);
}};
}
pub(crate) use trace;
+303
View File
@@ -0,0 +1,303 @@
//! Integration between Dioxus and Blitz
use crate::{qual_name, trace, NodeId};
use blitz_dom::{Attribute, BaseDocument, DocumentMutator};
use dioxus_core::{
AttributeValue, ElementId, Template, TemplateAttribute, TemplateNode, WriteMutations,
};
use rustc_hash::FxHashMap;
/// The state of the Dioxus integration with the RealDom
#[derive(Debug)]
pub struct DioxusState {
/// Store of templates keyed by unique name
templates: FxHashMap<Template, Vec<NodeId>>,
/// Stack machine state for applying dioxus mutations
stack: Vec<NodeId>,
/// Mapping from vdom ElementId -> rdom NodeId
node_id_mapping: Vec<Option<NodeId>>,
}
impl DioxusState {
/// Initialize the DioxusState in the RealDom
pub fn create(root_id: usize) -> Self {
Self {
templates: FxHashMap::default(),
stack: vec![root_id],
node_id_mapping: vec![Some(root_id)],
}
}
/// Convert an ElementId to a NodeId
pub fn element_to_node_id(&self, element_id: ElementId) -> NodeId {
self.try_element_to_node_id(element_id).unwrap()
}
/// Attempt to convert an ElementId to a NodeId. This will return None if the ElementId is not in the RealDom.
pub fn try_element_to_node_id(&self, element_id: ElementId) -> Option<NodeId> {
self.node_id_mapping.get(element_id.0).copied().flatten()
}
pub(crate) fn anchor_and_nodes(&mut self, id: ElementId, m: usize) -> (usize, Vec<usize>) {
let anchor_node_id = self.element_to_node_id(id);
let new_nodes = self.m_stack_nodes(m);
(anchor_node_id, new_nodes)
}
pub(crate) fn m_stack_nodes(&mut self, m: usize) -> Vec<usize> {
self.stack.split_off(self.stack.len() - m)
}
}
/// A writer for mutations that can be used with the RealDom.
pub struct MutationWriter<'a> {
/// The realdom associated with this writer
pub docm: DocumentMutator<'a>,
/// The state associated with this writer
pub state: &'a mut DioxusState,
}
impl<'a> MutationWriter<'a> {
pub fn new(doc: &'a mut BaseDocument, state: &'a mut DioxusState) -> Self {
MutationWriter {
docm: doc.mutate(),
state,
}
}
}
impl MutationWriter<'_> {
/// Update an ElementId -> NodeId mapping
fn set_id_mapping(&mut self, node_id: NodeId, element_id: ElementId) {
let element_id: usize = element_id.0;
// Ensure node_id_mapping is large enough to contain element_id
if self.state.node_id_mapping.len() <= element_id {
self.state.node_id_mapping.resize(element_id + 1, None);
}
// Set the new mapping
self.state.node_id_mapping[element_id] = Some(node_id);
}
/// Create a ElementId -> NodeId mapping and push the node to the stack
fn map_new_node(&mut self, node_id: NodeId, element_id: ElementId) {
self.set_id_mapping(node_id, element_id);
self.state.stack.push(node_id);
}
/// Find a child in the document by child index path
fn load_child(&self, path: &[u8]) -> NodeId {
let top_of_stack_node_id = *self.state.stack.last().unwrap();
self.docm.node_at_path(top_of_stack_node_id, path)
}
}
impl WriteMutations for MutationWriter<'_> {
fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
trace!("assign_node_id path:{:?} id:{}", path, id.0);
// If there is an existing node already mapped to that ID and it has no parent, then drop it
// TODO: more automated GC/ref-counted semantics for node lifetimes
if let Some(node_id) = self.state.try_element_to_node_id(id) {
self.docm.remove_node_if_unparented(node_id);
}
// Map the node at specified path
self.set_id_mapping(self.load_child(path), id);
}
fn create_placeholder(&mut self, id: ElementId) {
trace!("create_placeholder id:{}", id.0);
let node_id = self.docm.create_comment_node();
self.map_new_node(node_id, id);
}
fn create_text_node(&mut self, value: &str, id: ElementId) {
trace!("create_text_node id:{} text:{}", id.0, value);
let node_id = self.docm.create_text_node(value);
self.map_new_node(node_id, id);
}
fn append_children(&mut self, id: ElementId, m: usize) {
trace!("append_children id:{} m:{}", id.0, m);
let (parent_id, child_node_ids) = self.state.anchor_and_nodes(id, m);
self.docm.append_children(parent_id, &child_node_ids);
}
fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
trace!("insert_nodes_after id:{} m:{}", id.0, m);
let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
self.docm.insert_nodes_after(anchor_node_id, &new_node_ids);
}
fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
trace!("insert_nodes_before id:{} m:{}", id.0, m);
let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
self.docm.insert_nodes_before(anchor_node_id, &new_node_ids);
}
fn replace_node_with(&mut self, id: ElementId, m: usize) {
trace!("replace_node_with id:{} m:{}", id.0, m);
let (anchor_node_id, new_node_ids) = self.state.anchor_and_nodes(id, m);
self.docm.replace_node_with(anchor_node_id, &new_node_ids);
}
fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
trace!("replace_placeholder_with_nodes path:{:?} m:{}", path, m);
// WARNING: DO NOT REORDER
// The order of the following two lines is very important as "m_stack_nodes" mutates
// the stack and then "load_child" reads from the top of the stack.
let new_node_ids = self.state.m_stack_nodes(m);
let anchor_node_id = self.load_child(path);
self.docm.replace_node_with(anchor_node_id, &new_node_ids);
}
fn remove_node(&mut self, id: ElementId) {
trace!("remove_node id:{}", id.0);
let node_id = self.state.element_to_node_id(id);
self.docm.remove_node(node_id);
}
fn push_root(&mut self, id: ElementId) {
trace!("push_root id:{}", id.0);
let node_id = self.state.element_to_node_id(id);
self.state.stack.push(node_id);
}
fn set_node_text(&mut self, value: &str, id: ElementId) {
trace!("set_node_text id:{} value:{}", id.0, value);
let node_id = self.state.element_to_node_id(id);
self.docm.set_node_text(node_id, value);
}
fn set_attribute(
&mut self,
local_name: &'static str,
ns: Option<&'static str>,
value: &AttributeValue,
id: ElementId,
) {
let node_id = self.state.element_to_node_id(id);
trace!("set_attribute node_id:{node_id} ns: {ns:?} name:{local_name}, value:{value:?}");
// Dioxus has overloaded the style namespace to accumulate style attributes without a `style` block
// TODO: accumulate style attributes into a single style element.
if ns == Some("style") {
return;
}
fn is_falsy(val: &AttributeValue) -> bool {
match val {
AttributeValue::None => true,
AttributeValue::Text(val) => val == "false",
AttributeValue::Bool(val) => !val,
AttributeValue::Int(val) => *val == 0,
AttributeValue::Float(val) => *val == 0.0,
_ => false,
}
}
let name = qual_name(local_name, ns);
// FIXME: more principled handling of special case attributes
if value == &AttributeValue::None || (local_name == "checked" && is_falsy(value)) {
self.docm.clear_attribute(node_id, name);
} else {
match value {
AttributeValue::Text(value) => self.docm.set_attribute(node_id, name, value),
AttributeValue::Float(value) => {
let value = value.to_string();
self.docm.set_attribute(node_id, name, &value);
}
AttributeValue::Int(value) => {
let value = value.to_string();
self.docm.set_attribute(node_id, name, &value);
}
AttributeValue::Bool(value) => {
let value = value.to_string();
self.docm.set_attribute(node_id, name, &value);
}
_ => {
// FIXME: support all attribute types
}
};
}
}
fn load_template(&mut self, template: Template, index: usize, id: ElementId) {
// TODO: proper template node support
let template_entry = self.state.templates.entry(template).or_insert_with(|| {
let template_root_ids: Vec<NodeId> = template
.roots
.iter()
.map(|root| create_template_node(&mut self.docm, root))
.collect();
template_root_ids
});
let template_node_id = template_entry[index];
let clone_id = self.docm.deep_clone_node(template_node_id);
trace!("load_template template_node_id:{template_node_id} clone_id:{clone_id}");
self.map_new_node(clone_id, id);
}
fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
// We're going to actually set the listener here as a placeholder - in JS this would also be a placeholder
// we might actually just want to attach the attribute to the root element (delegation)
let value = AttributeValue::Text("<rust func>".into());
self.set_attribute(name, None, &value, id);
// Also set the data-dioxus-id attribute so we can find the element later
let value = AttributeValue::Text(id.0.to_string());
self.set_attribute("data-dioxus-id", None, &value, id);
// node.add_event_listener(name);
}
fn remove_event_listener(&mut self, _name: &'static str, _id: ElementId) {
// node.remove_event_listener(name);
}
}
fn create_template_node(docm: &mut DocumentMutator<'_>, node: &TemplateNode) -> NodeId {
match node {
TemplateNode::Element {
tag,
namespace,
attrs,
children,
} => {
let name = qual_name(tag, *namespace);
let attrs = attrs.iter().filter_map(map_template_attr).collect();
let node_id = docm.create_element(name, attrs);
let child_ids: Vec<NodeId> = children
.iter()
.map(|child| create_template_node(docm, child))
.collect();
docm.append_children(node_id, &child_ids);
node_id
}
TemplateNode::Text { text } => docm.create_text_node(text),
TemplateNode::Dynamic { .. } => docm.create_comment_node(),
}
}
fn map_template_attr(attr: &TemplateAttribute) -> Option<Attribute> {
let TemplateAttribute::Static {
name,
value,
namespace,
} = attr
else {
return None;
};
let name = qual_name(name, *namespace);
let value = value.to_string();
Some(Attribute { name, value })
}