mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-09-22 15:21:58 +00:00
Merge pull request #452 from Demonthos/fix_nonbubbling_web_events
Fix nonbubbling web events
This commit is contained in:
commit
83288e274f
10 changed files with 368 additions and 27 deletions
|
@ -63,6 +63,9 @@ pub struct UserEvent {
|
|||
/// The event type IE "onclick" or "onmouseover"
|
||||
pub name: &'static str,
|
||||
|
||||
/// If the event is bubbles up through the vdom
|
||||
pub bubbles: bool,
|
||||
|
||||
/// The event data to be passed onto the event handler
|
||||
pub data: Arc<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
|
|
@ -335,7 +335,7 @@ impl ScopeArena {
|
|||
log::trace!("calling listener {:?}", listener.event);
|
||||
if state.canceled.get() {
|
||||
// stop bubbling if canceled
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
|
@ -349,6 +349,10 @@ impl ScopeArena {
|
|||
data: event.data.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if !event.bubbles {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::any::Any;
|
|||
use std::sync::Arc;
|
||||
|
||||
use dioxus_core::{ElementId, EventPriority, UserEvent};
|
||||
use dioxus_html::event_bubbles;
|
||||
use dioxus_html::on::*;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
|
@ -47,8 +48,8 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
|||
} = serde_json::from_value(val).unwrap();
|
||||
|
||||
let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
|
||||
|
||||
let name = event_name_from_type(&event);
|
||||
|
||||
let event = make_synthetic_event(&event, contents);
|
||||
|
||||
UserEvent {
|
||||
|
@ -56,6 +57,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
|||
priority: EventPriority::Low,
|
||||
scope_id: None,
|
||||
element: mounted_dom_id,
|
||||
bubbles: event_bubbles(name),
|
||||
data: event,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1324,3 +1324,91 @@ pub(crate) fn _event_meta(event: &UserEvent) -> (bool, EventPriority) {
|
|||
_ => (true, Low),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event_bubbles(evt: &str) -> bool {
|
||||
match evt {
|
||||
"copy" => true,
|
||||
"cut" => true,
|
||||
"paste" => true,
|
||||
"compositionend" => true,
|
||||
"compositionstart" => true,
|
||||
"compositionupdate" => true,
|
||||
"keydown" => true,
|
||||
"keypress" => true,
|
||||
"keyup" => true,
|
||||
"focus" => false,
|
||||
"focusout" => true,
|
||||
"focusin" => true,
|
||||
"blur" => false,
|
||||
"change" => true,
|
||||
"input" => true,
|
||||
"invalid" => true,
|
||||
"reset" => true,
|
||||
"submit" => true,
|
||||
"click" => true,
|
||||
"contextmenu" => true,
|
||||
"doubleclick" => true,
|
||||
"dblclick" => true,
|
||||
"drag" => true,
|
||||
"dragend" => true,
|
||||
"dragenter" => false,
|
||||
"dragexit" => false,
|
||||
"dragleave" => true,
|
||||
"dragover" => true,
|
||||
"dragstart" => true,
|
||||
"drop" => true,
|
||||
"mousedown" => true,
|
||||
"mouseenter" => false,
|
||||
"mouseleave" => false,
|
||||
"mousemove" => true,
|
||||
"mouseout" => true,
|
||||
"scroll" => false,
|
||||
"mouseover" => true,
|
||||
"mouseup" => true,
|
||||
"pointerdown" => true,
|
||||
"pointermove" => true,
|
||||
"pointerup" => true,
|
||||
"pointercancel" => true,
|
||||
"gotpointercapture" => true,
|
||||
"lostpointercapture" => true,
|
||||
"pointerenter" => false,
|
||||
"pointerleave" => false,
|
||||
"pointerover" => true,
|
||||
"pointerout" => true,
|
||||
"select" => true,
|
||||
"touchcancel" => true,
|
||||
"touchend" => true,
|
||||
"touchmove" => true,
|
||||
"touchstart" => true,
|
||||
"wheel" => true,
|
||||
"abort" => false,
|
||||
"canplay" => true,
|
||||
"canplaythrough" => true,
|
||||
"durationchange" => true,
|
||||
"emptied" => true,
|
||||
"encrypted" => true,
|
||||
"ended" => true,
|
||||
"error" => false,
|
||||
"loadeddata" => true,
|
||||
"loadedmetadata" => true,
|
||||
"loadstart" => false,
|
||||
"pause" => true,
|
||||
"play" => true,
|
||||
"playing" => true,
|
||||
"progress" => false,
|
||||
"ratechange" => true,
|
||||
"seeked" => true,
|
||||
"seeking" => true,
|
||||
"stalled" => true,
|
||||
"suspend" => true,
|
||||
"timeupdate" => true,
|
||||
"volumechange" => true,
|
||||
"waiting" => true,
|
||||
"animationstart" => true,
|
||||
"animationend" => true,
|
||||
"animationiteration" => true,
|
||||
"transitionend" => true,
|
||||
"toggle" => true,
|
||||
_ => panic!("unsupported event type {:?}", evt),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,10 +48,16 @@ extern "C" {
|
|||
pub fn CreatePlaceholder(this: &Interpreter, root: u64);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn NewEventListener(this: &Interpreter, name: &str, root: u64, handler: &Function);
|
||||
pub fn NewEventListener(
|
||||
this: &Interpreter,
|
||||
name: &str,
|
||||
root: u64,
|
||||
handler: &Function,
|
||||
bubbles: bool,
|
||||
);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str);
|
||||
pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str, bubbles: bool);
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn SetText(this: &Interpreter, root: u64, text: JsValue);
|
||||
|
|
|
@ -5,11 +5,64 @@ export function main() {
|
|||
window.ipc.postMessage(serializeIpcMessage("initialize"));
|
||||
}
|
||||
}
|
||||
|
||||
class ListenerMap {
|
||||
constructor(root) {
|
||||
// bubbling events can listen at the root element
|
||||
this.global = {};
|
||||
// non bubbling events listen at the element the listener was created at
|
||||
this.local = {};
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
createBubbling(event_name, handler) {
|
||||
if (this.global[event_name] === undefined) {
|
||||
this.global[event_name] = {};
|
||||
this.global[event_name].active = 1;
|
||||
this.global[event_name].callback = handler;
|
||||
this.root.addEventListener(event_name, handler);
|
||||
} else {
|
||||
this.global[event_name].active++;
|
||||
}
|
||||
}
|
||||
|
||||
createNonBubbling(event_name, element, handler) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
if (!this.local[id]) {
|
||||
this.local[id] = {};
|
||||
}
|
||||
this.local[id][event_name] = handler;
|
||||
element.addEventListener(event_name, handler);
|
||||
}
|
||||
|
||||
removeBubbling(event_name) {
|
||||
this.global[event_name].active--;
|
||||
if (this.global[event_name].active === 0) {
|
||||
this.root.removeEventListener(event_name, this.global[event_name].callback);
|
||||
delete this.global[event_name];
|
||||
}
|
||||
}
|
||||
|
||||
removeNonBubbling(element, event_name) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id][event_name];
|
||||
if (this.local[id].length === 0) {
|
||||
delete this.local[id];
|
||||
}
|
||||
element.removeEventListener(event_name, handler);
|
||||
}
|
||||
|
||||
removeAllNonBubbling(element) {
|
||||
const id = element.getAttribute("data-dioxus-id");
|
||||
delete this.local[id];
|
||||
}
|
||||
}
|
||||
|
||||
export class Interpreter {
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.stack = [root];
|
||||
this.listeners = {};
|
||||
this.listeners = new ListenerMap(root);
|
||||
this.handlers = {};
|
||||
this.lastNodeWasText = false;
|
||||
this.nodes = [root];
|
||||
|
@ -40,6 +93,7 @@ export class Interpreter {
|
|||
ReplaceWith(root_id, m) {
|
||||
let root = this.nodes[root_id];
|
||||
let els = this.stack.splice(this.stack.length - m);
|
||||
this.listeners.removeAllNonBubbling(root);
|
||||
root.replaceWith(...els);
|
||||
}
|
||||
InsertAfter(root, n) {
|
||||
|
@ -54,6 +108,7 @@ export class Interpreter {
|
|||
}
|
||||
Remove(root) {
|
||||
let node = this.nodes[root];
|
||||
this.listeners.removeAllNonBubbling(node);
|
||||
if (node !== undefined) {
|
||||
node.remove();
|
||||
}
|
||||
|
@ -79,25 +134,24 @@ export class Interpreter {
|
|||
this.stack.push(el);
|
||||
this.nodes[root] = el;
|
||||
}
|
||||
NewEventListener(event_name, root, handler) {
|
||||
NewEventListener(event_name, root, handler, bubbles) {
|
||||
const element = this.nodes[root];
|
||||
element.setAttribute("data-dioxus-id", `${root}`);
|
||||
if (this.listeners[event_name] === undefined) {
|
||||
this.listeners[event_name] = 1;
|
||||
this.handlers[event_name] = handler;
|
||||
this.root.addEventListener(event_name, handler);
|
||||
} else {
|
||||
this.listeners[event_name]++;
|
||||
if (bubbles) {
|
||||
this.listeners.createBubbling(event_name, handler);
|
||||
}
|
||||
else {
|
||||
this.listeners.createNonBubbling(event_name, element, handler);
|
||||
}
|
||||
}
|
||||
RemoveEventListener(root, event_name) {
|
||||
RemoveEventListener(root, event_name, bubbles) {
|
||||
const element = this.nodes[root];
|
||||
element.removeAttribute(`data-dioxus-id`);
|
||||
this.listeners[event_name]--;
|
||||
if (this.listeners[event_name] === 0) {
|
||||
this.root.removeEventListener(event_name, this.handlers[event_name]);
|
||||
delete this.listeners[event_name];
|
||||
delete this.handlers[event_name];
|
||||
if (bubbles) {
|
||||
this.listeners.removeBubbling(event_name)
|
||||
}
|
||||
else {
|
||||
this.listeners.removeNonBubbling(element, event_name);
|
||||
}
|
||||
}
|
||||
SetText(root, text) {
|
||||
|
@ -198,12 +252,9 @@ export class Interpreter {
|
|||
this.RemoveEventListener(edit.root, edit.event_name);
|
||||
break;
|
||||
case "NewEventListener":
|
||||
console.log(this.listeners);
|
||||
|
||||
// this handler is only provided on desktop implementations since this
|
||||
// method is not used by the web implementation
|
||||
let handler = (event) => {
|
||||
console.log(event);
|
||||
|
||||
let target = event.target;
|
||||
if (target != null) {
|
||||
|
@ -292,7 +343,8 @@ export class Interpreter {
|
|||
);
|
||||
}
|
||||
};
|
||||
this.NewEventListener(edit.event_name, edit.root, handler);
|
||||
this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name));
|
||||
|
||||
break;
|
||||
case "SetText":
|
||||
this.SetText(edit.root, edit.text);
|
||||
|
@ -607,3 +659,172 @@ const bool_attrs = {
|
|||
selected: true,
|
||||
truespeed: true,
|
||||
};
|
||||
|
||||
function event_bubbles(event) {
|
||||
switch (event) {
|
||||
case "copy":
|
||||
return true;
|
||||
case "cut":
|
||||
return true;
|
||||
case "paste":
|
||||
return true;
|
||||
case "compositionend":
|
||||
return true;
|
||||
case "compositionstart":
|
||||
return true;
|
||||
case "compositionupdate":
|
||||
return true;
|
||||
case "keydown":
|
||||
return true;
|
||||
case "keypress":
|
||||
return true;
|
||||
case "keyup":
|
||||
return true;
|
||||
case "focus":
|
||||
return false;
|
||||
case "focusout":
|
||||
return true;
|
||||
case "focusin":
|
||||
return true;
|
||||
case "blur":
|
||||
return false;
|
||||
case "change":
|
||||
return true;
|
||||
case "input":
|
||||
return true;
|
||||
case "invalid":
|
||||
return true;
|
||||
case "reset":
|
||||
return true;
|
||||
case "submit":
|
||||
return true;
|
||||
case "click":
|
||||
return true;
|
||||
case "contextmenu":
|
||||
return true;
|
||||
case "doubleclick":
|
||||
return true;
|
||||
case "dblclick":
|
||||
return true;
|
||||
case "drag":
|
||||
return true;
|
||||
case "dragend":
|
||||
return true;
|
||||
case "dragenter":
|
||||
return false;
|
||||
case "dragexit":
|
||||
return false;
|
||||
case "dragleave":
|
||||
return true;
|
||||
case "dragover":
|
||||
return true;
|
||||
case "dragstart":
|
||||
return true;
|
||||
case "drop":
|
||||
return true;
|
||||
case "mousedown":
|
||||
return true;
|
||||
case "mouseenter":
|
||||
return false;
|
||||
case "mouseleave":
|
||||
return false;
|
||||
case "mousemove":
|
||||
return true;
|
||||
case "mouseout":
|
||||
return true;
|
||||
case "scroll":
|
||||
return false;
|
||||
case "mouseover":
|
||||
return true;
|
||||
case "mouseup":
|
||||
return true;
|
||||
case "pointerdown":
|
||||
return true;
|
||||
case "pointermove":
|
||||
return true;
|
||||
case "pointerup":
|
||||
return true;
|
||||
case "pointercancel":
|
||||
return true;
|
||||
case "gotpointercapture":
|
||||
return true;
|
||||
case "lostpointercapture":
|
||||
return true;
|
||||
case "pointerenter":
|
||||
return false;
|
||||
case "pointerleave":
|
||||
return false;
|
||||
case "pointerover":
|
||||
return true;
|
||||
case "pointerout":
|
||||
return true;
|
||||
case "select":
|
||||
return true;
|
||||
case "touchcancel":
|
||||
return true;
|
||||
case "touchend":
|
||||
return true;
|
||||
case "touchmove":
|
||||
return true;
|
||||
case "touchstart":
|
||||
return true;
|
||||
case "wheel":
|
||||
return true;
|
||||
case "abort":
|
||||
return false;
|
||||
case "canplay":
|
||||
return true;
|
||||
case "canplaythrough":
|
||||
return true;
|
||||
case "durationchange":
|
||||
return true;
|
||||
case "emptied":
|
||||
return true;
|
||||
case "encrypted":
|
||||
return true;
|
||||
case "ended":
|
||||
return true;
|
||||
case "error":
|
||||
return false;
|
||||
case "loadeddata":
|
||||
return true;
|
||||
case "loadedmetadata":
|
||||
return true;
|
||||
case "loadstart":
|
||||
return false;
|
||||
case "pause":
|
||||
return true;
|
||||
case "play":
|
||||
return true;
|
||||
case "playing":
|
||||
return true;
|
||||
case "progress":
|
||||
return false;
|
||||
case "ratechange":
|
||||
return true;
|
||||
case "seeked":
|
||||
return true;
|
||||
case "seeking":
|
||||
return true;
|
||||
case "stalled":
|
||||
return true;
|
||||
case "suspend":
|
||||
return true;
|
||||
case "timeupdate":
|
||||
return true;
|
||||
case "volumechange":
|
||||
return true;
|
||||
case "waiting":
|
||||
return true;
|
||||
case "animationstart":
|
||||
return true;
|
||||
case "animationend":
|
||||
return true;
|
||||
case "animationiteration":
|
||||
return true;
|
||||
case "transitionend":
|
||||
return true;
|
||||
case "toggle":
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ use std::any::Any;
|
|||
use std::sync::Arc;
|
||||
|
||||
use dioxus_core::{ElementId, EventPriority, UserEvent};
|
||||
use dioxus_html::event_bubbles;
|
||||
use dioxus_html::on::*;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
|
@ -46,6 +47,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
|||
scope_id: None,
|
||||
element: mounted_dom_id,
|
||||
data: event,
|
||||
bubbles: event_bubbles(name),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, S
|
|||
use dioxus_html::input_data::keyboard_types::Modifiers;
|
||||
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
|
||||
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
|
||||
use dioxus_html::{on::*, KeyCode};
|
||||
use dioxus_html::{event_bubbles, on::*, KeyCode};
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{RefCell, RefMut},
|
||||
|
@ -187,6 +187,7 @@ impl InnerInputState {
|
|||
name: "focus",
|
||||
element: Some(id),
|
||||
data: Arc::new(FocusData {}),
|
||||
bubbles: event_bubbles("focus"),
|
||||
});
|
||||
resolved_events.push(UserEvent {
|
||||
scope_id: None,
|
||||
|
@ -194,6 +195,7 @@ impl InnerInputState {
|
|||
name: "focusin",
|
||||
element: Some(id),
|
||||
data: Arc::new(FocusData {}),
|
||||
bubbles: event_bubbles("focusin"),
|
||||
});
|
||||
}
|
||||
if let Some(id) = old_focus {
|
||||
|
@ -203,6 +205,7 @@ impl InnerInputState {
|
|||
name: "focusout",
|
||||
element: Some(id),
|
||||
data: Arc::new(FocusData {}),
|
||||
bubbles: event_bubbles("focusout"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -248,6 +251,7 @@ impl InnerInputState {
|
|||
name,
|
||||
element: Some(node.id),
|
||||
data,
|
||||
bubbles: event_bubbles(name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -649,6 +653,7 @@ impl RinkInputHandler {
|
|||
name: event,
|
||||
element: Some(node.id),
|
||||
data: data.clone(),
|
||||
bubbles: event_bubbles(event),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
//! - Partial delegation?>
|
||||
|
||||
use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
|
||||
use dioxus_html::event_bubbles;
|
||||
use dioxus_interpreter_js::Interpreter;
|
||||
use js_sys::Function;
|
||||
use std::{any::Any, rc::Rc, sync::Arc};
|
||||
|
@ -45,6 +46,7 @@ impl WebsysDom {
|
|||
element: Some(ElementId(id)),
|
||||
scope_id: None,
|
||||
priority: dioxus_core::EventPriority::Medium,
|
||||
bubbles: event.bubbles(),
|
||||
});
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
|
@ -64,6 +66,7 @@ impl WebsysDom {
|
|||
element: None,
|
||||
scope_id: None,
|
||||
priority: dioxus_core::EventPriority::Low,
|
||||
bubbles: event.bubbles(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -121,12 +124,17 @@ impl WebsysDom {
|
|||
event_name, root, ..
|
||||
} => {
|
||||
let handler: &Function = self.handler.as_ref().unchecked_ref();
|
||||
self.interpreter.NewEventListener(event_name, root, handler);
|
||||
self.interpreter.NewEventListener(
|
||||
event_name,
|
||||
root,
|
||||
handler,
|
||||
event_bubbles(event_name),
|
||||
);
|
||||
}
|
||||
|
||||
DomEdit::RemoveEventListener { root, event } => {
|
||||
self.interpreter.RemoveEventListener(root, event)
|
||||
}
|
||||
DomEdit::RemoveEventListener { root, event } => self
|
||||
.interpreter
|
||||
.RemoveEventListener(root, event, event_bubbles(event)),
|
||||
|
||||
DomEdit::RemoveAttribute { root, name, ns } => {
|
||||
self.interpreter.RemoveAttribute(root, name, ns)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::dom::WebsysDom;
|
||||
use dioxus_core::{VNode, VirtualDom};
|
||||
use dioxus_html::event_bubbles;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Comment, Element, Node, Text};
|
||||
|
||||
|
@ -111,6 +112,7 @@ impl WebsysDom {
|
|||
listener.event,
|
||||
listener.mounted_node.get().unwrap().as_u64(),
|
||||
self.handler.as_ref().unchecked_ref(),
|
||||
event_bubbles(listener.event),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue