diff --git a/site/content/docs/03-template-syntax.md b/site/content/docs/03-template-syntax.md
index 419489211c..f0ea8af0c7 100644
--- a/site/content/docs/03-template-syntax.md
+++ b/site/content/docs/03-template-syntax.md
@@ -539,6 +539,7 @@ The following modifiers are available:
* `preventDefault` — calls `event.preventDefault()` before running the handler
* `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element
+* `stopImmediatePropagation` - calls `event.stopImmediatePropagation()`, preventing other listeners of the same event from being fired.
* `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
* `nonpassive` — explicitly set `passive: false`
* `capture` — fires the handler during the *capture* phase instead of the *bubbling* phase
diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts
index 4e43fe1824..b90d08ffbc 100644
--- a/src/compiler/compile/nodes/Element.ts
+++ b/src/compiler/compile/nodes/Element.ts
@@ -183,6 +183,7 @@ const invisible_elements = new Set(['meta', 'html', 'script', 'style']);
const valid_modifiers = new Set([
'preventDefault',
'stopPropagation',
+ 'stopImmediatePropagation',
'capture',
'once',
'passive',
diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
index b0ed4a73d6..b8c9f70c0f 100644
--- a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts
@@ -41,6 +41,7 @@ export default class EventHandlerWrapper {
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
+ if (this.node.modifiers.has('stopImmediatePropagation')) snippet = x`@stop_immediate_propagation(${snippet})`;
if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`;
if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`;
@@ -64,6 +65,7 @@ export default class EventHandlerWrapper {
if (block.renderer.options.dev) {
args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE);
args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE);
+ args.push(this.node.modifiers.has('stopImmediatePropagation') ? TRUE : FALSE);
}
block.event_listeners.push(
diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts
index 9d24ad2a07..be81a11514 100644
--- a/src/runtime/internal/dev.ts
+++ b/src/runtime/internal/dev.ts
@@ -49,10 +49,11 @@ export function detach_after_dev(before: Node) {
}
}
-export function listen_dev(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions, has_prevent_default?: boolean, has_stop_propagation?: boolean) {
+export function listen_dev(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions, has_prevent_default?: boolean, has_stop_propagation?: boolean, has_stop_immediate_propagation?: boolean) {
const modifiers = options === true ? [ 'capture' ] : options ? Array.from(Object.keys(options)) : [];
if (has_prevent_default) modifiers.push('preventDefault');
if (has_stop_propagation) modifiers.push('stopPropagation');
+ if (has_stop_immediate_propagation) modifiers.push('stopImmediatePropagation');
dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers });
diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts
index 1f87e91910..942e742e34 100644
--- a/src/runtime/internal/dom.ts
+++ b/src/runtime/internal/dom.ts
@@ -275,6 +275,14 @@ export function stop_propagation(fn) {
};
}
+export function stop_immediate_propagation(fn) {
+ return function (event) {
+ event.stopImmediatePropagation();
+ // @ts-ignore
+ return fn.call(this, event);
+ };
+}
+
export function self(fn) {
return function(event) {
// @ts-ignore
diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js
index 3901753661..5b75ff1192 100644
--- a/test/js/samples/event-modifiers/expected.js
+++ b/test/js/samples/event-modifiers/expected.js
@@ -12,6 +12,7 @@ import {
run_all,
safe_not_equal,
space,
+ stop_immediate_propagation,
stop_propagation
} from "svelte/internal";
@@ -24,6 +25,8 @@ function create_fragment(ctx) {
let button1;
let t5;
let button2;
+ let t7;
+ let button3;
let mounted;
let dispose;
@@ -41,6 +44,9 @@ function create_fragment(ctx) {
t5 = space();
button2 = element("button");
button2.textContent = "or me!";
+ t7 = space();
+ button3 = element("button");
+ button3.textContent = "or me!";
},
m(target, anchor) {
insert(target, div1, anchor);
@@ -51,6 +57,8 @@ function create_fragment(ctx) {
append(div1, button1);
append(div1, t5);
append(div1, button2);
+ append(div1, t7);
+ append(div1, button3);
if (!mounted) {
dispose = [
@@ -58,6 +66,8 @@ function create_fragment(ctx) {
listen(button0, "click", stop_propagation(prevent_default(handleClick))),
listen(button1, "click", handleClick, { once: true, capture: true }),
listen(button2, "click", handleClick, true),
+ listen(button3, "click", stop_immediate_propagation(handleClick)),
+ listen(button3, "click", handleTouchstart),
listen(div1, "touchstart", handleTouchstart, { passive: true })
];
diff --git a/test/js/samples/event-modifiers/input.svelte b/test/js/samples/event-modifiers/input.svelte
index c72d58dabb..f36ebd90ef 100644
--- a/test/js/samples/event-modifiers/input.svelte
+++ b/test/js/samples/event-modifiers/input.svelte
@@ -13,4 +13,9 @@
+
\ No newline at end of file
diff --git a/test/runtime/samples/event-handler-modifier-stop-immediate-propagation/_config.js b/test/runtime/samples/event-handler-modifier-stop-immediate-propagation/_config.js
new file mode 100644
index 0000000000..3ea00c8493
--- /dev/null
+++ b/test/runtime/samples/event-handler-modifier-stop-immediate-propagation/_config.js
@@ -0,0 +1,17 @@
+export default {
+ async test({ assert, component, target, window }) {
+ const button = target.querySelector('button');
+ const event = new window.MouseEvent('click');
+
+ await button.dispatchEvent(event);
+ assert.deepEqual(component.logs, ['click_1', 'click_2']);
+
+ component.click_2 = () => component.logs.push('22');
+ await button.dispatchEvent(event);
+ assert.deepEqual(component.logs, ['click_1', 'click_2', 'click_1', '22']);
+
+ component.click_1 = () => component.logs.push('11');
+ await button.dispatchEvent(event);
+ assert.deepEqual(component.logs, ['click_1', 'click_2', 'click_1', '22', '11', '22']);
+ }
+};
diff --git a/test/runtime/samples/event-handler-modifier-stop-immediate-propagation/main.svelte b/test/runtime/samples/event-handler-modifier-stop-immediate-propagation/main.svelte
new file mode 100644
index 0000000000..c038c213c4
--- /dev/null
+++ b/test/runtime/samples/event-handler-modifier-stop-immediate-propagation/main.svelte
@@ -0,0 +1,22 @@
+
+
+
diff --git a/test/validator/samples/event-modifiers-invalid/errors.json b/test/validator/samples/event-modifiers-invalid/errors.json
index 628bf2eaed..b3a2c0017c 100644
--- a/test/validator/samples/event-modifiers-invalid/errors.json
+++ b/test/validator/samples/event-modifiers-invalid/errors.json
@@ -1,5 +1,5 @@
[{
- "message": "Valid event modifiers are preventDefault, stopPropagation, capture, once, passive, nonpassive, self or trusted",
+ "message": "Valid event modifiers are preventDefault, stopPropagation, stopImmediatePropagation, capture, once, passive, nonpassive, self or trusted",
"code": "invalid-event-modifier",
"start": {
"line": 1,