-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] action types #7121
[feat] action types #7121
Conversation
Oh that's great! I think we need to enhance the exports of the package.json, too, although only with a "types" section. |
I knew I felt like something was missing as soon as I pressed the button. Docs seems to be in an odd state as well with no examples for TS usage. |
the parameters of callback provided to update can simply be empty to represent an optional value, restricting with the optional mark prevents authors from immediately destructuring because it can be undefined
I've played around and tried to create several actions, in action, and found that we need to have the optional That means, (1) authors that have their action params optionally receive any So, I believe optionally marking the parameters is the way, and this should be good to go, unless there's any other suggestions for the names. |
Looks like you're right. I did some quick tests locally, and |
Don't want to be pushy, but would love to see this land as it would make my live easier :) Any blockers outstanding or just no time yet? PS: Thanks for your hard work! |
I actually needed this today and came across a solution for the optional. I ended up with this: export interface ActionReturn<Parameters = void> {
update?: (parameters?: Parameters) => void;
destroy?: () => void;
}
export interface Action<Element extends HTMLElement, Parameters = void> {
(node: Element, parameters?: Parameters): ActionReturn<Parameters> | void;
} |
src/runtime/action/index.ts
Outdated
destroy?: () => void; | ||
} | ||
|
||
export interface Action<Parameters = any, Element = HTMLElement> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After going back and forth on this, I think it's better to have Element
as the first generic, to have the same order as the parameters. It just feels very unintuitive in my head the other way around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tbh, I like the way it is with the option to narrow down the element as an optional. Imho this reflects the common usage-scenario of actions quite well. But it is a matter of taste and explicit vs implicit logic.
Updated so the generic parameters matches the function parameters, downside would be that authors would need to explicitly pass in the base element every time they wanted to type On an unrelated note, would this be better if placed under |
Thought I'll try this out myself once more before merging and ran into the same "what to do with parameters" situation. As @seanlail mentions, it seems possible to mark the parameter as optional by making it export interface Action<Element = HTMLElement, Parameter = void> {
<Node extends Element>(node: Node, parameter: Parameter): void | ActionReturn<Parameter>;
}
// ActionReturn stays untouched/unchanged This allows people to write actions without a parameter simply by not defining it const actionWithoutParams: Action<HTMLElement> = (node, param) => {
// using the param in the function body will error
param.asd; // error: Property 'asd' does not exist on type 'void'
}; If people want a parameter, they are required to think about what the parameter looks like, and users have to use them const action: Action<HTMLElement, { a: true }> = (node, param) => {
param.a; // works
}; Buuuut, if people want optional params, they have to use void | TheirType, which almost works: using a default param doesn't make TS know that it's always defined const actionWithOptionalParam: Action<HTMLElement, void | { a: true }> = (node, param = {a: true}) => {
param.a; // error: Property 'a' does not exist on type 'void | { a: true; }'
}; This isn't good, and I agree with @ignatiusmb that authors should be nugded to use default/fallback params, so ultimatly I conclude that the action types are good as-is. |
By marking it I thought export interface ActionReturn<Parameters = void> {
update?: (parameters: Parameters) => void;
destroy?: () => void;
}
export interface Action<Parameters = any, Element extends HTMLElement = HTMLElement> {
(node: Element, parameters: Parameters): ActionReturn<Parameters> | void;
}
const untyped: Action = (node, param) => {
const a = param.anything;
const b: typeof param = { some: 'param' };
};
const param_required: Action<string> = (node, param) => {
const a = param.length;
const b: typeof param = 'string';
};
const param_optional_missing: Action<string | undefined> = (node, param) => {
const a = param.length;
const b: typeof param = 'string';
};
const param_optional_default: Action<string | undefined> = (node, param = 'abc') => {
const a = param.length;
const b: typeof param = 'string';
};
const param_rejected: Action<void> = function (node, param) {
const a = param.length;
const b: typeof param = 'string';
}; |
Hey @benbender, thank you for your review and input here, we've discussed this in the latest meeting and decided it would be more consistent to keep the order of generics the same with the order of function parameters. There's also some supporting arguments that actions are rarely made to generically work with all HTML elements, it's usually a specific action made for a/some specific elements, most also don't receive a parameter, and that would make typing the element harder if we were to put the generic order in reverse. When it is a generic purpose action, needing to (re)write the |
@ignatiusmb thanks for the feedback and totally fine to me. Glad this pr landed. Thanks to all of you for your work! Very much appreciated. |
Awesome! Leaving this finding from our meeting as well in case someone still wanted to only type the parameter when defining their action. Users can always make their own interface to avoid needing to type in the Element generic over and over again, here's an example for an action interface that takes in any HTML element export interface HTMLAction<T> extends Action<HTMLElement, T> {} |
Hey I saw this has just merged. Would be able to now import these types for use? If so how do we do so? |
Usage example: see docs of the interface. It's not released yet so you can't use it right now. |
This comment was marked as off-topic.
This comment was marked as off-topic.
@Antonio-Bennett this is unrelated, this is a type error, TypeScript doesn't know this event exist, see here for how to fix it: https://github.com/sveltejs/language-tools/blob/master/docs/preprocessors/typescript.md#im-using-an-attributeevent-on-a-dom-element-and-it-throws-a-type-error |
@dummdidumm Thanks a lot worked like a charm! |
Maybe I am overlooking something but I want a second parameter to be a export const clickOutside: Action<HTMLElement, () => void> = (node, callback) => {
function onClick(event: MouseEvent) {
if (!node.contains(event.target as Node)) {
// @ts-expect-error can't seem to type this
callback();
}
}
tick().then(() => document.addEventListener('click', onClick));
return {
destroy() {
document.removeEventListener('click', onClick);
}
};
}; but I need the |
This introduces a new
svelte/action
module, you can only import types for nowResolves #6538
Before submitting the PR, please make sure you do the following
[feat]
,[fix]
,[chore]
, or[docs]
.Tests
npm test
and lint the project withnpm run lint