Merge branch 'master' into pr/atty303/1349
This commit is contained in:
commit
e5df83ceec
278 files changed
+11299
-3674
No files matched your search
@@ -190,7 +190,7 @@ pub static BUILTIN_INTERNED_STRINGS: &[&str] = &[
|
||||
"oncopy",
|
||||
"oncuechange",
|
||||
"oncut",
|
||||
"ondblclick",
|
||||
"ondoubleclick",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
|
||||
+45
-187
@@ -1,33 +1,32 @@
|
||||
//! Implementation of a renderer for Dioxus on the web.
|
||||
//!
|
||||
//! Oustanding todos:
|
||||
//! - Removing event listeners (delegation)
|
||||
//! Outstanding todos:
|
||||
//! - Passive event listeners
|
||||
//! - no-op event listener patch for safari
|
||||
//! - tests to ensure dyn_into works for various event types.
|
||||
//! - Partial delegation?>
|
||||
//! - Partial delegation?
|
||||
|
||||
use dioxus_core::{
|
||||
BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
|
||||
};
|
||||
use dioxus_html::{event_bubbles, CompositionData, FormData, MountedData};
|
||||
use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel};
|
||||
use dioxus_html::{event_bubbles, MountedData, PlatformEventData};
|
||||
use dioxus_interpreter_js::get_node;
|
||||
use dioxus_interpreter_js::{minimal_bindings, save_template, Channel};
|
||||
use futures_channel::mpsc;
|
||||
use js_sys::Array;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{any::Any, rc::Rc};
|
||||
use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue};
|
||||
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
||||
use web_sys::{Document, Element, Event};
|
||||
|
||||
use crate::Config;
|
||||
use crate::{load_document, virtual_event_from_websys_event, Config, WebEventConverter};
|
||||
|
||||
pub struct WebsysDom {
|
||||
document: Document,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) root: Element,
|
||||
templates: FxHashMap<String, u32>,
|
||||
max_template_id: u32,
|
||||
templates: FxHashMap<String, u16>,
|
||||
max_template_id: u16,
|
||||
pub(crate) interpreter: Channel,
|
||||
#[cfg(feature = "mounted")]
|
||||
event_channel: mpsc::UnboundedSender<UiEvent>,
|
||||
}
|
||||
|
||||
@@ -35,7 +34,7 @@ pub struct UiEvent {
|
||||
pub name: String,
|
||||
pub bubbles: bool,
|
||||
pub element: ElementId,
|
||||
pub data: Rc<dyn Any>,
|
||||
pub data: PlatformEventData,
|
||||
}
|
||||
|
||||
impl WebsysDom {
|
||||
@@ -45,7 +44,16 @@ impl WebsysDom {
|
||||
let document = load_document();
|
||||
let root = match document.get_element_by_id(&cfg.rootname) {
|
||||
Some(root) => root,
|
||||
None => document.create_element("body").ok().unwrap(),
|
||||
None => {
|
||||
web_sys::console::error_1(
|
||||
&format!(
|
||||
"element '#{}' not found. mounting to the body.",
|
||||
cfg.rootname
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
document.create_element("body").ok().unwrap()
|
||||
}
|
||||
};
|
||||
let interpreter = Channel::default();
|
||||
|
||||
@@ -90,10 +98,11 @@ impl WebsysDom {
|
||||
}
|
||||
}));
|
||||
|
||||
dioxus_interpreter_js::initilize(
|
||||
dioxus_interpreter_js::initialize(
|
||||
root.clone().unchecked_into(),
|
||||
handler.as_ref().unchecked_ref(),
|
||||
);
|
||||
dioxus_html::set_event_converter(Box::new(WebEventConverter));
|
||||
handler.forget();
|
||||
Self {
|
||||
document,
|
||||
@@ -101,6 +110,7 @@ impl WebsysDom {
|
||||
interpreter,
|
||||
templates: FxHashMap::default(),
|
||||
max_template_id: 0,
|
||||
#[cfg(feature = "mounted")]
|
||||
event_channel,
|
||||
}
|
||||
}
|
||||
@@ -171,11 +181,12 @@ impl WebsysDom {
|
||||
pub fn apply_edits(&mut self, mut edits: Vec<Mutation>) {
|
||||
use Mutation::*;
|
||||
let i = &mut self.interpreter;
|
||||
#[cfg(feature = "mounted")]
|
||||
// we need to apply the mount events last, so we collect them here
|
||||
let mut to_mount = Vec::new();
|
||||
for edit in &edits {
|
||||
match edit {
|
||||
AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u32),
|
||||
AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u16),
|
||||
AssignId { path, id } => {
|
||||
i.assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32)
|
||||
}
|
||||
@@ -186,15 +197,15 @@ impl WebsysDom {
|
||||
}
|
||||
LoadTemplate { name, index, id } => {
|
||||
if let Some(tmpl_id) = self.templates.get(*name) {
|
||||
i.load_template(*tmpl_id, *index as u32, id.0 as u32)
|
||||
i.load_template(*tmpl_id, *index as u16, id.0 as u32)
|
||||
}
|
||||
}
|
||||
ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u32),
|
||||
ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u16),
|
||||
ReplacePlaceholder { path, m } => {
|
||||
i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u32)
|
||||
i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u16)
|
||||
}
|
||||
InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u32),
|
||||
InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u32),
|
||||
InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u16),
|
||||
InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u16),
|
||||
SetAttribute {
|
||||
name,
|
||||
value,
|
||||
@@ -226,6 +237,7 @@ impl WebsysDom {
|
||||
match *name {
|
||||
// mounted events are fired immediately after the element is mounted.
|
||||
"mounted" => {
|
||||
#[cfg(feature = "mounted")]
|
||||
to_mount.push(*id);
|
||||
}
|
||||
_ => {
|
||||
@@ -246,179 +258,25 @@ impl WebsysDom {
|
||||
edits.clear();
|
||||
i.flush();
|
||||
|
||||
#[cfg(feature = "mounted")]
|
||||
for id in to_mount {
|
||||
let node = get_node(id.0 as u32);
|
||||
if let Some(element) = node.dyn_ref::<Element>() {
|
||||
let data: MountedData = element.into();
|
||||
let data = Rc::new(data);
|
||||
let _ = self.event_channel.unbounded_send(UiEvent {
|
||||
name: "mounted".to_string(),
|
||||
bubbles: false,
|
||||
element: id,
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: some of these events are being casted to the wrong event type.
|
||||
// We need tests that simulate clicks/etc and make sure every event type works.
|
||||
pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) -> Rc<dyn Any> {
|
||||
use dioxus_html::events::*;
|
||||
|
||||
match event.type_().as_str() {
|
||||
"copy" | "cut" | "paste" => Rc::new(ClipboardData {}),
|
||||
"compositionend" | "compositionstart" | "compositionupdate" => {
|
||||
make_composition_event(&event)
|
||||
}
|
||||
"keydown" | "keypress" | "keyup" => Rc::new(KeyboardData::from(event)),
|
||||
"focus" | "blur" | "focusout" | "focusin" => Rc::new(FocusData {}),
|
||||
|
||||
"change" | "input" | "invalid" | "reset" | "submit" => read_input_to_data(target),
|
||||
|
||||
"click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter"
|
||||
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||
Rc::new(MouseData::from(event))
|
||||
}
|
||||
"drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart"
|
||||
| "drop" => {
|
||||
let mouse = MouseData::from(event);
|
||||
Rc::new(DragData { mouse })
|
||||
}
|
||||
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
|
||||
Rc::new(PointerData::from(event))
|
||||
}
|
||||
"select" => Rc::new(SelectionData {}),
|
||||
"touchcancel" | "touchend" | "touchmove" | "touchstart" => Rc::new(TouchData::from(event)),
|
||||
|
||||
"scroll" => Rc::new(ScrollData {}),
|
||||
"wheel" => Rc::new(WheelData::from(event)),
|
||||
"animationstart" | "animationend" | "animationiteration" => {
|
||||
Rc::new(AnimationData::from(event))
|
||||
}
|
||||
"transitionend" => Rc::new(TransitionData::from(event)),
|
||||
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
|
||||
| "ended" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
|
||||
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||
| "timeupdate" | "volumechange" | "waiting" => Rc::new(MediaData {}),
|
||||
"error" => Rc::new(ImageData { load_error: true }),
|
||||
"load" => Rc::new(ImageData { load_error: false }),
|
||||
"toggle" => Rc::new(ToggleData {}),
|
||||
|
||||
_ => Rc::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_composition_event(event: &Event) -> Rc<CompositionData> {
|
||||
let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
|
||||
Rc::new(CompositionData {
|
||||
data: evt.data().unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn load_document() -> Document {
|
||||
web_sys::window()
|
||||
.expect("should have access to the Window")
|
||||
.document()
|
||||
.expect("should have access to the Document")
|
||||
}
|
||||
|
||||
fn read_input_to_data(target: Element) -> Rc<FormData> {
|
||||
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
|
||||
// don't have a good solution with the serialized event problem
|
||||
|
||||
let value: String = target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlInputElement| {
|
||||
// todo: special case more input types
|
||||
match input.type_().as_str() {
|
||||
"checkbox" => {
|
||||
match input.checked() {
|
||||
true => "true".to_string(),
|
||||
false => "false".to_string(),
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
input.value()
|
||||
}
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
|
||||
})
|
||||
// select elements are NOT input events - because - why woudn't they be??
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlSelectElement| input.value())
|
||||
})
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref::<web_sys::HtmlElement>()
|
||||
.unwrap()
|
||||
.text_content()
|
||||
})
|
||||
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
|
||||
|
||||
let mut values = std::collections::HashMap::new();
|
||||
|
||||
// try to fill in form values
|
||||
if let Some(form) = target.dyn_ref::<web_sys::HtmlFormElement>() {
|
||||
let form_data = get_form_data(form);
|
||||
for value in form_data.entries().into_iter().flatten() {
|
||||
if let Ok(array) = value.dyn_into::<Array>() {
|
||||
if let Some(name) = array.get(0).as_string() {
|
||||
if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
|
||||
let item_values =
|
||||
item_values.iter().filter_map(|v| v.as_string()).collect();
|
||||
|
||||
values.insert(name, item_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.send_mount_event(id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "file_engine"))]
|
||||
let files = None;
|
||||
#[cfg(feature = "file_engine")]
|
||||
let files = target
|
||||
.dyn_ref()
|
||||
.and_then(|input: &web_sys::HtmlInputElement| {
|
||||
input.files().and_then(|files| {
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
crate::file_engine::WebFileEngine::new(files)
|
||||
.map(|f| std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>)
|
||||
})
|
||||
});
|
||||
|
||||
Rc::new(FormData {
|
||||
value,
|
||||
values,
|
||||
files,
|
||||
})
|
||||
}
|
||||
|
||||
// web-sys does not expose the keys api for form data, so we need to manually bind to it
|
||||
#[wasm_bindgen(inline_js = r#"
|
||||
export function get_form_data(form) {
|
||||
let values = new Map();
|
||||
const formData = new FormData(form);
|
||||
|
||||
for (let name of formData.keys()) {
|
||||
values.set(name, formData.getAll(name));
|
||||
pub(crate) fn send_mount_event(&self, id: ElementId) {
|
||||
let node = get_node(id.0 as u32);
|
||||
if let Some(element) = node.dyn_ref::<Element>() {
|
||||
let data: MountedData = element.into();
|
||||
let data = Box::new(data);
|
||||
let _ = self.event_channel.unbounded_send(UiEvent {
|
||||
name: "mounted".to_string(),
|
||||
bubbles: false,
|
||||
element: id,
|
||||
data: PlatformEventData::new(data),
|
||||
});
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
"#)]
|
||||
extern "C" {
|
||||
fn get_form_data(form: &web_sys::HtmlFormElement) -> js_sys::Map;
|
||||
}
|
||||
|
||||
fn walk_event_for_id(event: &web_sys::Event) -> Option<(ElementId, web_sys::Element)> {
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
use std::{any::Any, collections::HashMap};
|
||||
|
||||
use dioxus_html::{
|
||||
FileEngine, FormData, HasFormData, HasImageData, HtmlEventConverter, ImageData, MountedData,
|
||||
PlatformEventData, ScrollData,
|
||||
};
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
|
||||
use web_sys::{Document, Element, Event};
|
||||
|
||||
pub(crate) struct WebEventConverter;
|
||||
|
||||
#[inline(always)]
|
||||
fn downcast_event(event: &dioxus_html::PlatformEventData) -> &GenericWebSysEvent {
|
||||
event
|
||||
.downcast::<GenericWebSysEvent>()
|
||||
.expect("event should be a GenericWebSysEvent")
|
||||
}
|
||||
|
||||
impl HtmlEventConverter for WebEventConverter {
|
||||
#[inline(always)]
|
||||
fn convert_animation_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::AnimationData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_clipboard_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::ClipboardData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_composition_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::CompositionData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_focus_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FocusData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_form_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::FormData {
|
||||
let event = downcast_event(event);
|
||||
FormData::new(WebFormData::new(event.element.clone(), event.raw.clone()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_image_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::ImageData {
|
||||
let event = downcast_event(event);
|
||||
let error = event.raw.type_() == "error";
|
||||
ImageData::new(WebImageEvent::new(event.raw.clone(), error))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_keyboard_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::KeyboardData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_media_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MediaData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[inline(always)]
|
||||
fn convert_mounted_data(&self, event: &dioxus_html::PlatformEventData) -> MountedData {
|
||||
#[cfg(feature = "mounted")]
|
||||
{
|
||||
MountedData::from(
|
||||
event
|
||||
.downcast::<web_sys::Element>()
|
||||
.expect("event should be a web_sys::Element"),
|
||||
)
|
||||
}
|
||||
#[cfg(not(feature = "mounted"))]
|
||||
{
|
||||
panic!("mounted events are not supported without the mounted feature on the dioxus-web crate enabled")
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_mouse_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::MouseData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_pointer_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::PointerData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_scroll_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::ScrollData {
|
||||
ScrollData::from(downcast_event(event).raw.clone())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_selection_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::SelectionData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_toggle_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::ToggleData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_touch_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::TouchData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_transition_data(
|
||||
&self,
|
||||
event: &dioxus_html::PlatformEventData,
|
||||
) -> dioxus_html::TransitionData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn convert_wheel_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::WheelData {
|
||||
downcast_event(event).raw.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A extension trait for web-sys events that provides a way to get the event as a web-sys event.
|
||||
pub trait WebEventExt<E> {
|
||||
/// Get the event as a web-sys event.
|
||||
fn web_event(&self) -> &E;
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::AnimationEvent> for dioxus_html::AnimationData {
|
||||
fn web_event(&self) -> &web_sys::AnimationEvent {
|
||||
self.downcast::<web_sys::AnimationEvent>()
|
||||
.expect("event should be a WebAnimationEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::Event> for dioxus_html::ClipboardData {
|
||||
fn web_event(&self) -> &web_sys::Event {
|
||||
self.downcast::<web_sys::Event>()
|
||||
.expect("event should be a web_sys::Event")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::CompositionEvent> for dioxus_html::CompositionData {
|
||||
fn web_event(&self) -> &web_sys::CompositionEvent {
|
||||
self.downcast::<web_sys::CompositionEvent>()
|
||||
.expect("event should be a WebCompositionEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::MouseEvent> for dioxus_html::DragData {
|
||||
fn web_event(&self) -> &web_sys::MouseEvent {
|
||||
self.downcast::<web_sys::MouseEvent>()
|
||||
.expect("event should be a WebMouseEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::FocusEvent> for dioxus_html::FocusData {
|
||||
fn web_event(&self) -> &web_sys::FocusEvent {
|
||||
self.downcast::<web_sys::FocusEvent>()
|
||||
.expect("event should be a WebFocusEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::Event> for dioxus_html::FormData {
|
||||
fn web_event(&self) -> &web_sys::Event {
|
||||
self.downcast::<web_sys::Event>()
|
||||
.expect("event should be a WebFormData")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<WebImageEvent> for dioxus_html::ImageData {
|
||||
fn web_event(&self) -> &WebImageEvent {
|
||||
self.downcast::<WebImageEvent>()
|
||||
.expect("event should be a WebImageEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::KeyboardEvent> for dioxus_html::KeyboardData {
|
||||
fn web_event(&self) -> &web_sys::KeyboardEvent {
|
||||
self.downcast::<web_sys::KeyboardEvent>()
|
||||
.expect("event should be a WebKeyboardEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::Event> for dioxus_html::MediaData {
|
||||
fn web_event(&self) -> &web_sys::Event {
|
||||
self.downcast::<web_sys::Event>()
|
||||
.expect("event should be a WebMediaEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::Element> for MountedData {
|
||||
fn web_event(&self) -> &web_sys::Element {
|
||||
self.downcast::<web_sys::Element>()
|
||||
.expect("event should be a web_sys::Element")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::MouseEvent> for dioxus_html::MouseData {
|
||||
fn web_event(&self) -> &web_sys::MouseEvent {
|
||||
self.downcast::<web_sys::MouseEvent>()
|
||||
.expect("event should be a WebMouseEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::PointerEvent> for dioxus_html::PointerData {
|
||||
fn web_event(&self) -> &web_sys::PointerEvent {
|
||||
self.downcast::<web_sys::PointerEvent>()
|
||||
.expect("event should be a WebPointerEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::Event> for ScrollData {
|
||||
fn web_event(&self) -> &web_sys::Event {
|
||||
self.downcast::<web_sys::Event>()
|
||||
.expect("event should be a WebScrollEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::Event> for dioxus_html::SelectionData {
|
||||
fn web_event(&self) -> &web_sys::Event {
|
||||
self.downcast::<web_sys::Event>()
|
||||
.expect("event should be a WebSelectionEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::Event> for dioxus_html::ToggleData {
|
||||
fn web_event(&self) -> &web_sys::Event {
|
||||
self.downcast::<web_sys::Event>()
|
||||
.expect("event should be a WebToggleEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::TouchEvent> for dioxus_html::TouchData {
|
||||
fn web_event(&self) -> &web_sys::TouchEvent {
|
||||
self.downcast::<web_sys::TouchEvent>()
|
||||
.expect("event should be a WebTouchEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::TransitionEvent> for dioxus_html::TransitionData {
|
||||
fn web_event(&self) -> &web_sys::TransitionEvent {
|
||||
self.downcast::<web_sys::TransitionEvent>()
|
||||
.expect("event should be a WebTransitionEvent")
|
||||
}
|
||||
}
|
||||
|
||||
impl WebEventExt<web_sys::WheelEvent> for dioxus_html::WheelData {
|
||||
fn web_event(&self) -> &web_sys::WheelEvent {
|
||||
self.downcast::<web_sys::WheelEvent>()
|
||||
.expect("event should be a WebWheelEvent")
|
||||
}
|
||||
}
|
||||
|
||||
struct GenericWebSysEvent {
|
||||
raw: Event,
|
||||
element: Element,
|
||||
}
|
||||
|
||||
// todo: some of these events are being casted to the wrong event type.
|
||||
// We need tests that simulate clicks/etc and make sure every event type works.
|
||||
pub(crate) fn virtual_event_from_websys_event(
|
||||
event: web_sys::Event,
|
||||
target: Element,
|
||||
) -> PlatformEventData {
|
||||
PlatformEventData::new(Box::new(GenericWebSysEvent {
|
||||
raw: event,
|
||||
element: target,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn load_document() -> Document {
|
||||
web_sys::window()
|
||||
.expect("should have access to the Window")
|
||||
.document()
|
||||
.expect("should have access to the Document")
|
||||
}
|
||||
|
||||
struct WebImageEvent {
|
||||
raw: Event,
|
||||
error: bool,
|
||||
}
|
||||
|
||||
impl WebImageEvent {
|
||||
fn new(raw: Event, error: bool) -> Self {
|
||||
Self { raw, error }
|
||||
}
|
||||
}
|
||||
|
||||
impl HasImageData for WebImageEvent {
|
||||
fn load_error(&self) -> bool {
|
||||
self.error
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
&self.raw as &dyn Any
|
||||
}
|
||||
}
|
||||
|
||||
struct WebFormData {
|
||||
element: Element,
|
||||
raw: Event,
|
||||
}
|
||||
|
||||
impl WebFormData {
|
||||
fn new(element: Element, raw: Event) -> Self {
|
||||
Self { element, raw }
|
||||
}
|
||||
}
|
||||
|
||||
impl HasFormData for WebFormData {
|
||||
fn value(&self) -> String {
|
||||
let target = &self.element;
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlInputElement| {
|
||||
// todo: special case more input types
|
||||
match input.type_().as_str() {
|
||||
"checkbox" => {
|
||||
match input.checked() {
|
||||
true => "true".to_string(),
|
||||
false => "false".to_string(),
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
input.value()
|
||||
}
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
|
||||
})
|
||||
// select elements are NOT input events - because - why woudn't they be??
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlSelectElement| input.value())
|
||||
})
|
||||
.or_else(|| {
|
||||
target
|
||||
.dyn_ref::<web_sys::HtmlElement>()
|
||||
.unwrap()
|
||||
.text_content()
|
||||
})
|
||||
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
|
||||
}
|
||||
|
||||
fn values(&self) -> HashMap<String, Vec<String>> {
|
||||
let mut values = std::collections::HashMap::new();
|
||||
|
||||
// try to fill in form values
|
||||
if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
|
||||
let form_data = get_form_data(form);
|
||||
for value in form_data.entries().into_iter().flatten() {
|
||||
if let Ok(array) = value.dyn_into::<Array>() {
|
||||
if let Some(name) = array.get(0).as_string() {
|
||||
if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
|
||||
let item_values =
|
||||
item_values.iter().filter_map(|v| v.as_string()).collect();
|
||||
|
||||
values.insert(name, item_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
values
|
||||
}
|
||||
|
||||
fn files(&self) -> Option<std::sync::Arc<dyn FileEngine>> {
|
||||
#[cfg(not(feature = "file_engine"))]
|
||||
let files = None;
|
||||
#[cfg(feature = "file_engine")]
|
||||
let files = self
|
||||
.element
|
||||
.dyn_ref()
|
||||
.and_then(|input: &web_sys::HtmlInputElement| {
|
||||
input.files().and_then(|files| {
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
crate::file_engine::WebFileEngine::new(files).map(|f| {
|
||||
std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
&self.raw as &dyn Any
|
||||
}
|
||||
}
|
||||
|
||||
// web-sys does not expose the keys api for form data, so we need to manually bind to it
|
||||
#[wasm_bindgen(inline_js = r#"
|
||||
export function get_form_data(form) {
|
||||
let values = new Map();
|
||||
const formData = new FormData(form);
|
||||
|
||||
for (let name of formData.keys()) {
|
||||
values.set(name, formData.getAll(name));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
"#)]
|
||||
extern "C" {
|
||||
fn get_form_data(form: &web_sys::HtmlFormElement) -> js_sys::Map;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ use futures_channel::mpsc::UnboundedReceiver;
|
||||
use dioxus_core::Template;
|
||||
|
||||
pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
|
||||
use std::convert::TryInto;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{MessageEvent, WebSocket};
|
||||
@@ -31,14 +30,12 @@ pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
|
||||
// change the rsx when new data is received
|
||||
let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
|
||||
if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
|
||||
let text: Result<String, _> = text.try_into();
|
||||
if let Ok(string) = text {
|
||||
let val = serde_json::from_str::<serde_json::Value>(&string).unwrap();
|
||||
// leak the value
|
||||
let val: &'static serde_json::Value = Box::leak(Box::new(val));
|
||||
let template: Template<'_> = Template::deserialize(val).unwrap();
|
||||
tx.unbounded_send(template).unwrap();
|
||||
}
|
||||
let string: String = text.into();
|
||||
let val = serde_json::from_str::<serde_json::Value>(&string).unwrap();
|
||||
// leak the value
|
||||
let val: &'static serde_json::Value = Box::leak(Box::new(val));
|
||||
let template: Template<'_> = Template::deserialize(val).unwrap();
|
||||
tx.unbounded_send(template).unwrap();
|
||||
}
|
||||
}) as Box<dyn FnMut(MessageEvent)>);
|
||||
|
||||
|
||||
+20
-15
@@ -55,7 +55,10 @@
|
||||
// - Do the VDOM work during the idlecallback
|
||||
// - Do DOM work in the next requestAnimationFrame callback
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
pub use crate::cfg::Config;
|
||||
#[cfg(feature = "file_engine")]
|
||||
pub use crate::file_engine::WebFileEngineExt;
|
||||
use dioxus_core::{Element, Scope, VirtualDom};
|
||||
use futures_util::{
|
||||
@@ -68,6 +71,8 @@ mod cfg;
|
||||
mod dom;
|
||||
#[cfg(feature = "eval")]
|
||||
mod eval;
|
||||
mod event;
|
||||
pub use event::*;
|
||||
#[cfg(feature = "file_engine")]
|
||||
mod file_engine;
|
||||
#[cfg(all(feature = "hot_reload", debug_assertions))]
|
||||
@@ -215,14 +220,16 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
||||
// todo: we need to split rebuild and initialize into two phases
|
||||
// it's a waste to produce edits just to get the vdom loaded
|
||||
|
||||
let templates = dom.rebuild().templates;
|
||||
websys_dom.load_templates(&templates);
|
||||
|
||||
{
|
||||
let mutations = dom.rebuild();
|
||||
web_sys::console::log_1(&format!("mutations: {:#?}", mutations).into());
|
||||
let templates = mutations.templates;
|
||||
websys_dom.load_templates(&templates);
|
||||
websys_dom.interpreter.flush();
|
||||
}
|
||||
if let Err(err) = websys_dom.rehydrate(&dom) {
|
||||
tracing::error!(
|
||||
"Rehydration failed {:?}. Rebuild DOM into element from scratch",
|
||||
&err
|
||||
);
|
||||
tracing::error!("Rehydration failed. {:?}", err);
|
||||
tracing::error!("Rebuild DOM into element from scratch");
|
||||
websys_dom.root.set_text_content(None);
|
||||
|
||||
let edits = dom.rebuild();
|
||||
@@ -251,13 +258,6 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
||||
pin_mut!(work);
|
||||
|
||||
#[cfg(all(feature = "hot_reload", debug_assertions))]
|
||||
// futures_util::select! {
|
||||
// _ = work => (None, None),
|
||||
// new_template = hotreload_rx.next() => {
|
||||
// (None, new_template)
|
||||
// }
|
||||
// evt = rx.next() =>
|
||||
// }
|
||||
match select(work, select(hotreload_rx.next(), rx.next())).await {
|
||||
Either::Left((_, _)) => (None, None),
|
||||
Either::Right((Either::Left((new_template, _)), _)) => (None, new_template),
|
||||
@@ -277,7 +277,12 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
||||
// Dequeue all of the events from the channel in send order
|
||||
// todo: we should re-order these if possible
|
||||
while let Some(evt) = res {
|
||||
dom.handle_event(evt.name.as_str(), evt.data, evt.element, evt.bubbles);
|
||||
dom.handle_event(
|
||||
evt.name.as_str(),
|
||||
Rc::new(evt.data),
|
||||
evt.element,
|
||||
evt.bubbles,
|
||||
);
|
||||
res = rx.try_next().transpose().unwrap().ok();
|
||||
}
|
||||
|
||||
|
||||
+39
-188
@@ -1,105 +1,62 @@
|
||||
use crate::dom::WebsysDom;
|
||||
use dioxus_core::{
|
||||
AttributeValue, DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom,
|
||||
};
|
||||
use dioxus_html::event_bubbles;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Comment, Node};
|
||||
use dioxus_core::{DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub enum RehydrationError {
|
||||
NodeTypeMismatch,
|
||||
NodeNotFound,
|
||||
VNodeNotInitialized,
|
||||
}
|
||||
use RehydrationError::*;
|
||||
|
||||
fn set_node(hydrated: &mut Vec<bool>, id: ElementId, node: Node) {
|
||||
let idx = id.0;
|
||||
if idx >= hydrated.len() {
|
||||
hydrated.resize(idx + 1, false);
|
||||
}
|
||||
if !hydrated[idx] {
|
||||
dioxus_interpreter_js::set_node(idx as u32, node);
|
||||
hydrated[idx] = true;
|
||||
}
|
||||
}
|
||||
use RehydrationError::*;
|
||||
|
||||
impl WebsysDom {
|
||||
// we're streaming in patches, but the nodes already exist
|
||||
// so we're just going to write the correct IDs to the node and load them in
|
||||
pub fn rehydrate(&mut self, dom: &VirtualDom) -> Result<(), RehydrationError> {
|
||||
let mut root = self
|
||||
.root
|
||||
.clone()
|
||||
.dyn_into::<Node>()
|
||||
.map_err(|_| NodeTypeMismatch)?
|
||||
.first_child()
|
||||
.ok_or(NodeNotFound);
|
||||
|
||||
let root_scope = dom.base_scope();
|
||||
|
||||
let mut hydrated = vec![true];
|
||||
|
||||
let mut last_node_was_static_text = false;
|
||||
let mut ids = Vec::new();
|
||||
let mut to_mount = Vec::new();
|
||||
|
||||
// Recursively rehydrate the dom from the VirtualDom
|
||||
self.rehydrate_scope(
|
||||
root_scope,
|
||||
&mut root,
|
||||
&mut hydrated,
|
||||
dom,
|
||||
&mut last_node_was_static_text,
|
||||
)?;
|
||||
self.rehydrate_scope(root_scope, dom, &mut ids, &mut to_mount)?;
|
||||
|
||||
dioxus_interpreter_js::hydrate(ids);
|
||||
|
||||
for id in to_mount {
|
||||
self.send_mount_event(id);
|
||||
}
|
||||
|
||||
self.interpreter.flush();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rehydrate_scope(
|
||||
&mut self,
|
||||
scope: &ScopeState,
|
||||
current_child: &mut Result<Node, RehydrationError>,
|
||||
hydrated: &mut Vec<bool>,
|
||||
dom: &VirtualDom,
|
||||
last_node_was_static_text: &mut bool,
|
||||
ids: &mut Vec<u32>,
|
||||
to_mount: &mut Vec<ElementId>,
|
||||
) -> Result<(), RehydrationError> {
|
||||
let vnode = match scope.root_node() {
|
||||
dioxus_core::RenderReturn::Ready(ready) => ready,
|
||||
_ => return Err(VNodeNotInitialized),
|
||||
};
|
||||
self.rehydrate_vnode(
|
||||
current_child,
|
||||
hydrated,
|
||||
dom,
|
||||
vnode,
|
||||
last_node_was_static_text,
|
||||
)
|
||||
self.rehydrate_vnode(dom, vnode, ids, to_mount)
|
||||
}
|
||||
|
||||
fn rehydrate_vnode(
|
||||
&mut self,
|
||||
current_child: &mut Result<Node, RehydrationError>,
|
||||
hydrated: &mut Vec<bool>,
|
||||
dom: &VirtualDom,
|
||||
vnode: &VNode,
|
||||
last_node_was_static_text: &mut bool,
|
||||
ids: &mut Vec<u32>,
|
||||
to_mount: &mut Vec<ElementId>,
|
||||
) -> Result<(), RehydrationError> {
|
||||
for (i, root) in vnode.template.get().roots.iter().enumerate() {
|
||||
// make sure we set the root node ids even if the node is not dynamic
|
||||
set_node(
|
||||
hydrated,
|
||||
*vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?,
|
||||
current_child.clone()?,
|
||||
);
|
||||
|
||||
self.rehydrate_template_node(
|
||||
current_child,
|
||||
hydrated,
|
||||
dom,
|
||||
vnode,
|
||||
root,
|
||||
last_node_was_static_text,
|
||||
ids,
|
||||
to_mount,
|
||||
Some(*vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -107,24 +64,19 @@ impl WebsysDom {
|
||||
|
||||
fn rehydrate_template_node(
|
||||
&mut self,
|
||||
current_child: &mut Result<Node, RehydrationError>,
|
||||
hydrated: &mut Vec<bool>,
|
||||
dom: &VirtualDom,
|
||||
vnode: &VNode,
|
||||
node: &TemplateNode,
|
||||
last_node_was_static_text: &mut bool,
|
||||
ids: &mut Vec<u32>,
|
||||
to_mount: &mut Vec<ElementId>,
|
||||
root_id: Option<ElementId>,
|
||||
) -> Result<(), RehydrationError> {
|
||||
tracing::trace!("rehydrate template node: {:?}", node);
|
||||
if let Ok(current_child) = current_child {
|
||||
if tracing::event_enabled!(tracing::Level::TRACE) {
|
||||
web_sys::console::log_1(¤t_child.clone().into());
|
||||
}
|
||||
}
|
||||
match node {
|
||||
TemplateNode::Element {
|
||||
children, attrs, ..
|
||||
} => {
|
||||
let mut mounted_id = None;
|
||||
let mut mounted_id = root_id;
|
||||
for attr in *attrs {
|
||||
if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {
|
||||
let attribute = &vnode.dynamic_attrs[*id];
|
||||
@@ -132,159 +84,58 @@ impl WebsysDom {
|
||||
attribute.attribute_type().for_each(|attribute| {
|
||||
let value = &attribute.value;
|
||||
mounted_id = Some(id);
|
||||
let name = attribute.name;
|
||||
if let AttributeValue::Listener(_) = value {
|
||||
let event_name = &name[2..];
|
||||
self.interpreter.new_event_listener(
|
||||
event_name,
|
||||
id.0 as u32,
|
||||
event_bubbles(event_name) as u8,
|
||||
);
|
||||
if attribute.name == "onmounted" {
|
||||
to_mount.push(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(id) = mounted_id {
|
||||
set_node(hydrated, id, current_child.clone()?);
|
||||
ids.push(id.0 as u32);
|
||||
}
|
||||
if !children.is_empty() {
|
||||
let mut children_current_child = current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.first_child()
|
||||
.ok_or(NodeNotFound)?
|
||||
.dyn_into::<Node>()
|
||||
.map_err(|_| NodeTypeMismatch);
|
||||
for child in *children {
|
||||
self.rehydrate_template_node(
|
||||
&mut children_current_child,
|
||||
hydrated,
|
||||
dom,
|
||||
vnode,
|
||||
child,
|
||||
last_node_was_static_text,
|
||||
)?;
|
||||
self.rehydrate_template_node(dom, vnode, child, ids, to_mount, None)?;
|
||||
}
|
||||
}
|
||||
*current_child = current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.next_sibling()
|
||||
.ok_or(NodeNotFound);
|
||||
*last_node_was_static_text = false;
|
||||
}
|
||||
TemplateNode::Text { .. } => {
|
||||
// if the last node was static text, it got merged with this one
|
||||
if !*last_node_was_static_text {
|
||||
*current_child = current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.next_sibling()
|
||||
.ok_or(NodeNotFound);
|
||||
}
|
||||
*last_node_was_static_text = true;
|
||||
}
|
||||
TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
|
||||
self.rehydrate_dynamic_node(
|
||||
current_child,
|
||||
hydrated,
|
||||
dom,
|
||||
&vnode.dynamic_nodes[*id],
|
||||
last_node_was_static_text,
|
||||
)?;
|
||||
self.rehydrate_dynamic_node(dom, &vnode.dynamic_nodes[*id], ids, to_mount)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rehydrate_dynamic_node(
|
||||
&mut self,
|
||||
current_child: &mut Result<Node, RehydrationError>,
|
||||
hydrated: &mut Vec<bool>,
|
||||
dom: &VirtualDom,
|
||||
dynamic: &DynamicNode,
|
||||
last_node_was_static_text: &mut bool,
|
||||
ids: &mut Vec<u32>,
|
||||
to_mount: &mut Vec<ElementId>,
|
||||
) -> Result<(), RehydrationError> {
|
||||
tracing::trace!("rehydrate dynamic node: {:?}", dynamic);
|
||||
if let Ok(current_child) = current_child {
|
||||
if tracing::event_enabled!(tracing::Level::TRACE) {
|
||||
web_sys::console::log_1(¤t_child.clone().into());
|
||||
}
|
||||
}
|
||||
match dynamic {
|
||||
dioxus_core::DynamicNode::Text(text) => {
|
||||
let id = text.mounted_element();
|
||||
// skip comment separator before node
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.has_type::<Comment>());
|
||||
}
|
||||
*current_child = current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.next_sibling()
|
||||
.ok_or(NodeNotFound);
|
||||
|
||||
set_node(
|
||||
hydrated,
|
||||
id.ok_or(VNodeNotInitialized)?,
|
||||
current_child.clone()?,
|
||||
);
|
||||
*current_child = current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.next_sibling()
|
||||
.ok_or(NodeNotFound);
|
||||
|
||||
// skip comment separator after node
|
||||
if cfg!(debug_assertions) {
|
||||
assert!(current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.has_type::<Comment>());
|
||||
}
|
||||
*current_child = current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.next_sibling()
|
||||
.ok_or(NodeNotFound);
|
||||
|
||||
*last_node_was_static_text = false;
|
||||
ids.push(text.mounted_element().ok_or(VNodeNotInitialized)?.0 as u32);
|
||||
}
|
||||
dioxus_core::DynamicNode::Placeholder(placeholder) => {
|
||||
set_node(
|
||||
hydrated,
|
||||
placeholder.mounted_element().ok_or(VNodeNotInitialized)?,
|
||||
current_child.clone()?,
|
||||
);
|
||||
*current_child = current_child
|
||||
.as_mut()
|
||||
.map_err(|e| *e)?
|
||||
.next_sibling()
|
||||
.ok_or(NodeNotFound);
|
||||
*last_node_was_static_text = false;
|
||||
ids.push(placeholder.mounted_element().ok_or(VNodeNotInitialized)?.0 as u32);
|
||||
}
|
||||
dioxus_core::DynamicNode::Component(comp) => {
|
||||
let scope = comp.mounted_scope().ok_or(VNodeNotInitialized)?;
|
||||
self.rehydrate_scope(
|
||||
dom.get_scope(scope).unwrap(),
|
||||
current_child,
|
||||
hydrated,
|
||||
dom.get_scope(scope).ok_or(VNodeNotInitialized)?,
|
||||
dom,
|
||||
last_node_was_static_text,
|
||||
ids,
|
||||
to_mount,
|
||||
)?;
|
||||
}
|
||||
dioxus_core::DynamicNode::Fragment(fragment) => {
|
||||
for vnode in *fragment {
|
||||
self.rehydrate_vnode(
|
||||
current_child,
|
||||
hydrated,
|
||||
dom,
|
||||
vnode,
|
||||
last_node_was_static_text,
|
||||
)?;
|
||||
self.rehydrate_vnode(dom, vnode, ids, to_mount)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user