diff --git a/src/compile/render-dom/Block.ts b/src/compile/render-dom/Block.ts index e1eceac1e6..85e7850ef2 100644 --- a/src/compile/render-dom/Block.ts +++ b/src/compile/render-dom/Block.ts @@ -11,7 +11,7 @@ export interface BlockOptions { renderer?: Renderer; comment?: string; key?: string; - bindings?: Map string>; + bindings?: Map { object: string, property: string, snippet: string }>; contextOwners?: Map; dependencies?: Set; } @@ -29,7 +29,7 @@ export default class Block { dependencies: Set; - bindings: Map string>; + bindings: Map { object: string, property: string, snippet: string }>; contextOwners: Map; builders: { diff --git a/src/compile/render-dom/wrappers/EachBlock.ts b/src/compile/render-dom/wrappers/EachBlock.ts index bc274e7032..519e0ac933 100644 --- a/src/compile/render-dom/wrappers/EachBlock.ts +++ b/src/compile/render-dom/wrappers/EachBlock.ts @@ -101,7 +101,11 @@ export default class EachBlockWrapper extends Wrapper { this.block.contextOwners.set(prop.key.name, this); // TODO this doesn't feel great - this.block.bindings.set(prop.key.name, () => `ctx.${this.vars.each_block_value}[ctx.${this.indexName}]${prop.tail}`); + this.block.bindings.set(prop.key.name, () => ({ + object: this.vars.each_block_value, + property: this.indexName, + snippet: `${this.vars.each_block_value}[${this.indexName}]${prop.tail}` + })); }); if (this.node.index) { diff --git a/src/compile/render-dom/wrappers/Element/Binding.ts b/src/compile/render-dom/wrappers/Element/Binding.ts index ba3f2a3017..7ecff8b9de 100644 --- a/src/compile/render-dom/wrappers/Element/Binding.ts +++ b/src/compile/render-dom/wrappers/Element/Binding.ts @@ -160,7 +160,8 @@ export default class BindingWrapper { needsLock: !isReadOnly && needsLock, updateCondition: updateConditions.length ? updateConditions.join(' && ') : undefined, isReadOnlyMediaAttribute: this.isReadOnlyMediaAttribute(), - dependencies: this.node.expression.dependencies + dependencies: this.node.expression.dependencies, + contextual_dependencies: this.node.expression.contextual_dependencies }; } } @@ -226,13 +227,14 @@ function getEventHandler( ? getTailSnippet(binding.node.expression.node) : ''; - const head = block.bindings.get(name); + const { object, property, snippet } = block.bindings.get(name)(); return { usesContext: true, usesState: true, - mutation: `${head()}${tail} = ${value};`, - props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`) + mutation: `${snippet}${tail} = ${value};`, + props: dependenciesArray.map(prop => `${prop}: ctx.${prop}`), + contextual_dependencies: new Set([object, property]) }; } @@ -241,7 +243,8 @@ function getEventHandler( usesContext: false, usesState: true, mutation: `${snippet} = ${value};`, - props: dependenciesArray.map((prop: string) => `${prop}: ctx.${prop}`) + props: dependenciesArray.map((prop: string) => `${prop}: ctx.${prop}`), + contextual_dependencies: new Set() }; } @@ -251,7 +254,8 @@ function getEventHandler( usesContext: false, usesState: false, mutation: `${snippet} = ${value};`, - props + props, + contextual_dependencies: new Set() }; } diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index dff56bf5b4..a6d42595d8 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -439,10 +439,12 @@ export default class ElementWrapper extends Wrapper { const needsLock = group.bindings.some(binding => binding.needsLock); - const deps = new Set(); + const dependencies = new Set(); + const contextual_dependencies = new Set(); group.bindings.forEach(binding => { - addToSet(deps, binding.dependencies); + addToSet(dependencies, binding.dependencies); + addToSet(contextual_dependencies, binding.handler.contextual_dependencies); if (!binding.updateDom) return; @@ -467,21 +469,52 @@ export default class ElementWrapper extends Wrapper { // TODO figure out how to handle locks - this.renderer.component.event_handlers.push({ - name: handler, - body: deindent` + let callee; + + // TODO dry this out — similar code for event handlers and component bindings + if (contextual_dependencies.size > 0) { + const deps = Array.from(contextual_dependencies); + + block.builders.init.addBlock(deindent` function ${handler}() { - ${ - animation_frame && deindent` - cancelAnimationFrame(${animation_frame}); - if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});` - } - ${mutations.length > 0 && mutations} - ${Array.from(deps).map(dep => `$$make_dirty('${dep}');`)} - console.log('changed'); + ctx.${handler}.call(this, ctx); } - ` - }); + `); + + this.renderer.component.event_handlers.push({ + name: handler, + body: deindent` + function ${handler}({ ${deps.join(', ')} }) { + ${ + animation_frame && deindent` + cancelAnimationFrame(${animation_frame}); + if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});` + } + ${mutations.length > 0 && mutations} + ${Array.from(dependencies).map(dep => `$$make_dirty('${dep}');`)} + } + ` + }); + + callee = handler; + } else { + this.renderer.component.event_handlers.push({ + name: handler, + body: deindent` + function ${handler}() { + ${ + animation_frame && deindent` + cancelAnimationFrame(${animation_frame}); + if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});` + } + ${mutations.length > 0 && mutations} + ${Array.from(dependencies).map(dep => `$$make_dirty('${dep}');`)} + } + ` + }); + + callee = `ctx.${handler}`; + } group.events.forEach(name => { if (name === 'resize') { @@ -490,7 +523,7 @@ export default class ElementWrapper extends Wrapper { block.addVariable(resize_listener); block.builders.mount.addLine( - `${resize_listener} = @addResizeListener(${this.var}, ctx.${handler});` + `${resize_listener} = @addResizeListener(${this.var}, ${callee});` ); block.builders.destroy.addLine( @@ -498,11 +531,11 @@ export default class ElementWrapper extends Wrapper { ); } else { block.builders.hydrate.addLine( - `@addListener(${this.var}, "${name}", ctx.${handler});` + `@addListener(${this.var}, "${name}", ${callee});` ); block.builders.destroy.addLine( - `@removeListener(${this.var}, "${name}", ctx.${handler});` + `@removeListener(${this.var}, "${name}", ${callee});` ); } }); diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index 2094fe62e7..c5df9d9b2f 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -44,13 +44,13 @@ export default class InlineComponentWrapper extends Wrapper { if (binding.isContextual) { // we need to ensure that the each block creates a context including // the list and the index, if they're not otherwise referenced - const { name } = getObject(binding.value.node); + const { name } = getObject(binding.expression.node); const eachBlock = block.contextOwners.get(name); eachBlock.hasBinding = true; } - block.addDependencies(binding.value.dependencies); + block.addDependencies(binding.expression.dependencies); }); this.node.handlers.forEach(handler => { @@ -200,24 +200,24 @@ export default class InlineComponentWrapper extends Wrapper { const builder = new CodeBuilder(); this.node.bindings.forEach((binding: Binding) => { - let { name: key } = getObject(binding.value.node); + let { name: key } = getObject(binding.expression.node); let setFromChild; if (binding.isContextual) { - const computed = isComputed(binding.value.node); - const tail = binding.value.node.type === 'MemberExpression' ? getTailSnippet(binding.value.node) : ''; + const computed = isComputed(binding.expression.node); + const tail = binding.expression.node.type === 'MemberExpression' ? getTailSnippet(binding.expression.node) : ''; - const head = block.bindings.get(key); + const { object, property, snippet } = block.bindings.get(key)(); - const lhs = binding.value.node.type === 'MemberExpression' - ? binding.value.snippet - : `${head()}${tail} = childState${quotePropIfNecessary(binding.name)}`; + const lhs = binding.expression.node.type === 'MemberExpression' + ? binding.expression.snippet + : `${snippet} = childState${quotePropIfNecessary(binding.name)}`; setFromChild = deindent` ${lhs} = childState${quotePropIfNecessary(binding.name)}; - ${[...binding.value.dependencies] + ${[...binding.expression.dependencies] .map((name: string) => { const isStoreProp = name[0] === '$'; const prop = isStoreProp ? name.slice(1) : name; @@ -239,9 +239,9 @@ export default class InlineComponentWrapper extends Wrapper { if (isStoreProp) hasStoreBindings = true; else hasLocalBindings = true; - if (binding.value.node.type === 'MemberExpression') { + if (binding.expression.node.type === 'MemberExpression') { setFromChild = deindent` - ${binding.value.snippet} = childState${quotePropIfNecessary(binding.name)}; + ${binding.expression.snippet} = childState${quotePropIfNecessary(binding.name)}; ${newState}${quotePropIfNecessary(prop)} = ctx${quotePropIfNecessary(key)}; `; } @@ -252,8 +252,8 @@ export default class InlineComponentWrapper extends Wrapper { } statements.push(deindent` - if (${binding.value.snippet} !== void 0) { - ${name_initial_data}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet}; + if (${binding.expression.snippet} !== void 0) { + ${name_initial_data}${quotePropIfNecessary(binding.name)} = ${binding.expression.snippet}; ${name_updating}${quotePropIfNecessary(binding.name)} = true; }` ); @@ -264,9 +264,9 @@ export default class InlineComponentWrapper extends Wrapper { ); updates.push(deindent` - if (!${name_updating}${quotePropIfNecessary(binding.name)} && ${[...binding.value.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) { - ${name_changes}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet}; - ${name_updating}${quotePropIfNecessary(binding.name)} = ${binding.value.snippet} !== void 0; + if (!${name_updating}${quotePropIfNecessary(binding.name)} && ${[...binding.expression.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) { + ${name_changes}${quotePropIfNecessary(binding.name)} = ${binding.expression.snippet}; + ${name_updating}${quotePropIfNecessary(binding.name)} = ${binding.expression.snippet} !== void 0; } `); }); @@ -380,7 +380,7 @@ export default class InlineComponentWrapper extends Wrapper { #component.$$root._beforecreate.push(() => { const changed = {}; ${this.node.bindings.map(binding => deindent` - if (${binding.value.snippet} === void 0) changed.${binding.name} = 1;`)} + if (${binding.expression.snippet} === void 0) changed.${binding.name} = 1;`)} ${name}._bind(changed, ${name}.get()); });`} ${name}.$$fragment.c();