Craig Howell
Craig Howell

Reputation: 1184

Svelte strongly type class of components

I am working on a component library that has a bit of a twist on how components are grouped. I am grouping components into classes to make using the library simpler for end users. Rather than remembering which named slots are available they would instead access the custom slots using a dot notation. For example, a Card component could have a Header, Body, and a Footer. In my library I would like to use these componets like so:

<Card>
  <Card.Header>I am a Header</Card.Header>
  <Card.Body>I am a Body</Card.Body>
  <Card.Footer>I am a Footer</Card.Footer>
</Card>

I have this working currently but I am trying to get this strongly typed as well and that is where I am running into trouble. Here is my file structure:

card
  | Card.svelte
  | Body.svelte
  | Header.svelte
  | Footer.svelte
  | index.ts

My index.ts file has the following code:

import OriginalCard from './Card.svelte';
import Header from './Header.svelte';
import Footer from './Footer.svelte';
import Body from './Body.svelte';

const Card = OriginalCard;
Card.Header = Header;
Card.Body = Body;
Card.Footer = Footer;

export default Card;

Header, Body, & Footer all return the following error: Property 'Header' does not exist on type 'typeof default'. ts(2339)

When using the component like so:

<script lang="ts>
  import Card from '@components/card';
</script>

<Card>
  <Card.Header>I am a Header</Card.Header>
  <Card.Body>I am a Body</Card.Body>
  <Card.Footer>I am a Footer</Card.Footer>
</Card>

Header, Body, & Footer all return the following error: Property 'Body' does not exist on type 'typeof Card__SvelteComponent_'. ts(2339)

How do I fix these errors?

EDIT

After installing this package in another project I noticed two issues that the accepted answer does not solve:

1). <Card /> returns the following ts error - Expected 0 arguments, but got 1. and breaks all intellisense for the allowable properties.

2). Intellisense does not work with the dot notation. When typing <Card. I am not getting suggestions for any of the sub-components and instead it is just showing all components in the entire package.

The desired outcome is to not have the ts errors and not lose intellisense with the package.

I also attempted to extend the CardStatic like so: interface CardStatic extends OriginalCard and the same issues continued to persist.

Upvotes: 2

Views: 949

Answers (2)

Jos&#233; Ram&#237;rez
Jos&#233; Ram&#237;rez

Reputation: 2315

I found a simpler way of doing this in SvelteKit when used to create component libraries: Export an object named Card from the lib folder's index.ts file like this:

import _Card from "./Card.svelte";
import _CardBody from "./CardBody.svelte";
import _CardHeader from "./CardHeader.svelte";
import _CardFooter from "./CardFooter.svelte";

export const Card = {
    Root: _Card,
    Footer: _CardFooter,
    Header: _CardHeader,
    Body: _CardBody
};

With this you don't need additional typings, but there is one catch: the <Card> component is now <Card.Root>. It would be imported and used from other projects like this:

<script lang="ts">
    import { Card } from 'the-component-library';
</script>

<Card.Root>
    <Card.Header>My Header</Card.Header>
    <Card.Footer>My Footer</Card.Footer>
</Card.Root>

Because of the catch, I would say this cannot be considered an answer, but I still volunteer in case this catch is a minor thing to people over going through the typing part provided by the accepted answer.

Upvotes: 0

brunnerh
brunnerh

Reputation: 184526

You have to extend the known type to add the additional properties. Since they are static I would try something like this:

const Card = OriginalCard as CardStatic;
Card.Header = Header;
Card.Body = Body;
Card.Footer = Footer;

export default Card;

export interface CardStatic {
    new(...args: ConstructorParameters<typeof OriginalCard>): OriginalCard;
    Header: typeof Header;
    Body: typeof Body;
    Footer: typeof Footer;
}

Upvotes: 3

Related Questions