Skip to content

Commit 572f537

Browse files
authored
breaking: deprecate SvelteComponentTyped, add generics to SvelteComponent (#8512)
Also add data- attribute to HTMLAttributes and use available TS interfaces
1 parent df2f656 commit 572f537

File tree

3 files changed

+57
-86
lines changed

3 files changed

+57
-86
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* **breaking** Stricter types for `createEventDispatcher` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
99
* **breaking** Stricter types for `Action` and `ActionReturn` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
1010
* **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))
11+
* **breaking** Deprecate `SvelteComponentTyped`, use `SvelteComponent` instead ([#8512](https://github.com/sveltejs/svelte/pull/8512))
1112
* Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391))
1213
* Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251))
1314
* Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312))

elements/index.d.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ export interface DOMAttributes<T extends EventTarget> {
8484
'on:beforeinput'?: EventHandler<InputEvent, T> | undefined | null;
8585
'on:input'?: FormEventHandler<T> | undefined | null;
8686
'on:reset'?: FormEventHandler<T> | undefined | null;
87-
'on:submit'?: EventHandler<Event & { readonly submitter: HTMLElement | null; }, T> | undefined | null; // TODO make this SubmitEvent once we require TS>=4.4
87+
'on:submit'?: EventHandler<SubmitEvent, T> | undefined | null;
8888
'on:invalid'?: EventHandler<Event, T> | undefined | null;
89-
'on:formdata'?: EventHandler<Event & { readonly formData: FormData; }, T> | undefined | null; // TODO make this FormDataEvent once we require TS>=4.4
89+
'on:formdata'?: EventHandler<FormDataEvent, T> | undefined | null;
9090

9191
// Image Events
9292
'on:load'?: EventHandler | undefined | null;
@@ -547,9 +547,9 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
547547
'bind:innerText'?: string | undefined | null;
548548

549549
readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null;
550-
readonly 'bind:contentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
551-
readonly 'bind:borderBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
552-
readonly 'bind:devicePixelContentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4
550+
readonly 'bind:contentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
551+
readonly 'bind:borderBoxSize'?: Array<ResizeObserverSize> | undefined | null;
552+
readonly 'bind:devicePixelContentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
553553

554554
// SvelteKit
555555
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
@@ -558,6 +558,9 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
558558
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
559559
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
560560
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
561+
562+
// allow any data- attribute
563+
[key: `data-${string}`]: any;
561564
}
562565

563566
export type HTMLAttributeAnchorTarget =

src/runtime/internal/dev.ts

Lines changed: 48 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,13 @@ export function construct_svelte_component_dev(component, props) {
157157
}
158158
}
159159

