| 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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 |
58x
21x
15x
6x
4x
2x
2x
2x
1x
1x
1x
22x
22x
22x
22x
30x
30x
6x
6x
30x
22x
14x
47x
8x
8x
8x
24x
24x
24x
6x
18x
4x
4x
24x
14x
14x
22x
14x
14x
24x
24x
44x
22x
22x
22x
22x
1x
1x
1x
1x
22x
22x
22x
66x
| import { Easing } from '../easing/types';
import { progress } from './progress';
import { mix } from './mix';
import { mixColor } from './mix-color';
import { mixComplex, mixArray, mixObject } from './mix-complex';
import { color } from 'style-value-types';
import { clamp } from './clamp';
import { pipe } from './pipe';
import { invariant } from 'hey-listen';
type MixEasing = Easing | Easing[];
type InterpolateOptions<T> = {
clamp?: boolean;
ease?: MixEasing;
mixer?: MixerFactory<T>;
};
type Mix<T> = (v: number) => T;
export type MixerFactory<T> = (from: T, to: T) => Mix<T>;
const mixNumber = (from: number, to: number) => (p: number) => mix(from, to, p);
function detectMixerFactory<T>(v: T): MixerFactory<any> {
if (typeof v === 'number') {
return mixNumber;
} else if (typeof v === 'string') {
if (color.test(v)) {
return mixColor;
} else {
return mixComplex;
}
} else if (Array.isArray(v)) {
return mixArray;
} else Eif (typeof v === 'object') {
return mixObject;
}
}
function createMixers<T>(
output: T[],
ease?: MixEasing,
customMixer?: MixerFactory<T>
) {
const mixers: Array<Mix<T>> = [];
const mixerFactory: MixerFactory<T> =
customMixer || detectMixerFactory(output[0]);
const numMixers = output.length - 1;
for (let i = 0; i < numMixers; i++) {
let mixer = mixerFactory(output[i], output[i + 1]);
if (ease) {
const easingFunction = Array.isArray(ease) ? ease[i] : ease;
mixer = pipe(easingFunction, mixer) as Mix<T>;
}
mixers.push(mixer);
}
return mixers;
}
function fastInterpolate<T>([from, to]: number[], [mixer]: Array<Mix<T>>) {
return (v: number) => mixer(progress(from, to, v));
}
function slowInterpolate<T>(input: number[], mixers: Array<Mix<T>>) {
const inputLength = input.length;
const lastInputIndex = inputLength - 1;
return (v: number) => {
let mixerIndex = 0;
let foundMixerIndex = false;
if (v <= input[0]) {
foundMixerIndex = true;
} else if (v >= input[lastInputIndex]) {
mixerIndex = lastInputIndex - 1;
foundMixerIndex = true;
}
if (!foundMixerIndex) {
let i = 1;
for (; i < inputLength; i++) {
if (input[i] > v || i === lastInputIndex) {
break;
}
}
mixerIndex = i - 1;
}
const progressInRange = progress(
input[mixerIndex],
input[mixerIndex + 1],
v
);
return mixers[mixerIndex](progressInRange);
};
}
/**
* Create a function that maps from a numerical input array to a generic output array.
*
* Accepts:
* - Numbers
* - Colors (hex, hsl, hsla, rgb, rgba)
* - Complex (combinations of one or more numbers or strings)
*
* ```jsx
* const mixColor = interpolate([0, 1], ['#fff', '#000'])
*
* mixColor(0.5) // 'rgba(128, 128, 128, 1)'
* ```
*
* @public
*/
export function interpolate<T>(
input: number[],
output: T[],
{ clamp: isClamp = true, ease, mixer }: InterpolateOptions<T> = {}
) {
const inputLength = input.length;
invariant(
inputLength === output.length,
'Both input and output ranges must be the same length'
);
invariant(
!ease || !Array.isArray(ease) || ease.length === inputLength - 1,
'Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.'
);
// If input runs highest -> lowest, reverse both arrays
if (input[0] > input[inputLength - 1]) {
input = [].concat(input);
output = [].concat(output);
input.reverse();
output.reverse();
}
const mixers = createMixers(output, ease, mixer);
const interpolator =
inputLength === 2
? fastInterpolate(input, mixers)
: slowInterpolate(input, mixers);
return isClamp
? (v: number) => interpolator(clamp(input[0], input[inputLength - 1], v))
: interpolator;
}
|