implement event modifiers

pull/1819/head
Rich Harris 6 years ago
parent 8ec02b336d
commit 82b1b75afe

@ -595,12 +595,7 @@ export default class Element extends Node {
if (modifier === 'passive') { if (modifier === 'passive') {
if (passiveEvents.has(handler.name)) { if (passiveEvents.has(handler.name)) {
const usesEvent = ( if (!handler.usesEventObject) {
handler.callee.name === 'event' ||
handler.args.some(x => x.usesEvent)
);
if (!usesEvent) {
component.warn(handler, { component.warn(handler, {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
message: `Touch event handlers that don't use the 'event' object are passive by default` message: `Touch event handlers that don't use the 'event' object are passive by default`
@ -623,6 +618,11 @@ export default class Element extends Node {
}); });
} }
}); });
if (passiveEvents.has(handler.name) && !handler.usesEventObject) {
// touch/wheel events should be passive by default
handler.modifiers.add('passive');
}
}); });
} }

@ -16,6 +16,7 @@ export default class EventHandler extends Node {
usesComponent: boolean; usesComponent: boolean;
usesContext: boolean; usesContext: boolean;
usesEventObject: boolean;
isCustomEvent: boolean; isCustomEvent: boolean;
shouldHoist: boolean; shouldHoist: boolean;
@ -42,11 +43,13 @@ export default class EventHandler extends Node {
this.usesComponent = !validCalleeObjects.has(this.callee.name); this.usesComponent = !validCalleeObjects.has(this.callee.name);
this.usesContext = false; this.usesContext = false;
this.usesEventObject = this.callee.name === 'event';
this.args = info.expression.arguments.map(param => { this.args = info.expression.arguments.map(param => {
const expression = new Expression(component, this, scope, param); const expression = new Expression(component, this, scope, param);
addToSet(this.dependencies, expression.dependencies); addToSet(this.dependencies, expression.dependencies);
if (expression.usesContext) this.usesContext = true; if (expression.usesContext) this.usesContext = true;
if (expression.usesEvent) this.usesEventObject = true;
return expression; return expression;
}); });
@ -58,6 +61,7 @@ export default class EventHandler extends Node {
this.args = null; this.args = null;
this.usesComponent = true; this.usesComponent = true;
this.usesContext = false; this.usesContext = false;
this.usesEventObject = true;
this.snippet = null; // TODO handle shorthand events here? this.snippet = null; // TODO handle shorthand events here?
} }

@ -649,8 +649,13 @@ export default class ElementWrapper extends Wrapper {
${handlerName}.destroy(); ${handlerName}.destroy();
`); `);
} else { } else {
const modifiers = [];
if (handler.modifiers.has('preventDefault')) modifiers.push('event.preventDefault();');
if (handler.modifiers.has('stopPropagation')) modifiers.push('event.stopPropagation();');
const handlerFunction = deindent` const handlerFunction = deindent`
function ${handlerName}(event) { function ${handlerName}(event) {
${modifiers}
${handlerBody} ${handlerBody}
} }
`; `;
@ -661,6 +666,20 @@ export default class ElementWrapper extends Wrapper {
block.builders.init.addBlock(handlerFunction); block.builders.init.addBlock(handlerFunction);
} }
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));
if (opts.length) {
const optString = (opts.length === 1 && opts[0] === 'capture')
? 'true'
: `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`;
block.builders.hydrate.addLine(
`@addListener(${this.var}, "${handler.name}", ${handlerName}, ${optString});`
);
block.builders.destroy.addLine(
`@removeListener(${this.var}, "${handler.name}", ${handlerName}, ${optString});`
);
} else {
block.builders.hydrate.addLine( block.builders.hydrate.addLine(
`@addListener(${this.var}, "${handler.name}", ${handlerName});` `@addListener(${this.var}, "${handler.name}", ${handlerName});`
); );
@ -669,6 +688,7 @@ export default class ElementWrapper extends Wrapper {
`@removeListener(${this.var}, "${handler.name}", ${handlerName});` `@removeListener(${this.var}, "${handler.name}", ${handlerName});`
); );
} }
}
}); });
} }

@ -0,0 +1,91 @@
/* generated by Svelte vX.Y.Z */
import { addListener, append, assign, createElement, createText, detachNode, init, insert, noop, proto, removeListener } from "svelte/shared.js";
var methods = {
handleTouchstart() {
// ...
},
handleClick() {
// ...
}
};
function create_main_fragment(component, ctx) {
var div, button0, text1, button1, text3, button2;
function click_handler(event) {
event.preventDefault();
event.stopPropagation();
component.handleClick();
}
function click_handler_1(event) {
component.handleClick();
}
function click_handler_2(event) {
component.handleClick();
}
function touchstart_handler(event) {
component.handleTouchstart();
}
return {
c() {
div = createElement("div");
button0 = createElement("button");
button0.textContent = "click me";
text1 = createText("\n\t");
button1 = createElement("button");
button1.textContent = "or me";
text3 = createText("\n\t");
button2 = createElement("button");
button2.textContent = "or me!";
addListener(button0, "click", click_handler);
addListener(button1, "click", click_handler_1, { once: true, capture: true });
addListener(button2, "click", click_handler_2, true);
addListener(div, "touchstart", touchstart_handler, { passive: true });
},
m(target, anchor) {
insert(target, div, anchor);
append(div, button0);
append(div, text1);
append(div, button1);
append(div, text3);
append(div, button2);
},
p: noop,
d(detach) {
if (detach) {
detachNode(div);
}
removeListener(button0, "click", click_handler);
removeListener(button1, "click", click_handler_1, { once: true, capture: true });
removeListener(button2, "click", click_handler_2, true);
removeListener(div, "touchstart", touchstart_handler, { passive: true });
}
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);
this._intro = true;
this._fragment = create_main_fragment(this, this._state);
if (options.target) {
this._fragment.c();
this._mount(options.target, options.anchor);
}
}
assign(SvelteComponent.prototype, proto);
assign(SvelteComponent.prototype, methods);
export default SvelteComponent;

@ -0,0 +1,19 @@
<div on:touchstart="handleTouchstart()">
<button on:click|stopPropagation|preventDefault="handleClick()">click me</button>
<button on:click|once|capture="handleClick()">or me</button>
<button on:click|capture="handleClick()">or me!</button>
</div>
<script>
export default {
methods: {
handleTouchstart() {
// ...
},
handleClick() {
// ...
}
}
};
</script>

@ -15,6 +15,7 @@
"end": 45, "end": 45,
"type": "EventHandler", "type": "EventHandler",
"name": "click", "name": "click",
"modifiers": [],
"expression": { "expression": {
"type": "CallExpression", "type": "CallExpression",
"start": 18, "start": 18,

Loading…
Cancel
Save