juFo juFo - 11 days ago 6
TypeScript Question

Best way to define nested options in a TypeScript definition file

I'm writing a TypeScript definition file .d.ts and trying to define this piece of javascript code:

from: http://summernote.org/deep-dive/#initialization-options

$('#foo').bar({
toolbar: [
['style', ['bold', 'italic', 'underline', 'clear']],
['font', ['strikethrough', 'superscript', 'subscript']],
['fontsize', ['fontsize']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']],
],
otheroption: // ...
});


What is the best way to define such structure (nested options)?

Here is what I have found so far, but don't know what is recommended or if there are better solutions.

interface fooOptions {
toolbar?: any
}


or

type toolbarOptions = [string, string[]][];
interface foo {
toolbar?: toolbarOptions
}


or

type toolbarOptionNames = 'style' | 'font' | 'fontsize' | 'color' | 'para' | 'height';
type toolbarOptionValues = 'bold' | 'italic' | 'underline' | 'clear' | 'strikethrough' | 'superscript' | 'subscript' | 'fontsize' | 'color' | 'ul' | 'ol' | 'paragraph' | 'height';

interface foo {
toolbar?: [toolbarOptionNames, toolbarOptionValues[]][];
}


or

type toolbarStyleGroupOptions = 'bold' | 'italic' | 'underline' | 'clear';
type toolbarFontGroupOptions = 'strikethrough' | 'superscript' | 'subscript';
type toolbarFontsizeGroupOptions = 'fontsize';
// etc...

type toolbarDef = [
['style', toolbarStyleGroupOptions[]]
| ['font', toolbarFontGroupOptions[]]
| ['fontsize', toolbarFontsizeGroupOptions[]]
// | ...
];

interface foo {
toolbar?: toolbarDef;
}


or another way?

Answer

I think what you've come up with there is probably the best option right now. What you really want to do is to be able to define something generally though, right? You'd like to say this is a list of pairs of option name (from this list of valid option names) + option values (from those corresponding to the preceeding name). Unless you explicitly relate the two (as in your final example) then I don't think you can do that.

That final example probably is the best current option though for now, and that's what I'd do, although it will be annoyingly verbose.

In the short-term future though, there's a new nicer option coming: keyof. This is a bit complicated, but essentially lets you define one type in terms of the keys and corresponding values of another. I think an example for this case would look like:

type toolbarOptionsMap = {
    'style': 'bold' | 'italic' | 'underline',
    'font': 'strikethrough' | 'superscript' | 'subscript'
    ...
}

type toolbarOption<T extends keyof toolbarOptionsMap> = [T, toolbarOptionsMap[T][]];
// keyof toolbarOptionsMap = 'style' | 'font'
// T extends keyof toolbarOptionsMap, so is one of those strings.
// toolbarOptionsMap[T] is the type of the corresponding value for T

// Then we just make a list of these
type toolbar = toolbarOption<any>[];

With that you just define the valid sets of options once, as an object type, and then transform that type into the [option, optionValues] pair form that you're looking for, without duplicating every option over and over again.

This isn't all final and released yet, so I haven't actually checked this, but I'm pretty confident it'll work once this is live. This should coming in TypeScript 2.1, which is supposed to land before the end of November, i.e. this week. Watch this space!