0

I have an app with vanilla custom elements, like this:

class CardComponent extends HTMLElement {
  static get observedAttributes() {
    return ['header', 'text'];
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    ...
  }

  attributeChangedCallback(_name : string, _oldValue : string, _newValue : string) {
    ...
  }

  render() {
    ...
  }
}

Now, I have no TypeScript support and method definitions for these methods, since they are not part of the HTMLElement interface. Also haven't found a library that adds that support (like for example @types/node that adds Node.js type definitions). The only method that has documentation is attachShadow: attachShadow has type definition

How to add support?

3
  • Did you read this? typescriptlang.org/docs/handbook/declaration-files/templates/… It's a good starting point. Commented Jul 2 at 9:15
  • 1
    Generally if you want to locate and use HTML elements there are ways to narrow them down i.e. document.querySelector('.my-card') can be used with generics e.g. document.querySelector<CardComponent>('.my-card') this is not just for custom HTML elements, it also allows you to use functionality specific to things like HTMLFormElement etc. It's not clear how you actually use your elements here so I don't know if this suggestion is appropriate for your use case Commented Jul 2 at 11:47
  • @Sergiu Paraschiv The thing is that the typescript lib.dom.d.ts file already contains : declare var HTMLElement: { prototype: HTMLElement; new(): HTMLElement; } So actually they do contain the element that I'm based on, but it does not contain the interfaces I'm interested in. And I don't wan't to "monkey patch" the typescript basic modules. Commented Jul 2 at 19:27

2 Answers 2

2

I did not find any but since they are very simple you could easily do it yourself. Note that not all callbacks need to de implemented, and most of them has very simple signature that shouldn't really make it hard to code... Nonetheless, here's my suggestion:

interface OnConnected{
   connectedCallback:()=>void;
}
interface OnDisconnected{
   disconnectedCallback:()=>void;
}
interface OnMove{
   connectedMoveCallback:()=>void;
}
interface OnAdopted{
   adoptedCallback:()=>void;
}
interface OnAttributeChange{
   // Here I'm really not sure if the values can only be strings, maybe unknown would be better?
   attributeChangedCallback:(name:string,oldValue:string,newValue:string)=>void;
}

Having them separate would make things easy to implement:

class CardComponent extends HTMLElement implements OnConnected,OnAttributeChange{
  static get observedAttributes() {
    return ['header', 'text'];
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    ...
  }

  attributeChangedCallback(_name : string, _oldValue : string, _newValue : string) {
    ...
  }

  render() {
    ...
  }
}

Although this does not directly answers your question, this provides you a way to do it yourself without the need for a @types.

Sign up to request clarification or add additional context in comments.

Comments

0

So based on @Salketer's answer I ended up doing this:

interface CustomElementMethods  {
    /**
     * The `Element.observedAttributes` A static value (or a static getter) that returns an array of attribute names to observe for changes.These attributes will trigger the `attributeChangedCallback` method when they are changed, added, removed, or replaced.
     * @static
     * @return {string[]}
     * @see  {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes|MDN - observedAttributes}
     * @example
     * static observedAttributes() {
     *   return ['value', 'disabled'];
     * }
     */
    observedAttributes?(): string[];

    /**
     * The `Element.attributeChangedCallback()` is Called when attributes are changed, added, removed, or replaced.
     * The attributes that are observed are defined in the `observedAttributes` value.
     * Inside this method, you can check the name of the attribute that changed, and take appropriate action based on the new value.
     * @see  {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#responding_to_attribute_changes|MDN - attributeChangedCallback}
     * @example
     * attributeChangedCallback(name: string, oldValue: string, newValue: string) {
     *   if (name === 'value' && oldValue !== newValue) {
     *     this.render();
     *   }
     * }
     */
    attributeChangedCallback?(name: string, oldValue: string, newValue: string): void;

    /**
     * Called each time the element is added to the document.
     * The specification recommends that, as far as possible, developers should implement custom element setup in this callback rather than the constructor.
     * @see  {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks|MDN - connectedCallback}
     * @example
     * connectedCallback() {
     *   this.render();
     * }
     */
    connectedCallback?(): void

    /**
     * Called each time the element is removed from the document.
     * @see  {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks|MDN - disconnectedCallback}
     * @example
     * disconnectedCallback() {
     *   // Handle Cleanup logic
     * }
     */
    disconnectedCallback?(): void

    /**
     * When defined, this is called instead of connectedCallback() and disconnectedCallback() each time the element is moved to a different place in the DOM via Element.moveBefore().
     * Use this to avoid running initialization/cleanup code in the connectedCallback() and disconnectedCallback() callbacks when the element is not actually being added to or removed from the DOM.
     * See Lifecycle callbacks and state-preserving moves for more details.
     * @see  {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks|MDN - connectedMoveCallback}
     * @example
     * connectedMoveCallback() {
     *   // Handle move logic here
     * }
     */
    connectedMoveCallback?(): void

    /**
     * Called each time when the element is moved to a new document.
     * @see  {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks|MDN - adoptedCallback}
     * @example
     * adoptedCallback() {
     *   // Handle adoption logic here
     * }
     */
    adoptedCallback?(): void;


}

export type CustomElement = Partial<CustomElementMethods>

And then in my components:

class CardComponent extends HTMLElement implements CustomElement {
    constructor() {
        super();
        this.attachShadow({mode: 'open'});
    }
   ...
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.