language-tools
language-tools copied to clipboard
add a way to describe what `--style-props` a component expects
Is your feature request related to a problem? Please describe.
When working on a lot of components, I can't remember every prop a component has defined. Thats why VS Code offers a great auto-completion for "normal" props a component exports. But when using components that contain style props, I currently always have to go into the component to take a look what style props are used inside the style tag.
Describe the solution you'd like I want a way to be able to describe the style props my components intents to handle.
type
A basic approach (similar to the already existing $$Props
and $$Events
interfaces) could look like:
<!-- Hello.svelte -->
<script lang="ts">
type $$StyleProps = 'color' | 'font-size'
</script>
<div>Hello</div>
<style>
div {
color: var(--color, red);
font-size: var(--font-size, 1rem)
}
</style>
When you then use this component, you would get auto-completion for the available style props and you also get errors when you try to set a property that does not exist.
<!-- Parent.svelte -->
<script lang="ts">
import Hello from './Hello.svelte'
</script>
<Hello --colour="blue" />
^
<!-- error: there exists no style-prop named 'colour' for the component 'Hello'-->
When a component doesn't define $$StyleProps
, then it defaults to string
so all properties are accepted (the current behaviour)
Defining the props like in the example above would already be a great addition.
interface
An alternative way would be to use an interface:
interface $$StyleProps {
color: string
font-size: string
}
This would need a few more keystrokes to type but this could add the functionality to better describe what the passed prop should look like:
interface $$StyleProps {
color: 'red' | 'green' | 'blue'
font-size: `${number}rem`
}
In the example above, we only want a fixed set of values to be passed to the --color
prop and the --font-size
to be a number ending with 'rem'. And there are probably a lot more use-cases that can be enabled with the inferface-syntax.
One such use-case could be to define optional and required style props by simply using TypeScript syntax:
interface $$StyleProps {
color: string
font-size?: string
}
Here this component would require parent components to pass a --color
prop.
keys
I'm not sure if if its needed for the keys of the interface to begin with --
or if we should omit them since we already know that these are style-props and they have to be accessed via the --
prefix.
interface $$StyleProps {
color: string
font-size: string
}
vs.
interface $$StyleProps {
--color: string
--font-size: string
}
limitations
Adding this feature would not prevent users to "override" the restrictions a component defines on it's style props. Maybe the component uses internally a --color´ prop but only wants developers to be able to set the
--font-size` prop. Of course anyone could define the style prop like in this example to override it:
<div style="--color: white">
<Hello />
</div>
This feature would only be an additional way to describe components even better.
Describe alternatives you've considered
Leave it like it is. Offer no IDE hints for style props.
Thank you for the detailed thoughts. One thing I'm not sure about yet is if we should prevent people from setting other style props besides the ones that are defined within the component. Sub components could have style props, too. Then again, if one opts in to such strict typings maybe one expects this behavior (err on the side of caution/strictness).
For a start we could also try to infer the props by parsing the style sheet.
I feel like rather than using typing, which wouldn't help a JS user, I think JSDoc-like comments in a @component
doc-comment would work best.
<!--
@component Hello
@styleprop --color - Sets the color.
@styleprop --font-size - Sets the font size.
-->
<div>Hello</div>
<style>
div {
color: var(--color, red);
font-size: var(--font-size, 1rem)
}
</style>
Thank you for the detailed thoughts. One thing I'm not sure about yet is if we should prevent people from setting other style props besides the ones that are defined within the component. Sub components could have style props, too. Then again, if one opts in to such strict typings maybe one expects this behavior (err on the side of caution/strictness).
For a start we could also try to infer the props by parsing the style sheet.
@dummdidumm In my opinion if someone defines this type/interface then he only wants these props to be passed to that components.
Parsing the style sheet would also be a good idea but like I mentioned maybe there are some internal style-props a component author doesn't want to expose.
@Monkatraz how do $$Props
and $$Events
currently work in JSDoc syntax? $$StyleProps
could work the same way.
If there is no such support yet, you could open a new Issue to discuss the functionality for defining these interfaces with JSDoc annotation.
how do $$Props and $$Events currently work in JSDoc syntax? $$StyleProps could work the same way. If there is no such support yet, you could open a new Issue to discuss the functionality for defining these interfaces with JSDoc annotation.
You can use JSDoc comments on the exported variables, basically. I don't think you can get the full functionality of $$Props
and friends in JS.
Ah right. So this feature would need a syntax for defining this for TypeScript users and another syntax for defining it via JSDoc comments. And I think this then should also be added to SvelteComponentTyped
as a fourth parameter.
Re "where to put this": Should this be part of $$Props
such that you need to type it out?
<script lang="ts">
interface $$Props {
prop: string;
'--style-prop'?: string;
}
export let prop: string;
</script>
Advantages:
- Not another interface
- One could argue they are semantically closely related to each other
Disadvantages:
- If you only want to type style props, you now need to type all props, too (but if you want to type them, maybe you do that, anyway?)
- typing the
--
might be cumbersome (but so might be typinginterface $$StyleProps { .. }
)
I really do feel like making it part of the TypeScript interfaces is... weird? Like, it makes sense for normal exported props because they are JS values and types. But style props are always strings, and they can be any CSS expression. Trying to strongly type them using template strings seems like a bad idea because there are so many edge cases.
@dummdidumm I think putting it into the $$Props
interface is a bad idea. These aren't really props you have (or should have) access inside the script
tag. Another aspect is that also tooling must consider these edge-cases, to not display error/warnings/autocompletions for these props inside the script tag.
In my opinion splitting it into it's own interface would help with these issues.
@Monkatraz I'm totally fine to just have a way to tell what props are available like I mentioned in the types
section of the issue description.
For advanced use-cases I proposed the interface
solution. Moslty because it enables a way to define required an optional style-props. The template literal thing is just a feature we would get for "free". Most people would probably stick to string
and others who want stricter types could choose template strings.
To give some examples:
interface $$Props { '--color': 'red' | 'blue' }
// invalid values:
// - rgb(255, 0, 0)
// - rgb(0, 0, 255)
// - var(--my-red)
interface $$Props { '--font-size': `{number}em` }
// invalid values:
// - var(--font-size)
// - calc((2 * var(--base-font-size)) * 1em)
@Monkatraz good point! Restricting inputs via template strings is a bad idea.
I really do feel like making it part of the TypeScript interfaces is... weird? Like, it makes sense for normal exported props because they are JS values and types. But style props are always strings, and they can be any CSS expression. Trying to strongly type them using template strings seems like a bad idea because there are so many edge cases.
Fair, but it would be cool to be able to add descriptions to them 😅