diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 23022170eb..58217a7b9d 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -63,10 +63,21 @@ export function replay_events(dom) { * @param {AddEventListenerOptions} [options] */ export function create_event(event_name, dom, handler, options = {}) { + // Capture whether a custom renderer is active at creation time (during mount), + // since `renderer` will be null when the event actually fires + var is_custom_renderer = renderer != null; + /** * @this {EventTarget} */ function target_handler(/** @type {Event} */ event) { + if (is_custom_renderer) { + // Custom renderers don't use DOM event propagation/delegation, + // so just call the handler directly + return without_reactive_context(() => { + return handler?.call(this, event); + }); + } if (!options.capture) { // Only call in the bubble phase, else delegated events would be called before the capturing events handle_event_propagation.call(dom, event); diff --git a/packages/svelte/tests/custom-renderers/samples/event-handler-no-propagation/_config.js b/packages/svelte/tests/custom-renderers/samples/event-handler-no-propagation/_config.js new file mode 100644 index 0000000000..b902b602e6 --- /dev/null +++ b/packages/svelte/tests/custom-renderers/samples/event-handler-no-propagation/_config.js @@ -0,0 +1,24 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, target, serialize, logs }) { + const button = target.children.find( + (/** @type {any} */ n) => n.type === 'element' && n.name === 'button' + ); + assert.ok(button); + + const listeners = button.listeners?.click; + assert.ok(listeners, 'button should have click listeners'); + + // Call the handler with a plain object that is NOT a DOM Event. + // If handle_event_propagation is called, it will fail because + // it tries to access DOM-specific properties like composedPath, ownerDocument, etc. + for (const { handler } of listeners) { + handler.call(button, { type: 'click' }); + } + flushSync(); + + assert.deepEqual(logs, [{ type: 'click' }]); + } +}); diff --git a/packages/svelte/tests/custom-renderers/samples/event-handler-no-propagation/main.svelte b/packages/svelte/tests/custom-renderers/samples/event-handler-no-propagation/main.svelte new file mode 100644 index 0000000000..c5c7626fb2 --- /dev/null +++ b/packages/svelte/tests/custom-renderers/samples/event-handler-no-propagation/main.svelte @@ -0,0 +1,7 @@ + + +