fix non-bubbling event propagation

This commit is contained in:
Evan Almloff 2023-04-10 09:45:16 -05:00
parent 7c9295af38
commit d005f3481f

View file

@ -384,51 +384,73 @@ impl VirtualDom {
data, data,
}; };
// Loop through each dynamic attribute in this template before moving up to the template's parent. // If the event bubbles, we traverse through the tree until we find the target element.
while let Some(el_ref) = parent_path { if bubbles {
// safety: we maintain references of all vnodes in the element slab // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
let template = unsafe { el_ref.template.unwrap().as_ref() }; while let Some(el_ref) = parent_path {
let node_template = template.template.get(); // safety: we maintain references of all vnodes in the element slab
let target_path = el_ref.path; let template = unsafe { el_ref.template.unwrap().as_ref() };
let node_template = template.template.get();
let target_path = el_ref.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() { for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx]; let this_path = node_template.attr_paths[idx];
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
if attr.name.trim_start_matches("on") == name if attr.name.trim_start_matches("on") == name
&& target_path.is_decendant(&this_path) && target_path.is_decendant(&this_path)
{ {
listeners.push(&attr.value); listeners.push(&attr.value);
// Break if the event doesn't bubble anyways // Break if this is the exact target element.
if !bubbles { // This means we won't call two listeners with the same name on the same element. This should be
break; // documented, or be rejected from the rsx! macro outright
if target_path == this_path {
break;
}
} }
}
// Break if this is the exact target element. // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
// This means we won't call two listeners with the same name on the same element. This should be // We check the bubble state between each call to see if the event has been stopped from bubbling
// documented, or be rejected from the rsx! macro outright for listener in listeners.drain(..).rev() {
if target_path == this_path { if let AttributeValue::Listener(listener) = listener {
break; if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
if !uievent.propagates.get() {
return;
}
}
}
parent_path = template.parent.and_then(|id| self.elements.get(id.0));
}
} else {
// Otherwise, we just call the listener on the target element
if let Some(el_ref) = parent_path {
// safety: we maintain references of all vnodes in the element slab
let template = unsafe { el_ref.template.unwrap().as_ref() };
let node_template = template.template.get();
let target_path = el_ref.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx];
// 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.trim_start_matches("on") == name && target_path == this_path {
if let AttributeValue::Listener(listener) = &attr.value {
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
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
for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener {
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
if !uievent.propagates.get() {
return;
}
}
}
parent_path = template.parent.and_then(|id| self.elements.get(id.0));
} }
} }