diff --git a/compiler/generate/binding/index.js b/compiler/generate/binding/index.js index 4fffd99488..7f361bcab0 100644 --- a/compiler/generate/binding/index.js +++ b/compiler/generate/binding/index.js @@ -1,4 +1,6 @@ import deindent from '../utils/deindent.js'; +import isReference from '../utils/isReference.js'; +import flattenReference from '../utils/flattenReference.js'; export default function createBinding ( node, name, attribute, current, initStatements, updateStatements, teardownStatements, allUsedContexts ) { const parts = attribute.value.split( '.' ); @@ -20,8 +22,21 @@ export default function createBinding ( node, name, attribute, current, initStat } if ( contextual ) { - // TODO can we target only things that have changed? - // TODO computed values/observers that depend on this probably won't update... + // find the top-level property that this is a child of + let fragment = current; + let prop = parts[0]; + + do { + if ( fragment.expression && fragment.context === prop ) { + if ( !isReference( fragment.expression ) ) { + // TODO this should happen in prior validation step + throw new Error( `${prop} is read-only, it cannot be bound` ); + } + + prop = flattenReference( fragment.expression ).name; + } + } while ( fragment = fragment.parent ); + const listName = current.listNames[ parts[0] ]; const indexName = current.indexNames[ parts[0] ]; @@ -30,7 +45,7 @@ export default function createBinding ( node, name, attribute, current, initStat var index = this.__svelte.${indexName}; list[index]${parts.slice( 1 ).map( part => `.${part}` ).join( '' )} = this.${attribute.name}; - component.set({}); + component.set({ ${prop}: component.get( '${prop}' ) }); `; } else if ( deep ) { setter = deindent` diff --git a/compiler/generate/index.js b/compiler/generate/index.js index caaad6623d..75820de03a 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -495,6 +495,8 @@ export default function generate ( parsed, template ) { useAnchor: false, name: renderer, target: 'target', + expression: node.expression, + context: node.context, contexts, indexes, diff --git a/test/compiler/binding-input-checkbox-deep-contextual/_config.js b/test/compiler/binding-input-checkbox-deep-contextual/_config.js new file mode 100644 index 0000000000..0fa0b8cfa2 --- /dev/null +++ b/test/compiler/binding-input-checkbox-deep-contextual/_config.js @@ -0,0 +1,34 @@ +import * as assert from 'assert'; + +export default { + data: { + items: [ + { description: 'one', completed: true }, + { description: 'two', completed: false }, + { description: 'three', completed: false } + ] + }, + html: `

one

two

three

\n\n

1 completed

`, + test ( component, target, window ) { + const inputs = [ ...target.querySelectorAll( 'input' ) ]; + + assert.ok( inputs[0].checked ); + assert.ok( !inputs[1].checked ); + assert.ok( !inputs[2].checked ); + + const event = new window.Event( 'change' ); + + inputs[1].checked = true; + inputs[1].dispatchEvent( event ); + + assert.equal( component.get( 'numCompleted' ), 2 ); + assert.equal( target.innerHTML, `

one

two

three

\n\n

2 completed

` ); + + const items = component.get( 'items' ); + items[2].completed = true; + + component.set({ items }); + assert.ok( inputs[2].checked ); + assert.equal( target.innerHTML, `

one

two

three

\n\n

3 completed

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

{{item.description}}

+{{/each}} + +

{{numCompleted}} completed

+ +