From 572f5372d4d8ef75dbf245c1a00b81aa4b44452b Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 24 Apr 2023 22:03:33 +0200 Subject: [PATCH] breaking: deprecate SvelteComponentTyped, add generics to SvelteComponent (#8512) Also add data- attribute to HTMLAttributes and use available TS interfaces --- CHANGELOG.md | 1 + elements/index.d.ts | 13 ++-- src/runtime/internal/dev.ts | 129 ++++++++++++++---------------------- 3 files changed, 57 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 136f106121..42457e0844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * **breaking** Stricter types for `createEventDispatcher` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224)) * **breaking** Stricter types for `Action` and `ActionReturn` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224)) * **breaking** Stricter types for `onMount` - now throws a type error when returning a function asynchronously to catch potential mistakes around callback functions (see PR for migration instructions) ([#8136](https://github.com/sveltejs/svelte/pull/8136)) +* **breaking** Deprecate `SvelteComponentTyped`, use `SvelteComponent` instead ([#8512](https://github.com/sveltejs/svelte/pull/8512)) * Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391)) * Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251)) * Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312)) diff --git a/elements/index.d.ts b/elements/index.d.ts index ac32ae94c3..4d6e9f1c78 100644 --- a/elements/index.d.ts +++ b/elements/index.d.ts @@ -84,9 +84,9 @@ export interface DOMAttributes { 'on:beforeinput'?: EventHandler | undefined | null; 'on:input'?: FormEventHandler | undefined | null; 'on:reset'?: FormEventHandler | undefined | null; - 'on:submit'?: EventHandler | undefined | null; // TODO make this SubmitEvent once we require TS>=4.4 + 'on:submit'?: EventHandler | undefined | null; 'on:invalid'?: EventHandler | undefined | null; - 'on:formdata'?: EventHandler | undefined | null; // TODO make this FormDataEvent once we require TS>=4.4 + 'on:formdata'?: EventHandler | undefined | null; // Image Events 'on:load'?: EventHandler | undefined | null; @@ -547,9 +547,9 @@ export interface HTMLAttributes extends AriaAttributes, D 'bind:innerText'?: string | undefined | null; readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null; - readonly 'bind:contentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 - readonly 'bind:borderBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 - readonly 'bind:devicePixelContentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 + readonly 'bind:contentBoxSize'?: Array | undefined | null; + readonly 'bind:borderBoxSize'?: Array | undefined | null; + readonly 'bind:devicePixelContentBoxSize'?: Array | undefined | null; // SvelteKit 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; @@ -558,6 +558,9 @@ export interface HTMLAttributes extends AriaAttributes, D 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null; 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null; 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null; + + // allow any data- attribute + [key: `data-${string}`]: any; } export type HTMLAttributeAnchorTarget = diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 323312437b..efdd3a3469 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -157,10 +157,13 @@ export function construct_svelte_component_dev(component, props) { } } -type Props = Record; -export interface SvelteComponentDev { - $set(props?: Props): void; - $on(event: string, callback: ((event: any) => void) | null | undefined): () => void; +export interface SvelteComponentDev< + Props extends Record = any, + Events extends Record = any, + Slots extends Record = any // eslint-disable-line @typescript-eslint/no-unused-vars +> { + $set(props?: Partial): void; + $on>(type: K, callback: ((e: Events[K]) => void) | null | undefined): () => void; $destroy(): void; [accessor: string]: any; } @@ -177,8 +180,33 @@ export interface ComponentConstructorOptions = /** * Base class for Svelte components with some minor dev-enhancements. Used when dev=true. + * + * Can be used to create strongly typed Svelte components. + * + * ### Example: + * + * You have component library on npm called `component-library`, from which + * you export a component called `MyComponent`. For Svelte+TypeScript users, + * you want to provide typings. Therefore you create a `index.d.ts`: + * ```ts + * import { SvelteComponent } from "svelte"; + * export class MyComponent extends SvelteComponent<{foo: string}> {} + * ``` + * Typing this makes it possible for IDEs like VS Code with the Svelte extension + * to provide intellisense and to use the component like this in a Svelte file + * with TypeScript: + * ```svelte + * + * + * ``` */ -export class SvelteComponentDev extends SvelteComponent { +export class SvelteComponentDev< + Props extends Record = any, + Events extends Record = any, + Slots extends Record = any +> extends SvelteComponent { /** * @private * For type checking capabilities only. @@ -192,16 +220,16 @@ export class SvelteComponentDev extends SvelteComponent { * Does not exist at runtime. * ### DO NOT USE! */ - $$events_def: any; + $$events_def: Events; /** * @private * For type checking capabilities only. * Does not exist at runtime. * ### DO NOT USE! */ - $$slot_def: any; + $$slot_def: Slots; - constructor(options: ComponentConstructorOptions) { + constructor(options: ComponentConstructorOptions) { if (!options || (!options.target && !options.$$inline)) { throw new Error("'target' is a required option"); } @@ -221,82 +249,21 @@ export class SvelteComponentDev extends SvelteComponent { $inject_state() {} } -// TODO https://github.com/microsoft/TypeScript/issues/41770 is the reason -// why we have to split out SvelteComponentTyped to not break existing usage of SvelteComponent. -// Try to find a better way for Svelte 4.0. - +// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SvelteComponentTyped< Props extends Record = any, Events extends Record = any, - Slots extends Record = any // eslint-disable-line @typescript-eslint/no-unused-vars -> { - $set(props?: Partial): void; - $on>(type: K, callback: ((e: Events[K]) => void) | null | undefined): () => void; - $destroy(): void; - [accessor: string]: any; -} + Slots extends Record = any +> extends SvelteComponentDev {} + /** - * Base class to create strongly typed Svelte components. - * This only exists for typing purposes and should be used in `.d.ts` files. - * - * ### Example: - * - * You have component library on npm called `component-library`, from which - * you export a component called `MyComponent`. For Svelte+TypeScript users, - * you want to provide typings. Therefore you create a `index.d.ts`: - * ```ts - * import { SvelteComponentTyped } from "svelte"; - * export class MyComponent extends SvelteComponentTyped<{foo: string}> {} - * ``` - * Typing this makes it possible for IDEs like VS Code with the Svelte extension - * to provide intellisense and to use the component like this in a Svelte file - * with TypeScript: - * ```svelte - * - * - * ``` - * - * #### Why not make this part of `SvelteComponent(Dev)`? - * Because - * ```ts - * class ASubclassOfSvelteComponent extends SvelteComponent<{foo: string}> {} - * const component: typeof SvelteComponent = ASubclassOfSvelteComponent; - * ``` - * will throw a type error, so we need to separate the more strictly typed class. + * @deprecated Use `SvelteComponent` instead. See PR for more information: https://github.com/sveltejs/svelte/pull/8512 */ export class SvelteComponentTyped< Props extends Record = any, Events extends Record = any, Slots extends Record = any -> extends SvelteComponentDev { - /** - * @private - * For type checking capabilities only. - * Does not exist at runtime. - * ### DO NOT USE! - */ - $$prop_def: Props; - /** - * @private - * For type checking capabilities only. - * Does not exist at runtime. - * ### DO NOT USE! - */ - $$events_def: Events; - /** - * @private - * For type checking capabilities only. - * Does not exist at runtime. - * ### DO NOT USE! - */ - $$slot_def: Slots; - - constructor(options: ComponentConstructorOptions) { - super(options); - } -} +> extends SvelteComponentDev {} /** * Convenience type to get the type of a Svelte component. Useful for example in combination with @@ -305,21 +272,21 @@ export class SvelteComponentTyped< * Example: * ```html * * * * * ``` */ -export type ComponentType = new ( +export type ComponentType = new ( options: ComponentConstructorOptions< - Component extends SvelteComponentTyped ? Props : Record + Component extends SvelteComponentDev ? Props : Record > ) => Component; @@ -334,7 +301,7 @@ export type ComponentType * ``` */ -export type ComponentProps = Component extends SvelteComponentTyped +export type ComponentProps = Component extends SvelteComponentDev ? Props : never; @@ -354,7 +321,7 @@ export type ComponentProps = Component extend * ``` */ export type ComponentEvents = - Component extends SvelteComponentTyped ? Events : never; + Component extends SvelteComponentDev ? Events : never; export function loop_guard(timeout) { const start = Date.now();