Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc05abb5

Browse files
committed
fix: don't delegate events on custom elements, solve edge case stopPropagation issue
- don't delegate events on custom elements- still invoke listener for cancelled event on the element where it was cancelled: when you do `stopPropagation`, `event.cancelBubble` becomes `true`. We can't use this as an indicator to not invoke a listener directly, because the listner could be on the element where propagation was cancelled, i.e. it should still run for that listener. Instead, adjust the event propagation algorithm to detect when a delegated event listener caused the event to be cancelledfixes#14704
1 parentc4e9faa commitc05abb5

File tree

7 files changed

+69
-4
lines changed

7 files changed

+69
-4
lines changed

‎.changeset/kind-wombats-jam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte':patch
3+
---
4+
5+
fix: don't delegate events on custom elements

‎.changeset/six-steaks-provide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte':patch
3+
---
4+
5+
fix: still invoke listener for cancelled event on the element where it was cancelled

‎packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
get_attribute_expression,
88
is_event_attribute
99
}from'../../../utils/ast.js';
10+
import{is_custom_element_node}from'../../nodes.js';
1011
import{mark_subtree_dynamic}from'./shared/fragment.js';
1112

1213
/**
@@ -67,15 +68,21 @@ export function Attribute(node, context) {
6768
}
6869

6970
if(is_event_attribute(node)){
70-
constparent=context.path.at(-1);
7171
if(parent?.type==='RegularElement'||parent?.type==='SvelteElement'){
7272
context.state.analysis.uses_event_attributes=true;
7373
}
7474

7575
constexpression=get_attribute_expression(node);
7676
constdelegated_event=get_delegated_event(node.name.slice(2),expression,context);
7777

78-
if(delegated_event!==null){
78+
if(
79+
delegated_event!==null&&
80+
// We can't assume that the events from within the shadow root bubble beyond it.
81+
// If someone dispatches them without the composed option, they won't. Also
82+
// people could repurpose the event names to do something else, or call stopPropagation
83+
// on the shadow root so it doesn't bubble beyond it.
84+
!(parent?.type==='RegularElement'&&is_custom_element_node(parent))
85+
){
7986
if(delegated_event.hoisted){
8087
delegated_event.function.metadata.hoisted=true;
8188
}

‎packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ export function set_attributes(
318318
constopts={};
319319
constevent_handle_key='$$'+key;
320320
letevent_name=key.slice(2);
321-
vardelegated=is_delegated(event_name);
321+
// Events on custom elements can be anything, we can't assume they bubble
322+
vardelegated=!is_custom_element&&is_delegated(event_name);
322323

323324
if(is_capture_event(event_name)){
324325
event_name=event_name.slice(0,-7);

‎packages/svelte/src/internal/client/dom/elements/events.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ export function create_event(event_name, dom, handler, options) {
6161
// Only call in the bubble phase, else delegated events would be called before the capturing events
6262
handle_event_propagation.call(dom,event);
6363
}
64-
if(!event.cancelBubble){
64+
65+
//@ts-expect-error Use this instead of cancelBubble, because cancelBubble is also true if
66+
// we're the last element on which the event will be handled.
67+
if(!event.__cancelled||event.__cancelled===dom){
6568
returnwithout_reactive_context(()=>{
6669
returnhandler.call(this,event);
6770
});
@@ -171,6 +174,8 @@ export function handle_event_propagation(event) {
171174
// chain in case someone manually dispatches the same event object again.
172175
//@ts-expect-error
173176
event.__root=handler_element;
177+
//@ts-expect-error
178+
event.__cancelled=null;
174179
return;
175180
}
176181

@@ -216,6 +221,7 @@ export function handle_event_propagation(event) {
216221
set_active_effect(null);
217222

218223
try{
224+
varcancelled=event.cancelBubble;
219225
/**
220226
*@type {unknown}
221227
*/
@@ -253,6 +259,10 @@ export function handle_event_propagation(event) {
253259
}
254260
}
255261
if(event.cancelBubble||parent_element===handler_element||parent_element===null){
262+
if(!cancelled&&event.cancelBubble){
263+
//@ts-expect-error
264+
event.__cancelled=current_target;
265+
}
256266
break;
257267
}
258268
current_target=parent_element;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import{test}from'../../test';
2+
3+
exportdefaulttest({
4+
mode:['client'],
5+
asynctest({ assert, target, logs}){
6+
const[btn1,btn2]=[...target.querySelectorAll('custom-element')].map((c)=>
7+
c.shadowRoot?.querySelector('button')
8+
);
9+
10+
btn1?.click();
11+
awaitPromise.resolve();
12+
assert.deepEqual(logs,['reached shadow root1']);
13+
14+
btn2?.click();
15+
awaitPromise.resolve();
16+
assert.deepEqual(logs,['reached shadow root1','reached shadow root2']);
17+
}
18+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script>
2+
classCustomElementextendsHTMLElement {
3+
constructor() {
4+
super();
5+
this.attachShadow({ mode:'open' });
6+
this.shadowRoot.innerHTML='<button>click me</button>';
7+
// Looks weird, but some custom element implementations actually do this
8+
// to prevent unwanted side upwards event propagation
9+
this.addEventListener('click', (e)=>e.stopPropagation());
10+
}
11+
}
12+
13+
customElements.define('custom-element', CustomElement);
14+
</script>
15+
16+
<divonclick={()=>console.log('bubbled beyond shadow root')}>
17+
<custom-elementonclick={()=>console.log('reached shadow root1')}></custom-element>
18+
<custom-element {...{onclick:() => console.log('reached shadow root2')}}></custom-element>
19+
</div>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp