react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
186 lines (163 loc) • 5.12 kB
text/typescript
;
'worklet';
import { ReanimatedError } from '../../errors';
import type { TransformsArray, ValueProcessor } from '../../types';
import { isAngle, isNumber, isNumberArray, isPercentage } from '../../utils';
export const ERROR_MESSAGES = {
invalidTransform: (transform: string) =>
`Invalid transform property: ${transform}`,
};
function parseValues(valueString: string): (string | number)[] {
return valueString.split(',').map((value) => {
const trimmedValue = value.trim();
if (['deg', 'rad', '%'].some((unit) => trimmedValue.endsWith(unit))) {
return trimmedValue;
}
const numValue = parseFloat(trimmedValue);
return isNaN(numValue) ? trimmedValue : numValue;
});
}
function parseTranslateX(values: (number | string)[]): TransformsArray {
return values.length === 1 && (isNumber(values[0]) || isPercentage(values[0]))
? [{ translateX: values[0] }]
: [];
}
function parseTranslateY(values: (number | string)[]): TransformsArray {
return values.length === 1 && (isNumber(values[0]) || isPercentage(values[0]))
? [{ translateY: values[0] }]
: [];
}
function parseTranslate(values: (number | string)[]): TransformsArray {
if (values.length > 2) {
return [];
}
const result = parseTranslateX([values[0]]).concat(
parseTranslateY([values[1] ?? values[0]])
);
return result.length === 2 ? result : [];
}
function parseScaleX(values: (number | string)[]): TransformsArray {
return values.length === 1 && isNumber(values[0])
? [{ scaleX: values[0] }]
: [];
}
function parseScaleY(values: (number | string)[]): TransformsArray {
return values.length === 1 && isNumber(values[0])
? [{ scaleY: values[0] }]
: [];
}
function parseScale(values: (number | string)[]): TransformsArray {
if (values.length > 2) {
return [];
}
if (values.length === 1) {
return isNumber(values[0]) ? [{ scale: values[0] }] : [];
}
const result = parseScaleX([values[0]]).concat(
parseScaleY([values[1] ?? values[0]])
);
return result.length === 2 ? result : [];
}
function parseRotate(
key: string,
values: (string | number)[]
): TransformsArray {
return values.length === 1 && (isAngle(values[0]) || values[0] === 0)
? ([
{ [key]: values[0] === 0 ? '0deg' : values[0] },
] as unknown as TransformsArray)
: [];
}
function parseSkewX(values: (number | string)[]): TransformsArray {
return values.length === 1 && (isAngle(values[0]) || values[0] === 0)
? [{ skewX: values[0] === 0 ? '0deg' : values[0] }]
: [];
}
function parseSkewY(values: (number | string)[]): TransformsArray {
return values.length === 1 && (isAngle(values[0]) || values[0] === 0)
? [{ skewY: values[0] === 0 ? '0deg' : values[0] }]
: [];
}
function parseSkew(values: (number | string)[]): TransformsArray {
if (values.length > 2) {
return [];
}
const result = parseSkewX([values[0]]).concat(
parseSkewY([values[1] ?? values[0]])
);
return result.length === 2 ? result : [];
}
function parseMatrix(values: (number | string)[]): TransformsArray {
let matrixValues: number[] = [];
if (isNumberArray(values)) {
if (values.length === 6) {
// prettier-ignore
matrixValues = [
values[0], values[1], 0, 0,
values[2], values[3], 0, 0,
0, 0, 1, 0,
values[4], values[5], 0, 1
];
} else if (values.length === 16) {
matrixValues = values;
}
}
return matrixValues.length > 0 ? [{ matrix: matrixValues }] : [];
}
function parsePerspective(values: (number | string)[]): TransformsArray {
return values.length === 1 && isNumber(values[0])
? [{ perspective: values[0] }]
: [];
}
const parseTransformProperty = (transform: string): TransformsArray => {
const [key, valueString] = transform.split(/\(\s*/);
const values = parseValues(valueString.replace(/\)$/g, ''));
switch (key) {
case 'translate':
return parseTranslate(values);
case 'translateX':
return parseTranslateX(values);
case 'translateY':
return parseTranslateY(values);
case 'scale':
return parseScale(values);
case 'scaleX':
return parseScaleX(values);
case 'scaleY':
return parseScaleY(values);
case 'rotate':
case 'rotateX':
case 'rotateY':
case 'rotateZ':
return parseRotate(key, values);
case 'skew':
return parseSkew(values);
case 'skewX':
return parseSkewX(values);
case 'skewY':
return parseSkewY(values);
case 'matrix':
return parseMatrix(values);
case 'perspective':
return parsePerspective(values);
default:
return [];
}
};
export const processTransform: ValueProcessor<TransformsArray | string> = (
value
) => {
if (typeof value !== 'string') {
return value;
}
return value
.split(/\)\s*/)
.filter(Boolean)
.flatMap((part) => {
const parsed = parseTransformProperty(part);
if (parsed.length === 0) {
throw new ReanimatedError(ERROR_MESSAGES.invalidTransform(`${part})`));
}
return parsed;
});
};