160-
type Props = Record<string, any>;
161-
export interface SvelteComponentDev {
162-
$set(props?: Props): void;
163-
$on(event: string, callback: ((event: any) => void) | null | undefined): () => void;
160+
export interface SvelteComponentDev<
161+
Props extends Record<string, any> = any,
162+
Events extends Record<string, any> = any,
163+
Slots extends Record<string, any> = any // eslint-disable-line @typescript-eslint/no-unused-vars
164+
> {
165+
$set(props?: Partial<Props>): void;
166+
$on<K extends Extract<keyof Events, string>>(type: K, callback: ((e: Events[K]) => void) | null | undefined): () => void;
164167
$destroy(): void;
165168
[accessor: string]: any;
166169
}
@@ -177,8 +180,33 @@ export interface ComponentConstructorOptions<Props extends Record<string, any> =
177180

178181
/**
179182
* Base class for Svelte components with some minor dev-enhancements. Used when dev=true.
183+
*
184+
* Can be used to create strongly typed Svelte components.
185+
*
186+
* ### Example:
187+
*
188+
* You have component library on npm called `component-library`, from which
189+
* you export a component called `MyComponent`. For Svelte+TypeScript users,
190+
* you want to provide typings. Therefore you create a `index.d.ts`:
191+
* ```ts
192+
* import { SvelteComponent } from "svelte";
193+
* export class MyComponent extends SvelteComponent<{foo: string}> {}
194+
* ```
195+
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
196+
* to provide intellisense and to use the component like this in a Svelte file
197+
* with TypeScript:
198+
* ```svelte
199+
* <script lang="ts">
200+
* import { MyComponent } from "component-library";
201+
* </script>
202+
* <MyComponent foo={'bar'} />
203+
* ```
180204
*/
181-
export class SvelteComponentDev extends SvelteComponent {
205+
export class SvelteComponentDev<
206+
Props extends Record<string, any> = any,
207+
Events extends Record<string, any> = any,
208+
Slots extends Record<string, any> = any
209+
> extends SvelteComponent {
182210
/**
183211
* @private
184212
* For type checking capabilities only.
@@ -192,16 +220,16 @@ export class SvelteComponentDev extends SvelteComponent {
192220
* Does not exist at runtime.
193221
* ### DO NOT USE!
194222
*/
195-
$$events_def: any;
223+
$$events_def: Events;
196224
/**
197225
* @private
198226
* For type checking capabilities only.
199227
* Does not exist at runtime.
200228
* ### DO NOT USE!
201229
*/
202-
$$slot_def: any;
230+
$$slot_def: Slots;
203231

204-
constructor(options: ComponentConstructorOptions) {
232+
constructor(options: ComponentConstructorOptions<Props>) {
205233
if (!options || (!options.target && !options.$$inline)) {
206234
throw new Error("'target' is a required option");
207235
}
@@ -221,82 +249,21 @@ export class SvelteComponentDev extends SvelteComponent {
221249
$inject_state() {}
222250
}
223251

224-
// TODO https://github.com/microsoft/TypeScript/issues/41770 is the reason
225-
// why we have to split out SvelteComponentTyped to not break existing usage of SvelteComponent.
226-
// Try to find a better way for Svelte 4.0.
227-
252+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
228253
export interface SvelteComponentTyped<
229254
Props extends Record<string, any> = any,
230255
Events extends Record<string, any> = any,
231-
Slots extends Record<string, any> = any // eslint-disable-line @typescript-eslint/no-unused-vars
232-
> {
233-
$set(props?: Partial<Props>): void;
234-
$on<K extends Extract<keyof Events, string>>(type: K, callback: ((e: Events[K]) => void) | null | undefined): () => void;
235-
$destroy(): void;
236-
[accessor: string]: any;
237-
}
256+
Slots extends Record<string, any> = any
257+
> extends SvelteComponentDev<Props, Events, Slots> {}
258+
238259
/**
239-
* Base class to create strongly typed Svelte components.
240-
* This only exists for typing purposes and should be used in `.d.ts` files.
241-
*
242-
* ### Example:
243-
*
244-
* You have component library on npm called `component-library`, from which
245-
* you export a component called `MyComponent`. For Svelte+TypeScript users,
246-
* you want to provide typings. Therefore you create a `index.d.ts`:
247-
* ```ts
248-
* import { SvelteComponentTyped } from "svelte";
249-
* export class MyComponent extends SvelteComponentTyped<{foo: string}> {}
250-
* ```
251-
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
252-
* to provide intellisense and to use the component like this in a Svelte file
253-
* with TypeScript:
254-
* ```svelte
255-
* <script lang="ts">
256-
* import { MyComponent } from "component-library";
257-
* </script>
258-
* <MyComponent foo={'bar'} />
259-
* ```
260-
*
261-
* #### Why not make this part of `SvelteComponent(Dev)`?
262-
* Because
263-
* ```ts
264-
* class ASubclassOfSvelteComponent extends SvelteComponent<{foo: string}> {}
265-
* const component: typeof SvelteComponent = ASubclassOfSvelteComponent;
266-
* ```
267-
* will throw a type error, so we need to separate the more strictly typed class.
260+
* @deprecated Use `SvelteComponent` instead. See PR for more information: https://github.com/sveltejs/svelte/pull/8512
268261
*/
269262
export class SvelteComponentTyped<
270263
Props extends Record<string, any> = any,
271264
Events extends Record<string, any> = any,
272265
Slots extends Record<string, any> = any
273-
> extends SvelteComponentDev {
274-
/**
275-
* @private
276-
* For type checking capabilities only.
277-
* Does not exist at runtime.
278-
* ### DO NOT USE!
279-
*/
280-
$$prop_def: Props;
281-
/**
282-
* @private
283-
* For type checking capabilities only.
284-
* Does not exist at runtime.
285-
* ### DO NOT USE!
286-
*/
287-
$$events_def: Events;
288-
/**
289-
* @private
290-
* For type checking capabilities only.
291-
* Does not exist at runtime.
292-
* ### DO NOT USE!
293-
*/
294-
$$slot_def: Slots;
295-
296-
constructor(options: ComponentConstructorOptions<Props>) {
297-
super(options);
298-
}
299-
}
266+
> extends SvelteComponentDev<Props, Events, Slots> {}
300267

301268
/**
302269
* Convenience type to get the type of a Svelte component. Useful for example in combination with
@@ -305,21 +272,21 @@ export class SvelteComponentTyped<
305272
* Example:
306273
* ```html
307274
* <script lang="ts">
308-
* import type { ComponentType, SvelteComponentTyped } from 'svelte';
275+
* import type { ComponentType, SvelteComponent } from 'svelte';
309276
* import Component1 from './Component1.svelte';
310277
* import Component2 from './Component2.svelte';
311278
*
312279
* const component: ComponentType = someLogic() ? Component1 : Component2;
313-
* const componentOfCertainSubType: ComponentType<SvelteComponentTyped<{ needsThisProp: string }>> = someLogic() ? Component1 : Component2;
280+
* const componentOfCertainSubType: ComponentType<SvelteComponent<{ needsThisProp: string }>> = someLogic() ? Component1 : Component2;
314281
* </script>
315282
*
316283
* <svelte:component this={component} />
317284
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
318285
* ```
319286
*/
320-
export type ComponentType<Component extends SvelteComponentTyped = SvelteComponentTyped> = new (
287+
export type ComponentType<Component extends SvelteComponentDev = SvelteComponentDev> = new (
321288
options: ComponentConstructorOptions<
322-
Component extends SvelteComponentTyped<infer Props> ? Props : Record<string, any>
289+
Component extends SvelteComponentDev<infer Props> ? Props : Record<string, any>
323290
>
324291
) => Component;
325292

@@ -334,7 +301,7 @@ export type ComponentType<Component extends SvelteComponentTyped = SvelteCompone
334301
* </script>
335302
* ```
336303
*/
337-
export type ComponentProps<Component extends SvelteComponent> = Component extends SvelteComponentTyped<infer Props>
304+
export type ComponentProps<Component extends SvelteComponent> = Component extends SvelteComponentDev<infer Props>
338305
? Props
339306
: never;
340307

@@ -354,7 +321,7 @@ export type ComponentProps<Component extends SvelteComponent> = Component extend
354321
* ```
355322
*/
356323
export type ComponentEvents<Component extends SvelteComponent> =
357-
Component extends SvelteComponentTyped<any, infer Events> ? Events : never;
324+
Component extends SvelteComponentDev<any, infer Events> ? Events : never;
358325

359326
export function loop_guard(timeout) {
360327
const start = Date.now();

0 commit comments

Comments
 (0)