18

I am implementing the following class:

export class BbSVGElement extends HTMLElement implements HTMLCanvasElement { ... }

The problem is that I can't find the type of the <svg> element in typescript: <span> elements are HTMLSpanElement, <canvas> elements are HTMLCanvasElement, etc.

I ask because to implement a simple task such as the height() function, I need to get the bounding box of the element. According to this stack overflow answer, this is the right way to do it:

height(): number { return this.getBBox().height}

But since this refers to an HTMLElement, of course Property 'getBBox' does not exist on type 'BbSVGElement'.ts(2339)

So I tried with castings of all kinds; things like:

return (<SVGElement> this).getBBox().width
return (<SVGSVGElement> this).getBBox().width

of course I could do something like

return (<any> this).getBBox().width

or

return this.offsetWidth // <-- This property here comes from HTMLElement

But I'd rather not if possible.

So: What would be the right casting to do? Or would there be another option I missed?

ecstrema
  • 543
  • 1
  • 5
  • 20
  • The SVG-DOM interface types for SVG are documented here: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model#SVG_interfaces – Dai Jul 30 '20 at 03:53
  • SVGElement isn't going to be under HTML... it's independent. I don't use Typescript, but I'm guessing you'll have to import something else. – Brad Jul 30 '20 at 03:54
  • Are you looking for `SVGGraphicsElement`? That's the type of thing with a [`getBBox()` method](https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphicsElement/getBBox), right? – jcalz Jul 30 '20 at 03:57
  • What is `BbSVGElement` meant to represent? You shouldn't be doing `extends HTMLElement implements HTMLCanvasElement` because you're not reimplementing `` (and `` and SVG are completely separate things anyway) - and you shouldn't extend `HTMLElement` unless you're defining HTML5 Web Components. What are you actually trying to accomplish? – Dai Jul 30 '20 at 04:02
  • After thinking about it more - can you confirm that the `` element you're referring to **is an `` element inside HTML** - rather than an SVG element within an SVG document? – Dai Jul 30 '20 at 04:05
  • @Dai I can confirm that the element I am referring to is an element inside HTML. – ecstrema Jul 30 '20 at 20:11
  • @Dai I am trying to implement dual painting mode: provide the option to paint as SVG or as canvas. I therefore must implement every function in HTMLCanvasElement. extending HTMLElement does most of the job. – ecstrema Jul 30 '20 at 20:12
  • @Brad Yes, SVGElement is under html, no import required. – ecstrema Jul 30 '20 at 20:13
  • @jcalz No, I'm not looking for SVGGraphicsElement. as @Dai pointed out in his answer, I was looking for `type SVGOrHtmlElement = HTMLElement & SVGElement;` – ecstrema Jul 30 '20 at 20:14
  • 1
    @MarcheRemi The name `SVGOrHtmlElement ` is incorrect because it's an intersection type, not a union-type. The correct name for your case would be `SVGAndHtmlElement` (or the `SvgInHtml` name I used in my posted answer). – Dai Jul 30 '20 at 22:35

1 Answers1

21

What is the type of the <svg> element in TypeScript?

The interface type of <svg> elements in an *.svg document (i.e. the SVG DOM) is SVGElement. The type of an <svg> element within a HTML document (i.e. the HTML DOM) is actually a prototype-less object that implements both HTMLElement and SVGElement!

So in TypeScript, you can represent an <svg> element in the DOM by using an intersection type (which represents a type that combines all members of two or more types, compared to a union type which represents a type that combines shared members of two or more types).

Like so:

type SvgInHtml = HTMLElement & SVGElement;

const svgElement: SvgInHtml = document.createElement('svg') as SvgInHtml;

const svgHeight = svgElement.getBBox().height; // Using `SVGElement.getBBox()`.
const htmlWidth = svgElement.offsetWidth;      // Using `HTMLElement.offsetWidth`

I see you want to represent this using a derived class - but that is incorrect (as class inheritance only allows a single parent class), instead either use a non-encapsulated type SvgInHtml reference, or encapsulate it in an object or new class (with no superclass):

type SvgInHtml = HTMLElement & SVGElement;

class MySvgWrapper {

    constructor( private readonly svgElement: SvgInHtml ) {
    }

    get height(): number {
        return this.svgElement.getBBox().height;
    }
}

(This is my original answer I posted before I understood your problem correctly - I'm keeping it here and accessible because I feel it may still be useful for other people who find this question but have a different problem to yours):

The base SVG-DOM interface for all (most?) SVG elements is SVGElement which is documented on MDN.

Assuming you're using TypeScript 3.x or later which has the SVG-DOM interfaces in lib.dom.d.ts (see the content of my answer below the line) then you need to change your class declaration to this:

export class MySVGElement extends SVGElement {
    
}

Since TypeScript 3.x, TypeScript's "standard library" of typings for the HTML DOM includes the SVG DOM interfaces in lib/lib.dom.d.ts, so you shouldn't need to download, import, or add anything - just use SVGElement (and its derived interfaces) directly.

If you open the current TypeScript lib.dom.d.ts in GitHub you can search for "SVGElement" (and any other SVG-DOM interface, like SVGGraphicsElement) and see it's declared in there:

e.g.:

/** SVG elements whose primary purpose is to directly render graphics into a group. */
interface SVGGraphicsElement extends SVGElement, SVGTests {
    readonly transform: SVGAnimatedTransformList;
    getBBox(options?: SVGBoundingBoxOptions): DOMRect;
    getCTM(): DOMMatrix | null;
    getScreenCTM(): DOMMatrix | null;
    addEventListener<K extends keyof SVGElementEventMap>(type: K, listener: (this: SVGGraphicsElement, ev: SVGElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
    addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
    removeEventListener<K extends keyof SVGElementEventMap>(type: K, listener: (this: SVGGraphicsElement, ev: SVGElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
    removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

If you get any errors relating to unknown/undefined types then please post that as a new StackOverflow question.

Dai
  • 141,631
  • 28
  • 261
  • 374
  • Thank you for your response! Although what worked for me was `HTMLElement & SVGSVGElement`! Like this: `const [ref, { width }] = useMeasure();` – Paul Melero Feb 13 '21 at 21:25
  • maybe use `HTMLOrSVGElement` predefined interface – Erfan Azary Jan 16 '23 at 13:50
  • Why not simply, `SVGSVGElement`? It's exactly what the OP asked for https://developer.mozilla.org/en-US/docs/Web/API/SVGSVGElement – tvanc Mar 07 '23 at 05:17
  • 1
    @tvanc `SVGSVGElement` is not a `HTMLElement`: the `SVGSVGElement` type is only appropriate for use on the "inner" representation of an "outer" `` element in HTML. For example, `SVGSVGElement` lacks members defined on `HTMLElement` which _do_ apply to "outer" `` elements in HTML, such as `hidden`, `title`, and so on. – Dai Mar 07 '23 at 06:10