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,