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} * @this {EventTarget}
*/ */
function target_handler(/** @type {Event} */ event) { 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 // Only call in the bubble phase, else delegated events would be called before the capturing events
handle_event_propagation(dom, event); handle_event_propagation(dom, event);
} }
@ -165,13 +165,15 @@ export function handle_event_propagation(handler_element, event) {
} }
if (at_idx <= handler_idx) { 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. path_idx = at_idx;
// Avoids on:click and onclick on the same event resulting in onclick being fired twice.
path_idx = at_idx + 1;
} }
} }
current_target = /** @type {Element} */ (path[path_idx] || event.target); 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 // Proxy currentTarget to correct target
define_property(event, 'currentTarget', { define_property(event, 'currentTarget', {
@ -190,6 +192,7 @@ export function handle_event_propagation(handler_element, event) {
* @type {unknown[]} * @type {unknown[]}
*/ */
var other_errors = []; var other_errors = [];
while (current_target !== null) { while (current_target !== null) {
/** @type {null | Element} */ /** @type {null | Element} */
var parent_element = var parent_element =
@ -214,12 +217,7 @@ export function handle_event_propagation(handler_element, event) {
throw_error = error; throw_error = error;
} }
} }
if ( if (event.cancelBubble || parent_element === handler_element || parent_element === null) {
event.cancelBubble ||
parent_element === handler_element ||
parent_element === null ||
current_target === handler_element
) {
break; break;
} }
current_target = parent_element; current_target = parent_element;

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

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

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