Skip to content

Commit 0119f25

Browse files
authored
fix: better event delegation behavior around multiple listeners (#12024)
follow up to #12014, which wasn't quite the right fix. It would mean that delegated listeners between two manual listeners wouldn't be called at the correct time.
1 parent de3bb78 commit 0119f25

File tree

5 files changed

+32
-28
lines changed

5 files changed

+32
-28
lines changed

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function create_event(event_name, dom, handler, options) {
4242
* @this {EventTarget}
4343
*/
4444
function target_handler(/** @type {Event} */ event) {
45-
if (!options.capture && /** @type {Event & {__root: any}} */ (event).__root === undefined) {
45+
if (!options.capture) {
4646
// Only call in the bubble phase, else delegated events would be called before the capturing events
4747
handle_event_propagation(dom, event);
4848
}
@@ -165,13 +165,15 @@ export function handle_event_propagation(handler_element, event) {
165165
}
166166

167167
if (at_idx <= handler_idx) {
168-
// +1 because at_idx is the element which was already handled, and there can only be one delegated event per element.
169-
// Avoids on:click and onclick on the same event resulting in onclick being fired twice.
170-
path_idx = at_idx + 1;
168+
path_idx = at_idx;
171169
}
172170
}
173171

174172
current_target = /** @type {Element} */ (path[path_idx] || event.target);
173+
// there can only be one delegated event per element, and we either already handled the current target,
174+
// or this is the very first target in the chain which has a non-delegated listener, in which case it's safe
175+
// to handle a possible delegated event on it later (through the root delegation listener for example).
176+
if (current_target === handler_element) return;
175177

176178
// Proxy currentTarget to correct target
177179
define_property(event, 'currentTarget', {
@@ -190,6 +192,7 @@ export function handle_event_propagation(handler_element, event) {
190192
* @type {unknown[]}
191193
*/
192194
var other_errors = [];
195+
193196
while (current_target !== null) {
194197
/** @type {null | Element} */
195198
var parent_element =
@@ -214,12 +217,7 @@ export function handle_event_propagation(handler_element, event) {
214217
throw_error = error;
215218
}
216219
}
217-
if (
218-
event.cancelBubble ||
219-
parent_element === handler_element ||
220-
parent_element === null ||
221-
current_target === handler_element
222-
) {
220+
if (event.cancelBubble || parent_element === handler_element || parent_element === null) {
223221
break;
224222
}
225223
current_target = parent_element;

packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-5/_config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ export default test({
77
btn?.click();
88
await Promise.resolve();
99
assert.deepEqual(logs, [
10-
'button onclick',
1110
'button on:click',
11+
'button onclick',
1212
'inner div on:click',
1313
'outer div onclick'
1414
]);

packages/svelte/tests/runtime-runes/samples/event-on-2/_config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ export default test({
1111

1212
b1?.dispatchEvent(keydown);
1313
flushSync();
14-
assert.deepEqual(logs, ['parent keydown']);
14+
assert.deepEqual(logs, ['one', 'two', 'three', 'parent keydown', 'wrapper']);
1515
}
1616
});
Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
<script>
2-
import { on } from "svelte/events";
2+
import { on } from 'svelte/events';
3+
import Wrapper from './wrapper.svelte';
34
4-
function handleParentKeyDown() {
5-
console.log("parent keydown");
6-
}
7-
function keydownOne(node) {
8-
on(node, "keydown", (e) => {});
9-
}
10-
function keydownTwo(node) {
11-
on(node, "keydown", (e) => {});
12-
}
13-
function keydownThree(node) {
14-
on(node, "keydown", (e) => {});
15-
}
5+
function handleParentKeyDown() {
6+
console.log('parent keydown');
7+
}
8+
function keydownOne(node) {
9+
on(node, 'keydown', (e) => console.log('one'));
10+
}
11+
function keydownTwo(node) {
12+
on(node, 'keydown', (e) => console.log('two'));
13+
}
14+
function keydownThree(node) {
15+
on(node, 'keydown', (e) => console.log('three'));
16+
}
1617
</script>
1718

18-
<div onkeydown={handleParentKeyDown}>
19-
<button use:keydownOne use:keydownTwo use:keydownThree>button</button>
20-
</div>
19+
<Wrapper>
20+
<div onkeydown={handleParentKeyDown}>
21+
<button use:keydownOne use:keydownTwo use:keydownThree>button</button>
22+
</div>
23+
</Wrapper>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div on:keydown={() => console.log('wrapper')}>
2+
<slot />
3+
</div>

0 commit comments

Comments
 (0)