diff --git a/.changeset/long-humans-dress.md b/.changeset/long-humans-dress.md new file mode 100644 index 0000000000..bc9cf8b40e --- /dev/null +++ b/.changeset/long-humans-dress.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure `createEventDispatcher` and `ActionReturn` work with types from generic function parameters diff --git a/documentation/docs/05-misc/03-typescript.md b/documentation/docs/05-misc/03-typescript.md index 36378365fd..778e13ce37 100644 --- a/documentation/docs/05-misc/03-typescript.md +++ b/documentation/docs/05-misc/03-typescript.md @@ -96,7 +96,7 @@ Events can be typed with `createEventDispatcher`: import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher<{ - event: never; // does not accept a payload + event: null; // does not accept a payload type: string; // has a required string payload click: string | null; // has an optional string payload }>(); diff --git a/documentation/docs/05-misc/04-v4-migration-guide.md b/documentation/docs/05-misc/04-v4-migration-guide.md index 25faea67ce..7da5f3e118 100644 --- a/documentation/docs/05-misc/04-v4-migration-guide.md +++ b/documentation/docs/05-misc/04-v4-migration-guide.md @@ -36,7 +36,7 @@ import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher<{ optional: number | null; required: string; - noArgument: never; + noArgument: null; }>(); // Svelte version 3: @@ -50,10 +50,10 @@ dispatch('required'); // error, missing 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 --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 = (node, params) => { .. } // params is of type string ``` diff --git a/packages/svelte/src/runtime/action/public.d.ts b/packages/svelte/src/runtime/action/public.d.ts index d52b8a726a..afd92e116d 100644 --- a/packages/svelte/src/runtime/action/public.d.ts +++ b/packages/svelte/src/runtime/action/public.d.ts @@ -1,8 +1,8 @@ /** * 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, - * immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn` both - * mean that the action accepts no parameters, which makes it illegal to set the `update` method. + * immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn` both + * mean that the action accepts no parameters. * - 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. @@ -27,10 +27,10 @@ * Docs: https://svelte.dev/docs/svelte-action */ export interface ActionReturn< - Parameter = never, + Parameter = undefined, Attributes extends Record = Record > { - update?: [Parameter] extends [never] ? never : (parameter: Parameter) => void; + update?: (parameter: Parameter) => void; destroy?: () => void; /** * ### DO NOT USE THIS @@ -50,7 +50,7 @@ export interface ActionReturn< * // ... * } * ``` - * `Action` and `Action` both signal that the action accepts no parameters. + * `Action` and `Action` 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. * See interface `ActionReturn` for more details. @@ -59,13 +59,11 @@ export interface ActionReturn< */ export interface Action< Element = HTMLElement, - Parameter = never, + Parameter = undefined, Attributes extends Record = Record > { ( - ...args: [Parameter] extends [never] - ? [node: Node] - : undefined extends Parameter + ...args: undefined extends Parameter ? [node: Node, parameter?: Parameter] : [node: Node, parameter: Parameter] ): void | ActionReturn; @@ -73,4 +71,3 @@ export interface Action< // Implementation notes: // - 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 diff --git a/packages/svelte/src/runtime/internal/public.d.ts b/packages/svelte/src/runtime/internal/public.d.ts index 6eab766077..1f1011740d 100644 --- a/packages/svelte/src/runtime/internal/public.d.ts +++ b/packages/svelte/src/runtime/internal/public.d.ts @@ -80,14 +80,12 @@ export interface DispatchOptions { export interface EventDispatcher> { // Implementation notes: // - 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) ( - ...args: [EventMap[Type]] extends [never] - ? [type: Type, parameter?: null | undefined, options?: DispatchOptions] - : null extends EventMap[Type] - ? [type: Type, parameter?: EventMap[Type], options?: DispatchOptions] + ...args: null extends EventMap[Type] + ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] : 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] ): boolean; } diff --git a/packages/svelte/test/types/actions.ts b/packages/svelte/test/types/actions.ts index bc1225a8ca..da4e660a1a 100644 --- a/packages/svelte/test/types/actions.ts +++ b/packages/svelte/test/types/actions.ts @@ -1,4 +1,4 @@ -import type { Action, ActionReturn } from '$runtime/action'; +import type { Action, ActionReturn } from '$runtime/action/public'; // ---------------- Action @@ -65,30 +65,27 @@ const optional4: Action = (_node, _param?) => }; optional4; -const no: Action = (_node) => {}; +const no: Action = (_node) => {}; // @ts-expect-error second param no(null as any, true); no(null as any); // @ts-expect-error second param no(null as any, 'string'); -const no1: Action = (_node) => { +const no1: Action = (_node) => { return { destroy: () => {} }; }; no1; -// @ts-expect-error param given -const no2: Action = (_node, _param?) => {}; -no2; +const no2: Action = (_node, _param?) => {}; +no2(null as any); -// @ts-expect-error param given -const no3: Action = (_node, _param) => {}; +const no3: Action = (_node, _param) => {}; no3; -// @ts-expect-error update method given -const no4: Action = (_node) => { +const no4: Action = (_node) => { return { update: () => {}, destroy: () => {} @@ -106,7 +103,7 @@ requiredReturn; const optionalReturn: ActionReturn = { update: (p) => { p === true; - // @ts-expect-error could be undefined + // @ts-expect-error (only in strict mode) could be undefined p.toString(); } }; @@ -118,7 +115,7 @@ const invalidProperty: ActionReturn = { }; invalidProperty; -type Attributes = ActionReturn['$$_attributes']; +type Attributes = ActionReturn['$$_attributes']; const attributes: Attributes = { a: 'a' }; attributes; // @ts-expect-error wrong type diff --git a/packages/svelte/test/types/create-event-dispatcher.ts b/packages/svelte/test/types/create-event-dispatcher.ts index 31f06f71b5..f85196930b 100644 --- a/packages/svelte/test/types/create-event-dispatcher.ts +++ b/packages/svelte/test/types/create-event-dispatcher.ts @@ -1,7 +1,7 @@ import { createEventDispatcher } from '$runtime/internal/lifecycle'; const dispatch = createEventDispatcher<{ - loaded: never; + loaded: null; change: string; valid: boolean; optional: number | null;