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
pull/8855/head
Simon H 1 year ago committed by GitHub
parent 7934a7f4d2
commit b0a3fa1766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure `createEventDispatcher` and `ActionReturn` work with types from generic function parameters

@ -96,7 +96,7 @@ Events can be typed with `createEventDispatcher`:
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
event: never; // does not accept a payload event: null; // does not accept a payload
type: string; // has a required string payload type: string; // has a required string payload
click: string | null; // has an optional string payload click: string | null; // has an optional string payload
}>(); }>();

@ -36,7 +36,7 @@ import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
optional: number | null; optional: number | null;
required: string; required: string;
noArgument: never; noArgument: null;
}>(); }>();
// Svelte version 3: // Svelte version 3:
@ -50,10 +50,10 @@ dispatch('required'); // error, missing argument
dispatch('noArgument', 'surprise'); // error, cannot pass an argument dispatch('noArgument', 'surprise'); // error, cannot pass an argument
``` ```
- `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)) - `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))
```diff ```diff
-const action: Action = (node, params) => { .. } // this is now an error, as params is expected to not exist -const action: Action = (node, params) => { .. } // this is now an error if you use params in any way
+const action: Action<HTMLElement, string> = (node, params) => { .. } // params is of type string +const action: Action<HTMLElement, string> = (node, params) => { .. } // params is of type string
``` ```

