Skip to content

Commit e0c2a55

Browse files
committed
make set_cancel_bubble work
1 parent f549648 commit e0c2a55

File tree

3 files changed

+80
-10
lines changed

3 files changed

+80
-10
lines changed

packages/yew/src/virtual_dom/listeners.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ impl Registry {
502502

503503
run_handler(&target);
504504

505-
if unsafe { BUBBLE_EVENTS } {
505+
if unsafe { BUBBLE_EVENTS } && !event.cancel_bubble() {
506506
let mut el = target;
507507
loop {
508508
el = match el.parent_element() {
@@ -523,7 +523,7 @@ mod tests {
523523
use std::marker::PhantomData;
524524

525525
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
526-
use web_sys::{Event, EventInit};
526+
use web_sys::{Event, EventInit, MouseEvent};
527527
wasm_bindgen_test_configure!(run_in_browser);
528528

529529
use crate::{html, html::TargetCast, AppHandle, Component, Context, Html};
@@ -794,6 +794,40 @@ mod tests {
794794
assert_count(&el, 4);
795795
}
796796

797+
#[test]
798+
fn cancel_bubbling() {
799+
struct CencelBubbling;
800+
801+
impl Mixin for CencelBubbling {
802+
fn view<C>(ctx: &Context<C>, state: &State) -> Html
803+
where
804+
C: Component<Message = Message>,
805+
{
806+
html! {
807+
<div onclick={ctx.link().callback(|_| Message::Action)}>
808+
<a onclick={ctx.link().callback(|mouse_event: MouseEvent| {
809+
let event: Event = mouse_event.dyn_into().unwrap();
810+
event.set_cancel_bubble(true);
811+
Message::Action
812+
})}>
813+
{state.action}
814+
</a>
815+
</div>
816+
}
817+
}
818+
}
819+
820+
let (_, el) = init::<CencelBubbling>("a");
821+
822+
assert_count(&el, 0);
823+
824+
el.click();
825+
assert_count(&el, 1);
826+
827+
el.click();
828+
assert_count(&el, 2);
829+
}
830+
797831
fn test_input_listener<E>(make_event: impl Fn() -> E)
798832
where
799833
E: JsCast + std::fmt::Debug,
@@ -858,7 +892,7 @@ mod tests {
858892
test_input_listener(|| {
859893
web_sys::InputEvent::new_with_event_init_dict(
860894
"input",
861-
&web_sys::InputEventInit::new().bubbles(true),
895+
web_sys::InputEventInit::new().bubbles(true),
862896
)
863897
.unwrap()
864898
})
@@ -869,7 +903,7 @@ mod tests {
869903
test_input_listener(|| {
870904
web_sys::Event::new_with_event_init_dict(
871905
"change",
872-
&web_sys::EventInit::new().bubbles(true),
906+
web_sys::EventInit::new().bubbles(true),
873907
)
874908
.unwrap()
875909
})

packages/yew/src/virtual_dom/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ mod benchmarks {
721721
let static_ = Attributes::Static(&[]);
722722
let dynamic = Attributes::Dynamic {
723723
keys: &[],
724-
values: vec![],
724+
values: Box::new([]),
725725
};
726726
let map = Attributes::IndexMap(Default::default());
727727

@@ -776,7 +776,7 @@ mod benchmarks {
776776
fn make_dynamic(values: Vec<AttrValue>) -> Attributes {
777777
Attributes::Dynamic {
778778
keys: sample_keys(),
779-
values: values.into_iter().map(|v| Some(v)).collect(),
779+
values: values.into_iter().map(Some).collect(),
780780
}
781781
}
782782

website/docs/concepts/html/events.md

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ listens for `click` events.
141141
In this section **target ([Event.target](https://developer.mozilla.org/en-US/docs/Web/API/Event/target))**
142142
is always referring to the element at which the event was dispatched from.
143143

144-
145144
This will **not** always be the element at which the `Callback` is placed.
146145
:::
147146

@@ -257,6 +256,7 @@ impl Component for Comp {
257256
}
258257
}
259258
```
259+
260260
:::tip
261261
Use `batch_callback` when you want to conditionally return a value from a `Callback`.
262262
:::
@@ -274,7 +274,6 @@ At this point you might be thinking... when is the dangerous version ok to use?
274274
is safe<sup>1</sup> as we've set the `Callback` on to an element with no children so the target can
275275
only be that same element.
276276

277-
278277
_<sup>1</sup> As safe as anything can be when JS land is involved._
279278

280279
### Using `TargetCast`
@@ -357,12 +356,12 @@ impl Component for Comp {
357356
}
358357
}
359358
```
359+
360360
If you followed the advice above and read about `JsCast`, or know the trait, you can probably
361361
see that `TargetCast::target_dyn_into` feels similar to `JsCast::dyn_into` but specifically
362362
does the cast on the target of the event. `TargetCast::target_unchecked_into` is similar to
363363
`JsCast::unchecked_into`, and as such all the same warnings above `JsCast` apply to `TargetCast`.
364364

365-
366365
### Using `NodeRef`
367366

368367
[`NodeRef`](../components/refs.md) can be used instead of querying the event given to a `Callback`.
@@ -421,6 +420,7 @@ impl Component for Comp {
421420
}
422421
}
423422
```
423+
424424
Using `NodeRef`, you can ignore the event and use the `NodeRef::cast` method to get an
425425
`Option<HtmlInputElement>` - this is optional as calling `cast` before the `NodeRef` has been
426426
set, or when the type doesn't match will return `None`.
@@ -491,6 +491,42 @@ impl Component for Comp {
491491
Which approach you take depends on your component and your preferences, there is no _blessed_ way
492492
per se.
493493

494+
## Event bubbling
495+
496+
:::caution
497+
498+
`event.stop_propagation()` will not work! Read further for solutions
499+
500+
:::
501+
502+
Yew is in early stages of optimizing and rewriting its events system.
503+
Currently all events are managed by using 1 event listener on the top html element available to yew.
504+
Then yew itself works through the event flow.
505+
When you want to stop propagation there are the following two ways.
506+
507+
### set_cancel_bubble
508+
509+
To stop propagation, `set_cancel_bubble(true)` on `web_sys::Event` can be used.
510+
511+
```rust ,ignore
512+
// In your event handler
513+
let event: Event = mouse_event.dyn_into().unwrap();
514+
event.set_cancel_bubble(true);
515+
```
516+
517+
### set_event_bubbling
518+
519+
To improve yew performance it is recommended to use `set_event_bubbling` before the start of the app to disable bubbling all together, though there are known issues with some in-house yew components like `yew_router::Link`.
520+
521+
```rust ,ignore
522+
use yew::prelude::*;
523+
524+
fn main() {
525+
yew::set_event_bubbling(false);
526+
yew::start_app::<app::App>();
527+
}
528+
```
529+
494530
## Manual event listener
495531

496532
You may want to listen to an event that is not supported by Yew's `html` macro, see the
@@ -693,4 +729,4 @@ component is about to be destroyed as the `EventListener` has a `drop` implement
693729
which will remove the event listener from the element.
694730

695731
For more information on `EventListener`, see the
696-
[gloo_events docs.rs](https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html).
732+
[gloo_events docs.rs](https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html).

0 commit comments

Comments
 (0)