Skip to content

Commit b0a3fa1

Browse files
authored
fix: ensure createEventDispatcher and ActionReturn work with generic function types (#8872)
fixes #8860 This contains a small but unfortunately unavoidable breaking change: If you used `never` to type that the second parameter of `createEventDispatcher` shouldn't be set or that the action accepts no parameters (which the docs recommended for a short time), then you need to change that to `null` and `undefined` respectively
1 parent 7934a7f commit b0a3fa1

File tree

7 files changed

+30
-33
lines changed

7 files changed

+30
-33
lines changed

.changeset/long-humans-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: ensure `createEventDispatcher` and `ActionReturn` work with types from generic function parameters

documentation/docs/05-misc/03-typescript.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Events can be typed with `createEventDispatcher`:
9696
import { createEventDispatcher } from 'svelte';
9797
9898
const dispatch = createEventDispatcher<{
99-
event: never; // does not accept a payload
99+
event: null; // does not accept a payload
100100
type: string; // has a required string payload
101101
click: string | null; // has an optional string payload
102102
}>();

documentation/docs/05-misc/04-v4-migration-guide.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { createEventDispatcher } from 'svelte';
3636
const dispatch = createEventDispatcher<{
3737
optional: number | null;
3838
required: string;
39-
noArgument: never;
39+
noArgument: null;
4040
}>();
4141

4242
// Svelte version 3:
@@ -50,10 +50,10 @@ dispatch('required'); // error, missing argument
5050
dispatch('noArgument', 'surprise'); // error, cannot pass an argument
5151
```
5252

53-
- `Action` and `ActionReturn` have a default parameter type of `never` now, which means you need to type the generic if you want to specify that this action receives a parameter. The migration script will migrate this automatically ([#7442](https://github.com/sveltejs/svelte/pull/7442))
53+
- `Action` and `ActionReturn` have a default parameter type of `undefined` now, which means you need to type the generic if you want to specify that this action receives a parameter. The migration script will migrate this automatically ([#7442](https://github.com/sveltejs/svelte/pull/7442))
5454

5555
```diff
56-
-const action: Action = (node, params) => { .. } // this is now an error, as params is expected to not exist
56+
-const action: Action = (node, params) => { .. } // this is now an error if you use params in any way
5757
+const action: Action<HTMLElement, string> = (node, params) => { .. } // params is of type string
5858
```
5959

packages/svelte/src/runtime/action/public.d.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
22
* Actions can return an object containing the two properties defined in this interface. Both are optional.
33
* - update: An action can have a parameter. This method will be called whenever that parameter changes,
4-
* immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn<never>` both
5-
* mean that the action accepts no parameters, which makes it illegal to set the `update` method.
4+
* immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn<undefined>` both
5+
* mean that the action accepts no parameters.
66
* - destroy: Method that is called after the element is unmounted
77
*
88
* Additionally, you can specify which additional attributes and events the action enables on the applied element.
@@ -27,10 +27,10 @@
2727
* Docs: https://svelte.dev/docs/svelte-action
2828
*/
2929
export interface ActionReturn<
30-
Parameter = never,
30+
Parameter = undefined,
3131
Attributes extends Record<string, any> = Record<never, any>
3232
> {
33-
update?: [Parameter] extends [never] ? never : (parameter: Parameter) => void;
33+
update?: (parameter: Parameter) => void;
3434
destroy?: () => void;
3535
/**
3636
* ### DO NOT USE THIS
@@ -50,7 +50,7 @@ export interface ActionReturn<
5050
* // ...
5151
* }
5252
* ```
53-
* `Action<HTMLDivElement>` and `Action<HTMLDiveElement, never>` both signal that the action accepts no parameters.
53+
* `Action<HTMLDivElement>` and `Action<HTMLDiveElement, undefined>` both signal that the action accepts no parameters.
5454
*
5555
* You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has.
5656
* See interface `ActionReturn` for more details.
@@ -59,18 +59,15 @@ export interface ActionReturn<
5959
*/
6060
export interface Action<
6161
Element = HTMLElement,
62-
Parameter = never,
62+
Parameter = undefined,
6363
Attributes extends Record<string, any> = Record<never, any>
6464
> {
6565
<Node extends Element>(
66-
...args: [Parameter] extends [never]
67-
? [node: Node]
68-
: undefined extends Parameter
66+
...args: undefined extends Parameter
6967
? [node: Node, parameter?: Parameter]
7068
: [node: Node, parameter: Parameter]
7169
): void | ActionReturn<Parameter, Attributes>;
7270
}
7371

7472
// Implementation notes:
7573
// - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode
76-
// - [X] extends [never] is needed, X extends never would reduce the whole resulting type to never and not to one of the condition outcomes

packages/svelte/src/runtime/internal/public.d.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,12 @@ export interface DispatchOptions {
8080
export interface EventDispatcher<EventMap extends Record<string, any>> {
8181
// Implementation notes:
8282
// - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode
83-
// - [X] extends [never] is needed, X extends never would reduce the whole resulting type to never and not to one of the condition outcomes
83+
// - | null | undefined is added for convenience, as they are equivalent for the custom event constructor (both result in a null detail)
8484
<Type extends keyof EventMap>(
85-
...args: [EventMap[Type]] extends [never]
86-
? [type: Type, parameter?: null | undefined, options?: DispatchOptions]
87-
: null extends EventMap[Type]
88-
? [type: Type, parameter?: EventMap[Type], options?: DispatchOptions]
85+
...args: null extends EventMap[Type]
86+
? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions]
8987
: undefined extends EventMap[Type]
90-
? [type: Type, parameter?: EventMap[Type], options?: DispatchOptions]
88+
? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions]
9189
: [type: Type, parameter: EventMap[Type], options?: DispatchOptions]
9290
): boolean;
9391
}

packages/svelte/test/types/actions.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Action, ActionReturn } from '$runtime/action';
1+
import type { Action, ActionReturn } from '$runtime/action/public';
22

33
// ---------------- Action
44

@@ -65,30 +65,27 @@ const optional4: Action<HTMLElement, boolean | undefined> = (_node, _param?) =>
6565
};
6666
optional4;
6767

68-
const no: Action<HTMLElement, never> = (_node) => {};
68+
const no: Action<HTMLElement, undefined> = (_node) => {};
6969
// @ts-expect-error second param
7070
no(null as any, true);
7171
no(null as any);
7272
// @ts-expect-error second param
7373
no(null as any, 'string');
7474

75-
const no1: Action<HTMLElement, never> = (_node) => {
75+
const no1: Action<HTMLElement, undefined> = (_node) => {
7676
return {
7777
destroy: () => {}
7878
};
7979
};
8080
no1;
8181

82-
// @ts-expect-error param given
83-
const no2: Action<HTMLElement, never> = (_node, _param?) => {};
84-
no2;
82+
const no2: Action<HTMLElement, undefined> = (_node, _param?) => {};
83+
no2(null as any);
8584

86-
// @ts-expect-error param given
87-
const no3: Action<HTMLElement, never> = (_node, _param) => {};
85+
const no3: Action<HTMLElement, undefined> = (_node, _param) => {};
8886
no3;
8987

90-
// @ts-expect-error update method given
91-
const no4: Action<HTMLElement, never> = (_node) => {
88+
const no4: Action<HTMLElement, undefined> = (_node) => {
9289
return {
9390
update: () => {},
9491
destroy: () => {}
@@ -106,7 +103,7 @@ requiredReturn;
106103
const optionalReturn: ActionReturn<boolean | undefined> = {
107104
update: (p) => {
108105
p === true;
109-
// @ts-expect-error could be undefined
106+
// @ts-expect-error (only in strict mode) could be undefined
110107
p.toString();
111108
}
112109
};
@@ -118,7 +115,7 @@ const invalidProperty: ActionReturn = {
118115
};
119116
invalidProperty;
120117

121-
type Attributes = ActionReturn<never, { a: string }>['$$_attributes'];
118+
type Attributes = ActionReturn<undefined, { a: string }>['$$_attributes'];
122119
const attributes: Attributes = { a: 'a' };
123120
attributes;
124121
// @ts-expect-error wrong type

packages/svelte/test/types/create-event-dispatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createEventDispatcher } from '$runtime/internal/lifecycle';
22

33
const dispatch = createEventDispatcher<{
4-
loaded: never;
4+
loaded: null;
55
change: string;
66
valid: boolean;
77
optional: number | null;

0 commit comments

Comments
 (0)