From 75bbd6b878eedbcc8c282d3640e821bcbe15f18f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 8 Dec 2018 20:51:53 -0500 Subject: [PATCH] implement event modifiers --- src/compile/nodes/Attribute.ts | 5 +++++ src/compile/nodes/Element.ts | 6 +++--- .../wrappers/shared/addEventHandlers.ts | 8 +++----- src/internal/dom.js | 14 ++++++++++++++ test/js/samples/event-modifiers/expected.js | 10 ++-------- .../event-handler-modifier-once/_config.js | 16 ++++++++++++++++ .../event-handler-modifier-once/main.html | 5 +++++ .../_config.js | 16 ++++++++++++++++ .../main.html | 9 +++++++++ .../_config.js | 19 +++++++++++++++++++ .../main.html | 16 ++++++++++++++++ 11 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 test/runtime/samples/event-handler-modifier-once/_config.js create mode 100644 test/runtime/samples/event-handler-modifier-once/main.html create mode 100644 test/runtime/samples/event-handler-modifier-prevent-default/_config.js create mode 100644 test/runtime/samples/event-handler-modifier-prevent-default/main.html create mode 100644 test/runtime/samples/event-handler-modifier-stop-propagation/_config.js create mode 100644 test/runtime/samples/event-handler-modifier-stop-propagation/main.html diff --git a/src/compile/nodes/Attribute.ts b/src/compile/nodes/Attribute.ts index 32e1d6da3a..88aaadff26 100644 --- a/src/compile/nodes/Attribute.ts +++ b/src/compile/nodes/Attribute.ts @@ -17,6 +17,7 @@ export default class Attribute extends Node { isSpread: boolean; isTrue: boolean; isDynamic: boolean; + isStatic: boolean; isSynthetic: boolean; shouldCache: boolean; expression?: Expression; @@ -37,12 +38,14 @@ export default class Attribute extends Node { this.chunks = null; this.isDynamic = true; // TODO not necessarily + this.isStatic = false; this.shouldCache = false; // TODO does this mean anything here? } else { this.name = info.name; this.isTrue = info.value === true; + this.isStatic = true; this.isSynthetic = info.synthetic; this.dependencies = new Set(); @@ -52,6 +55,8 @@ export default class Attribute extends Node { : info.value.map(node => { if (node.type === 'Text') return node; + this.isStatic = false; + const expression = new Expression(component, this, scope, node.expression); addToSet(this.dependencies, expression.dynamic_dependencies); diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 2e2a0f8008..391194d004 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -337,7 +337,7 @@ export default class Element extends Node { } if (name === 'slot') { - if (attribute.isDynamic) { + if (!attribute.isStatic) { component.error(attribute, { code: `invalid-slot-attribute`, message: `slot attribute cannot have a dynamic value` @@ -425,7 +425,7 @@ export default class Element extends Node { if (!attribute) return null; - if (attribute.isDynamic) { + if (!attribute.isStatic) { component.error(attribute, { code: `invalid-type`, message: `'type' attribute cannot be dynamic if input uses two-way binding` @@ -464,7 +464,7 @@ export default class Element extends Node { (attribute: Attribute) => attribute.name === 'multiple' ); - if (attribute && attribute.isDynamic) { + if (attribute && !attribute.isStatic) { component.error(attribute, { code: `dynamic-multiple-attribute`, message: `'multiple' attribute cannot be dynamic if select uses two-way binding` diff --git a/src/compile/render-dom/wrappers/shared/addEventHandlers.ts b/src/compile/render-dom/wrappers/shared/addEventHandlers.ts index 7a82f72860..e8183cdffc 100644 --- a/src/compile/render-dom/wrappers/shared/addEventHandlers.ts +++ b/src/compile/render-dom/wrappers/shared/addEventHandlers.ts @@ -7,14 +7,12 @@ export default function addEventHandlers( handlers: EventHandler[] ) { handlers.forEach(handler => { - const modifiers = []; - if (handler.modifiers.has('preventDefault')) modifiers.push('event.preventDefault();'); - if (handler.modifiers.has('stopPropagation')) modifiers.push('event.stopPropagation();'); + let snippet = handler.render(); + if (handler.modifiers.has('preventDefault')) snippet = `@preventDefault(${snippet})`; + if (handler.modifiers.has('stopPropagation')) snippet = `@stopPropagation(${snippet})`; const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod)); - const snippet = handler.render(); - if (opts.length) { const optString = (opts.length === 1 && opts[0] === 'capture') ? 'true' diff --git a/src/internal/dom.js b/src/internal/dom.js index abde45502d..f16aea7eba 100644 --- a/src/internal/dom.js +++ b/src/internal/dom.js @@ -78,6 +78,20 @@ export function addListener(node, event, handler, options) { return () => node.removeEventListener(event, handler, options); } +export function preventDefault(fn) { + return function(event) { + event.preventDefault(); + return fn.call(this, event); + }; +} + +export function stopPropagation(fn) { + return function(event) { + event.stopPropagation(); + return fn.call(this, event); + }; +} + export function setAttribute(node, attribute, value) { if (value == null) node.removeAttribute(attribute); else node.setAttribute(attribute, value); diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js index e582c1b030..ea2818627e 100644 --- a/test/js/samples/event-modifiers/expected.js +++ b/test/js/samples/event-modifiers/expected.js @@ -1,15 +1,9 @@ /* generated by Svelte vX.Y.Z-alpha1 */ -import { SvelteComponent as SvelteComponent_1, addListener, append, createElement, createText, detachNode, init, insert, noop, run, run_all, safe_not_equal } from "svelte/internal.js"; +import { SvelteComponent as SvelteComponent_1, addListener, append, createElement, createText, detachNode, init, insert, noop, preventDefault, run, run_all, safe_not_equal, stopPropagation } from "svelte/internal.js"; function create_fragment(component, ctx) { var div, button0, text1, button1, text3, button2, current, dispose; - function click_handler(event) { - event.stopPropagation(); - event.preventDefault(); - handleClick(event); - } - return { c() { div = createElement("div"); @@ -22,7 +16,7 @@ function create_fragment(component, ctx) { button2 = createElement("button"); button2.textContent = "or me!"; dispose = [ - addListener(button0, click_handler), + addListener(button0, "click", stopPropagation(preventDefault(handleClick))), addListener(button1, "click", handleClick, { once: true, capture: true }), addListener(button2, "click", handleClick, true), addListener(div, "touchstart", handleTouchstart, { passive: true }) diff --git a/test/runtime/samples/event-handler-modifier-once/_config.js b/test/runtime/samples/event-handler-modifier-once/_config.js new file mode 100644 index 0000000000..41daf374c8 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-once/_config.js @@ -0,0 +1,16 @@ +export default { + html: ` + + `, + + async test({ assert, component, target, window }) { + const button = target.querySelector('button'); + const event = new window.MouseEvent('click'); + + await button.dispatchEvent(event); + assert.equal(component.count, 1); + + await button.dispatchEvent(event); + assert.equal(component.count, 1); + } +}; diff --git a/test/runtime/samples/event-handler-modifier-once/main.html b/test/runtime/samples/event-handler-modifier-once/main.html new file mode 100644 index 0000000000..f31f8f4983 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-once/main.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/event-handler-modifier-prevent-default/_config.js b/test/runtime/samples/event-handler-modifier-prevent-default/_config.js new file mode 100644 index 0000000000..4fa032bf37 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-prevent-default/_config.js @@ -0,0 +1,16 @@ +export default { + html: ` + + `, + + async test({ assert, component, target, window }) { + const button = target.querySelector('button'); + const event = new window.MouseEvent('click', { + cancelable: true + }); + + await button.dispatchEvent(event); + + assert.ok(component.default_was_prevented); + } +}; diff --git a/test/runtime/samples/event-handler-modifier-prevent-default/main.html b/test/runtime/samples/event-handler-modifier-prevent-default/main.html new file mode 100644 index 0000000000..90aea62f01 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-prevent-default/main.html @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/event-handler-modifier-stop-propagation/_config.js b/test/runtime/samples/event-handler-modifier-stop-propagation/_config.js new file mode 100644 index 0000000000..8517429e5c --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-stop-propagation/_config.js @@ -0,0 +1,19 @@ +export default { + html: ` +
+ +
+ `, + + async test({ assert, component, target, window }) { + const button = target.querySelector('button'); + const event = new window.MouseEvent('click', { + bubbles: true + }); + + await button.dispatchEvent(event); + + assert.ok(component.inner_clicked); + assert.ok(!component.outer_clicked); + } +}; diff --git a/test/runtime/samples/event-handler-modifier-stop-propagation/main.html b/test/runtime/samples/event-handler-modifier-stop-propagation/main.html new file mode 100644 index 0000000000..a1e3680313 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-stop-propagation/main.html @@ -0,0 +1,16 @@ + + +
+ +
\ No newline at end of file