wip: overhaul event system

This commit is contained in:
Jonathan Kelley committed 2021-10-04 01:28:04 -04:00
1 parent 82953f2ac3
commit 7a03c1d2b4
34 files changed
+2488 -1468

No files matched your search

+178 -57
View File
@@ -8,7 +8,7 @@
//! - Partial delegation?>
use dioxus_core::{
events::{SyntheticEvent, UserEvent},
events::{DioxusEvent, KeyCode, SyntheticEvent, UserEvent},
mutations::NodeRefMutation,
scheduler::SchedulerMsg,
DomEdit, ElementId, ScopeId,
@@ -110,7 +110,6 @@ impl WebsysDom {
DomEdit::AppendChildren { many } => self.append_children(many),
DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
DomEdit::Remove { root } => self.remove(root),
DomEdit::RemoveAllChildren => self.remove_all_children(),
DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id),
DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id),
DomEdit::CreateElementNs { tag, id, ns } => self.create_element(tag, Some(ns), id),
@@ -208,10 +207,6 @@ impl WebsysDom {
}
}
fn remove_all_children(&mut self) {
todo!()
}
fn create_placeholder(&mut self, id: u64) {
self.create_element("pre", None, id);
self.set_attribute("hidden", "", None);
@@ -464,87 +459,213 @@ impl Stack {
}
}
pub struct DioxusWebsysEvent(web_sys::Event);
unsafe impl Send for DioxusWebsysEvent {}
unsafe impl Sync for DioxusWebsysEvent {}
// trait MyTrait {}
// impl MyTrait for web_sys::Event {}
// 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.
fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent {
use crate::events::*;
use dioxus_core::events::on::*;
match event.type_().as_str() {
"copy" | "cut" | "paste" => {
SyntheticEvent::ClipboardEvent(ClipboardEvent(Arc::new(WebsysClipboardEvent(event))))
}
"copy" | "cut" | "paste" => SyntheticEvent::ClipboardEvent(ClipboardEvent(
DioxusEvent::new(ClipboardEventInner(), DioxusWebsysEvent(event)),
)),
"compositionend" | "compositionstart" | "compositionupdate" => {
let evt: web_sys::CompositionEvent = event.dyn_into().unwrap();
SyntheticEvent::CompositionEvent(CompositionEvent(Arc::new(WebsysCompositionEvent(
evt,
))))
let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
SyntheticEvent::CompositionEvent(CompositionEvent(DioxusEvent::new(
CompositionEventInner {
data: evt.data().unwrap_or_default(),
},
DioxusWebsysEvent(event),
)))
}
"keydown" | "keypress" | "keyup" => {
let evt: web_sys::KeyboardEvent = event.dyn_into().unwrap();
SyntheticEvent::KeyboardEvent(KeyboardEvent(Arc::new(WebsysKeyboardEvent(evt))))
}
"focus" | "blur" => {
let evt: web_sys::FocusEvent = event.dyn_into().unwrap();
SyntheticEvent::FocusEvent(FocusEvent(Arc::new(WebsysFocusEvent(evt))))
}
"change" => {
let evt = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
SyntheticEvent::KeyboardEvent(KeyboardEvent(DioxusEvent::new(
KeyboardEventInner {
alt_key: evt.alt_key(),
char_code: evt.char_code(),
key: evt.key(),
key_code: KeyCode::from_raw_code(evt.key_code() as u8),
ctrl_key: evt.ctrl_key(),
locale: "not implemented".to_string(),
location: evt.location() as usize,
meta_key: evt.meta_key(),
repeat: evt.repeat(),
shift_key: evt.shift_key(),
which: evt.which() as usize,
},
DioxusWebsysEvent(event),
)))
}
"focus" | "blur" => SyntheticEvent::FocusEvent(FocusEvent(DioxusEvent::new(
FocusEventInner {},
DioxusWebsysEvent(event),
))),
"change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
// 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
"input" | "invalid" | "reset" | "submit" => {
let evt: web_sys::Event = event.dyn_into().unwrap();
SyntheticEvent::FormEvent(FormEvent(Arc::new(WebsysFormEvent(evt))))
let evt: &web_sys::Event = event.dyn_ref().unwrap();
let target: web_sys::EventTarget = evt.target().unwrap();
let value: String = (&target)
.dyn_ref()
.map(|input: &web_sys::HtmlInputElement| 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");
SyntheticEvent::FormEvent(FormEvent(DioxusEvent::new(
FormEventInner { value },
DioxusWebsysEvent(event),
)))
}
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
let evt: web_sys::MouseEvent = event.dyn_into().unwrap();
SyntheticEvent::MouseEvent(MouseEvent(Arc::new(WebsysMouseEvent(evt))))
let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
SyntheticEvent::MouseEvent(MouseEvent(DioxusEvent::new(
MouseEventInner {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
},
DioxusWebsysEvent(event),
)))
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
let evt: web_sys::PointerEvent = event.dyn_into().unwrap();
SyntheticEvent::PointerEvent(PointerEvent(Arc::new(WebsysPointerEvent(evt))))
}
"select" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::SelectionEvent(SelectionEvent(Arc::new(WebsysGenericUiEvent(evt))))
let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
SyntheticEvent::PointerEvent(PointerEvent(DioxusEvent::new(
PointerEventInner {
alt_key: evt.alt_key(),
button: evt.button(),
buttons: evt.buttons(),
client_x: evt.client_x(),
client_y: evt.client_y(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
page_x: evt.page_x(),
page_y: evt.page_y(),
screen_x: evt.screen_x(),
screen_y: evt.screen_y(),
shift_key: evt.shift_key(),
pointer_id: evt.pointer_id(),
width: evt.width(),
height: evt.height(),
pressure: evt.pressure(),
tangential_pressure: evt.tangential_pressure(),
tilt_x: evt.tilt_x(),
tilt_y: evt.tilt_y(),
twist: evt.twist(),
pointer_type: evt.pointer_type(),
is_primary: evt.is_primary(),
// get_modifier_state: evt.get_modifier_state(),
},
DioxusWebsysEvent(event),
)))
}
"select" => SyntheticEvent::SelectionEvent(SelectionEvent(DioxusEvent::new(
SelectionEventInner {},
DioxusWebsysEvent(event),
))),
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
let evt: web_sys::TouchEvent = event.dyn_into().unwrap();
SyntheticEvent::TouchEvent(TouchEvent(Arc::new(WebsysTouchEvent(evt))))
}
"scroll" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
SyntheticEvent::TouchEvent(TouchEvent(DioxusEvent::new(
TouchEventInner {
alt_key: evt.alt_key(),
ctrl_key: evt.ctrl_key(),
meta_key: evt.meta_key(),
shift_key: evt.shift_key(),
},
DioxusWebsysEvent(event),
)))
}
"scroll" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
"wheel" => {
let evt: web_sys::WheelEvent = event.dyn_into().unwrap();
SyntheticEvent::WheelEvent(WheelEvent(Arc::new(WebsysWheelEvent(evt))))
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
SyntheticEvent::WheelEvent(WheelEvent(DioxusEvent::new(
WheelEventInner {
delta_x: evt.delta_x(),
delta_y: evt.delta_y(),
delta_z: evt.delta_z(),
delta_mode: evt.delta_mode(),
},
DioxusWebsysEvent(event),
)))
}
"animationstart" | "animationend" | "animationiteration" => {
let evt: web_sys::AnimationEvent = event.dyn_into().unwrap();
SyntheticEvent::AnimationEvent(AnimationEvent(Arc::new(WebsysAnimationEvent(evt))))
let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
SyntheticEvent::AnimationEvent(AnimationEvent(DioxusEvent::new(
AnimationEventInner {
elapsed_time: evt.elapsed_time(),
animation_name: evt.animation_name(),
pseudo_element: evt.pseudo_element(),
},
DioxusWebsysEvent(event),
)))
}
"transitionend" => {
let evt: web_sys::TransitionEvent = event.dyn_into().unwrap();
SyntheticEvent::TransitionEvent(TransitionEvent(Arc::new(WebsysTransitionEvent(evt))))
let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
SyntheticEvent::TransitionEvent(TransitionEvent(DioxusEvent::new(
TransitionEventInner {
elapsed_time: evt.elapsed_time(),
property_name: evt.property_name(),
pseudo_element: evt.pseudo_element(),
},
DioxusWebsysEvent(event),
)))
}
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::MediaEvent(MediaEvent(Arc::new(WebsysMediaEvent(evt))))
}
"toggle" => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::ToggleEvent(ToggleEvent(Arc::new(WebsysToggleEvent(evt))))
}
_ => {
let evt: web_sys::UiEvent = event.dyn_into().unwrap();
SyntheticEvent::GenericEvent(GenericEvent(Arc::new(WebsysGenericUiEvent(evt))))
}
| "timeupdate" | "volumechange" | "waiting" => SyntheticEvent::MediaEvent(MediaEvent(
DioxusEvent::new(MediaEventInner {}, DioxusWebsysEvent(event)),
)),
"toggle" => SyntheticEvent::ToggleEvent(ToggleEvent(DioxusEvent::new(
ToggleEventInner {},
DioxusWebsysEvent(event),
))),
_ => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
}
}
-477
View File
@@ -3,480 +3,3 @@
use dioxus_core::events::on::*;
use wasm_bindgen::JsCast;
use web_sys::{Event, UiEvent};
/// All events implement the generic event type - they're all `UI events`
trait WebsysGenericEvent {
fn as_ui_event(&self) -> &UiEvent;
}
impl GenericEventInner for &dyn WebsysGenericEvent {
/// On WebSys, this returns an &UiEvent which can be casted via dyn_ref into the correct sub type.
fn raw_event(&self) -> &dyn std::any::Any {
self.as_ui_event()
}
fn bubbles(&self) -> bool {
self.as_ui_event().bubbles()
}
fn cancel_bubble(&self) {
self.as_ui_event().cancel_bubble();
}
fn cancelable(&self) -> bool {
self.as_ui_event().cancelable()
}
fn composed(&self) -> bool {
self.as_ui_event().composed()
}
fn current_target(&self) {
if cfg!(debug_assertions) {
todo!("Current target does not return anything useful.\nPlease try casting the event directly.");
}
// self.as_ui_event().current_target();
}
fn default_prevented(&self) -> bool {
self.as_ui_event().default_prevented()
}
fn event_phase(&self) -> u16 {
self.as_ui_event().event_phase()
}
fn is_trusted(&self) -> bool {
self.as_ui_event().is_trusted()
}
fn prevent_default(&self) {
self.as_ui_event().prevent_default()
}
fn stop_immediate_propagation(&self) {
self.as_ui_event().stop_immediate_propagation()
}
fn stop_propagation(&self) {
self.as_ui_event().stop_propagation()
}
fn target(&self) {
todo!()
}
fn time_stamp(&self) -> f64 {
self.as_ui_event().time_stamp()
}
}
macro_rules! implement_generic_event {
(
$($event:ident),*
) => {
$(
impl WebsysGenericEvent for $event {
fn as_ui_event(&self) -> &UiEvent {
self.0.dyn_ref().unwrap()
}
}
)*
};
}
implement_generic_event! {
WebsysClipboardEvent,
WebsysCompositionEvent,
WebsysKeyboardEvent,
WebsysGenericUiEvent,
WebsysFocusEvent,
WebsysFormEvent,
WebsysMouseEvent,
WebsysPointerEvent,
WebsysWheelEvent,
WebsysAnimationEvent,
WebsysTransitionEvent,
WebsysTouchEvent,
WebsysMediaEvent,
WebsysToggleEvent
}
// unfortunately, currently experimental, and web_sys needs to be configured to use it :>(
pub struct WebsysClipboardEvent(pub Event);
impl ClipboardEventInner for WebsysClipboardEvent {}
pub struct WebsysCompositionEvent(pub web_sys::CompositionEvent);
impl CompositionEventInner for WebsysCompositionEvent {
fn data(&self) -> String {
self.0.data().unwrap_or_else(|| String::new())
}
}
pub struct WebsysKeyboardEvent(pub web_sys::KeyboardEvent);
impl KeyboardEventInner for WebsysKeyboardEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
fn char_code(&self) -> u32 {
self.0.char_code()
}
fn key(&self) -> String {
self.0.key()
}
fn key_code(&self) -> KeyCode {
KeyCode::from_raw_code(self.0.key_code() as u8)
}
fn ctrl_key(&self) -> bool {
self.0.ctrl_key()
}
fn get_modifier_state(&self, key_code: &str) -> bool {
self.0.get_modifier_state(key_code)
}
fn locale(&self) -> String {
if cfg!(debug_assertions) {
todo!("Locale is currently not supported. :(")
} else {
String::from("en-US")
}
}
fn location(&self) -> usize {
self.0.location() as usize
}
fn meta_key(&self) -> bool {
self.0.meta_key()
}
fn repeat(&self) -> bool {
self.0.repeat()
}
fn shift_key(&self) -> bool {
self.0.shift_key()
}
fn which(&self) -> usize {
self.0.which() as usize
}
}
pub struct WebsysGenericUiEvent(pub UiEvent);
impl GenericEventInner for WebsysGenericUiEvent {
fn raw_event(&self) -> &dyn std::any::Any {
// self.0.raw_event()
todo!()
}
fn bubbles(&self) -> bool {
self.0.bubbles()
}
fn cancel_bubble(&self) {
self.0.cancel_bubble();
}
fn cancelable(&self) -> bool {
self.0.cancelable()
}
fn composed(&self) -> bool {
self.0.composed()
}
fn current_target(&self) {
// self.0.current_target()
}
fn default_prevented(&self) -> bool {
self.0.default_prevented()
}
fn event_phase(&self) -> u16 {
self.0.event_phase()
}
fn is_trusted(&self) -> bool {
self.0.is_trusted()
}
fn prevent_default(&self) {
self.0.prevent_default()
}
fn stop_immediate_propagation(&self) {
self.0.stop_immediate_propagation()
}
fn stop_propagation(&self) {
self.0.stop_propagation()
}
fn target(&self) {
// self.0.target()
}
fn time_stamp(&self) -> f64 {
self.0.time_stamp()
}
}
impl UIEventInner for WebsysGenericUiEvent {
fn detail(&self) -> i32 {
todo!()
}
}
impl SelectionEventInner for WebsysGenericUiEvent {}
pub struct WebsysFocusEvent(pub web_sys::FocusEvent);
impl FocusEventInner for WebsysFocusEvent {}
pub struct WebsysFormEvent(pub web_sys::Event);
impl FormEventInner for WebsysFormEvent {
// technically a controlled component, so we need to manually grab out the target data
fn value(&self) -> String {
let this: web_sys::EventTarget = self.0.target().unwrap();
(&this)
.dyn_ref()
.map(|input: &web_sys::HtmlInputElement| input.value())
.or_else(|| {
this
.dyn_ref()
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
})
// select elements are NOT input events - because - why woudn't they be??
.or_else(|| {
this
.dyn_ref()
.map(|input: &web_sys::HtmlSelectElement| input.value())
})
.or_else(|| {
this
.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")
}
}
pub struct WebsysMouseEvent(pub web_sys::MouseEvent);
impl MouseEventInner for WebsysMouseEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
fn button(&self) -> i16 {
self.0.button()
}
fn buttons(&self) -> u16 {
self.0.buttons()
}
fn client_x(&self) -> i32 {
self.0.client_x()
}
fn client_y(&self) -> i32 {
self.0.client_y()
}
fn ctrl_key(&self) -> bool {
self.0.ctrl_key()
}
fn meta_key(&self) -> bool {
self.0.meta_key()
}
fn page_x(&self) -> i32 {
self.0.page_x()
}
fn page_y(&self) -> i32 {
self.0.page_y()
}
fn screen_x(&self) -> i32 {
self.0.screen_x()
}
fn screen_y(&self) -> i32 {
self.0.screen_y()
}
fn shift_key(&self) -> bool {
self.0.shift_key()
}
// yikes
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
fn get_modifier_state(&self, key_code: &str) -> bool {
self.0.get_modifier_state(key_code)
}
}
pub struct WebsysPointerEvent(pub web_sys::PointerEvent);
impl PointerEventInner for WebsysPointerEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
fn button(&self) -> i16 {
self.0.button()
}
fn buttons(&self) -> u16 {
self.0.buttons()
}
fn client_x(&self) -> i32 {
self.0.client_x()
}
fn client_y(&self) -> i32 {
self.0.client_y()
}
fn ctrl_key(&self) -> bool {
self.0.ctrl_key()
}
fn meta_key(&self) -> bool {
self.0.meta_key()
}
fn page_x(&self) -> i32 {
self.0.page_x()
}
fn page_y(&self) -> i32 {
self.0.page_y()
}
fn screen_x(&self) -> i32 {
self.0.screen_x()
}
fn screen_y(&self) -> i32 {
self.0.screen_y()
}
fn shift_key(&self) -> bool {
self.0.shift_key()
}
// yikes
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
fn get_modifier_state(&self, key_code: &str) -> bool {
self.0.get_modifier_state(key_code)
}
fn pointer_id(&self) -> i32 {
self.0.pointer_id()
}
fn width(&self) -> i32 {
self.0.width()
}
fn height(&self) -> i32 {
self.0.height()
}
fn pressure(&self) -> f32 {
self.0.pressure()
}
fn tangential_pressure(&self) -> f32 {
self.0.tangential_pressure()
}
fn tilt_x(&self) -> i32 {
self.0.tilt_x()
}
fn tilt_y(&self) -> i32 {
self.0.tilt_y()
}
fn twist(&self) -> i32 {
self.0.twist()
}
fn pointer_type(&self) -> String {
self.0.pointer_type()
}
fn is_primary(&self) -> bool {
self.0.is_primary()
}
}
pub struct WebsysWheelEvent(pub web_sys::WheelEvent);
impl WheelEventInner for WebsysWheelEvent {
fn delta_mode(&self) -> u32 {
self.0.delta_mode()
}
fn delta_x(&self) -> f64 {
self.0.delta_x()
}
fn delta_y(&self) -> f64 {
self.0.delta_y()
}
fn delta_z(&self) -> f64 {
self.0.delta_z()
}
}
pub struct WebsysAnimationEvent(pub web_sys::AnimationEvent);
impl AnimationEventInner for WebsysAnimationEvent {
fn animation_name(&self) -> String {
self.0.animation_name()
}
fn pseudo_element(&self) -> String {
self.0.pseudo_element()
}
fn elapsed_time(&self) -> f32 {
self.0.elapsed_time()
}
}
pub struct WebsysTransitionEvent(pub web_sys::TransitionEvent);
impl TransitionEventInner for WebsysTransitionEvent {
fn property_name(&self) -> String {
self.0.property_name()
}
fn pseudo_element(&self) -> String {
self.0.pseudo_element()
}
fn elapsed_time(&self) -> f32 {
self.0.elapsed_time()
}
}
pub struct WebsysTouchEvent(pub web_sys::TouchEvent);
impl TouchEventInner for WebsysTouchEvent {
fn alt_key(&self) -> bool {
self.0.alt_key()
}
fn ctrl_key(&self) -> bool {
self.0.ctrl_key()
}
fn meta_key(&self) -> bool {
self.0.meta_key()
}
fn shift_key(&self) -> bool {
self.0.shift_key()
}
fn get_modifier_state(&self, key_code: &str) -> bool {
if cfg!(debug_assertions) {
todo!("get_modifier_state is not currently supported for touch events");
} else {
false
}
}
}
pub struct WebsysMediaEvent(pub web_sys::UiEvent);
impl MediaEventInner for WebsysMediaEvent {}
pub struct WebsysToggleEvent(pub web_sys::UiEvent);
impl ToggleEventInner for WebsysToggleEvent {}
+2 -2
View File
@@ -116,7 +116,7 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W
/// ```
pub fn launch_with_props<T, F>(root_component: FC<T>, root_properties: T, configuration_builder: F)
where
T: Properties + 'static,
T: Send + 'static,
F: FnOnce(WebConfig) -> WebConfig,
{
let config = configuration_builder(WebConfig::default());
@@ -135,7 +135,7 @@ where
/// wasm_bindgen_futures::spawn_local(app_fut);
/// }
/// ```
pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T, cfg: WebConfig) {
pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg: WebConfig) {
let mut dom = VirtualDom::new_with_props(root, root_props);
intern_cached_strings();