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.
pull/12026/head
Simon H 1 year ago committed by GitHub
parent de3bb78da4
commit 0119f25125
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -42,7 +42,7 @@ export function create_event(event_name, dom, handler, options) {
* @this {EventTarget}
*/
function target_handler(/** @type {Event} */ event) {
if (!options.capture && /** @type {Event & {__root: any}} */ (event).__root === undefined) {
if (!options.capture) {
// Only call in the bubble phase, else delegated events would be called before the capturing events
handle_event_propagation(dom, event);
}
@ -165,13 +165,15 @@ export function handle_event_propagation(handler_element, event) {
}
if (at_idx <= handler_idx) {
// +1 because at_idx is the element which was already handled, and there can only be one delegated event per element.
// Avoids on:click and onclick on the same event resulting in onclick being fired twice.
path_idx = at_idx + 1;
path_idx = at_idx;
}
}
current_target = /** @type {Element} */ (path[path_idx] || event.target);
// there can only be one delegated event per element, and we either already handled the current target,
// or this is the very first target in the chain which has a non-delegated listener, in which case it's safe
// to handle a possible delegated event on it later (through the root delegation listener for example).
if (current_target === handler_element) return;
// Proxy currentTarget to correct target
define_property(event, 'currentTarget', {
@ -190,6 +192,7 @@ export function handle_event_propagation(handler_element, event) {
* @type {unknown[]}
*/
var other_errors = [];
while (current_target !== null) {
/** @type {null | Element} */
var parent_element =
@ -214,12 +217,7 @@ export function handle_event_propagation(handler_element, event) {
throw_error = error;
}
}
if (
event.cancelBubble ||
parent_element === handler_element ||
parent_element === null ||
current_target === handler_element
) {
if (event.cancelBubble || parent_element === handler_element || parent_element === null) {
break;
}
current_target = parent_element;

@ -7,8 +7,8 @@ export default test({
btn?.click();
await Promise.resolve();
assert.deepEqual(logs, [
'button onclick',
'button on:click',
'button onclick',
'inner div on:click',
'outer div onclick'
]);

@ -11,6 +11,6 @@ export default test({
b1?.dispatchEvent(keydown);
flushSync();
assert.deepEqual(logs, ['parent keydown']);
assert.deepEqual(logs, ['one', 'two', 'three', 'parent keydown', 'wrapper']);
}
});

@ -1,20 +1,23 @@
<script>
import { on } from "svelte/events";
import { on } from 'svelte/events';
import Wrapper from './wrapper.svelte';
function handleParentKeyDown() {
console.log("parent keydown");
}
function keydownOne(node) {
on(node, "keydown", (e) => {});
}
function keydownTwo(node) {
on(node, "keydown", (e) => {});
}
function keydownThree(node) {
on(node, "keydown", (e) => {});
}
function handleParentKeyDown() {
console.log('parent keydown');
}
function keydownOne(node) {
on(node, 'keydown', (e) => console.log('one'));
}
function keydownTwo(node) {
on(node, 'keydown', (e) => console.log('two'));
}
function keydownThree(node) {
on(node, 'keydown', (e) => console.log('three'));
}
</script>
<div onkeydown={handleParentKeyDown}>
<button use:keydownOne use:keydownTwo use:keydownThree>button</button>
</div>
<Wrapper>
<div onkeydown={handleParentKeyDown}>
<button use:keydownOne use:keydownTwo use:keydownThree>button</button>
</div>
</Wrapper>

@ -0,0 +1,3 @@
<div on:keydown={() => console.log('wrapper')}>
<slot />
</div>
Loading…
Cancel
Save