diff --git a/compiler/generate/index.js b/compiler/generate/index.js index 5e405566f2..22b37ee52c 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -235,17 +235,29 @@ export default function generate ( parsed, template ) { } else if ( attribute.type === 'Binding' ) { - if ( attribute.value in current.contexts ) { - throw new Error( `Can only bind top-level properties` ); - } + const parts = attribute.value.split( '.' ); + const contextual = parts[0] in current.contexts; const handler = current.counter( `${name}ChangeHandler` ); + let setter; + + if ( contextual ) { + allUsedContexts.add( parts[0] ); + setter = deindent` + var context = this.__context.${parts[0]}; + context.${parts.slice( 1 ).join( '.' )} = this.value; + component.set({ ${current.contextChain[0]}: component.get( '${current.contextChain[0]}' ) }); + `; + } else { + setter = `component.set({ ${attribute.value}: ${name}.value });`; + } initStatements.push( deindent` var ${name}_updating = false; + function ${handler} () { ${name}_updating = true; - component.set({ ${attribute.value}: ${name}.value }); + ${setter} ${name}_updating = false; } @@ -253,7 +265,7 @@ export default function generate ( parsed, template ) { ` ); updateStatements.push( deindent` - if ( !${name}_updating ) ${name}.value = root.${attribute.value}; + if ( !${name}_updating ) ${name}.value = ${contextual ? attribute.value : `root.${attribute.value}`} ` ); teardownStatements.push( deindent` diff --git a/compiler/parse/read/directives.js b/compiler/parse/read/directives.js index 3573a5ded3..e1bd3f6c30 100644 --- a/compiler/parse/read/directives.js +++ b/compiler/parse/read/directives.js @@ -49,7 +49,7 @@ export function readBindingDirective ( parser, start, name ) { null ); - const value = parser.read( /[a-zA-Z_$][a-zA-Z0-9_$]*/ ); // TODO – keypaths? /([a-zA-Z_$][a-zA-Z0-9_$]*)(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*/ + const value = parser.read( /([a-zA-Z_$][a-zA-Z0-9_$]*)(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*/ ); if ( !value ) parser.error( `Expected valid property name` ); if ( quoteMark ) { diff --git a/test/compiler/binding-input-text-deep/_config.js b/test/compiler/binding-input-text-deep/_config.js new file mode 100644 index 0000000000..ada981c7a5 --- /dev/null +++ b/test/compiler/binding-input-text-deep/_config.js @@ -0,0 +1,31 @@ +import * as assert from 'assert'; + +export default { + data: { + items: [ + { description: 'one' }, + { description: 'two' }, + { description: 'three' } + ] + }, + html: `

one

two

three

`, + test ( component, target, window ) { + const inputs = [ ...target.querySelectorAll( 'input' ) ]; + + assert.equal( inputs[0].value, 'one' ); + + const event = new window.Event( 'input' ); + + inputs[1].value = 'four'; + inputs[1].dispatchEvent( event ); + + assert.equal( target.innerHTML, `

one

four

three

` ); + + const items = component.get( 'items' ); + items[2].description = 'five'; + + component.set({ items }); + assert.equal( inputs[2].value, 'five' ); + assert.equal( target.innerHTML, `

one

four

five

` ); + } +}; diff --git a/test/compiler/binding-input-text-deep/main.svelte b/test/compiler/binding-input-text-deep/main.svelte new file mode 100644 index 0000000000..210e4e4fea --- /dev/null +++ b/test/compiler/binding-input-text-deep/main.svelte @@ -0,0 +1,3 @@ +{{#each items as item}} +

{{item.description}}

+{{/each}}