From ffbc8d3ba698d4c4020b13a7039a05c93a8c9ce2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 25 Apr 2018 11:06:52 -0400 Subject: [PATCH] fix contextual events --- src/generators/nodes/Element.ts | 87 ++++++++++++---------------- src/generators/nodes/EventHandler.ts | 22 ++++++- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/generators/nodes/Element.ts b/src/generators/nodes/Element.ts index 2bf174da45..1858ddf727 100644 --- a/src/generators/nodes/Element.ts +++ b/src/generators/nodes/Element.ts @@ -300,49 +300,49 @@ export default class Element extends Node { }); } - this.addBindings(block, allUsedContexts); - const eventHandlerUsesComponent = this.addEventHandlers(block, allUsedContexts); - if (this.ref) this.addRef(block); - this.addAttributes(block); - this.addTransitions(block); - this.addActions(block); + let hasHoistedEventHandlerOrBinding = ( + (this.hasAncestor('EachBlock') && this.bindings.length > 0) || + this.handlers.some(handler => handler.shouldHoist) + ); + let eventHandlerOrBindingUsesComponent; + let eventHandlerOrBindingUsesContext; + + if (this.bindings.length > 0) { + eventHandlerOrBindingUsesComponent = true; + eventHandlerOrBindingUsesContext = true; + } else { + eventHandlerOrBindingUsesComponent = this.handlers.some(handler => handler.usesComponent); + eventHandlerOrBindingUsesContext = this.handlers.some(handler => handler.usesContext); + } - if (allUsedContexts.size || eventHandlerUsesComponent) { + if (hasHoistedEventHandlerOrBinding) { const initialProps: string[] = []; const updates: string[] = []; - if (eventHandlerUsesComponent) { - initialProps.push(`component: #component`); + if (eventHandlerOrBindingUsesComponent) { + const component = block.alias('component'); + initialProps.push(component === 'component' ? 'component' : `component: ${component}`); } - allUsedContexts.forEach((contextName: string) => { - if (contextName === 'state') return; - if (block.contextTypes.get(contextName) !== 'each') return; - - const listName = block.listNames.get(contextName); - const indexName = block.indexNames.get(contextName); - - initialProps.push( - `${listName}: ctx.${listName},\n${indexName}: ctx.${indexName}` - ); - updates.push( - `${name}._svelte.${listName} = ctx.${listName};\n${name}._svelte.${indexName} = ctx.${indexName};` - ); - }); + if (eventHandlerOrBindingUsesContext) { + initialProps.push(`ctx`); + block.builders.update.addLine(`${name}._svelte.ctx = ctx;`); + } if (initialProps.length) { block.builders.hydrate.addBlock(deindent` - ${name}._svelte = { - ${initialProps.join(',\n')} - }; + ${name}._svelte = { ${initialProps.join(', ')} }; `); } - - if (updates.length) { - block.builders.update.addBlock(updates.join('\n')); - } } + this.addBindings(block, allUsedContexts); + this.addEventHandlers(block, allUsedContexts); + if (this.ref) this.addRef(block); + this.addAttributes(block); + this.addTransitions(block); + this.addActions(block); + if (this.initialUpdate) { block.builders.mount.addBlock(this.initialUpdate); } @@ -557,33 +557,18 @@ export default class Element extends Node { addEventHandlers(block: Block, allUsedContexts) { const { compiler } = this; - let eventHandlerUsesComponent = false; this.handlers.forEach(handler => { const isCustomEvent = compiler.events.has(handler.name); const shouldHoist = !isCustomEvent && this.hasAncestor('EachBlock'); const context = shouldHoist ? null : this.var; - const usedContexts: string[] = []; if (handler.callee) { handler.render(this.compiler, block); - - if (!validCalleeObjects.has(handler.callee.name)) { - if (shouldHoist) eventHandlerUsesComponent = true; // this feels a bit hacky but it works! - } - - // handler.expression.arguments.forEach((arg: Node) => { - // const { contexts } = block.contextualise(arg, context, true); - - // contexts.forEach(context => { - // if (!~usedContexts.indexOf(context)) usedContexts.push(context); - // allUsedContexts.add(context); - // }); - // }); } - const ctx = context || 'this'; + const target = context || 'this'; // get a name for the event handler that is globally unique // if hoisted, locally unique otherwise @@ -595,9 +580,12 @@ export default class Element extends Node { // create the handler body const handlerBody = deindent` - ${eventHandlerUsesComponent && - `var ${component} = ${ctx}._svelte.component;`} - ${handler.dependencies.size > 0 && `const ctx = ${component}.get();`} + ${handler.shouldHoist && ( + handler.usesComponent || handler.usesContext + ? `const { ${[handler.usesComponent && 'component', handler.usesContext && 'ctx'].filter(Boolean).join(', ')} } = ${target}._svelte;` + : null + )} + ${handler.snippet ? handler.snippet : `${component}.fire("${handler.name}", event);`} @@ -637,7 +625,6 @@ export default class Element extends Node { ); } }); - return eventHandlerUsesComponent; } addRef(block: Block) { diff --git a/src/generators/nodes/EventHandler.ts b/src/generators/nodes/EventHandler.ts index 9cd97de382..bd66fc25cc 100644 --- a/src/generators/nodes/EventHandler.ts +++ b/src/generators/nodes/EventHandler.ts @@ -9,6 +9,12 @@ export default class EventHandler extends Node { dependencies: Set; expression: Node; callee: any; // TODO + + usesComponent: boolean; + usesContext: boolean; + isCustomEvent: boolean; + shouldHoist: boolean; + insertionPoint: number; args: Expression[]; snippet: string; @@ -22,38 +28,50 @@ export default class EventHandler extends Node { if (info.expression) { this.callee = flattenReference(info.expression.callee); this.insertionPoint = info.expression.start; + this.args = info.expression.arguments.map(param => { const expression = new Expression(compiler, this, scope, param); addToSet(this.dependencies, expression.dependencies); return expression; }); + this.usesComponent = !validCalleeObjects.has(this.callee.name); + this.usesContext = this.dependencies.size > 0; + this.snippet = `[✂${info.expression.start}-${info.expression.end}✂]`; } else { this.callee = null; this.insertionPoint = null; + this.args = null; + this.usesComponent = true; + this.usesContext = false; this.snippet = null; // TODO handle shorthand events here? } + + this.isCustomEvent = compiler.events.has(this.name); + this.shouldHoist = !this.isCustomEvent && parent.hasAncestor('EachBlock'); } render(compiler, block) { if (this.insertionPoint === null) return; // TODO handle shorthand events here? if (!validCalleeObjects.has(this.callee.name)) { + const component = this.shouldHoist ? `component` : block.alias(`component`); + // allow event.stopPropagation(), this.select() etc // TODO verify that it's a valid callee (i.e. built-in or declared method) if (this.callee.name[0] === '$' && !compiler.methods.has(this.callee.name)) { compiler.code.overwrite( this.insertionPoint, this.insertionPoint + 1, - `${block.alias('component')}.store.` + `${component}.store.` ); } else { compiler.code.prependRight( this.insertionPoint, - `${block.alias('component')}.` + `${component}.` ); } }