I have a strongly-typed style object in my React Native project in TypeScript:
const styles = StyleSheet.create({
container:{
backgroundColor: 'red',
flex: 1
},
});
This works as normal as StyleSheet.create expects arbitrary named keys with the target values of ViewStyle, TextStyle, and ImageStyle as defined below:
export namespace StyleSheet {
type NamedStyles<T> = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
/**
* Creates a StyleSheet style reference from the given object.
*/
export function create<T extends NamedStyles<T> | NamedStyles<any>>(styles: T | NamedStyles<T>): T;
[...]
}
container is an arbitrary key, and backgroundColor is defined in ViewStyle to have a value of the type ColorValue, which is defined as string | OpaqueColorValue where OpaqueColorValue is a unique symbol.
However, I'm building a function that I want to feed with the following type signature wherever ColorValue was previously accepted in original type:
ColorValue | { light: ColorValue, dark: ColorValue }
Along with a parameter named either light or dark supplied to my function, I will use it like:
const styles = myFunction({
container:{
backgroundColor: {
light: 'red',
dark: 'blue'
},
flex: 1
},
}, 'dark');
And it will pick the appropriate key, e.g. it will return:
{
container:{
backgroundColor: 'blue' //as we picked the 'dark' key from original input
flex: 1
}
}
In plain English, I want my function's input type to accept anything that StyleSheet.create accepts, and in addition to that, also accept {light: ColorValue, dark: ColorValue} wherever it accepted ColorValue. It then picks the appropriate keys, then calls StyleSheet.create internally and returns the result.
I wrote the function and it works, but I'm struggling to make it accept objects with the light/dark keys in IntelliSense and linter (of course, without casting to any or unknown).
How do I achieve this?
I think you might benefit from having a few conditional, mapped types like this:
The idea is that
DeepReplaceSupertype<T, S, D>takes a typeT, a source typeS, and a destination typeDand it checks: ifSis assignable toT, then replace it withD. Otherwise, walk down recursively throughTand replace all properties and subproperties the same way.DeepReplaceSubtype<T, S, D>is similar but it checks ifTis assignable toS.Then your function's signature could look like this:
Meaning: accept anything of type
Twhere you take thatNameStylesand replace subproperties that acceptColorValueand change them toShade. And then, the return value should takeTand replace any subproperties that are a subtype ofShadeand turn them back toColorValue. It should work like this:Looks like what you want, I think. You might be able to get even more clever and have the output type be specifically the type of the value picked from
lightordark(sostringinstead ofColorValue), but that would be more complicated and probably not necessary, so I'll stop there.Playground link to code