diff --git a/apps/common-app/src/apps/css/examples/animations/routes/properties/svg.ts b/apps/common-app/src/apps/css/examples/animations/routes/properties/svg.ts index 5a98fe61e07b..e3f1582a7fe9 100644 --- a/apps/common-app/src/apps/css/examples/animations/routes/properties/svg.ts +++ b/apps/common-app/src/apps/css/examples/animations/routes/properties/svg.ts @@ -29,6 +29,10 @@ export const svgPropertiesRoutes = { name: 'Stroke', Component: svgAnimatedProperties.common.Stroke, }, + Transforms: { + name: 'Transforms', + Component: svgAnimatedProperties.common.Transforms, + }, }, }, } satisfies Routes; diff --git a/apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/common/Transforms.tsx b/apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/common/Transforms.tsx new file mode 100644 index 000000000000..af286d32be18 --- /dev/null +++ b/apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/common/Transforms.tsx @@ -0,0 +1,52 @@ +import Animated, { type CSSAnimationKeyframes } from 'react-native-reanimated'; +import { Rect, type RectProps, Svg } from 'react-native-svg'; + +import { ExamplesScreen } from '@/apps/css/components'; +import { colors } from '@/theme'; + +const AnimatedRect = Animated.createAnimatedComponent(Rect); + +export default function TransformsExample() { + return ( + }, RectProps> + buildAnimation={({ keyframes }) => ({ + animationName: keyframes, + animationDirection: 'alternate', + animationDuration: '1s', + animationIterationCount: 'infinite', + animationTimingFunction: 'linear', + })} + renderExample={({ animation }) => ( + + + + )} + sections={[ + { + examples: [ + { + keyframes: { + from: { + transform: [{ translateX: 0 }], + }, + to: { + transform: [{ translateX: 20 }], + }, + }, + title: 'Opacity', + }, + ], + title: 'Opacity', + }, + ]} + /> + ); +} diff --git a/apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/common/index.ts b/apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/common/index.ts index 2725d8573cde..da5188502bf1 100644 --- a/apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/common/index.ts +++ b/apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/svg/common/index.ts @@ -1,7 +1,9 @@ import FillAndColor from './FillAndColor'; import Stroke from './Stroke'; +import Transforms from './Transforms'; export default { FillAndColor, Stroke, + Transforms, }; diff --git a/apps/fabric-example/ios/Podfile.lock b/apps/fabric-example/ios/Podfile.lock index 4dc9dba2277c..18721c88e364 100644 --- a/apps/fabric-example/ios/Podfile.lock +++ b/apps/fabric-example/ios/Podfile.lock @@ -3187,7 +3187,7 @@ SPEC CHECKSUMS: RNCClipboard: 4b58c780f63676367640f23c8e114e9bd0cf86ac RNCMaskedView: 5ef8c95cbab95334a32763b72896a7b7d07e6299 RNGestureHandler: f1dd7f92a0faa2868a919ab53bb9d66eb4ebfcf5 - RNReanimated: 3b47c33660454c6f9700b463e92daa282030866a + RNReanimated: 4b3866c617506e8b5dc5a6fef5eb61f93ac0d1c0 RNScreens: 6ced6ae8a526512a6eef6e28c2286e1fc2d378c3 RNSVG: 287504b73fa0e90a605225aa9f852a86d5461e84 RNWorklets: 991f94e4fa31fc20853e74d5d987426f8580cb0d diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/InterpolatorRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/InterpolatorRegistry.cpp index 2626b3dc9b3a..af8ae5d2608b 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/InterpolatorRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/InterpolatorRegistry.cpp @@ -1,6 +1,10 @@ #include + +#include #include +#include + #include #include #include @@ -9,14 +13,8 @@ #include #include #include - -#include #include -#include -#include - -#include #include #include #include @@ -24,6 +22,11 @@ #include #include +#include + +#include +#include + #include namespace reanimated::css { @@ -303,23 +306,26 @@ const InterpolatorFactoriesRecord SVG_CLIP_INTERPOLATORS = { }; const InterpolatorFactoriesRecord SVG_TRANSFORM_INTERPOLATORS = { - {"translateX", value(0)}, - {"translateY", value(0)}, - {"originX", value(0)}, - {"originY", value(0)}, - {"scaleX", value(1)}, - {"scaleY", value(1)}, - {"skewX", value(0)}, - {"skewY", value(0)}, - {"rotation", value(0)}, -}; + {"matrix", + svg::transforms( + {{"rotate", transformOp("0deg")}, + {"rotateZ", transformOp("0deg")}, + {"scale", transformOp(1)}, + {"scaleX", transformOp(1)}, + {"scaleY", transformOp(1)}, + {"translateX", + transformOp(0, {RelativeTo::Self, "width"})}, + {"translateY", + transformOp(0, {RelativeTo::Self, "height"})}, + {"skewX", transformOp("0deg")}, + {"skewY", transformOp("0deg")}, + {"matrix", transformOp(TransformMatrix2D())}})}}; -const InterpolatorFactoriesRecord SVG_COMMON_INTERPOLATORS = - mergeInterpolators({ - SVG_COLOR_INTERPOLATORS, - SVG_FILL_INTERPOLATORS, - SVG_STROKE_INTERPOLATORS, - }); +const InterpolatorFactoriesRecord SVG_COMMON_INTERPOLATORS = mergeInterpolators( + {SVG_COLOR_INTERPOLATORS, + SVG_FILL_INTERPOLATORS, + SVG_STROKE_INTERPOLATORS, + SVG_TRANSFORM_INTERPOLATORS}); const InterpolatorFactoriesRecord SVG_CIRCLE_INTERPOLATORS = mergeInterpolators( {SVG_COMMON_INTERPOLATORS, diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformsStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformsStyleInterpolator.cpp index b18746fb2167..0fdb0728f5f8 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformsStyleInterpolator.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformsStyleInterpolator.cpp @@ -148,6 +148,18 @@ void TransformsStyleInterpolator::updateKeyframesFromStyleChange( parseTransformOperations(newStyleValue).value_or(TransformOperations{}))); } +folly::dynamic TransformsStyleInterpolator::convertOperationsToDynamic( + const TransformOperations &operations) const { + auto result = folly::dynamic::array(); + result.reserve(operations.size()); + + for (const auto &operation : operations) { + result.push_back(operation->toDynamic()); + } + + return result; +} + std::optional TransformsStyleInterpolator::parseTransformOperations( jsi::Runtime &rt, @@ -395,16 +407,4 @@ folly::dynamic TransformsStyleInterpolator::interpolateOperations( return convertOperationsToDynamic(result); } -folly::dynamic TransformsStyleInterpolator::convertOperationsToDynamic( - const TransformOperations &operations) { - auto result = folly::dynamic::array(); - result.reserve(operations.size()); - - for (const auto &operation : operations) { - result.push_back(operation->toDynamic()); - } - - return result; -} - } // namespace reanimated::css diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformsStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformsStyleInterpolator.h index 795ba507912a..8c426587f42f 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformsStyleInterpolator.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformsStyleInterpolator.h @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -23,7 +22,7 @@ struct TransformKeyframe { const std::optional toOperations; }; -class TransformsStyleInterpolator final : public PropertyInterpolator { +class TransformsStyleInterpolator : public PropertyInterpolator { public: TransformsStyleInterpolator( const PropertyPath &propertyPath, @@ -52,6 +51,10 @@ class TransformsStyleInterpolator final : public PropertyInterpolator { const folly::dynamic &newStyleValue, const folly::dynamic &lastUpdateValue) override; + protected: + virtual folly::dynamic convertOperationsToDynamic( + const TransformOperations &operations) const; + private: const std::shared_ptr interpolators_; static const TransformOperations defaultStyleValue_; @@ -85,14 +88,12 @@ class TransformsStyleInterpolator final : public PropertyInterpolator { const std::shared_ptr &progressProvider) const; TransformOperations getFallbackValue( const std::shared_ptr &shadowNode) const; - folly::dynamic interpolateOperations( + virtual folly::dynamic interpolateOperations( const std::shared_ptr &shadowNode, double keyframeProgress, const TransformOperations &fromOperations, const TransformOperations &toOperations) const; - static folly::dynamic convertOperationsToDynamic( - const TransformOperations &operations); TransformInterpolationContext createUpdateContext( const std::shared_ptr &shadowNode) const; }; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/operations/matrix.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/operations/matrix.cpp index 97f2cb1c63c8..589dd2c95d2c 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/operations/matrix.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/operations/matrix.cpp @@ -7,7 +7,7 @@ namespace reanimated::css { -TransformMatrix3D matrixFromOperations3D(TransformOperations &operations) { +TransformMatrix3D matrixFromOperations3D(const TransformOperations &operations) { TransformMatrix3D result; for (int i = static_cast(operations.size()) - 1; i >= 0; i--) { result *= @@ -16,7 +16,7 @@ TransformMatrix3D matrixFromOperations3D(TransformOperations &operations) { return result; } -TransformMatrix2D matrixFromOperations2D(TransformOperations &operations) { +TransformMatrix2D matrixFromOperations2D(const TransformOperations &operations) { TransformMatrix2D result; for (int i = static_cast(operations.size()) - 1; i >= 0; i--) { result *= diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/operations/matrix.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/operations/matrix.h index a35a9ee1b4ef..e8158823c920 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/operations/matrix.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/operations/matrix.h @@ -17,13 +17,13 @@ using MatrixOperationValue = * Multiplies all operations in the vector to a single 3D matrix * @param operations - the vector of operations that can be represented in 3D */ -TransformMatrix3D matrixFromOperations3D(TransformOperations &operations); +TransformMatrix3D matrixFromOperations3D(const TransformOperations &operations); /** * Multiplies all operations in the vector to a single 2D matrix * @param operations - the vector of operations that can be represented in 2D */ -TransformMatrix2D matrixFromOperations2D(TransformOperations &operations); +TransformMatrix2D matrixFromOperations2D(const TransformOperations &operations); struct MatrixOperation final : public TransformOperation { const MatrixOperationValue value; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.cpp index 70406e6993db..a4eff6bc9143 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.cpp @@ -189,6 +189,9 @@ void CSSAnimationsRegistry::updateViewAnimations( const auto updates = animation->update(timestamp); const auto newState = animation->getState(timestamp); + LOG(INFO) << "Animation: " << animation->getName() + << " updates: " << updates; + if (newState == AnimationProgressState::Finished) { // Revert changes applied during animation if there is no forwards fill // mode diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/InterpolatorFactory.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/InterpolatorFactory.cpp new file mode 100644 index 000000000000..724565f72af9 --- /dev/null +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/InterpolatorFactory.cpp @@ -0,0 +1,54 @@ +#include + +#include + +namespace reanimated::css::svg { + +class TransformsInterpolatorFactory : public PropertyInterpolatorFactory { + public: + explicit TransformsInterpolatorFactory( + const std::shared_ptr &interpolators) + : PropertyInterpolatorFactory(), interpolators_(interpolators) {} + + const CSSValue &getDefaultValue() const override { + static EmptyTransformsValue emptyTransformsValue; + return emptyTransformsValue; + } + + std::shared_ptr create( + const PropertyPath &propertyPath, + const std::shared_ptr &viewStylesRepository) + const override { + return std::make_shared( + propertyPath, interpolators_, viewStylesRepository); + } + + private: + // Helper private type just for a default value + struct EmptyTransformsValue : public CSSValue { + folly::dynamic toDynamic() const override { + return folly::dynamic::array(0, 0, 0, 0, 0, 0); + } + + std::string toString() const override { + return "0, 0, 0, 0, 0, 0"; + } + }; + + const std::shared_ptr interpolators_; +}; + +std::shared_ptr transforms( + const std::unordered_map< + std::string, + std::shared_ptr> &interpolators) { + TransformOperationInterpolators result; + result.reserve(interpolators.size()); + for (const auto &[property, interpolator] : interpolators) { + result.emplace(getTransformOperationType(property), interpolator); + } + return std::make_shared( + std::make_shared(std::move(result))); +} + +} // namespace reanimated::css::svg diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/InterpolatorFactory.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/InterpolatorFactory.h new file mode 100644 index 000000000000..514648972a97 --- /dev/null +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/InterpolatorFactory.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#include + +namespace reanimated::css::svg { + +/** + * Transform interpolators + */ +std::shared_ptr transforms( + const std::unordered_map< + std::string, + std::shared_ptr> &interpolators); + +} // namespace reanimated::css::svg diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/transforms/TransformsStyleInterpolator.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/transforms/TransformsStyleInterpolator.cpp new file mode 100644 index 000000000000..4b1d354f7cb7 --- /dev/null +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/transforms/TransformsStyleInterpolator.cpp @@ -0,0 +1,22 @@ +#include + +namespace reanimated::css::svg { + +TransformsStyleInterpolator::TransformsStyleInterpolator( + const PropertyPath &propertyPath, + const std::shared_ptr &interpolators, + const std::shared_ptr &viewStylesRepository) + : css::TransformsStyleInterpolator( + propertyPath, + interpolators, + viewStylesRepository) {} + +folly::dynamic TransformsStyleInterpolator::convertOperationsToDynamic( + const TransformOperations &operations) const { + const auto matrix = matrixFromOperations2D(operations); + // SVGs expect a 6-element array with 2D transform values + return folly::dynamic::array( + matrix[0], matrix[1], matrix[3], matrix[4], matrix[6], matrix[7]); +} + +} // namespace reanimated::css::svg diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/transforms/TransformsStyleInterpolator.h b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/transforms/TransformsStyleInterpolator.h new file mode 100644 index 000000000000..d05f30ac1e1a --- /dev/null +++ b/packages/react-native-reanimated/Common/cpp/reanimated/CSS/svg/interpolation/transforms/TransformsStyleInterpolator.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace reanimated::css::svg { + +class TransformsStyleInterpolator final + : public css::TransformsStyleInterpolator { + public: + TransformsStyleInterpolator( + const PropertyPath &propertyPath, + const std::shared_ptr &interpolators, + const std::shared_ptr &viewStylesRepository); + + protected: + folly::dynamic convertOperationsToDynamic( + const TransformOperations &operations) const override; +}; + +} // namespace reanimated::css::svg diff --git a/packages/react-native-reanimated/src/css/native/style/processors/transform.ts b/packages/react-native-reanimated/src/css/native/style/processors/transform.ts index 09c2f9d07cc8..d7c08533569f 100644 --- a/packages/react-native-reanimated/src/css/native/style/processors/transform.ts +++ b/packages/react-native-reanimated/src/css/native/style/processors/transform.ts @@ -140,6 +140,7 @@ function parseMatrix(values: (number | string)[]): TransformsArray { if (isNumberArray(values)) { if (values.length === 6) { + // TODO - CHANGE TO 2D // prettier-ignore matrixValues = [ values[0], values[1], 0, 0, diff --git a/packages/react-native-reanimated/src/css/svg/native/builders/index.ts b/packages/react-native-reanimated/src/css/svg/native/builders/index.ts new file mode 100644 index 000000000000..3b8c5e3e3102 --- /dev/null +++ b/packages/react-native-reanimated/src/css/svg/native/builders/index.ts @@ -0,0 +1 @@ +export type * from './transform'; diff --git a/packages/react-native-reanimated/src/css/svg/native/builders/transform.ts b/packages/react-native-reanimated/src/css/svg/native/builders/transform.ts new file mode 100644 index 000000000000..34bfaa60ef04 --- /dev/null +++ b/packages/react-native-reanimated/src/css/svg/native/builders/transform.ts @@ -0,0 +1 @@ +// TODO - implement transform rule builder diff --git a/packages/react-native-reanimated/src/css/svg/native/configs/common.ts b/packages/react-native-reanimated/src/css/svg/native/configs/common.ts index e0a373be3125..67a61963f055 100644 --- a/packages/react-native-reanimated/src/css/svg/native/configs/common.ts +++ b/packages/react-native-reanimated/src/css/svg/native/configs/common.ts @@ -16,7 +16,7 @@ import type { TransformProps, } from 'react-native-svg'; -import type { StyleBuilderConfig } from '../../../native'; +import { processTransform, type StyleBuilderConfig } from '../../../native'; import { convertStringToNumber, processColorSVG, @@ -80,22 +80,22 @@ const clipProps: StyleBuilderConfig = { }; const transformProps: StyleBuilderConfig = { - translate: true, // TODO - add preprocessor (NumberArray) and split to translateX and translateY - translateX: true, - translateY: true, + translate: false, + translateX: false, + translateY: false, origin: true, // TODO - add preprocessor (NumberArray) and split to originX and originY originX: true, originY: true, - scale: true, // TODO - add preprocessor (NumberArray) and split to scaleX and scaleY - scaleX: true, - scaleY: true, - skew: true, // TODO - add preprocessor (NumberArray) and split to skewX and skewY - skewX: true, - skewY: true, - rotation: true, - x: true, - y: true, - transform: true, // TODO - add preprocessor + scale: false, + scaleX: false, + scaleY: false, + skew: false, + skewX: false, + skewY: false, + rotation: false, + x: false, + y: false, + matrix: { process: processTransform }, }; const responderProps: StyleBuilderConfig< diff --git a/packages/react-native-reanimated/src/css/svg/native/processors/transform.ts b/packages/react-native-reanimated/src/css/svg/native/processors/transform.ts new file mode 100644 index 000000000000..db39d23d9a9d --- /dev/null +++ b/packages/react-native-reanimated/src/css/svg/native/processors/transform.ts @@ -0,0 +1,175 @@ +'use strict'; +import type { TransformProps } from 'react-native-svg'; + +import type { ValueProcessor } from '../../../../common'; +import { ReanimatedError } from '../../../../common'; +import { isAngle, isNumber, isNumberArray, isPercentage } from '../../../utils'; + +type TransformsArray = TransformProps['transform']; + +export const ERROR_MESSAGES = { + invalidTransform: (transform: string) => + `Invalid transform property: ${transform}`, +}; + +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 '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); + default: + return []; + } +}; + +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 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 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 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 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 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 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 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 parseMatrix(values: (number | string)[]): TransformsArray { + let matrixValues: number[] = []; + + if (isNumberArray(values)) { + if (values.length === 6) { + // prettier-ignore + matrixValues = [ + values[0], values[1], 0, + values[2], values[3], 0, + values[4], values[5], 1 + ]; + } + } + + return matrixValues.length > 0 ? [{ matrix: matrixValues }] : []; +} + +export const processTransform: ValueProcessor = ( + 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; + }); +};