Synchronous prevent default (#2792)
* add prevent default methods to the event * sync prevent default almost working * sync prevent default working * Move event handling into the runtime * update core tests * restore desktop file dialog * implement prevent default on web * add a hint about the new prevent default method * fix web prevent default * Fix CTRL+click on links * fix values memorize in place test * Fix a few more tests * Add a playwright test for sync prevent default * Fix core doc tests * create a deprecated VirtualDom::handle_event * fix macos imports in desktop * Fix onmounted event * Fix liveview support * switch to RefCell for metadata * Remove println * remove prevent default attribute * remove web specific link behavior * Fix liveview links * more liveview fixes for link * Fix merge conflicts * Fix clippy * use the new prevent default in the file upload example
This commit is contained in:
1 parent
8b62b71e2d
commit
cab573eefd
54 files changed
+940
-729
No files matched your search
@@ -7,13 +7,10 @@ use crate::properties::RootProps;
|
||||
use crate::root_wrapper::RootScopeWrapper;
|
||||
use crate::{
|
||||
arena::ElementId,
|
||||
innerlude::{
|
||||
ElementRef, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VNodeMount, VProps,
|
||||
WriteMutations,
|
||||
},
|
||||
innerlude::{NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VProps, WriteMutations},
|
||||
runtime::{Runtime, RuntimeGuard},
|
||||
scopes::ScopeId,
|
||||
AttributeValue, ComponentFunction, Element, Event, Mutations,
|
||||
ComponentFunction, Element, Mutations,
|
||||
};
|
||||
use crate::{Task, VComponent};
|
||||
use futures_util::StreamExt;
|
||||
@@ -97,15 +94,16 @@ use tracing::instrument;
|
||||
/// let edits = vdom.rebuild_to_vec();
|
||||
/// ```
|
||||
///
|
||||
/// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
|
||||
/// To call listeners inside the VirtualDom, call [`Runtime::handle_event`] with the appropriate event data.
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// # fn app() -> Element { rsx! { div {} } }
|
||||
/// # let mut vdom = VirtualDom::new(app);
|
||||
/// let event = std::rc::Rc::new(0);
|
||||
/// vdom.handle_event("onclick", event, ElementId(0), true);
|
||||
/// # let runtime = vdom.runtime();
|
||||
/// let event = Event::new(std::rc::Rc::new(0) as std::rc::Rc<dyn std::any::Any>, true);
|
||||
/// runtime.handle_event("onclick", event, ElementId(0));
|
||||
/// ```
|
||||
///
|
||||
/// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
||||
@@ -174,7 +172,10 @@ use tracing::instrument;
|
||||
/// loop {
|
||||
/// tokio::select! {
|
||||
/// _ = dom.wait_for_work() => {}
|
||||
/// evt = real_dom.wait_for_event() => dom.handle_event("onclick", evt, ElementId(0), true),
|
||||
/// evt = real_dom.wait_for_event() => {
|
||||
/// let evt = dioxus_core::Event::new(evt, true);
|
||||
/// dom.runtime().handle_event("onclick", evt, ElementId(0))
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
/// dom.render_immediate(&mut real_dom.apply());
|
||||
@@ -206,15 +207,6 @@ pub struct VirtualDom {
|
||||
|
||||
pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
|
||||
|
||||
// The element ids that are used in the renderer
|
||||
// These mark a specific place in a whole rsx block
|
||||
pub(crate) elements: Slab<Option<ElementRef>>,
|
||||
|
||||
// Once nodes are mounted, the information about where they are mounted is stored here
|
||||
// We need to store this information on the virtual dom so that we know what nodes are mounted where when we bubble events
|
||||
// Each mount is associated with a whole rsx block. [`VirtualDom::elements`] link to a specific node in the block
|
||||
pub(crate) mounts: Slab<VNodeMount>,
|
||||
|
||||
pub(crate) runtime: Rc<Runtime>,
|
||||
|
||||
// The scopes that have been resolved since the last render
|
||||
@@ -328,8 +320,6 @@ impl VirtualDom {
|
||||
runtime: Runtime::new(tx),
|
||||
scopes: Default::default(),
|
||||
dirty_scopes: Default::default(),
|
||||
elements: Default::default(),
|
||||
mounts: Default::default(),
|
||||
resolved_scopes: Default::default(),
|
||||
};
|
||||
|
||||
@@ -341,9 +331,6 @@ impl VirtualDom {
|
||||
);
|
||||
dom.new_scope(Box::new(root), "app");
|
||||
|
||||
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||
dom.elements.insert(None);
|
||||
|
||||
dom
|
||||
}
|
||||
|
||||
@@ -423,34 +410,6 @@ impl VirtualDom {
|
||||
self.queue_task(task, order);
|
||||
}
|
||||
|
||||
/// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.**
|
||||
///
|
||||
/// This method will identify the appropriate element. The data must match up with the listener declared. Note that
|
||||
/// this method does not give any indication as to the success of the listener call. If the listener is not found,
|
||||
/// nothing will happen.
|
||||
///
|
||||
/// It is up to the listeners themselves to mark nodes as dirty.
|
||||
///
|
||||
/// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
|
||||
#[instrument(skip(self), level = "trace", name = "VirtualDom::handle_event")]
|
||||
pub fn handle_event(
|
||||
&mut self,
|
||||
name: &str,
|
||||
data: Rc<dyn Any>,
|
||||
element: ElementId,
|
||||
bubbles: bool,
|
||||
) {
|
||||
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
||||
|
||||
if let Some(Some(parent_path)) = self.elements.get(element.0).copied() {
|
||||
if bubbles {
|
||||
self.handle_bubbling_event(parent_path, name, Event::new(data, bubbles));
|
||||
} else {
|
||||
self.handle_non_bubbling_event(parent_path, name, Event::new(data, bubbles));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for the scheduler to have any work.
|
||||
///
|
||||
/// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
|
||||
@@ -772,121 +731,11 @@ impl VirtualDom {
|
||||
self.runtime.clone()
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
|
||||
we find the target path.
|
||||
|
||||
With the target path, we try and move up to the parent until there is no parent.
|
||||
Due to how bubbling works, we call the listeners before walking to the parent.
|
||||
|
||||
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
|
||||
----------------------
|
||||
|
||||
For a visual demonstration, here we present a tree on the left and whether or not a listener is collected on the
|
||||
right.
|
||||
|
||||
| <-- yes (is ascendant)
|
||||
| | | <-- no (is not direct ascendant)
|
||||
| | <-- yes (is ascendant)
|
||||
| | | | | <--- target element, break early, don't check other listeners
|
||||
| | | <-- no, broke early
|
||||
| <-- no, broke early
|
||||
*/
|
||||
#[instrument(
|
||||
skip(self, uievent),
|
||||
level = "trace",
|
||||
name = "VirtualDom::handle_bubbling_event"
|
||||
)]
|
||||
fn handle_bubbling_event(&mut self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
|
||||
// If the event bubbles, we traverse through the tree until we find the target element.
|
||||
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
|
||||
let mut parent = Some(parent);
|
||||
while let Some(path) = parent {
|
||||
let mut listeners = vec![];
|
||||
|
||||
let Some(mount) = self.mounts.get(path.mount.0) else {
|
||||
// If the node is suspended and not mounted, we can just ignore the event
|
||||
return;
|
||||
};
|
||||
let el_ref = &mount.node;
|
||||
let node_template = el_ref.template;
|
||||
let target_path = path.path;
|
||||
|
||||
// Accumulate listeners into the listener list bottom to top
|
||||
for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
|
||||
let attrs = &*el_ref.dynamic_attrs[idx];
|
||||
|
||||
for attr in attrs.iter() {
|
||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||
if attr.name.get(2..) == Some(name) && target_path.is_descendant(this_path) {
|
||||
listeners.push(&attr.value);
|
||||
|
||||
// Break if this is the exact target element.
|
||||
// This means we won't call two listeners with the same name on the same element. This should be
|
||||
// documented, or be rejected from the rsx! macro outright
|
||||
if target_path == this_path {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
|
||||
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
||||
tracing::event!(
|
||||
tracing::Level::TRACE,
|
||||
"Calling {} listeners",
|
||||
listeners.len()
|
||||
);
|
||||
for listener in listeners.into_iter().rev() {
|
||||
if let AttributeValue::Listener(listener) = listener {
|
||||
self.runtime.rendering.set(false);
|
||||
listener.call(uievent.clone());
|
||||
self.runtime.rendering.set(true);
|
||||
|
||||
if !uievent.propagates.get() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mount = el_ref.mount.get().as_usize();
|
||||
parent = mount.and_then(|id| self.mounts.get(id).and_then(|el| el.parent));
|
||||
}
|
||||
}
|
||||
|
||||
/// Call an event listener in the simplest way possible without bubbling upwards
|
||||
#[instrument(
|
||||
skip(self, uievent),
|
||||
level = "trace",
|
||||
name = "VirtualDom::handle_non_bubbling_event"
|
||||
)]
|
||||
fn handle_non_bubbling_event(&mut self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
|
||||
let Some(mount) = self.mounts.get(node.mount.0) else {
|
||||
// If the node is suspended and not mounted, we can just ignore the event
|
||||
return;
|
||||
};
|
||||
let el_ref = &mount.node;
|
||||
let node_template = el_ref.template;
|
||||
let target_path = node.path;
|
||||
|
||||
for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
|
||||
let attrs = &*el_ref.dynamic_attrs[idx];
|
||||
|
||||
for attr in attrs.iter() {
|
||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||
// Only call the listener if this is the exact target element.
|
||||
if attr.name.get(2..) == Some(name) && target_path == this_path {
|
||||
if let AttributeValue::Listener(listener) = &attr.value {
|
||||
self.runtime.rendering.set(false);
|
||||
listener.call(uievent.clone());
|
||||
self.runtime.rendering.set(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Handle an event with the Virtual Dom. This method is deprecated in favor of [VirtualDom::runtime().handle_event] and will be removed in a future release.
|
||||
#[deprecated = "Use [VirtualDom::runtime().handle_event] instead"]
|
||||
pub fn handle_event(&self, name: &str, event: Rc<dyn Any>, element: ElementId, bubbling: bool) {
|
||||
let event = crate::Event::new(event, bubbling);
|
||||
self.runtime().handle_event(name, event, element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user