TypeScript-DOM-lib-generator
TypeScript-DOM-lib-generator copied to clipboard
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()