Merge pull request #732 from sveltejs/gh-638

Event propagation shorthand
pull/733/head
Rich Harris 7 years ago committed by GitHub
commit 71047c2961

@ -13,22 +13,25 @@ export default function visitEventHandler(
local
) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.addSourcemapLocations(attribute.expression);
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
const usedContexts: string[] = [];
attribute.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, null, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
if (attribute.expression) {
generator.addSourcemapLocations(attribute.expression);
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
attribute.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, null, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
});
});
});
}
// TODO hoist event handlers? can do `this.__component.method(...)`
const declarations = usedContexts.map(name => {
@ -42,7 +45,9 @@ export default function visitEventHandler(
const handlerBody =
(declarations.length ? declarations.join('\n') + '\n\n' : '') +
`[✂${attribute.expression.start}-${attribute.expression.end}✂];`;
(attribute.expression ?
`[✂${attribute.expression.start}-${attribute.expression.end}✂];` :
`${block.alias('component')}.fire('${attribute.name}', event);`);
local.create.addBlock(deindent`
${local.name}.on( '${attribute.name}', function ( event ) {

@ -16,30 +16,33 @@ export default function visitEventHandler(
const isCustomEvent = generator.events.has(name);
const shouldHoist = !isCustomEvent && state.inEachBlock;
generator.addSourcemapLocations(attribute.expression);
const flattened = flattenReference(attribute.expression.callee);
if (flattened.name !== 'event' && flattened.name !== 'this') {
// allow event.stopPropagation(), this.select() etc
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works!
}
const context = shouldHoist ? null : state.parentNode;
const usedContexts: string[] = [];
attribute.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, context, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~state.allUsedContexts.indexOf(context))
state.allUsedContexts.push(context);
if (attribute.expression) {
generator.addSourcemapLocations(attribute.expression);
const flattened = flattenReference(attribute.expression.callee);
if (flattened.name !== 'event' && flattened.name !== 'this') {
// allow event.stopPropagation(), this.select() etc
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works!
}
attribute.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, context, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~state.allUsedContexts.indexOf(context))
state.allUsedContexts.push(context);
});
});
});
}
const _this = context || 'this';
const declarations = usedContexts.map(name => {
@ -66,7 +69,9 @@ export default function visitEventHandler(
${state.usesComponent &&
`var ${block.alias('component')} = ${_this}._svelte.component;`}
${declarations}
[${attribute.expression.start}-${attribute.expression.end}];
${attribute.expression ?
`[✂${attribute.expression.start}-${attribute.expression.end}✂];` :
`${block.alias('component')}.fire('${attribute.name}', event);`}
`;
if (isCustomEvent) {

@ -111,6 +111,8 @@ export class Parser {
if (required) {
this.error(`Expected ${str}`);
}
return false;
}
match(str: string) {

@ -43,16 +43,21 @@ function readExpression(parser: Parser, start: number, quoteMark) {
export function readEventHandlerDirective(
parser: Parser,
start: number,
name: string
name: string,
hasValue: boolean
) {
const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
let expression;
const expressionStart = parser.index;
if (hasValue) {
const quoteMark = parser.eat(`'`) ? `'` : parser.eat(`"`) ? `"` : null;
const expression = readExpression(parser, expressionStart, quoteMark);
const expressionStart = parser.index;
if (expression.type !== 'CallExpression') {
parser.error(`Expected call expression`, expressionStart);
expression = readExpression(parser, expressionStart, quoteMark);
if (expression.type !== 'CallExpression') {
parser.error(`Expected call expression`, expressionStart);
}
}
return {

@ -256,8 +256,7 @@ function readAttribute(parser: Parser, uniqueNames) {
parser.allowWhitespace();
if (/^on:/.test(name)) {
parser.eat('=', true);
return readEventHandlerDirective(parser, start, name.slice(3));
return readEventHandlerDirective(parser, start, name.slice(3), parser.eat('='));
}
if (/^bind:/.test(name)) {

@ -10,6 +10,8 @@ export default function validateEventHandlerCallee(
attribute: Node,
refCallees: Node[]
) {
if (!attribute.expression) return;
const { callee, start, type } = attribute.expression;
if (type !== 'CallExpression') {

@ -0,0 +1 @@
<button on:click='fire("foo", { answer: 42 })'>click me</button>

@ -0,0 +1,18 @@
export default {
html: `
<button>click me</button>
`,
test (assert, component, target, window) {
const button = target.querySelector('button');
const event = new window.MouseEvent('click');
let answer;
component.on('foo', event => {
answer = event.answer;
});
button.dispatchEvent(event);
assert.equal(answer, 42);
}
};

@ -0,0 +1,11 @@
<Widget on:foo/>
<script>
import Widget from './Widget.html';
export default {
components: {
Widget
}
};
</script>

@ -0,0 +1,13 @@
export default {
html: `
<button>click me</button>
`,
test (assert, component, target, window) {
const button = target.querySelector('button');
const event = new window.MouseEvent('click');
button.dispatchEvent(event);
assert.ok(component.clicked);
}
};

@ -0,0 +1,11 @@
<button on:click>click me</button>
<script>
export default {
oncreate () {
this.on('click', () => {
this.clicked = true;
});
}
};
</script>
Loading…
Cancel
Save