1

As it is possible to set CSS variables using VueJS v-bind:style binding, I am trying to create a union type where the object passed to v-bind:style retains the typings for CssStyleDelcaration and yet is relaxed enough to accept an arbitrary property name:

type Dictionary<T> = { [key: string]: T };
type Nullable<T> = T | null;
type CssStyleObject =
  Partial<CSSStyleDeclaration> |
  Dictionary<Nullable<string>>;

An example of the implementation is as such (see live code on TypeScript playground):

<!-- Template -->
<div v-bind:style="myStyle"></div>
// Component code
@Component
export default class MyComponent extends Vue {
  public get myStyle(): CssStyleObject {
    const style: CssStyleObject = { };
    style.backgroundColor = 'red';
    style['--my-custom-css-property'] = '16px';
  }
}

However, I get a type error when I have noImplicitAny flag enabled (a flag that I cannot be turned off because of a project-wide configuration), because TypeScript then complains about:

Element implicitly has an 'any' type because type 'CssStyleObject' has no index signature.

I've seen a proposed solution here: How do I prevent the error "Index signature of object type implicitly has an 'any' type" when compiling typescript with noImplicitAny flag enabled?, but I would love to avoid simply casting to any, if there is another viable solution possible.

The funny thing is, the error message goes away once I use a custom property name when assigning to the style variable:

const style: CssStyleObject = {
    '--foobar': '50%'
};
style.backgroundColor = 'red';
style['--my-custom-css-property'] = '16px';

See the code above on TypeScript Playground.

I suspect that is because the type for style is then somehow modified to be simply Dictionary<string> instead of the union type, so no further errors will be thrown.


Update and solution

So, it turns out that I inherently confused intersection and union types in TypeScript. In this case, the CssStyleObject is an intersection of Partial<CSSStyleDeclaration> and Dictionary<Nullable<string>>, so this should suffice:

type Dictionary<T> = { [key: string]: T };
type Nullable<T> = T | null;

// Use `&` for creating an intersection type!
type CssStyleObject =
  Partial<CSSStyleDeclaration> &
  Dictionary<Nullable<string>>;
Terry
  • 63,248
  • 15
  • 96
  • 118

1 Answers1

4

First off, the current behavior of your code is because the way discriminated union type works. CSSStyleDeclaration doesn't have a index signature while Dictionary does. So when you add a custom property in declaration, the type is inferred as Dictionary.

Secondly, you shouldn't use union type if you want add a index to a existing type. You should use intersection type instead. That is said, CssStyleObject is a CSSStyleDeclaration and has a index signature, but not a object which is either CssStyleObject or has a index signature

Lastly, here is working code

type CssStyleObject = Partial<CSSStyleDeclaration> & Record<string, string | null>
// or 
interface CssStyleObject extends Partial<CSSStyleDeclaration> {
    [key: string]: string | null
}
Austaras
  • 901
  • 8
  • 24