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
@@ -56,7 +56,8 @@ pub struct ElementPath {
|
||||
|
||||
impl VirtualDom {
|
||||
pub(crate) fn next_element(&mut self) -> ElementId {
|
||||
ElementId(self.elements.insert(None))
|
||||
let mut elements = self.runtime.elements.borrow_mut();
|
||||
ElementId(elements.insert(None))
|
||||
}
|
||||
|
||||
pub(crate) fn reclaim(&mut self, el: ElementId) {
|
||||
@@ -71,7 +72,8 @@ impl VirtualDom {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.elements.try_remove(el.0).is_some()
|
||||
let mut elements = self.runtime.elements.borrow_mut();
|
||||
elements.try_remove(el.0).is_some()
|
||||
}
|
||||
|
||||
// Drop a scope without dropping its children
|
||||
|
||||
@@ -161,10 +161,10 @@ impl VNode {
|
||||
dom: &mut VirtualDom,
|
||||
mut to: Option<&mut impl WriteMutations>,
|
||||
) {
|
||||
let scope = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx));
|
||||
|
||||
// Remove the scope id from the mount
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = ScopeId::PLACEHOLDER.0;
|
||||
dom.set_mounted_dyn_node(mount, idx, ScopeId::PLACEHOLDER.0);
|
||||
let m = self.create_component_node(mount, idx, new, parent, dom, to.as_deref_mut());
|
||||
|
||||
// Instead of *just* removing it, we can use the replace mutation
|
||||
@@ -188,7 +188,7 @@ impl VNode {
|
||||
return SuspenseBoundaryProps::create(mount, idx, component, parent, dom, to);
|
||||
}
|
||||
|
||||
let mut scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
|
||||
|
||||
// If the scopeid is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that
|
||||
if scope_id.is_placeholder() {
|
||||
@@ -198,7 +198,7 @@ impl VNode {
|
||||
.id;
|
||||
|
||||
// Store the scope id for the next render
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = scope_id.0;
|
||||
dom.set_mounted_dyn_node(mount, idx, scope_id.0);
|
||||
|
||||
// If this is a new scope, we also need to run it once to get the initial state
|
||||
let new = dom.run_scope(scope_id);
|
||||
@@ -207,7 +207,7 @@ impl VNode {
|
||||
dom.scopes[scope_id.0].last_rendered_node = Some(new);
|
||||
}
|
||||
|
||||
let scope = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx));
|
||||
|
||||
let new_node = dom.scopes[scope.0]
|
||||
.last_rendered_node
|
||||
|
||||
@@ -469,7 +469,8 @@ impl VNode {
|
||||
) -> usize {
|
||||
let template = self.template;
|
||||
|
||||
let mount = dom.mounts.get(self.mount.get().0).unwrap();
|
||||
let mounts = dom.runtime.mounts.borrow();
|
||||
let mount = mounts.get(self.mount.get().0).unwrap();
|
||||
|
||||
template
|
||||
.roots
|
||||
|
||||
@@ -10,10 +10,11 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use crate::{
|
||||
arena::MountId,
|
||||
innerlude::{ElementRef, WriteMutations},
|
||||
nodes::VNode,
|
||||
virtual_dom::VirtualDom,
|
||||
TemplateNode,
|
||||
ElementId, TemplateNode,
|
||||
};
|
||||
|
||||
mod component;
|
||||
@@ -33,6 +34,46 @@ impl VirtualDom {
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub(crate) fn get_mounted_parent(&self, mount: MountId) -> Option<ElementRef> {
|
||||
let mounts = self.runtime.mounts.borrow();
|
||||
mounts[mount.0].parent
|
||||
}
|
||||
|
||||
pub(crate) fn get_mounted_dyn_node(&self, mount: MountId, dyn_node_idx: usize) -> usize {
|
||||
let mounts = self.runtime.mounts.borrow();
|
||||
mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx]
|
||||
}
|
||||
|
||||
pub(crate) fn set_mounted_dyn_node(&self, mount: MountId, dyn_node_idx: usize, value: usize) {
|
||||
let mut mounts = self.runtime.mounts.borrow_mut();
|
||||
mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx] = value;
|
||||
}
|
||||
|
||||
pub(crate) fn get_mounted_dyn_attr(&self, mount: MountId, dyn_attr_idx: usize) -> ElementId {
|
||||
let mounts = self.runtime.mounts.borrow();
|
||||
mounts[mount.0].mounted_attributes[dyn_attr_idx]
|
||||
}
|
||||
|
||||
pub(crate) fn set_mounted_dyn_attr(
|
||||
&self,
|
||||
mount: MountId,
|
||||
dyn_attr_idx: usize,
|
||||
value: ElementId,
|
||||
) {
|
||||
let mut mounts = self.runtime.mounts.borrow_mut();
|
||||
mounts[mount.0].mounted_attributes[dyn_attr_idx] = value;
|
||||
}
|
||||
|
||||
pub(crate) fn get_mounted_root_node(&self, mount: MountId, root_idx: usize) -> ElementId {
|
||||
let mounts = self.runtime.mounts.borrow();
|
||||
mounts[mount.0].root_ids[root_idx]
|
||||
}
|
||||
|
||||
pub(crate) fn set_mounted_root_node(&self, mount: MountId, root_idx: usize, value: ElementId) {
|
||||
let mut mounts = self.runtime.mounts.borrow_mut();
|
||||
mounts[mount.0].root_ids[root_idx] = value;
|
||||
}
|
||||
|
||||
/// Remove these nodes from the dom
|
||||
/// Wont generate mutations for the inner nodes
|
||||
fn remove_nodes(
|
||||
|
||||
@@ -19,13 +19,19 @@ impl VNode {
|
||||
mut to: Option<&mut impl WriteMutations>,
|
||||
) {
|
||||
// The node we are diffing from should always be mounted
|
||||
debug_assert!(dom.mounts.get(self.mount.get().0).is_some() || to.is_none());
|
||||
debug_assert!(
|
||||
dom.runtime
|
||||
.mounts
|
||||
.borrow()
|
||||
.get(self.mount.get().0)
|
||||
.is_some()
|
||||
|| to.is_none()
|
||||
);
|
||||
|
||||
// If the templates are different, we need to replace the entire template
|
||||
if self.template != new.template {
|
||||
let mount_id = self.mount.get();
|
||||
let mount = &dom.mounts[mount_id.0];
|
||||
let parent = mount.parent;
|
||||
let parent = dom.get_mounted_parent(mount_id);
|
||||
return self.replace(std::slice::from_ref(new), parent, dom, to);
|
||||
}
|
||||
|
||||
@@ -62,7 +68,8 @@ impl VNode {
|
||||
new.mount.set(mount_id);
|
||||
|
||||
if mount_id.mounted() {
|
||||
let mount = &mut dom.mounts[mount_id.0];
|
||||
let mut mounts = dom.runtime.mounts.borrow_mut();
|
||||
let mount = &mut mounts[mount_id.0];
|
||||
|
||||
// Update the reference to the node for bubbling events
|
||||
mount.node = new.clone_mounted();
|
||||
@@ -83,8 +90,8 @@ impl VNode {
|
||||
(Text(old), Text(new)) => {
|
||||
// Diffing text is just a side effect, if we are diffing suspended nodes and are not outputting mutations, we can skip it
|
||||
if let Some(to) = to {
|
||||
let mount = &dom.mounts[mount.0];
|
||||
self.diff_vtext(to, mount, idx, old, new)
|
||||
let id = ElementId(dom.get_mounted_dyn_node(mount, idx));
|
||||
self.diff_vtext(to, id, old, new)
|
||||
}
|
||||
}
|
||||
(Placeholder(_), Placeholder(_)) => {}
|
||||
@@ -95,7 +102,7 @@ impl VNode {
|
||||
Some(self.reference_to_dynamic_node(mount, idx)),
|
||||
),
|
||||
(Component(old), Component(new)) => {
|
||||
let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
|
||||
self.diff_vcomponent(
|
||||
mount,
|
||||
idx,
|
||||
@@ -114,20 +121,20 @@ impl VNode {
|
||||
// Mark the mount as unused. When a scope is created, it reads the mount and
|
||||
// if it is the placeholder value, it will create the scope, otherwise it will
|
||||
// reuse the scope
|
||||
let old_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = usize::MAX;
|
||||
let old_mount = dom.get_mounted_dyn_node(mount, idx);
|
||||
dom.set_mounted_dyn_node(mount, idx, usize::MAX);
|
||||
|
||||
let new_nodes_on_stack =
|
||||
self.create_dynamic_node(new, mount, idx, dom, to.as_deref_mut());
|
||||
|
||||
// Restore the mount for the scope we are removing
|
||||
let new_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = old_mount;
|
||||
let new_mount = dom.get_mounted_dyn_node(mount, idx);
|
||||
dom.set_mounted_dyn_node(mount, idx, old_mount);
|
||||
|
||||
self.remove_dynamic_node(mount, dom, to, true, idx, old, Some(new_nodes_on_stack));
|
||||
|
||||
// Restore the mount for the node we created
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = new_mount;
|
||||
dom.set_mounted_dyn_node(mount, idx, new_mount);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -143,12 +150,14 @@ impl VNode {
|
||||
}
|
||||
|
||||
pub(crate) fn find_first_element(&self, dom: &VirtualDom) -> ElementId {
|
||||
let mount = &dom.mounts[self.mount.get().0];
|
||||
let mount_id = self.mount.get();
|
||||
let first = match self.get_dynamic_root_node_and_id(0) {
|
||||
// This node is static, just get the root id
|
||||
None => mount.root_ids[0],
|
||||
None => dom.get_mounted_root_node(mount_id, 0),
|
||||
// If it is dynamic and shallow, grab the id from the mounted dynamic nodes
|
||||
Some((idx, Placeholder(_) | Text(_))) => ElementId(mount.mounted_dynamic_nodes[idx]),
|
||||
Some((idx, Placeholder(_) | Text(_))) => {
|
||||
ElementId(dom.get_mounted_dyn_node(mount_id, idx))
|
||||
}
|
||||
// The node is a fragment, so we need to find the first element in the fragment
|
||||
Some((_, Fragment(children))) => {
|
||||
let child = children.first().unwrap();
|
||||
@@ -156,7 +165,7 @@ impl VNode {
|
||||
}
|
||||
// The node is a component, so we need to find the first element in the component
|
||||
Some((id, Component(_))) => {
|
||||
let scope = ScopeId(mount.mounted_dynamic_nodes[id]);
|
||||
let scope = ScopeId(dom.get_mounted_dyn_node(mount_id, id));
|
||||
dom.get_scope(scope)
|
||||
.unwrap()
|
||||
.root_node()
|
||||
@@ -171,13 +180,15 @@ impl VNode {
|
||||
}
|
||||
|
||||
pub(crate) fn find_last_element(&self, dom: &VirtualDom) -> ElementId {
|
||||
let mount = &dom.mounts[self.mount.get().0];
|
||||
let mount_id = self.mount.get();
|
||||
let last_root_index = self.template.roots.len() - 1;
|
||||
let last = match self.get_dynamic_root_node_and_id(last_root_index) {
|
||||
// This node is static, just get the root id
|
||||
None => mount.root_ids[last_root_index],
|
||||
None => dom.get_mounted_root_node(mount_id, last_root_index),
|
||||
// If it is dynamic and shallow, grab the id from the mounted dynamic nodes
|
||||
Some((idx, Placeholder(_) | Text(_))) => ElementId(mount.mounted_dynamic_nodes[idx]),
|
||||
Some((idx, Placeholder(_) | Text(_))) => {
|
||||
ElementId(dom.get_mounted_dyn_node(mount_id, idx))
|
||||
}
|
||||
// The node is a fragment, so we need to find the first element in the fragment
|
||||
Some((_, Fragment(children))) => {
|
||||
let child = children.first().unwrap();
|
||||
@@ -185,7 +196,7 @@ impl VNode {
|
||||
}
|
||||
// The node is a component, so we need to find the first element in the component
|
||||
Some((id, Component(_))) => {
|
||||
let scope = ScopeId(mount.mounted_dynamic_nodes[id]);
|
||||
let scope = ScopeId(dom.get_mounted_dyn_node(mount_id, id));
|
||||
dom.get_scope(scope)
|
||||
.unwrap()
|
||||
.root_node()
|
||||
@@ -202,16 +213,8 @@ impl VNode {
|
||||
/// Diff the two text nodes
|
||||
///
|
||||
/// This just sets the text of the node if it's different.
|
||||
fn diff_vtext(
|
||||
&self,
|
||||
to: &mut impl WriteMutations,
|
||||
mount: &VNodeMount,
|
||||
idx: usize,
|
||||
left: &VText,
|
||||
right: &VText,
|
||||
) {
|
||||
fn diff_vtext(&self, to: &mut impl WriteMutations, id: ElementId, left: &VText, right: &VText) {
|
||||
if left.value != right.value {
|
||||
let id = ElementId(mount.mounted_dynamic_nodes[idx]);
|
||||
to.set_node_text(&right.value, id);
|
||||
}
|
||||
}
|
||||
@@ -291,7 +294,7 @@ impl VNode {
|
||||
|
||||
if destroy_component_state {
|
||||
// Remove the mount information
|
||||
dom.mounts.remove(mount.0);
|
||||
dom.runtime.mounts.borrow_mut().remove(mount.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,8 +321,7 @@ impl VNode {
|
||||
replace_with.filter(|_| last_node),
|
||||
);
|
||||
} else if let Some(to) = to.as_deref_mut() {
|
||||
let mount = &dom.mounts[mount.0];
|
||||
let id = mount.root_ids[idx];
|
||||
let id = dom.get_mounted_root_node(mount, idx);
|
||||
if let (true, Some(replace_with)) = (last_node, replace_with) {
|
||||
to.replace_node_with(id, replace_with);
|
||||
} else {
|
||||
@@ -366,11 +368,11 @@ impl VNode {
|
||||
) {
|
||||
match node {
|
||||
Component(_comp) => {
|
||||
let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
|
||||
dom.remove_component_node(to, destroy_component_state, scope_id, replace_with);
|
||||
}
|
||||
Text(_) | Placeholder(_) => {
|
||||
let id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let id = ElementId(dom.get_mounted_dyn_node(mount, idx));
|
||||
if let Some(to) = to {
|
||||
if let Some(replace_with) = replace_with {
|
||||
to.replace_node_with(id, replace_with);
|
||||
@@ -400,7 +402,7 @@ impl VNode {
|
||||
}
|
||||
|
||||
// only reclaim the new element if it's different from the previous one
|
||||
let new_id = dom.mounts[mount.0].mounted_attributes[idx];
|
||||
let new_id = dom.get_mounted_dyn_attr(mount, idx);
|
||||
if Some(new_id) != next_id {
|
||||
dom.reclaim(new_id);
|
||||
next_id = Some(new_id);
|
||||
@@ -423,7 +425,7 @@ impl VNode {
|
||||
{
|
||||
let mut old_attributes_iter = old_attrs.iter().peekable();
|
||||
let mut new_attributes_iter = new_attrs.iter().peekable();
|
||||
let attribute_id = dom.mounts[mount_id.0].mounted_attributes[idx];
|
||||
let attribute_id = dom.get_mounted_dyn_attr(mount_id, idx);
|
||||
let path = self.template.attr_paths[idx];
|
||||
|
||||
loop {
|
||||
@@ -505,7 +507,8 @@ impl VNode {
|
||||
path: ElementPath { path },
|
||||
mount,
|
||||
};
|
||||
dom.elements[id.0] = Some(element_ref);
|
||||
let mut elements = dom.runtime.elements.borrow_mut();
|
||||
elements[id.0] = Some(element_ref);
|
||||
to.create_event_listener(&attribute.name[2..], id);
|
||||
}
|
||||
_ => {
|
||||
@@ -526,7 +529,8 @@ impl VNode {
|
||||
|
||||
// Initialize the mount information for this vnode if it isn't already mounted
|
||||
if !self.mount.get().mounted() {
|
||||
let entry = dom.mounts.vacant_entry();
|
||||
let mut mounts = dom.runtime.mounts.borrow_mut();
|
||||
let entry = mounts.vacant_entry();
|
||||
let mount = MountId(entry.key());
|
||||
self.mount.set(mount);
|
||||
tracing::trace!(?self, ?mount, "creating template");
|
||||
@@ -549,7 +553,7 @@ impl VNode {
|
||||
// Get the mounted id of this block
|
||||
// At this point, we should have already mounted the block
|
||||
debug_assert!(
|
||||
dom.mounts.contains(
|
||||
dom.runtime.mounts.borrow().contains(
|
||||
self.mount
|
||||
.get()
|
||||
.as_usize()
|
||||
@@ -752,7 +756,7 @@ impl VNode {
|
||||
|
||||
for attr in &**attribute {
|
||||
self.write_attribute(attribute_path, attr, id, mount, dom, to);
|
||||
dom.mounts[mount.0].mounted_attributes[attribute_idx] = id;
|
||||
dom.set_mounted_dyn_attr(mount, attribute_idx, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -766,7 +770,7 @@ impl VNode {
|
||||
) -> ElementId {
|
||||
// Get an ID for this root since it's a real root
|
||||
let this_id = dom.next_element();
|
||||
dom.mounts[mount.0].root_ids[root_idx] = this_id;
|
||||
dom.set_mounted_root_node(mount, root_idx, this_id);
|
||||
|
||||
to.load_template(self.template, root_idx, this_id);
|
||||
|
||||
@@ -789,7 +793,7 @@ impl VNode {
|
||||
) -> ElementId {
|
||||
// This is just the root node. We already know it's id
|
||||
if let [root_idx] = path {
|
||||
return dom.mounts[mount.0].root_ids[*root_idx as usize];
|
||||
return dom.get_mounted_root_node(mount, *root_idx as usize);
|
||||
}
|
||||
|
||||
// The node is deeper in the template and we should create a new id for it
|
||||
@@ -835,7 +839,7 @@ impl VNode {
|
||||
impl MountId {
|
||||
fn mount_node(self, node_index: usize, dom: &mut VirtualDom) -> ElementId {
|
||||
let id = dom.next_element();
|
||||
dom.mounts[self.0].mounted_dynamic_nodes[node_index] = id.0;
|
||||
dom.set_mounted_dyn_node(self, node_index, id.0);
|
||||
id
|
||||
}
|
||||
}
|
||||
+63
-14
@@ -1,10 +1,6 @@
|
||||
use crate::{global_context::current_scope_id, properties::SuperFrom, Runtime, ScopeId};
|
||||
use generational_box::GenerationalBox;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
marker::PhantomData,
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{cell::RefCell, marker::PhantomData, rc::Rc};
|
||||
|
||||
/// A wrapper around some generic data that handles the event's state
|
||||
///
|
||||
@@ -26,19 +22,29 @@ use std::{
|
||||
pub struct Event<T: 'static + ?Sized> {
|
||||
/// The data associated with this event
|
||||
pub data: Rc<T>,
|
||||
pub(crate) propagates: Rc<Cell<bool>>,
|
||||
pub(crate) metadata: Rc<RefCell<EventMetadata>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct EventMetadata {
|
||||
pub(crate) propagates: bool,
|
||||
pub(crate) prevent_default: bool,
|
||||
}
|
||||
|
||||
impl<T: ?Sized + 'static> Event<T> {
|
||||
pub(crate) fn new(data: Rc<T>, bubbles: bool) -> Self {
|
||||
/// Create a new event from the inner data
|
||||
pub fn new(data: Rc<T>, propagates: bool) -> Self {
|
||||
Self {
|
||||
data,
|
||||
propagates: Rc::new(Cell::new(bubbles)),
|
||||
metadata: Rc::new(RefCell::new(EventMetadata {
|
||||
propagates,
|
||||
prevent_default: false,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Event<T> {
|
||||
impl<T: ?Sized> Event<T> {
|
||||
/// Map the event data to a new type
|
||||
///
|
||||
/// # Example
|
||||
@@ -57,7 +63,7 @@ impl<T> Event<T> {
|
||||
pub fn map<U: 'static, F: FnOnce(&T) -> U>(&self, f: F) -> Event<U> {
|
||||
Event {
|
||||
data: Rc::new(f(&self.data)),
|
||||
propagates: self.propagates.clone(),
|
||||
metadata: self.metadata.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +84,12 @@ impl<T> Event<T> {
|
||||
/// ```
|
||||
#[deprecated = "use stop_propagation instead"]
|
||||
pub fn cancel_bubble(&self) {
|
||||
self.propagates.set(false);
|
||||
self.metadata.borrow_mut().propagates = false;
|
||||
}
|
||||
|
||||
/// Check if the event propagates up the tree to parent elements
|
||||
pub fn propagates(&self) -> bool {
|
||||
self.metadata.borrow().propagates
|
||||
}
|
||||
|
||||
/// Prevent this event from continuing to bubble up the tree to parent elements.
|
||||
@@ -96,7 +107,7 @@ impl<T> Event<T> {
|
||||
/// };
|
||||
/// ```
|
||||
pub fn stop_propagation(&self) {
|
||||
self.propagates.set(false);
|
||||
self.metadata.borrow_mut().propagates = false;
|
||||
}
|
||||
|
||||
/// Get a reference to the inner data from this event
|
||||
@@ -117,12 +128,49 @@ impl<T> Event<T> {
|
||||
pub fn data(&self) -> Rc<T> {
|
||||
self.data.clone()
|
||||
}
|
||||
|
||||
/// Prevent the default action of the event.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// a {
|
||||
/// // You can prevent the default action of the event with `prevent_default`
|
||||
/// onclick: move |event| {
|
||||
/// event.prevent_default();
|
||||
/// },
|
||||
/// href: "https://dioxuslabs.com",
|
||||
/// "don't go to the link"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note: This must be called synchronously when handling the event. Calling it after the event has been handled will have no effect.
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// This method is not available on the LiveView renderer because LiveView handles all events over a websocket which cannot block.
|
||||
///
|
||||
/// </div>
|
||||
#[track_caller]
|
||||
pub fn prevent_default(&self) {
|
||||
self.metadata.borrow_mut().prevent_default = true;
|
||||
}
|
||||
|
||||
/// Check if the default action of the event is enabled.
|
||||
pub fn default_action_enabled(&self) -> bool {
|
||||
!self.metadata.borrow().prevent_default
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Clone for Event<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
propagates: self.propagates.clone(),
|
||||
metadata: self.metadata.clone(),
|
||||
data: self.data.clone(),
|
||||
}
|
||||
}
|
||||
@@ -138,7 +186,8 @@ impl<T> std::ops::Deref for Event<T> {
|
||||
impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UiEvent")
|
||||
.field("bubble_state", &self.propagates)
|
||||
.field("bubble_state", &self.propagates())
|
||||
.field("prevent_default", &!self.default_action_enabled())
|
||||
.field("data", &self.data)
|
||||
.finish()
|
||||
}
|
||||
|
||||
+17
-11
@@ -300,12 +300,14 @@ impl VNode {
|
||||
let mount = self.mount.get().as_usize()?;
|
||||
|
||||
match &self.dynamic_nodes[dynamic_node_idx] {
|
||||
DynamicNode::Text(_) | DynamicNode::Placeholder(_) => dom
|
||||
.mounts
|
||||
.get(mount)?
|
||||
.mounted_dynamic_nodes
|
||||
.get(dynamic_node_idx)
|
||||
.map(|id| ElementId(*id)),
|
||||
DynamicNode::Text(_) | DynamicNode::Placeholder(_) => {
|
||||
let mounts = dom.runtime.mounts.borrow();
|
||||
mounts
|
||||
.get(mount)?
|
||||
.mounted_dynamic_nodes
|
||||
.get(dynamic_node_idx)
|
||||
.map(|id| ElementId(*id))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -314,7 +316,8 @@ impl VNode {
|
||||
pub fn mounted_root(&self, root_idx: usize, dom: &VirtualDom) -> Option<ElementId> {
|
||||
let mount = self.mount.get().as_usize()?;
|
||||
|
||||
dom.mounts.get(mount)?.root_ids.get(root_idx).copied()
|
||||
let mounts = dom.runtime.mounts.borrow();
|
||||
mounts.get(mount)?.root_ids.get(root_idx).copied()
|
||||
}
|
||||
|
||||
/// Get the mounted id for a dynamic attribute index
|
||||
@@ -325,7 +328,8 @@ impl VNode {
|
||||
) -> Option<ElementId> {
|
||||
let mount = self.mount.get().as_usize()?;
|
||||
|
||||
dom.mounts
|
||||
let mounts = dom.runtime.mounts.borrow();
|
||||
mounts
|
||||
.get(mount)?
|
||||
.mounted_attributes
|
||||
.get(dynamic_attribute_idx)
|
||||
@@ -623,7 +627,8 @@ impl VComponent {
|
||||
) -> Option<ScopeId> {
|
||||
let mount = vnode.mount.get().as_usize()?;
|
||||
|
||||
let scope_id = dom.mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
|
||||
let mounts = dom.runtime.mounts.borrow();
|
||||
let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
|
||||
|
||||
Some(ScopeId(scope_id))
|
||||
}
|
||||
@@ -641,7 +646,8 @@ impl VComponent {
|
||||
) -> Option<&'a ScopeState> {
|
||||
let mount = vnode.mount.get().as_usize()?;
|
||||
|
||||
let scope_id = dom.mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
|
||||
let mounts = dom.runtime.mounts.borrow();
|
||||
let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
|
||||
|
||||
dom.scopes.get(scope_id)
|
||||
}
|
||||
@@ -791,7 +797,7 @@ impl AttributeValue {
|
||||
AttributeValue::Listener(EventHandler::leak(move |event: Event<dyn Any>| {
|
||||
let data = event.data.downcast::<T>().unwrap();
|
||||
callback(Event {
|
||||
propagates: event.propagates,
|
||||
metadata: event.metadata.clone(),
|
||||
data,
|
||||
});
|
||||
}))
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::arena::ElementRef;
|
||||
use crate::innerlude::{DirtyTasks, Effect};
|
||||
use crate::nodes::VNodeMount;
|
||||
use crate::scope_context::SuspenseLocation;
|
||||
use crate::{
|
||||
innerlude::{LocalTask, SchedulerMsg},
|
||||
@@ -6,13 +8,17 @@ use crate::{
|
||||
scopes::ScopeId,
|
||||
Task,
|
||||
};
|
||||
use crate::{AttributeValue, ElementId, Event};
|
||||
use slab::Slab;
|
||||
use slotmap::DefaultKey;
|
||||
use std::any::Any;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
use std::{
|
||||
cell::{Cell, Ref, RefCell},
|
||||
rc::Rc,
|
||||
};
|
||||
use tracing::instrument;
|
||||
|
||||
thread_local! {
|
||||
static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = const { RefCell::new(vec![]) };
|
||||
@@ -48,10 +54,23 @@ pub struct Runtime {
|
||||
|
||||
// Tasks that are waiting to be polled
|
||||
pub(crate) dirty_tasks: RefCell<BTreeSet<DirtyTasks>>,
|
||||
|
||||
// The element ids that are used in the renderer
|
||||
// These mark a specific place in a whole rsx block
|
||||
pub(crate) elements: RefCell<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: RefCell<Slab<VNodeMount>>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
|
||||
let mut elements = Slab::default();
|
||||
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||
elements.insert(None);
|
||||
|
||||
Rc::new(Self {
|
||||
sender,
|
||||
rendering: Cell::new(true),
|
||||
@@ -63,6 +82,8 @@ impl Runtime {
|
||||
suspended_tasks: Default::default(),
|
||||
pending_effects: Default::default(),
|
||||
dirty_tasks: Default::default(),
|
||||
elements: RefCell::new(elements),
|
||||
mounts: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -245,6 +266,150 @@ impl Runtime {
|
||||
let scope = &scopes[scope_id.0].as_ref().unwrap();
|
||||
!matches!(scope.suspense_location(), SuspenseLocation::UnderSuspense(suspense) if suspense.is_suspended())
|
||||
}
|
||||
|
||||
/// 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, event), level = "trace", name = "Runtime::handle_event")]
|
||||
pub fn handle_event(self: &Rc<Self>, name: &str, event: Event<dyn Any>, element: ElementId) {
|
||||
let _runtime = RuntimeGuard::new(self.clone());
|
||||
let elements = self.elements.borrow();
|
||||
|
||||
if let Some(Some(parent_path)) = elements.get(element.0).copied() {
|
||||
if event.propagates() {
|
||||
self.handle_bubbling_event(parent_path, name, event);
|
||||
} else {
|
||||
self.handle_non_bubbling_event(parent_path, name, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
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(&self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
|
||||
let mounts = self.mounts.borrow();
|
||||
|
||||
// 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) = 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.rendering.set(false);
|
||||
listener.call(uievent.clone());
|
||||
self.rendering.set(true);
|
||||
let metadata = uievent.metadata.borrow();
|
||||
|
||||
if !metadata.propagates {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mount = el_ref.mount.get().as_usize();
|
||||
parent = mount.and_then(|id| 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(&self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
|
||||
let mounts = self.mounts.borrow();
|
||||
let Some(mount) = 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.rendering.set(false);
|
||||
listener.call(uievent.clone());
|
||||
self.rendering.set(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A guard for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
|
||||
|
||||
@@ -275,7 +275,7 @@ impl SuspenseBoundaryProps {
|
||||
dom: &mut VirtualDom,
|
||||
to: Option<&mut M>,
|
||||
) -> usize {
|
||||
let mut scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
|
||||
// If the ScopeId is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that
|
||||
if scope_id.is_placeholder() {
|
||||
{
|
||||
@@ -297,7 +297,7 @@ impl SuspenseBoundaryProps {
|
||||
}
|
||||
|
||||
// Store the scope id for the next render
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = scope_id.0;
|
||||
dom.set_mounted_dyn_node(mount, idx, scope_id.0);
|
||||
}
|
||||
dom.runtime.clone().with_scope_on_stack(scope_id, || {
|
||||
let scope_state = &mut dom.scopes[scope_id.0];
|
||||
@@ -404,11 +404,13 @@ impl SuspenseBoundaryProps {
|
||||
// Get the parent of the suspense boundary to later create children with the right parent
|
||||
let currently_rendered = scope_state.last_rendered_node.as_ref().unwrap().clone();
|
||||
let mount = currently_rendered.mount.get();
|
||||
let parent = dom
|
||||
.mounts
|
||||
.get(mount.0)
|
||||
.expect("suspense placeholder is not mounted")
|
||||
.parent;
|
||||
let parent = {
|
||||
let mounts = dom.runtime.mounts.borrow();
|
||||
mounts
|
||||
.get(mount.0)
|
||||
.expect("suspense placeholder is not mounted")
|
||||
.parent
|
||||
};
|
||||
|
||||
let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();
|
||||
|
||||
@@ -524,8 +526,7 @@ impl SuspenseBoundaryProps {
|
||||
|
||||
// Move the children to the background
|
||||
let mount = old_children.mount.get();
|
||||
let mount = dom.mounts.get(mount.0).expect("mount should exist");
|
||||
let parent = mount.parent;
|
||||
let parent = dom.get_mounted_parent(mount);
|
||||
|
||||
suspense_context.in_suspense_placeholder(&dom.runtime(), || {
|
||||
old_children.move_node_to_background(
|
||||
@@ -566,8 +567,7 @@ impl SuspenseBoundaryProps {
|
||||
|
||||
// Then replace the placeholder with the new children
|
||||
let mount = old_placeholder.mount.get();
|
||||
let mount = dom.mounts.get(mount.0).expect("mount should exist");
|
||||
let parent = mount.parent;
|
||||
let parent = dom.get_mounted_parent(mount);
|
||||
old_placeholder.replace(
|
||||
std::slice::from_ref(&*new_children),
|
||||
parent,
|
||||
|
||||
@@ -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