CustomElementConstructor is missing observedAttributes
The CustomElementConstructor interface defined in lib.dom.d.ts seems to be missing the observedAttributes static property (supported in all modern browser engines).
Steps to reproduce (playground):
class MyElement extends HTMLElement {
static observedAttributes = ['foo']
}
function define(tagName: string, ctor: CustomElementConstructor) {
customElements.define(tagName, ctor)
console.log(ctor.observedAttributes)
}
define('my-element', MyElement)
Expected result: no error.
Actual result: TypeScript finds an error for the line with console.log(ctor.observedAttributes):
Property 'observedAttributes' does not exist on type 'CustomElementConstructor'.(2339)
Here is a CodePen demonstrating that this does indeed work in the browser. The logged value should be ["foo"].
To clarify: the observedAttributes property is optional, so my proposed change is:
interface CustomElementConstructor {
new (...params: any[]): HTMLElement;
+ observedAttributes?: string[];
}
It's missing all the lifecycle callbacks too. It makes it tricky to derive custom element constructors without some workarounds.
function derive(ctor: CustomElementConstructor) {
static get observedAttributes() {
return [...super.observedAttributes || [], "my-extra-attr"]; // error, as above
}
attributeChangedCallback(...params: any[]) {
super.attributeChangedCallback?.(...params); // Property 'attributeChangedCallback' does not exist on type 'HTMLElement'.
}
}
This isn't as straightforward to fix as adding those properties to the interface, since the constructor is defined as returning a HTMLElement instance (which is obviously incorrect). I suspect this will become more of an issue as decorators become more widely used on custom element classes.
The interface should probably look something more like this:
interface CustomElementConstructor extends HTMLElement {
new (...params: any[]): CustomElementConstructor;
observedAttributes?: any;
adoptedCallback?(...params: any[]): any;
attributeChangedCallback?(...params: any[]): any;
connectedCallback?(...params: any[]): any;
disconnectedCallback?(...params: any[]): any;
}
NB: I've used any types here because, technically, you can register classes with different signatures and return values as custom elements. If narrow types were used, e.g. string[] for observedAttributes, it would restrict what you could pass to customElements.define()