@ -1,8 +1,8 @@
/** /**
* Actions can return an object containing the two properties defined in this interface. Both are optional. * Actions can return an object containing the two properties defined in this interface. Both are optional.
* - update: An action can have a parameter. This method will be called whenever that parameter changes, * - update: An action can have a parameter. This method will be called whenever that parameter changes,
* immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn<never>` both * immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn<undefined>` both
* mean that the action accepts no parameters, which makes it illegal to set the `update` method. * mean that the action accepts no parameters.
* - destroy: Method that is called after the element is unmounted * - destroy: Method that is called after the element is unmounted
* *
* Additionally, you can specify which additional attributes and events the action enables on the applied element. * Additionally, you can specify which additional attributes and events the action enables on the applied element.
@ -27,10 +27,10 @@
* Docs: https://svelte.dev/docs/svelte-action * Docs: https://svelte.dev/docs/svelte-action
*/ */
export interface ActionReturn< export interface ActionReturn<
Parameter = never, Parameter = undefined,
Attributes extends Record<string, any> = Record<never, any> Attributes extends Record<string, any> = Record<never, any>
> { > {
update?: [Parameter] extends [never] ? never : (parameter: Parameter) => void; update?: (parameter: Parameter) => void;
destroy?: () => void; destroy?: () => void;
/** /**
* ### DO NOT USE THIS * ### DO NOT USE THIS
@ -50,7 +50,7 @@ export interface ActionReturn<
* // ... * // ...
* } * }
* ``` * ```
* `Action<HTMLDivElement>` and `Action<HTMLDiveElement, never>` both signal that the action accepts no parameters. * `Action<HTMLDivElement>` and `Action<HTMLDiveElement, undefined>` both signal that the action accepts no parameters.
* *
* You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has. * You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has.
* See interface `ActionReturn` for more details. * See interface `ActionReturn` for more details.
@ -59,13 +59,11 @@ export interface ActionReturn<
*/ */
export interface Action< export interface Action<
Element = HTMLElement, Element = HTMLElement,
Parameter = never, Parameter = undefined,
Attributes extends Record<string, any> = Record<never, any> Attributes extends Record<string, any> = Record<never, any>
> { > {
<Node extends Element>( <Node extends Element>(
...args: [Parameter] extends [never] ...args: undefined extends Parameter
? [node: Node]
: undefined extends Parameter
? [node: Node, parameter?: Parameter] ? [node: Node, parameter?: Parameter]
: [node: Node, parameter: Parameter] : [node: Node, parameter: Parameter]
): void | ActionReturn<Parameter, Attributes>; ): void | ActionReturn<Parameter, Attributes>;
@ -73,4 +71,3 @@ export interface Action<
// Implementation notes: // Implementation notes:
// - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode // - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode
// - [X] extends [never] is needed, X extends never would reduce the whole resulting type to never and not to one of the condition outcomes

@ -80,14 +80,12 @@ export interface DispatchOptions {
export interface EventDispatcher<EventMap extends Record<string, any>> { export interface EventDispatcher<EventMap extends Record<string, any>> {
// Implementation notes: // Implementation notes:
// - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode // - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode
// - [X] extends [never] is needed, X extends never would reduce the whole resulting type to never and not to one of the condition outcomes // - | null | undefined is added for convenience, as they are equivalent for the custom event constructor (both result in a null detail)
<Type extends keyof EventMap>( <Type extends keyof EventMap>(
...args: [EventMap[Type]] extends [never] ...args: null extends EventMap[Type]
? [type: Type, parameter?: null | undefined, options?: DispatchOptions] ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions]
: null extends EventMap[Type]
? [type: Type, parameter?: EventMap[Type], options?: DispatchOptions]
: undefined extends EventMap[Type] : undefined extends EventMap[Type]
? [type: Type, parameter?: EventMap[Type], options?: DispatchOptions] ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions]
: [type: Type, parameter: EventMap[Type], options?: DispatchOptions] : [type: Type, parameter: EventMap[Type], options?: DispatchOptions]
): boolean; ): boolean;
} }

@ -1,4 +1,4 @@
import type { Action, ActionReturn } from '$runtime/action'; import type { Action, ActionReturn } from '$runtime/action/public';
// ---------------- Action // ---------------- Action
@ -65,30 +65,27 @@ const optional4: Action<HTMLElement, boolean | undefined> = (_node, _param?) =>
}; };
optional4; optional4;
const no: Action<HTMLElement, never> = (_node) => {}; const no: Action<HTMLElement, undefined> = (_node) => {};
// @ts-expect-error second param // @ts-expect-error second param
no(null as any, true); no(null as any, true);
no(null as any); no(null as any);
// @ts-expect-error second param // @ts-expect-error second param
no(null as any, 'string'); no(null as any, 'string');
const no1: Action<HTMLElement, never> = (_node) => { const no1: Action<HTMLElement, undefined> = (_node) => {
return { return {
destroy: () => {} destroy: () => {}
}; };
}; };
no1; no1;
// @ts-expect-error param given const no2: Action<HTMLElement, undefined> = (_node, _param?) => {};
const no2: Action<HTMLElement, never> = (_node, _param?) => {}; no2(null as any);
no2;
// @ts-expect-error param given const no3: Action<HTMLElement, undefined> = (_node, _param) => {};
const no3: Action<HTMLElement, never> = (_node, _param) => {};
no3; no3;
// @ts-expect-error update method given const no4: Action<HTMLElement, undefined> = (_node) => {
const no4: Action<HTMLElement, never> = (_node) => {
return { return {
update: () => {}, update: () => {},
destroy: () => {} destroy: () => {}
@ -106,7 +103,7 @@ requiredReturn;
const optionalReturn: ActionReturn<boolean | undefined> = { const optionalReturn: ActionReturn<boolean | undefined> = {
update: (p) => { update: (p) => {
p === true; p === true;
// @ts-expect-error could be undefined // @ts-expect-error (only in strict mode) could be undefined
p.toString(); p.toString();
} }
}; };
@ -118,7 +115,7 @@ const invalidProperty: ActionReturn = {
}; };
invalidProperty; invalidProperty;
type Attributes = ActionReturn<never, { a: string }>['$$_attributes']; type Attributes = ActionReturn<undefined, { a: string }>['$$_attributes'];
const attributes: Attributes = { a: 'a' }; const attributes: Attributes = { a: 'a' };
attributes; attributes;
// @ts-expect-error wrong type // @ts-expect-error wrong type

@ -1,7 +1,7 @@
import { createEventDispatcher } from '$runtime/internal/lifecycle'; import { createEventDispatcher } from '$runtime/internal/lifecycle';
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
loaded: never; loaded: null;
change: string; change: string;
valid: boolean; valid: boolean;
optional: number | null; optional: number | null;

Loading…
Cancel
Save