Skip to content

unmount breaks event delegation if event is already attached to mount target #17492

@brunnerh

Description

@brunnerh

Describe the bug

The unmount logic removes handlers from the target element, but if the target element is e.g. the document.body this can lead to event delegation no longer working properly.

For document listeners there is some logic that checks whether the listener is still needed but there is no check for the target.

return () => {
for (var event_name of registered_events) {
target.removeEventListener(event_name, handle_event_propagation);
var n = /** @type {number} */ (document_listeners.get(event_name));
if (--n === 0) {
document.removeEventListener(event_name, handle_event_propagation);
document_listeners.delete(event_name);
} else {
document_listeners.set(event_name, n);
}
}

Still, some fairly specific conditions need to be met for this bug to occur, e.g. with SvelteKit the default wrapper div in app.html seems to cause handlers to be attached there instead of on the body, so the issue disappears.

The component with the handler also needed a forwarding instruction (e.g. on:click in my case), otherwise the event handler did not exist before the mount. (I observed this issue with carbon-components-svelte, which ships in syntax for Svelte 3/4 compatibility, hence it has a lot of manual event forwarding.)

Reproduction

Playground

Standalone code/steps
<script>
	import { mount, unmount } from 'svelte';
	import Dialog from './dialog.svelte';
	import Button from './button.svelte';

	function onClick() {
		const dialog = mount(Dialog, {
			target: document.body,
			props: {},
			events: { close() { unmount(dialog); } },
		});
	}
</script>

<Button onclick={onClick}>
	Direct Text
	<div>Child</div>
</Button>
<!-- button.svelte -->
<button {...$$restProps} on:click>
	<slot></slot>
</button>
<!-- dialog.svelte -->
<dialog {@attach node => node.showModal()} on:close>
	<form method="dialog">
		<button>Close</button>
		<p>Dialog</p>
	</form>
</dialog>

Steps:

  • Click button to open dialog
  • Close dialog
  • Click button again:
    • Hitting the child element in the button does not open the dialog
    • Hitting the direct text/padding opens the dialog

Logs

System Info

REPL ?version=5.47.1

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions