//! Web backend — DOM rendering in browsers. //! //! This backend formalizes what `runtime/src/renderer.js` does, as a Rust //! description of the DOM patching strategy. In a WASM build, this would call //! into the browser's DOM API directly via `web-sys`. use crate::{EventHandler, PlatformBackend, PlatformError, PlatformNode, PlatformResult}; /// Web DOM backend. /// /// Renders component trees to the browser DOM. In this Rust implementation, /// `render_to_string` produces an HTML string (matching what the JS renderer /// does for its initial hydration pass). A full WASM build would instead /// call `document.createElement()` etc. via `web-sys`. pub struct WebBackend; impl WebBackend { pub fn new() -> Self { Self } } impl Default for WebBackend { fn default() -> Self { Self::new() } } impl PlatformBackend for WebBackend { fn name(&self) -> &'static str { "web" } fn create_element(&self, tag: &str) -> PlatformResult { Ok(PlatformNode::element(tag)) } fn create_text(&self, content: &str) -> PlatformResult { Ok(PlatformNode::text(content)) } fn set_attribute( &self, node: &mut PlatformNode, name: &str, value: &str, ) -> PlatformResult<()> { // Remove existing attribute with same name then push new one. node.attributes.retain(|a| a.name != name); node.attributes.push(crate::Attribute::new(name, value)); Ok(()) } fn remove_attribute(&self, node: &mut PlatformNode, name: &str) -> PlatformResult<()> { node.attributes.retain(|a| a.name != name); Ok(()) } fn append_child( &self, parent: &mut PlatformNode, child: PlatformNode, ) -> PlatformResult<()> { parent.children.push(child); Ok(()) } fn remove_child(&self, parent: &mut PlatformNode, child_index: usize) -> PlatformResult<()> { if child_index >= parent.children.len() { return Err(PlatformError::Render(format!( "web: child index {} out of bounds (len {})", child_index, parent.children.len() ))); } parent.children.remove(child_index); Ok(()) } fn replace_child( &self, parent: &mut PlatformNode, index: usize, new_child: PlatformNode, ) -> PlatformResult<()> { if index >= parent.children.len() { return Err(PlatformError::Render(format!( "web: replace_child index {} out of bounds", index ))); } parent.children[index] = new_child; Ok(()) } fn bind_event( &self, node: &mut PlatformNode, event: &str, _handler: EventHandler, ) -> PlatformResult<()> { // In a real WASM build: node.add_event_listener_with_callback(event, &closure) // Here we record the binding in a data attribute so the JS renderer can pick it up. node.attributes .push(crate::Attribute::new(format!("data-el-{}", event), "[bound]")); Ok(()) } fn render_to_string(&self, node: &PlatformNode) -> PlatformResult { Ok(node.to_html()) } fn mount(&self, _root: PlatformNode, _container_id: &str) -> PlatformResult<()> { // In a real WASM build: // let container = document.query_selector(container_id)?; // container.set_inner_html(&root.to_html()); // For the Rust-side representation, mount is a no-op. Ok(()) } fn patch(&self, _old: &PlatformNode, _new: &PlatformNode) -> PlatformResult<()> { // Full DOM patching mirrors renderer.js patch(): // 1. Walk old and new trees in parallel. // 2. For each position: if node kinds differ, replace; else update attributes. // 3. Recurse into children. // In a WASM build this calls web-sys DOM mutation APIs. Ok(()) } fn supports_ssr(&self) -> bool { // Web backend can produce HTML strings for hydration. true } }