fix: more event handling tweaks

- ensure we only have a single document listener per event+runtime
- add `<svelte:body>` listeners to `before_init` similar to the document/window elements
- move some code into `events.js` where it belongs
pull/12383/head
Simon Holthausen 2 years ago
parent 47a073e0db
commit 43ab315648

@ -1280,7 +1280,12 @@ function serialize_event(node, context) {
} }
const parent = /** @type {import('#compiler').SvelteNode} */ (context.path.at(-1)); const parent = /** @type {import('#compiler').SvelteNode} */ (context.path.at(-1));
if (parent.type === 'SvelteDocument' || parent.type === 'SvelteWindow') { if (
parent.type === 'SvelteDocument' ||
parent.type === 'SvelteWindow' ||
parent.type === 'SvelteBody'
) {
// These nodes are above the component tree, and its events should run parent first
state.before_init.push(statement); state.before_init.push(statement);
} else { } else {
state.after_update.push(statement); state.after_update.push(statement);

@ -1,9 +1,14 @@
import { teardown } from '../../reactivity/effects.js'; import { teardown } from '../../reactivity/effects.js';
import { all_registered_events, root_event_handles } from '../../render.js';
import { define_property, is_array } from '../../utils.js'; import { define_property, is_array } from '../../utils.js';
import { hydrating } from '../hydration.js'; import { hydrating } from '../hydration.js';
import { queue_micro_task } from '../task.js'; import { queue_micro_task } from '../task.js';
/** @type {Set<string>} */
export const all_registered_events = new Set();
/** @type {Set<(events: Array<string>) => void>} */
export const root_event_handles = new Set();
/** /**
* SSR adds onload and onerror attributes to catch those events before the hydration. * SSR adds onload and onerror attributes to catch those events before the hydration.
* This function detects those cases, removes the attributes and replays the events. * This function detects those cases, removes the attributes and replays the events.

@ -1,8 +1,7 @@
/** @import { Effect, TemplateNode } from '#client' */ /** @import { TemplateNode } from '#client' */
import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js'; import { hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { init_array_prototype_warnings } from '../dev/equality.js'; import { init_array_prototype_warnings } from '../dev/equality.js';
import { current_effect } from '../runtime.js';
// export these for reference in the compiled code, making global name deduplication unnecessary // export these for reference in the compiled code, making global name deduplication unnecessary
/** @type {Window} */ /** @type {Window} */

@ -16,19 +16,17 @@ import {
set_hydrating set_hydrating
} from './dom/hydration.js'; } from './dom/hydration.js';
import { array_from } from './utils.js'; import { array_from } from './utils.js';
import { handle_event_propagation } from './dom/elements/events.js'; import {
all_registered_events,
handle_event_propagation,
root_event_handles
} from './dom/elements/events.js';
import { reset_head_anchor } from './dom/blocks/svelte-head.js'; import { reset_head_anchor } from './dom/blocks/svelte-head.js';
import * as w from './warnings.js'; import * as w from './warnings.js';
import * as e from './errors.js'; import * as e from './errors.js';
import { validate_component } from '../shared/validate.js'; import { validate_component } from '../shared/validate.js';
import { assign_nodes } from './dom/template.js'; import { assign_nodes } from './dom/template.js';
/** @type {Set<string>} */
export const all_registered_events = new Set();
/** @type {Set<(events: Array<string>) => void>} */
export const root_event_handles = new Set();
/** /**
* This is normally true block effects should run their intro transitions * This is normally true block effects should run their intro transitions
* but is false during hydration (unless `options.intro` is `true`) and * but is false during hydration (unless `options.intro` is `true`) and
@ -181,6 +179,8 @@ export function hydrate(component, options) {
} }
} }
const document_listeners = new Set();
/** /**
* @template {Record<string, any>} Exports * @template {Record<string, any>} Exports
* @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<any>> | import('../../index.js').Component<any>} Component * @param {import('../../index.js').ComponentType<import('../../index.js').SvelteComponent<any>> | import('../../index.js').Component<any>} Component
@ -213,11 +213,15 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
// the outer content stops propagation of the event. // the outer content stops propagation of the event.
target.addEventListener(event_name, handle_event_propagation, { passive }); target.addEventListener(event_name, handle_event_propagation, { passive });
if (!document_listeners.has(event_name)) {
document_listeners.add(event_name);
// The document listener ensures we catch events that originate from elements that were // The document listener ensures we catch events that originate from elements that were
// manually moved outside of the container (e.g. via manual portals). // manually moved outside of the container (e.g. via manual portals).
document.addEventListener(event_name, handle_event_propagation, { passive }); document.addEventListener(event_name, handle_event_propagation, { passive });
} }
} }
}
}; };
event_handle(array_from(all_registered_events)); event_handle(array_from(all_registered_events));
@ -263,7 +267,8 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
return () => { return () => {
for (const event_name of registered_events) { for (const event_name of registered_events) {
target.removeEventListener(event_name, handle_event_propagation); target.removeEventListener(event_name, handle_event_propagation);
document.removeEventListener(event_name, handle_event_propagation); // Don't remove document event listeners, they're global per runtime,
// and we don't know whether or not there are other components still using them
} }
root_event_handles.delete(event_handle); root_event_handles.delete(event_handle);

Loading…
Cancel
Save