diff --git a/.changeset/blue-olives-fetch.md b/.changeset/blue-olives-fetch.md new file mode 100644 index 0000000000..5ad7ead6cb --- /dev/null +++ b/.changeset/blue-olives-fetch.md @@ -0,0 +1,5 @@ +--- +'@lit/reactive-element': patch +--- + +Initializers added to subclasses are no longer improperly added to superclass. diff --git a/packages/reactive-element/src/reactive-element.ts b/packages/reactive-element/src/reactive-element.ts index 9dd8573f61..c9f964c7ae 100644 --- a/packages/reactive-element/src/reactive-element.ts +++ b/packages/reactive-element/src/reactive-element.ts @@ -490,8 +490,8 @@ export abstract class ReactiveElement * @nocollapse */ static addInitializer(initializer: Initializer) { - this._initializers ??= []; - this._initializers.push(initializer); + this.finalize(); + (this._initializers ??= []).push(initializer); } static _initializers?: Initializer[]; @@ -762,6 +762,12 @@ export abstract class ReactiveElement // finalize any superclasses const superCtor = Object.getPrototypeOf(this) as typeof ReactiveElement; superCtor.finalize(); + // Create own set of initializers for this class if any exist on the + // superclass and copy them down. Note, for a small perf boost, avoid + // creating initializers unless needed. + if (superCtor._initializers !== undefined) { + this._initializers = [...superCtor._initializers]; + } this.elementProperties = new Map(superCtor.elementProperties); // initialize Map populated in observedAttributes this.__attributeToPropertyMap = new Map(); diff --git a/packages/reactive-element/src/test/reactive-element_test.ts b/packages/reactive-element/src/test/reactive-element_test.ts index 427a40c8dc..82a47a5f23 100644 --- a/packages/reactive-element/src/test/reactive-element_test.ts +++ b/packages/reactive-element/src/test/reactive-element_test.ts @@ -2550,28 +2550,55 @@ suite('ReactiveElement', () => { assert.equal(a.getAttribute('bar'), 'yo'); }); - test('addInitializer', () => { - class A extends ReactiveElement { + suite('initializers', () => { + class Base extends ReactiveElement { prop1?: string; prop2?: string; event?: string; } - A.addInitializer((a) => { - (a as A).prop1 = 'prop1'; + Base.addInitializer((a) => { + (a as Base).prop1 = 'prop1'; }); - A.addInitializer((a) => { - (a as A).prop2 = 'prop2'; + Base.addInitializer((a) => { + (a as Base).prop2 = 'prop2'; }); - A.addInitializer((a) => { - a.addEventListener('click', (e) => ((a as A).event = e.type)); + Base.addInitializer((a) => { + a.addEventListener('click', (e) => ((a as Base).event = e.type)); + }); + customElements.define(generateElementName(), Base); + + test('addInitializer', () => { + const a = new Base(); + container.appendChild(a); + assert.equal(a.prop1, 'prop1'); + assert.equal(a.prop2, 'prop2'); + a.dispatchEvent(new Event('click')); + assert.equal(a.event, 'click'); + }); + + class Sub extends Base { + prop3?: string; + } + Sub.addInitializer((a) => { + (a as Sub).prop3 = 'prop3'; + }); + customElements.define(generateElementName(), Sub); + + test('addInitializer on subclass', () => { + const s = new Sub(); + container.appendChild(s); + assert.equal(s.prop1, 'prop1'); + assert.equal(s.prop2, 'prop2'); + assert.equal(s.prop3, 'prop3'); + s.dispatchEvent(new Event('click')); + assert.equal(s.event, 'click'); + }); + + test('addInitializer on subclass independent from superclass', () => { + const b = new Base(); + container.appendChild(b); + assert.notOk((b as any).prop3); }); - customElements.define(generateElementName(), A); - const a = new A(); - container.appendChild(a); - assert.equal(a.prop1, 'prop1'); - assert.equal(a.prop2, 'prop2'); - a.dispatchEvent(new Event('click')); - assert.equal(a.event, 'click'); }); suite('exceptions', () => {