react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
146 lines (130 loc) • 4.49 kB
text/typescript
import { ReanimatedError } from '../errors';
const INDEX_X = 0;
const INDEX_Y = 1;
const INDEX_Z = 2;
// Implementation based on https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/StyleSheet/processTransformOrigin.js
function validateTransformOrigin(transformOrigin: Array<string | number>) {
'worklet';
if (transformOrigin.length !== 3) {
throw new ReanimatedError('Transform origin must have exactly 3 values.');
}
const [x, y, z] = transformOrigin;
if (!(typeof x === 'number' || (typeof x === 'string' && x.endsWith('%')))) {
throw new ReanimatedError(
`Transform origin x-position must be a number or a percentage string. Passed value: ${x}.`
);
}
if (!(typeof y === 'number' || (typeof y === 'string' && y.endsWith('%')))) {
throw new ReanimatedError(
`Transform origin y-position must be a number or a percentage string. Passed value: ${y}.`
);
}
if (typeof z !== 'number') {
throw new ReanimatedError(
`Transform origin z-position must be a number. Passed value: ${z}.`
);
}
}
export function processTransformOrigin(
transformOriginIn: Array<string | number> | string | undefined
): Array<string | number> {
'worklet';
let transformOrigin: Array<string | number> = Array.isArray(transformOriginIn)
? transformOriginIn
: ['50%', '50%', 0];
if (typeof transformOriginIn === 'string') {
const transformOriginString = transformOriginIn;
const regex = /(top|bottom|left|right|center|\d+(?:%|px)|0)/gi;
const transformOriginArray: Array<string | number> = ['50%', '50%', 0];
let index = INDEX_X;
let matches;
while ((matches = regex.exec(transformOriginString))) {
let nextIndex = index + 1;
const value = matches[0];
const valueLower = value.toLowerCase();
switch (valueLower) {
case 'left':
case 'right': {
if (index !== INDEX_X) {
throw new ReanimatedError(
`Transform-origin ${value} can only be used for x-position`
);
}
transformOriginArray[INDEX_X] = valueLower === 'left' ? 0 : '100%';
break;
}
case 'top':
case 'bottom': {
if (index === INDEX_Z) {
throw new ReanimatedError(
`Transform-origin ${value} can only be used for y-position`
);
}
transformOriginArray[INDEX_Y] = valueLower === 'top' ? 0 : '100%';
// Handle [[ center | left | right ] && [ center | top | bottom ]] <length>?
if (index === INDEX_X) {
const horizontal = regex.exec(transformOriginString);
if (horizontal == null) {
break;
}
switch (horizontal?.[0].toLowerCase()) {
case 'left':
transformOriginArray[INDEX_X] = 0;
break;
case 'right':
transformOriginArray[INDEX_X] = '100%';
break;
case 'center':
transformOriginArray[INDEX_X] = '50%';
break;
default:
throw new ReanimatedError(
`Could not parse transform-origin: ${transformOriginString}`
);
}
nextIndex = INDEX_Z;
}
break;
}
case 'center': {
if (index === INDEX_Z) {
throw new ReanimatedError(
`Transform-origin value ${value} cannot be used for z-position`
);
}
transformOriginArray[index] = '50%';
break;
}
default: {
if (value.endsWith('%')) {
transformOriginArray[index] = value;
} else {
const numericValue = parseFloat(value);
if (isNaN(numericValue)) {
throw new ReanimatedError(
`Invalid numeric value in transform-origin: ${value}`
);
}
transformOriginArray[index] = numericValue;
}
break;
}
}
index = nextIndex;
}
transformOrigin = transformOriginArray;
}
if (
typeof transformOriginIn !== 'string' &&
!Array.isArray(transformOriginIn)
) {
throw new ReanimatedError(
`Invalid transformOrigin type: ${typeof transformOriginIn}`
);
}
if (__DEV__) {
validateTransformOrigin(transformOrigin);
}
return transformOrigin;
}
;