diff --git a/src/generators/dom/visitors/Component/Binding.ts b/src/generators/dom/visitors/Component/Binding.ts index fa7d539a42..ac8e90f0f2 100644 --- a/src/generators/dom/visitors/Component/Binding.ts +++ b/src/generators/dom/visitors/Component/Binding.ts @@ -5,6 +5,7 @@ import { DomGenerator } from '../../index'; import Block from '../../Block'; import { Node } from '../../../../interfaces'; import { State } from '../../interfaces'; +import getObject from '../../../../utils/getObject'; export default function visitBinding( generator: DomGenerator, @@ -14,16 +15,11 @@ export default function visitBinding( attribute, local ) { - const { name } = flattenReference(attribute.value); + const { name } = getObject(attribute.value); const { snippet, contexts, dependencies } = block.contextualise( attribute.value ); - if (dependencies.length > 1) - throw new Error( - 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' - ); - contexts.forEach(context => { if (!~local.allUsedContexts.indexOf(context)) local.allUsedContexts.push(context); @@ -38,8 +34,9 @@ export default function visitBinding( obj = block.listNames.get(name); prop = block.indexNames.get(name); } else if (attribute.value.type === 'MemberExpression') { - prop = `'[✂${attribute.value.property.start}-${attribute.value.property - .end}✂]'`; + prop = `[✂${attribute.value.property.start}-${attribute.value.property + .end}✂]`; + if (!attribute.value.computed) prop = `'${prop}'`; obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`; } else { obj = 'state'; @@ -85,7 +82,7 @@ export default function visitBinding( local.update.addBlock(deindent` if ( !${updating} && ${dependencies .map(dependency => `'${dependency}' in changed`) - .join('||')} ) { + .join(' || ')} ) { ${updating} = true; ${local.name}._set({ ${attribute.name}: ${snippet} }); ${updating} = false; diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts index fdac104a87..7963677d19 100644 --- a/src/generators/dom/visitors/Element/Binding.ts +++ b/src/generators/dom/visitors/Element/Binding.ts @@ -6,12 +6,7 @@ import { DomGenerator } from '../../index'; import Block from '../../Block'; import { Node } from '../../../../interfaces'; import { State } from '../../interfaces'; - -function getObject(node) { - // TODO validation should ensure this is an Identifier or a MemberExpression - while (node.type === 'MemberExpression') node = node.object; - return node; -} +import getObject from '../../../../utils/getObject'; export default function visitBinding( generator: DomGenerator, @@ -21,15 +16,7 @@ export default function visitBinding( attribute: Node ) { const { name } = getObject(attribute.value); - const { snippet, contexts } = block.contextualise(attribute.value); - const dependencies = block.contextDependencies.has(name) - ? block.contextDependencies.get(name) - : [name]; - - if (dependencies.length > 1) - throw new Error( - 'An unexpected situation arose. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!' - ); + const { snippet, contexts, dependencies } = block.contextualise(attribute.value); contexts.forEach(context => { if (!~state.allUsedContexts.indexOf(context)) diff --git a/src/generators/dom/visitors/shared/binding/getSetter.ts b/src/generators/dom/visitors/shared/binding/getSetter.ts index 5cb4cc4c62..7ce77b4489 100644 --- a/src/generators/dom/visitors/shared/binding/getSetter.ts +++ b/src/generators/dom/visitors/shared/binding/getSetter.ts @@ -1,4 +1,6 @@ import deindent from '../../../../../utils/deindent'; +import getTailSnippet from '../../../../../utils/getTailSnippet'; +import { Node } from '../../../../../interfaces'; export default function getSetter({ block, @@ -40,15 +42,7 @@ export default function getSetter({ return `${block.component}._set({ ${name}: ${value} });`; } -function getTailSnippet(node) { - const end = node.end; - while (node.type === 'MemberExpression') node = node.object; - const start = node.end; - - return `[✂${start}-${end}✂]`; -} - -function isComputed(node) { +function isComputed(node: Node) { while (node.type === 'MemberExpression') { if (node.computed) return true; node = node.object; diff --git a/src/generators/server-side-rendering/Block.ts b/src/generators/server-side-rendering/Block.ts index 1ad36fff5a..8d908ca8ea 100644 --- a/src/generators/server-side-rendering/Block.ts +++ b/src/generators/server-side-rendering/Block.ts @@ -2,6 +2,7 @@ import deindent from '../../utils/deindent'; import flattenReference from '../../utils/flattenReference'; import { SsrGenerator } from './index'; import { Node } from '../../interfaces'; +import getObject from '../../utils/getObject'; interface BlockOptions { // TODO @@ -25,13 +26,13 @@ export default class Block { this.conditions.map(c => `(${c})`) ); - const { keypath } = flattenReference(binding.value); + const { name: prop } = getObject(binding.value); this.generator.bindings.push(deindent` if ( ${conditions.join('&&')} ) { tmp = ${name}.data(); - if ( '${keypath}' in tmp ) { - state.${binding.name} = tmp.${keypath}; + if ( '${prop}' in tmp ) { + state.${binding.name} = tmp.${prop}; settled = false; } } diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index 05523a29a8..6fb87973e4 100644 --- a/src/generators/server-side-rendering/visitors/Component.ts +++ b/src/generators/server-side-rendering/visitors/Component.ts @@ -3,6 +3,8 @@ import visit from '../visit'; import { SsrGenerator } from '../index'; import Block from '../Block'; import { Node } from '../../../interfaces'; +import getObject from '../../../utils/getObject'; +import getTailSnippet from '../../../utils/getTailSnippet'; export default function visitComponent( generator: SsrGenerator, @@ -52,9 +54,13 @@ export default function visitComponent( }) .concat( bindings.map(binding => { - const { name, keypath } = flattenReference(binding.value); - const value = block.contexts.has(name) ? keypath : `state.${keypath}`; - return `${binding.name}: ${value}`; + const { name } = getObject(binding.value); + const tail = binding.value.type === 'MemberExpression' + ? getTailSnippet(binding.value) + : ''; + + const keypath = block.contexts.has(name) ? `${name}${tail}` : `state.${name}${tail}`; + return `${binding.name}: ${keypath}`; }) ) .join(', '); diff --git a/src/utils/deindent.ts b/src/utils/deindent.ts index 13278b26b1..5f42e89604 100644 --- a/src/utils/deindent.ts +++ b/src/utils/deindent.ts @@ -1,6 +1,6 @@ const start = /\n(\t+)/; -export default function deindent(strings: string[], ...values: any[]) { +export default function deindent(strings: TemplateStringsArray, ...values: any[]) { const indentation = start.exec(strings[0])[1]; const pattern = new RegExp(`^${indentation}`, 'gm'); diff --git a/src/utils/getObject.ts b/src/utils/getObject.ts new file mode 100644 index 0000000000..c46e8e7583 --- /dev/null +++ b/src/utils/getObject.ts @@ -0,0 +1,7 @@ +import { Node } from '../interfaces'; + +export default function getObject(node: Node) { + // TODO validation should ensure this is an Identifier or a MemberExpression + while (node.type === 'MemberExpression') node = node.object; + return node; +} \ No newline at end of file diff --git a/src/utils/getTailSnippet.ts b/src/utils/getTailSnippet.ts new file mode 100644 index 0000000000..499e705ec6 --- /dev/null +++ b/src/utils/getTailSnippet.ts @@ -0,0 +1,9 @@ +import { Node } from '../interfaces'; + +export default function getTailSnippet(node: Node) { + const end = node.end; + while (node.type === 'MemberExpression') node = node.object; + const start = node.end; + + return `[✂${start}-${end}✂]`; +} \ No newline at end of file diff --git a/test/runtime/samples/component-binding-computed/Nested.html b/test/runtime/samples/component-binding-computed/Nested.html new file mode 100644 index 0000000000..ca75184db2 --- /dev/null +++ b/test/runtime/samples/component-binding-computed/Nested.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-binding-computed/_config.js b/test/runtime/samples/component-binding-computed/_config.js new file mode 100644 index 0000000000..08590c4954 --- /dev/null +++ b/test/runtime/samples/component-binding-computed/_config.js @@ -0,0 +1,34 @@ +export default { + html: ` + + + `, + + test ( assert, component, target, window ) { + const input = new window.Event( 'input' ); + const inputs = target.querySelectorAll( 'input' ); + + inputs[0].value = 'Ada'; + inputs[0].dispatchEvent(input); + assert.deepEqual(component.get('values'), { + firstname: 'Ada', + lastname: '' + }); + + inputs[1].value = 'Lovelace'; + inputs[1].dispatchEvent(input); + assert.deepEqual(component.get('values'), { + firstname: 'Ada', + lastname: 'Lovelace' + }); + + component.set({ + values: { + firstname: 'Grace', + lastname: 'Hopper' + } + }); + assert.equal(inputs[0].value, 'Grace'); + assert.equal(inputs[1].value, 'Hopper'); + } +}; diff --git a/test/runtime/samples/component-binding-computed/main.html b/test/runtime/samples/component-binding-computed/main.html new file mode 100644 index 0000000000..9ac402fec8 --- /dev/null +++ b/test/runtime/samples/component-binding-computed/main.html @@ -0,0 +1,22 @@ +{{#each fields as field}} + +{{/each}} + + \ No newline at end of file