diff --git a/src/compile/render-dom/wrappers/Element/Binding.ts b/src/compile/render-dom/wrappers/Element/Binding.ts index 710b6e8c6d..7604c16cc2 100644 --- a/src/compile/render-dom/wrappers/Element/Binding.ts +++ b/src/compile/render-dom/wrappers/Element/Binding.ts @@ -221,6 +221,17 @@ function getEventHandler( snippet: string ) { const value = getValueFromDom(renderer, binding.parent, binding); + const store = binding.object[0] === '$' ? binding.object.slice(1) : null; + + if (store && binding.node.expression.node.type === 'MemberExpression') { + // TODO is there a way around this? Mutating an object doesn't work, + // because stores check for referential equality (i.e. immutable data). + // But we can't safely or easily clone objects. So for now, we bail + renderer.component.error(binding.node.expression.node.property, { + code: 'invalid-store-binding', + message: 'Cannot bind to a nested property of a store' + }); + } if (binding.node.isContextual) { let tail = ''; @@ -233,15 +244,21 @@ function getEventHandler( return { usesContext: true, - mutation: `${snippet}${tail} = ${value};`, + mutation: store + ? `${store}.set(${value});` + : `${snippet}${tail} = ${value};`, contextual_dependencies: new Set([object, property]) }; } + const mutation = store + ? `${store}.set(${value});` + : `${snippet} = ${value};`; + if (binding.node.expression.node.type === 'MemberExpression') { return { usesContext: binding.node.expression.usesContext, - mutation: `${snippet} = ${value};`, + mutation, contextual_dependencies: binding.node.expression.contextual_dependencies, snippet }; @@ -249,7 +266,7 @@ function getEventHandler( return { usesContext: false, - mutation: `${snippet} = ${value};`, + mutation, 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 03ce9eed9f..97d0a90459 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -456,7 +456,7 @@ export default class ElementWrapper extends Wrapper { this.renderer.component.partly_hoisted.push(deindent` function ${handler}(${contextual_dependencies.size > 0 ? `{ ${[...contextual_dependencies].join(', ')} }` : ``}) { ${group.bindings.map(b => b.handler.mutation)} - ${Array.from(dependencies).map(dep => `$$invalidate('${dep}', ${dep});`)} + ${Array.from(dependencies).filter(dep => dep[0] !== '$').map(dep => `$$invalidate('${dep}', ${dep});`)} } `); diff --git a/test/runtime/samples/binding-store-deep/_config.js b/test/runtime/samples/binding-store-deep/_config.js new file mode 100644 index 0000000000..e43df6cfae --- /dev/null +++ b/test/runtime/samples/binding-store-deep/_config.js @@ -0,0 +1,5 @@ +export default { + error(assert, err) { + assert.equal(err.message, `Cannot bind to a nested property of a store`); + } +}; diff --git a/test/runtime/samples/binding-store-deep/main.html b/test/runtime/samples/binding-store-deep/main.html new file mode 100644 index 0000000000..3bfd8d5717 --- /dev/null +++ b/test/runtime/samples/binding-store-deep/main.html @@ -0,0 +1,8 @@ + + + +

hello {$user.name}

\ No newline at end of file diff --git a/test/runtime/samples/binding-store/_config.js b/test/runtime/samples/binding-store/_config.js new file mode 100644 index 0000000000..eb22d3e96c --- /dev/null +++ b/test/runtime/samples/binding-store/_config.js @@ -0,0 +1,41 @@ +export default { + html: ` + +

hello world

+ `, + + ssrHtml: ` + +

hello world

+ `, + + async test({ assert, component, target, window }) { + const input = target.querySelector('input'); + assert.equal(input.value, 'world'); + + const event = new window.Event('input'); + + const names = []; + const unsubscribe = component.name.subscribe(name => { + names.push(name); + }); + + input.value = 'everybody'; + await input.dispatchEvent(event); + + assert.htmlEqual(target.innerHTML, ` + +

hello everybody

+ `); + + await component.name.set('goodbye'); + assert.equal(input.value, 'goodbye'); + assert.htmlEqual(target.innerHTML, ` + +

hello goodbye

+ `); + + assert.deepEqual(names, ['world', 'everybody', 'goodbye']); + unsubscribe(); + }, +}; diff --git a/test/runtime/samples/binding-store/main.html b/test/runtime/samples/binding-store/main.html new file mode 100644 index 0000000000..cfcdb0cf20 --- /dev/null +++ b/test/runtime/samples/binding-store/main.html @@ -0,0 +1,8 @@ + + + +

hello {$name}

\ No newline at end of file