UNPKG

7.15 kBPlain TextView Raw
1'use strict';
2
3/**
4 * Extrapolation type.
5 *
6 * @param IDENTITY - Returns the provided value as is.
7 * @param CLAMP - Clamps the value to the edge of the output range.
8 * @param EXTEND - Predicts the values beyond the output range.
9 */
10export enum Extrapolation {
11 IDENTITY = 'identity',
12 CLAMP = 'clamp',
13 EXTEND = 'extend',
14}
15
16/**
17 * Represents the possible values for extrapolation as a string.
18 */
19type ExtrapolationAsString = 'identity' | 'clamp' | 'extend';
20
21interface InterpolationNarrowedInput {
22 leftEdgeInput: number;
23 rightEdgeInput: number;
24 leftEdgeOutput: number;
25 rightEdgeOutput: number;
26}
27
28/**
29 * Allows to specify extrapolation for left and right edge of the interpolation.
30 */
31export interface ExtrapolationConfig {
32 extrapolateLeft?: Extrapolation | string;
33 extrapolateRight?: Extrapolation | string;
34}
35
36interface RequiredExtrapolationConfig {
37 extrapolateLeft: Extrapolation;
38 extrapolateRight: Extrapolation;
39}
40
41/**
42 * Configuration options for extrapolation.
43 */
44export type ExtrapolationType =
45 | ExtrapolationConfig
46 | Extrapolation
47 | ExtrapolationAsString
48 | undefined;
49
50function getVal(
51 type: Extrapolation,
52 coef: number,
53 val: number,
54 leftEdgeOutput: number,
55 rightEdgeOutput: number,
56 x: number
57): number {
58 'worklet';
59
60 switch (type) {
61 case Extrapolation.IDENTITY:
62 return x;
63 case Extrapolation.CLAMP:
64 if (coef * val < coef * leftEdgeOutput) {
65 return leftEdgeOutput;
66 }
67 return rightEdgeOutput;
68 case Extrapolation.EXTEND:
69 default:
70 return val;
71 }
72}
73
74function isExtrapolate(value: string): value is Extrapolation {
75 'worklet';
76
77 return (
78 /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
79 value === Extrapolation.EXTEND ||
80 value === Extrapolation.CLAMP ||
81 value === Extrapolation.IDENTITY
82 /* eslint-enable @typescript-eslint/no-unsafe-enum-comparison */
83 );
84}
85
86// validates extrapolations type
87// if type is correct, converts it to ExtrapolationConfig
88function validateType(type: ExtrapolationType): RequiredExtrapolationConfig {
89 'worklet';
90 // initialize extrapolationConfig with default extrapolation
91 const extrapolationConfig: RequiredExtrapolationConfig = {
92 extrapolateLeft: Extrapolation.EXTEND,
93 extrapolateRight: Extrapolation.EXTEND,
94 };
95
96 if (!type) {
97 return extrapolationConfig;
98 }
99
100 if (typeof type === 'string') {
101 if (!isExtrapolate(type)) {
102 throw new Error(
103 `[Reanimated] Unsupported value for "interpolate" \nSupported values: ["extend", "clamp", "identity", Extrapolatation.CLAMP, Extrapolatation.EXTEND, Extrapolatation.IDENTITY]\n Valid example:
104 interpolate(value, [inputRange], [outputRange], "clamp")`
105 );
106 }
107 extrapolationConfig.extrapolateLeft = type;
108 extrapolationConfig.extrapolateRight = type;
109 return extrapolationConfig;
110 }
111
112 // otherwise type is extrapolation config object
113 if (
114 (type.extrapolateLeft && !isExtrapolate(type.extrapolateLeft)) ||
115 (type.extrapolateRight && !isExtrapolate(type.extrapolateRight))
116 ) {
117 throw new Error(
118 `[Reanimated] Unsupported value for "interpolate" \nSupported values: ["extend", "clamp", "identity", Extrapolatation.CLAMP, Extrapolatation.EXTEND, Extrapolatation.IDENTITY]\n Valid example:
119 interpolate(value, [inputRange], [outputRange], {
120 extrapolateLeft: Extrapolation.CLAMP,
121 extrapolateRight: Extrapolation.IDENTITY
122 }})`
123 );
124 }
125
126 Object.assign(extrapolationConfig, type);
127 return extrapolationConfig;
128}
129
130function internalInterpolate(
131 x: number,
132 narrowedInput: InterpolationNarrowedInput,
133 extrapolationConfig: RequiredExtrapolationConfig
134) {
135 'worklet';
136 const { leftEdgeInput, rightEdgeInput, leftEdgeOutput, rightEdgeOutput } =
137 narrowedInput;
138 if (rightEdgeInput - leftEdgeInput === 0) {
139 return leftEdgeOutput;
140 }
141 const progress = (x - leftEdgeInput) / (rightEdgeInput - leftEdgeInput);
142 const val = leftEdgeOutput + progress * (rightEdgeOutput - leftEdgeOutput);
143 const coef = rightEdgeOutput >= leftEdgeOutput ? 1 : -1;
144
145 if (coef * val < coef * leftEdgeOutput) {
146 return getVal(
147 extrapolationConfig.extrapolateLeft,
148 coef,
149 val,
150 leftEdgeOutput,
151 rightEdgeOutput,
152 x
153 );
154 } else if (coef * val > coef * rightEdgeOutput) {
155 return getVal(
156 extrapolationConfig.extrapolateRight,
157 coef,
158 val,
159 leftEdgeOutput,
160 rightEdgeOutput,
161 x
162 );
163 }
164
165 return val;
166}
167
168/**
169 * Lets you map a value from one range to another using linear interpolation.
170 *
171 * @param value - A number from the `input` range that is going to be mapped to the `output` range.
172 * @param inputRange - An array of numbers specifying the input range of the interpolation.
173 * @param outputRange - An array of numbers specifying the output range of the interpolation.
174 * @param extrapolate - determines what happens when the `value` goes beyond the `input` range. Defaults to `Extrapolation.EXTEND` - {@link ExtrapolationType}.
175 * @returns A mapped value within the output range.
176 * @see https://docs.swmansion.com/react-native-reanimated/docs/utilities/interpolate
177 */
178export function interpolate(
179 x: number,
180 inputRange: readonly number[],
181 outputRange: readonly number[],
182 type?: ExtrapolationType
183): number {
184 'worklet';
185 if (inputRange.length < 2 || outputRange.length < 2) {
186 throw new Error(
187 '[Reanimated] Interpolation input and output ranges should contain at least two values.'
188 );
189 }
190
191 const extrapolationConfig = validateType(type);
192 const length = inputRange.length;
193 const narrowedInput: InterpolationNarrowedInput = {
194 leftEdgeInput: inputRange[0],
195 rightEdgeInput: inputRange[1],
196 leftEdgeOutput: outputRange[0],
197 rightEdgeOutput: outputRange[1],
198 };
199 if (length > 2) {
200 if (x > inputRange[length - 1]) {
201 narrowedInput.leftEdgeInput = inputRange[length - 2];
202 narrowedInput.rightEdgeInput = inputRange[length - 1];
203 narrowedInput.leftEdgeOutput = outputRange[length - 2];
204 narrowedInput.rightEdgeOutput = outputRange[length - 1];
205 } else {
206 for (let i = 1; i < length; ++i) {
207 if (x <= inputRange[i]) {
208 narrowedInput.leftEdgeInput = inputRange[i - 1];
209 narrowedInput.rightEdgeInput = inputRange[i];
210 narrowedInput.leftEdgeOutput = outputRange[i - 1];
211 narrowedInput.rightEdgeOutput = outputRange[i];
212 break;
213 }
214 }
215 }
216 }
217
218 return internalInterpolate(x, narrowedInput, extrapolationConfig);
219}
220
221/**
222 * Lets you limit a value within a specified range.
223 *
224 * @param value - A number that will be returned as long as the provided value is in range between `min` and `max`.
225 * @param min - A number which will be returned when provided `value` is lower than `min`.
226 * @param max - A number which will be returned when provided `value` is higher than `max`.
227 * @returns A number between min and max bounds.
228 * @see https://docs.swmansion.com/react-native-reanimated/docs/utilities/clamp/
229 */
230export function clamp(value: number, min: number, max: number) {
231 'worklet';
232 return Math.min(Math.max(value, min), max);
233}