diff --git a/CHANGELOG.md b/CHANGELOG.md index 597c043c..9709e527 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). +### Added +* The protected `performUpdate()` method may now be called to syncronously "flush" a pending update, for example via a property setter. Note, performing a synchronous update only updates the element and not any potentially pending descendants in the element's local DOM ([#959](https://github.com/Polymer/lit-element/issues/959)). + +* Constructible stylesheets may now be provided directly as styles, in addition to using the `css` tagged template function ([#853](https://github.com/Polymer/lit-element/issues/853)). + ## [2.3.1] - 2020-03-19 ### Fixed diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index e6dd8ccc..c9e26bbd 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -751,6 +751,12 @@ export abstract class UpdatingElement extends HTMLElement { * ``` */ protected performUpdate(): void|Promise { + // Abort any update if one is not pending when this is called. + // This can happen if `performUpdate` is called early to "flush" + // the update. + if (!this._hasRequestedUpdate) { + return; + } // Mixin instance properties once, if they exist. if (this._instanceProperties) { this._applyInstanceProperties(); diff --git a/src/test/lib/updating-element_test.ts b/src/test/lib/updating-element_test.ts index 72c96e3c..f6346c6a 100644 --- a/src/test/lib/updating-element_test.ts +++ b/src/test/lib/updating-element_test.ts @@ -1975,6 +1975,84 @@ suite('UpdatingElement', () => { assert.deepEqual(el._observedZot2, {value: 'zot', oldValue: ''}); }); + test('can customize properties to update synchronously', async () => { + + interface MyPropertyDeclaration extends PropertyDeclaration { + sync: boolean; + } + + @customElement(generateElementName()) + class E extends UpdatingElement { + + static getPropertyDescriptor( + name: PropertyKey, + key: string|symbol, + options: MyPropertyDeclaration) { + const defaultDescriptor = super.getPropertyDescriptor(name, key, options); + const setter = defaultDescriptor.set; + return Object.assign(defaultDescriptor, { + set(this: E, value: unknown) { + setter.call(this, value); + if (options.sync && this.hasUpdated && !this.isUpdating) { + (this as unknown as E).performUpdate(); + } + } + }); + } + + isUpdating = false; + + updateCount = 0; + + performUpdate() { + // While it's dubious to have a computed property that's + // also settable but this just demonstrates it's possible. + this.isUpdating = true; + super.performUpdate(); + this.isUpdating = false; + } + + update(changedProperties: PropertyValues) { + this.zug = this.foo + 1; + super.update(changedProperties); + } + + updated() { + this.updateCount++; + } + + @property({type: Number, sync: true, reflect: true} as PropertyDeclaration) + foo = 5; + + @property({type: Number, sync: true} as PropertyDeclaration) + zug = this.foo; + + @property({}) + bar = 'bar'; + + } + + const el = new E(); + container.appendChild(el); + await el.updateComplete; + el.foo = 10; + assert.equal(el.updateCount, 2); + el.foo = 1; + el.foo = 2; + assert.equal(el.updateCount, 4); + el.foo = 3; + await el.updateComplete; + assert.equal(el.updateCount, 5); + el.bar = 'bar2'; + assert.equal(el.updateCount, 5); + await el.updateComplete; + assert.equal(el.updateCount, 6); + el.foo = 5; + assert.equal(el.updateCount, 7); + el.zug = 60; + assert.equal(el.updateCount, 8); + }); + test('attribute-based property storage', async () => { class E extends UpdatingElement { _updateCount = 0;