diff --git a/packages/component-base/src/polylit-mixin.js b/packages/component-base/src/polylit-mixin.js index e262c4b42f..01f23b4b57 100644 --- a/packages/component-base/src/polylit-mixin.js +++ b/packages/component-base/src/polylit-mixin.js @@ -93,6 +93,9 @@ const PolylitMixinImplementation = (superclass) => { let result = defaultDescriptor; + // Set the key for this property + this.getOrCreateMap('__propKeys').set(name, key); + if (options.sync) { result = { get: defaultDescriptor.get, @@ -232,6 +235,26 @@ const PolylitMixinImplementation = (superclass) => { } } + /** + * Set several properties at once and perform synchronous update. + * @protected + */ + setProperties(props) { + Object.entries(props).forEach(([name, value]) => { + // Use private key and not setter to not trigger + // update for properties marked as `sync: true`. + const key = this.constructor.__propKeys.get(name); + const oldValue = this[key]; + this[key] = value; + this.requestUpdate(name, oldValue); + }); + + // Perform sync update + if (this.hasUpdated) { + this.performUpdate(); + } + } + /** @protected */ _createMethodObserver(observer) { const dynamicObservers = getOrCreateMap(this, '__dynamicObservers'); diff --git a/packages/component-base/test/polylit-mixin.test.js b/packages/component-base/test/polylit-mixin.test.js index 2dcdacc590..760ab8b2db 100644 --- a/packages/component-base/test/polylit-mixin.test.js +++ b/packages/component-base/test/polylit-mixin.test.js @@ -1003,4 +1003,56 @@ describe('PolylitMixin', () => { expect(element.count).to.equal(1); }); }); + + describe('setProperties()', () => { + let element; + + const tag = defineCE( + class extends PolylitMixin(LitElement) { + static get properties() { + return { + disabled: { + type: Boolean, + sync: true, + }, + + value: { + type: String, + }, + }; + } + }, + ); + + beforeEach(async () => { + element = fixtureSync(`<${tag}>`); + await element.updateComplete; + }); + + it('should set property values on the element', () => { + element.setProperties({ value: 'foo', disabled: true }); + expect(element.value).to.equal('foo'); + expect(element.disabled).to.be.true; + }); + + it('should request update for each passed property', () => { + const spy = sinon.spy(element, 'requestUpdate'); + element.setProperties({ value: 'foo', disabled: true }); + expect(spy).to.be.calledTwice; + expect(spy.firstCall.args[0]).to.equal('value'); + expect(spy.secondCall.args[0]).to.equal('disabled'); + }); + + it('should only call performUpdate() method once', () => { + const spy = sinon.spy(element, 'performUpdate'); + element.setProperties({ value: 'foo', disabled: true }); + expect(spy).to.be.calledOnce; + }); + + it('should not throw when calling before first render', () => { + expect(() => { + document.createElement(tag).setProperties({ disabled: true }); + }).to.not.throw(Error); + }); + }); });