Richardson
Richardson

Reputation: 2285

How to use spread operator in typescript as argument to a function?

I want to be able to spread the array in to the argment of the function, but I am getting A spread argument must either have a tuple type or be passed to a rest parameter.ts(2556)

  const currentColor: string = context.brandingPersonal.customColor; // rgba string 
  const numberColor: [number, number, number, number] = currentColor
    .substring(5, currentColor.length - 1)
    .replace(/ /g, '')
    .split(',')
    .map((item) => Number(item));

  function getContrastColor(R: number, G: number, B: number, A?: number) {
    const brightness = R * 0.299 + G * 0.587 + B * 0.114 + (1 - A || 0) * 255;
    return brightness > 186 ? '#000000' : '#FFFFFF';
  }
  getContrastColor(...numberColor);

Upvotes: 1

Views: 2346

Answers (3)

Here you can find explanation of creating number range in typescript.

Here you can find my article about creating number range.

It is possible to achieve extra safety with static type analysis:

type MAXIMUM_ALLOWED_BOUNDARY = 256

type ComputeRange<
    N extends number,
    Result extends Array<unknown> = [],
    > =
    (Result['length'] extends N
        ? Result
        : ComputeRange<N, [...Result, Result['length']]>
    )

type Octal = ComputeRange<MAXIMUM_ALLOWED_BOUNDARY>[number]

type Digits = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

type AlphaChanel = `0.${Digits}` | '1.0'

type RGBA<Alpha extends number = 1.0> = [Octal, Octal, Octal, (`${Alpha}` extends AlphaChanel ? Alpha : never)?]

function getContrastColor<Alpha extends number>(...[R, G, B, a]: RGBA<Alpha>) {
    const A = a || 1;

    const brightness = R * 0.299 + G * 0.587 + B * 0.114 + (1 - A || 0) * 255;

    return brightness > 186 ? '#000000' : '#FFFFFF';
}

getContrastColor(10, 20, 30, 0.2); // ok
getContrastColor(256, 20, 30, 0.2); // error, 256 is out of the range
getContrastColor(255, 20, 30, 0.22); // error, 0.22 should be 0.2

Playground

RGBA utility type acts as a type validator and at the same time as a type for all valid RGB tuples. You can find more info here and here. If A is allowed alpha channel value it returns A, otherwise - never.

AlphaChanel - is a union of all allowed values. As you might have noticed I have allowed only one digit after 0.. If you want to allow more, please use this:

type AlphaChanel = `0.${ComputeRange<999>[number]}` | '1.0'

If you are interested in hex validation, you can check this article or this answer

Upvotes: 3

How about using tuples?

const numberColor = [255,50,50,0.1]

// Check RGBA
function getContrastColor([ R, G, B, A ]:[number, number, number, number]) {
  const brightness = R * 0.299 + G * 0.587 + B * 0.114 + (1 - A) * 255;

  return brightness > 186 ? '#000000' : '#FFFFFF';
}

// function call with spread operator 
getContrastColor(numberColor);

Upvotes: 1

Chris Heald
Chris Heald

Reputation: 62638

split makes no guarantees about how many pieces it'll output at runtime. Passing a string like "foo" would make your routine fail, because your function would not return a valid 4-tuple.

You could use something like this, which guarantees a proper tuple output:

function getNumberTuple(color: string) : [number, number, number, number?] {
  const rgba = color.substring(5, color.length - 1).replace(/ /g, "")
  const numbers = rgba.split(",").map((item) => Number(item));
  return [numbers[0] || 0, numbers[1] || 0, numbers[2] || 0, numbers[3]]
}

function getContrastColor(R: number, G: number, B: number, A?: number) { /* ... */}

getContrastColor(...getNumberTuple("color 123,123,123"));

Upvotes: 1

Related Questions