All files / src/utils mix-complex.ts

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101                              35x 29x 12x 9x   3x       6x 13x 13x   30x   13x 17x 40x     17x       6x 2x 2x   2x 5x 5x       2x 2x 5x   2x         24x 24x 24x 24x 24x   24x     50x 34x   16x 1x   15x         24x     6x 12x 12x 12x       12x             11x          
import { color, complex, RGBA, HSLA } from 'style-value-types';
import { mix } from './mix';
import { mixColor } from './mix-color';
import { isNum } from './inc';
import { pipe } from './pipe';
import { invariant } from 'hey-listen';
 
type MixComplex = (p: number) => string;
 
type BlendableArray = Array<number | RGBA | HSLA | string>;
type BlendableObject = {
  [key: string]: string | number | RGBA | HSLA;
};
 
function getMixer(origin: any, target: any) {
  if (isNum(origin)) {
    return (v: number) => mix(origin, target as number, v);
  } else if (color.test(origin)) {
    return mixColor(origin, target as HSLA | RGBA | string);
  } else {
    return mixComplex(origin as string, target as string);
  }
}
 
export const mixArray = (from: BlendableArray, to: BlendableArray) => {
  const output = [...from];
  const numValues = output.length;
 
  const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i]));
 
  return (v: number) => {
    for (let i = 0; i < numValues; i++) {
      output[i] = blendValue[i](v);
    }
 
    return output;
  };
};
 
export const mixObject = (origin: BlendableObject, target: BlendableObject) => {
  const output = { ...origin, ...target };
  const blendValue: { [key: string]: (v: number) => any } = {};
 
  for (const key in output) {
    Eif (origin[key] !== undefined && target[key] !== undefined) {
      blendValue[key] = getMixer(origin[key], target[key]);
    }
  }
 
  return (v: number) => {
    for (const key in blendValue) {
      output[key] = blendValue[key](v);
    }
    return output;
  };
};
 
function analyse(value: string) {
  const parsed = complex.parse(value);
  const numValues = parsed.length;
  let numNumbers = 0;
  let numRGB = 0;
  let numHSL = 0;
 
  for (let i = 0; i < numValues; i++) {
    // Parsed complex values return with colors first, so if we've seen any number
    // we're already past that part of the array and don't need to continue running typeof
    if (numNumbers || typeof parsed[i] === 'number') {
      numNumbers++;
    } else {
      if ((parsed[i] as HSLA).hue !== undefined) {
        numHSL++;
      } else {
        numRGB++;
      }
    }
  }
 
  return { parsed, numNumbers, numRGB, numHSL };
}
 
export const mixComplex = (origin: string, target: string): MixComplex => {
  const template = complex.createTransformer(target);
  const originStats = analyse(origin);
  const targetStats = analyse(target);
 
  // Test if both values have the same number of each value type (number/rgb/hsla), or that the origin
  // has the same or more numbers as the target, and throw if not.
  invariant(
    originStats.numHSL === targetStats.numHSL &&
      originStats.numRGB === targetStats.numRGB &&
      originStats.numNumbers >= targetStats.numNumbers,
    `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type.`
  );
 
  return pipe(
    mixArray(originStats.parsed, targetStats.parsed),
    template
  ) as MixComplex;
};