diff --git a/packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js b/packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js index d86712540ea..71d5ff54dd1 100644 --- a/packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js +++ b/packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js @@ -8,6 +8,8 @@ * @format */ +import type {AnimatedPropsAllowlist} from './nodes/AnimatedProps'; + import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags'; /** @@ -16,7 +18,7 @@ import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNa * In general native animated implementation should support any numeric or color property that * doesn't need to be updated through the shadow view hierarchy (all non-layout properties). */ -const SUPPORTED_COLOR_STYLES: {[string]: boolean} = { +const SUPPORTED_COLOR_STYLES: {[string]: true} = { backgroundColor: true, borderBottomColor: true, borderColor: true, @@ -29,7 +31,7 @@ const SUPPORTED_COLOR_STYLES: {[string]: boolean} = { tintColor: true, }; -const SUPPORTED_STYLES: {[string]: boolean} = { +const SUPPORTED_STYLES: {[string]: true} = { ...SUPPORTED_COLOR_STYLES, borderBottomEndRadius: true, borderBottomLeftRadius: true, @@ -58,7 +60,7 @@ const SUPPORTED_STYLES: {[string]: boolean} = { translateY: true, }; -const SUPPORTED_TRANSFORMS: {[string]: boolean} = { +const SUPPORTED_TRANSFORMS: {[string]: true} = { translateX: true, translateY: true, scale: true, @@ -71,10 +73,12 @@ const SUPPORTED_TRANSFORMS: {[string]: boolean} = { perspective: true, skewX: true, skewY: true, - matrix: ReactNativeFeatureFlags.shouldUseAnimatedObjectForTransform(), + ...(ReactNativeFeatureFlags.shouldUseAnimatedObjectForTransform() + ? {matrix: true} + : {}), }; -const SUPPORTED_INTERPOLATION_PARAMS: {[string]: boolean} = { +const SUPPORTED_INTERPOLATION_PARAMS: {[string]: true} = { inputRange: true, outputRange: true, extrapolate: true, @@ -82,6 +86,13 @@ const SUPPORTED_INTERPOLATION_PARAMS: {[string]: boolean} = { extrapolateLeft: true, }; +/** + * Default allowlist for component props that support native animated values. + */ +export default { + style: SUPPORTED_STYLES, +} as AnimatedPropsAllowlist; + export function allowInterpolationParam(param: string): void { SUPPORTED_INTERPOLATION_PARAMS[param] = true; } @@ -95,17 +106,17 @@ export function allowTransformProp(prop: string): void { } export function isSupportedColorStyleProp(prop: string): boolean { - return SUPPORTED_COLOR_STYLES[prop] === true; + return Object.hasOwn(SUPPORTED_COLOR_STYLES, prop); } export function isSupportedInterpolationParam(param: string): boolean { - return SUPPORTED_INTERPOLATION_PARAMS[param] === true; + return Object.hasOwn(SUPPORTED_INTERPOLATION_PARAMS, param); } export function isSupportedStyleProp(prop: string): boolean { - return SUPPORTED_STYLES[prop] === true; + return Object.hasOwn(SUPPORTED_STYLES, prop); } export function isSupportedTransformProp(prop: string): boolean { - return SUPPORTED_TRANSFORMS[prop] === true; + return Object.hasOwn(SUPPORTED_TRANSFORMS, prop); } diff --git a/packages/react-native/Libraries/Animated/__tests__/NativeAnimatedAllowlist-test.js b/packages/react-native/Libraries/Animated/__tests__/NativeAnimatedAllowlist-test.js new file mode 100644 index 00000000000..9bdfd3e7b9a --- /dev/null +++ b/packages/react-native/Libraries/Animated/__tests__/NativeAnimatedAllowlist-test.js @@ -0,0 +1,87 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +describe('NativeAnimatedAllowlist', () => { + beforeEach(() => { + jest.restoreAllMocks(); + jest.resetModules(); + }); + + it('checks invalid style props', () => { + const {isSupportedStyleProp} = require('../NativeAnimatedAllowlist'); + + // $FlowExpectedError[incompatible-call] + expect(isSupportedStyleProp(null)).toBe(false); + // $FlowExpectedError[incompatible-call] + expect(isSupportedStyleProp(undefined)).toBe(false); + // $FlowExpectedError[incompatible-call] + expect(isSupportedStyleProp({})).toBe(false); + // $FlowExpectedError[incompatible-call] + expect(isSupportedStyleProp([])).toBe(false); + // $FlowExpectedError[incompatible-call] + expect(isSupportedStyleProp(true)).toBe(false); + // $FlowExpectedError[incompatible-call] + expect(isSupportedStyleProp(false)).toBe(false); + }); + + it('checks supported interpolation params', () => { + const { + isSupportedInterpolationParam, + } = require('../NativeAnimatedAllowlist'); + + expect(isSupportedInterpolationParam('inputRange')).toBe(true); + expect(isSupportedInterpolationParam('outputRange')).toBe(true); + expect(isSupportedInterpolationParam('extrapolate')).toBe(true); + expect(isSupportedInterpolationParam('extrapolateRight')).toBe(true); + expect(isSupportedInterpolationParam('extrapolateLeft')).toBe(true); + }); + + it('allows new interpolation params', () => { + const { + allowInterpolationParam, + isSupportedInterpolationParam, + } = require('../NativeAnimatedAllowlist'); + + expect(isSupportedInterpolationParam('other')).toBe(false); + allowInterpolationParam('other'); + expect(isSupportedInterpolationParam('other')).toBe(true); + }); + + it('checks supported transform props', () => { + jest + .spyOn( + require('../../../src/private/featureflags/ReactNativeFeatureFlags'), + 'shouldUseAnimatedObjectForTransform', + ) + .mockReturnValue(false); + const {isSupportedTransformProp} = require('../NativeAnimatedAllowlist'); + + expect(isSupportedTransformProp('translateX')).toBe(true); + expect(isSupportedTransformProp('translateY')).toBe(true); + + expect(isSupportedTransformProp('matrix')).toBe(false); + }); + + it('checks supported transform props with object for transform', () => { + jest + .spyOn( + require('../../../src/private/featureflags/ReactNativeFeatureFlags'), + 'shouldUseAnimatedObjectForTransform', + ) + .mockReturnValue(true); + const {isSupportedTransformProp} = require('../NativeAnimatedAllowlist'); + + expect(isSupportedTransformProp('translateX')).toBe(true); + expect(isSupportedTransformProp('translateY')).toBe(true); + + expect(isSupportedTransformProp('matrix')).toBe(true); + }); +}); diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index de62a3b54bd..8362293e4d1 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -373,7 +373,8 @@ declare export default typeof Easing; `; exports[`public API should not change unintentionally Libraries/Animated/NativeAnimatedAllowlist.js 1`] = ` -"declare export function allowInterpolationParam(param: string): void; +"declare export default AnimatedPropsAllowlist; +declare export function allowInterpolationParam(param: string): void; declare export function allowStyleProp(prop: string): void; declare export function allowTransformProp(prop: string): void; declare export function isSupportedColorStyleProp(prop: string): boolean;