first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
/**
* 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
* @format
*/
'use strict';
import type {Rect, RectOrSize} from './Rect';
// TODO: allow all EdgeInsets-like property to be set using a single number
// and unify EdgeInsetsProp with EdgeInsetsOrSizeProp
export type EdgeInsetsProp = Rect;
export type EdgeInsetsOrSizeProp = RectOrSize;

View File

@@ -0,0 +1,40 @@
/**
* 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
*/
import type {ProcessedColorValue} from './processColor';
import type {ColorValue, NativeColorValue} from './StyleSheet';
/** The actual type of the opaque NativeColorValue on Android platform */
type LocalNativeColorValue = {
resource_paths?: Array<string>,
};
export const PlatformColor = (...names: Array<string>): ColorValue => {
/* $FlowExpectedError[incompatible-type]
* LocalNativeColorValue is the actual type of the opaque NativeColorValue on Android platform */
return ({resource_paths: names}: LocalNativeColorValue);
};
export const normalizeColorObject = (
color: NativeColorValue,
): ?ProcessedColorValue => {
/* $FlowExpectedError[incompatible-type]
* LocalNativeColorValue is the actual type of the opaque NativeColorValue on Android platform */
if ('resource_paths' in (color: LocalNativeColorValue)) {
return color;
}
return null;
};
export const processColorObject = (
color: NativeColorValue,
): ?NativeColorValue => {
return color;
};

View File

@@ -0,0 +1,18 @@
/**
* 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.
*
* @format
*/
import {OpaqueColorValue} from './StyleSheet';
/**
* Select native platform color
* The color must match the string that exists on the native platform
*
* @see https://reactnative.dev/docs/platformcolor#example
*/
export function PlatformColor(...colors: string[]): OpaqueColorValue;

View File

@@ -0,0 +1,113 @@
/**
* 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
*/
import type {ProcessedColorValue} from './processColor';
import type {ColorValue, NativeColorValue} from './StyleSheet';
/** The actual type of the opaque NativeColorValue on iOS platform */
type LocalNativeColorValue = {
semantic?: Array<string>,
dynamic?: {
light: ?(ColorValue | ProcessedColorValue),
dark: ?(ColorValue | ProcessedColorValue),
highContrastLight?: ?(ColorValue | ProcessedColorValue),
highContrastDark?: ?(ColorValue | ProcessedColorValue),
},
};
export const PlatformColor = (...names: Array<string>): ColorValue => {
// $FlowExpectedError[incompatible-type] LocalNativeColorValue is the iOS LocalNativeColorValue type
return ({semantic: names}: LocalNativeColorValue);
};
export type DynamicColorIOSTuplePrivate = {
light: ColorValue,
dark: ColorValue,
highContrastLight?: ColorValue,
highContrastDark?: ColorValue,
};
export const DynamicColorIOSPrivate = (
tuple: DynamicColorIOSTuplePrivate,
): ColorValue => {
return ({
dynamic: {
light: tuple.light,
dark: tuple.dark,
highContrastLight: tuple.highContrastLight,
highContrastDark: tuple.highContrastDark,
},
/* $FlowExpectedError[incompatible-type]
* LocalNativeColorValue is the actual type of the opaque NativeColorValue on iOS platform */
}: LocalNativeColorValue);
};
const _normalizeColorObject = (
color: LocalNativeColorValue,
): ?LocalNativeColorValue => {
if ('semantic' in color) {
// an ios semantic color
return color;
} else if ('dynamic' in color && color.dynamic !== undefined) {
const normalizeColor = require('./normalizeColor').default;
// a dynamic, appearance aware color
const dynamic = color.dynamic;
const dynamicColor: LocalNativeColorValue = {
dynamic: {
// $FlowFixMe[incompatible-use]
light: normalizeColor(dynamic.light),
// $FlowFixMe[incompatible-use]
dark: normalizeColor(dynamic.dark),
// $FlowFixMe[incompatible-use]
highContrastLight: normalizeColor(dynamic.highContrastLight),
// $FlowFixMe[incompatible-use]
highContrastDark: normalizeColor(dynamic.highContrastDark),
},
};
return dynamicColor;
}
return null;
};
export const normalizeColorObject: (
color: NativeColorValue,
/* $FlowExpectedError[incompatible-type]
* LocalNativeColorValue is the actual type of the opaque NativeColorValue on iOS platform */
) => ?ProcessedColorValue = _normalizeColorObject;
const _processColorObject = (
color: LocalNativeColorValue,
): ?LocalNativeColorValue => {
if ('dynamic' in color && color.dynamic != null) {
const processColor = require('./processColor').default;
const dynamic = color.dynamic;
const dynamicColor: LocalNativeColorValue = {
dynamic: {
// $FlowFixMe[incompatible-use]
light: processColor(dynamic.light),
// $FlowFixMe[incompatible-use]
dark: processColor(dynamic.dark),
// $FlowFixMe[incompatible-use]
highContrastLight: processColor(dynamic.highContrastLight),
// $FlowFixMe[incompatible-use]
highContrastDark: processColor(dynamic.highContrastDark),
},
};
return dynamicColor;
}
return color;
};
export const processColorObject: (
color: NativeColorValue,
/* $FlowExpectedError[incompatible-type]
* LocalNativeColorValue is the actual type of the opaque NativeColorValue on iOS platform */
) => ?NativeColorValue = _processColorObject;

View File

@@ -0,0 +1,15 @@
/**
* 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
*/
// NOTE: This file supports backwards compatibility of subpath (deep) imports
// from 'react-native' with platform-specific extensions. It can be deleted
// once we remove the "./*" mapping from package.json "exports".
export * from './PlatformColorValueTypes';

View File

@@ -0,0 +1,22 @@
/**
* 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
*/
import type {ProcessedColorValue} from './processColor';
import type {ColorValue, NativeColorValue} from './StyleSheet';
declare export function PlatformColor(...names: Array<string>): ColorValue;
declare export function normalizeColorObject(
color: NativeColorValue,
): ?ProcessedColorValue;
declare export function processColorObject(
color: NativeColorValue,
): ?NativeColorValue;

View File

@@ -0,0 +1,25 @@
/**
* 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.
*
* @format
*/
import {ColorValue, OpaqueColorValue} from './StyleSheet';
type DynamicColorIOSTuple = {
light: ColorValue;
dark: ColorValue;
highContrastLight?: ColorValue | undefined;
highContrastDark?: ColorValue | undefined;
};
/**
* Specify color to display depending on the current system appearance settings
*
* @param tuple Colors you want to use for "light mode" and "dark mode"
* @platform ios
*/
export function DynamicColorIOS(tuple: DynamicColorIOSTuple): OpaqueColorValue;

View File

@@ -0,0 +1,29 @@
/**
* 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
*/
import type {ColorValue} from './StyleSheet';
import {DynamicColorIOSPrivate} from './PlatformColorValueTypes.ios';
export type DynamicColorIOSTuple = {
light: ColorValue,
dark: ColorValue,
highContrastLight?: ColorValue,
highContrastDark?: ColorValue,
};
export const DynamicColorIOS = (tuple: DynamicColorIOSTuple): ColorValue => {
return DynamicColorIOSPrivate({
light: tuple.light,
dark: tuple.dark,
highContrastLight: tuple.highContrastLight,
highContrastDark: tuple.highContrastDark,
});
};

View File

@@ -0,0 +1,28 @@
/**
* 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
*/
import type {ColorValue} from './StyleSheet';
export type DynamicColorIOSTuple = {
light: ColorValue,
dark: ColorValue,
highContrastLight?: ColorValue,
highContrastDark?: ColorValue,
};
/**
* Specify color to display depending on the current system appearance settings
*
* @param tuple Colors you want to use for "light mode" and "dark mode"
* @platform ios
*/
export const DynamicColorIOS = (tuple: DynamicColorIOSTuple): ColorValue => {
throw new Error('DynamicColorIOS is not available on this platform.');
};

View File

@@ -0,0 +1,17 @@
/**
* 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
* @format
*/
'use strict';
export type PointProp = $ReadOnly<{
x: number,
y: number,
...
}>;

27
node_modules/react-native/Libraries/StyleSheet/Rect.js generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/**
* 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
* @format
*/
export type Rect = $ReadOnly<{
bottom?: ?number,
left?: ?number,
right?: ?number,
top?: ?number,
}>;
export type Insets = Rect;
export type RectOrSize = Rect | number;
export function createSquare(size: number): Rect {
return {bottom: size, left: size, right: size, top: size};
}
export function normalizeRect(rectOrSize: ?RectOrSize): ?Rect {
return typeof rectOrSize === 'number' ? createSquare(rectOrSize) : rectOrSize;
}

View File

@@ -0,0 +1,135 @@
/**
* 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.
*
* @format
*/
import {ImageStyle, TextStyle, ViewStyle} from './StyleSheetTypes';
export interface StyleSheetProperties {
hairlineWidth: number;
flatten<T extends string>(style: T): T;
}
type Falsy = undefined | null | false | '';
interface RecursiveArray<T>
extends Array<T | ReadonlyArray<T> | RecursiveArray<T>> {}
export type StyleProp<T> = T | RecursiveArray<T | Falsy> | Falsy;
type OpaqueColorValue = symbol & {__TYPE__: 'Color'};
export type ColorValue = string | OpaqueColorValue;
export namespace StyleSheet {
type NamedStyles<T> = {[P in keyof T]: ViewStyle | TextStyle | ImageStyle};
/**
* An identity function for creating style sheets.
*/
export function create<T extends NamedStyles<T> | NamedStyles<any>>(
// The extra & NamedStyles<any> here helps Typescript catch typos: e.g.,
// the following code would not error with `styles: T | NamedStyles<T>`,
// but would error with `styles: T & NamedStyles<any>`
//
// ```ts
// StyleSheet.create({
// someComponent: { marginLeft: 1, magrinRight: 1 },
// });
// ```
styles: T & NamedStyles<any>,
): T;
/**
* Flattens an array of style objects, into one aggregated style object.
*
* Example:
* ```
* const styles = StyleSheet.create({
* listItem: {
* flex: 1,
* fontSize: 16,
* color: 'white'
* },
* selectedListItem: {
* color: 'green'
* }
* });
*
* StyleSheet.flatten([styles.listItem, styles.selectedListItem])
* // returns { flex: 1, fontSize: 16, color: 'green' }
* ```
*/
export function flatten<T>(
style?: StyleProp<T>,
): T extends (infer U)[] ? U : T;
/**
* Combines two styles such that style2 will override any styles in style1.
* If either style is falsy, the other one is returned without allocating
* an array, saving allocations and maintaining reference equality for
* PureComponent checks.
*/
export function compose<
T extends ViewStyle | TextStyle | ImageStyle,
U extends T,
V extends T,
>(
style1: StyleProp<U> | Array<StyleProp<U>>,
style2: StyleProp<V> | Array<StyleProp<V>>,
): StyleProp<T>;
/**
* WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
* not be reliably announced. The whole thing might be deleted, who knows? Use
* at your own risk.
*
* Sets a function to use to pre-process a style property value. This is used
* internally to process color and transform values. You should not use this
* unless you really know what you are doing and have exhausted other options.
*/
export function setStyleAttributePreprocessor(
property: string,
process: (nextProp: any) => any,
): void;
/**
* This is defined as the width of a thin line on the platform. It can be
* used as the thickness of a border or division between two elements.
* Example:
* ```
* {
* borderBottomColor: '#bbb',
* borderBottomWidth: StyleSheet.hairlineWidth
* }
* ```
*
* This constant will always be a round number of pixels (so a line defined
* by it look crisp) and will try to match the standard width of a thin line
* on the underlying platform. However, you should not rely on it being a
* constant size, because on different platforms and screen densities its
* value may be calculated differently.
*/
export const hairlineWidth: number;
interface AbsoluteFillStyle {
position: 'absolute';
left: 0;
right: 0;
top: 0;
bottom: 0;
}
/**
* A very common pattern is to create overlays with position absolute and zero positioning,
* so `absoluteFill` can be used for convenience and to reduce duplication of these repeated
* styles.
*/
export const absoluteFill: AbsoluteFillStyle;
/**
* @deprecated Use `StyleSheet.absoluteFill`.
*/
export const absoluteFillObject: AbsoluteFillStyle;
}

View File

@@ -0,0 +1,189 @@
/**
* 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
* @format
*/
'use strict';
import typeof * as StyleSheetExports from './StyleSheetExports';
import type {
____ColorValue_Internal,
____DangerouslyImpreciseStyle_Internal,
____DangerouslyImpreciseStyleProp_Internal,
____FontVariant_Internal,
____ImageStyle_Internal,
____ImageStyleProp_Internal,
____TextStyle_Internal,
____TextStyleProp_Internal,
____TransformStyle_Internal,
____ViewStyle_Internal,
____ViewStyleProp_Internal,
NativeColorValue,
} from './StyleSheetTypes';
const StyleSheet: StyleSheetExports = (
require('./StyleSheetExports') as $FlowFixMe
).default;
export type {
BoxShadowValue,
NativeColorValue,
FilterFunction,
} from './StyleSheetTypes';
/**
* This type should be used as the type for anything that is a color. It is
* most useful when using DynamicColorIOS which can be a string or a dynamic
* color object.
*
* type props = {backgroundColor: ColorValue};
*/
export type ColorValue = ____ColorValue_Internal;
/**
* Expose the opaque type for NativeColorValue.
* @deprecated Use NativeColorValue instead.
*/
export type OpaqueColorValue = NativeColorValue;
/**
* This type is an object of the properties related to transforms.
*/
export type TransformsStyle = ____TransformStyle_Internal;
/**
* This type holds possible values for the `fontVariant` style property.
*/
export type FontVariant = ____FontVariant_Internal;
/**
* This type should be used as the type for a prop that is passed through
* to a <View>'s `style` prop. This ensures call sites of the component
* can't pass styles that View doesn't support such as `fontSize`.`
*
* type Props = {style: ViewStyleProp}
* const MyComponent = (props: Props) => <View style={props.style} />
*/
export type ViewStyleProp = ____ViewStyleProp_Internal;
/**
* This type should be used as the type for a prop that is passed through
* to a <Text>'s `style` prop. This ensures call sites of the component
* can't pass styles that Text doesn't support such as `resizeMode`.`
*
* type Props = {style: TextStyleProp}
* const MyComponent = (props: Props) => <Text style={props.style} />
*/
export type TextStyleProp = ____TextStyleProp_Internal;
/**
* This type should be used as the type for a prop that is passed through
* to an <Image>'s `style` prop. This ensures call sites of the component
* can't pass styles that Image doesn't support such as `fontSize`.`
*
* type Props = {style: ImageStyleProp}
* const MyComponent = (props: Props) => <Image style={props.style} />
*/
export type ImageStyleProp = ____ImageStyleProp_Internal;
/**
* WARNING: You probably shouldn't be using this type. This type
* is similar to the ones above except it allows styles that are accepted
* by all of View, Text, or Image. It is therefore very unsafe to pass this
* through to an underlying component. Using this is almost always a mistake
* and using one of the other more restrictive types is likely the right choice.
*/
export type DangerouslyImpreciseStyleProp =
____DangerouslyImpreciseStyleProp_Internal;
/**
* Utility type for getting the values for specific style keys.
*
* The following is bad because position is more restrictive than 'string':
* ```
* type Props = {position: string};
* ```
*
* You should use the following instead:
*
* ```
* type Props = {position: TypeForStyleKey<'position'>};
* ```
*
* This will correctly give you the type 'absolute' | 'relative'
*/
export type TypeForStyleKey<
+key: $Keys<____DangerouslyImpreciseStyle_Internal>,
> = ____DangerouslyImpreciseStyle_Internal[key];
/**
* This type is an object of the different possible style
* properties that can be specified for View.
*
* Note that this isn't a safe way to type a style prop for a component as
* results from StyleSheet.create return an internal identifier, not
* an object of styles.
*
* If you want to type the style prop of a function,
* consider using ViewStyleProp.
*
* A reasonable usage of this type is for helper functions that return an
* object of styles to pass to a View that can't be precomputed with
* StyleSheet.create.
*/
export type ViewStyle = ____ViewStyle_Internal;
/**
* This type is an object of the different possible style
* properties that can be specified for Text.
*
* Note that this isn't a safe way to type a style prop for a component as
* results from StyleSheet.create return an internal identifier, not
* an object of styles.
*
* If you want to type the style prop of a function,
* consider using TextStyleProp.
*
* A reasonable usage of this type is for helper functions that return an
* object of styles to pass to a Text that can't be precomputed with
* StyleSheet.create.
*/
export type TextStyle = ____TextStyle_Internal;
/**
* This type is an object of the different possible style
* properties that can be specified for Image.
*
* Note that this isn't a safe way to type a style prop for a component as
* results from StyleSheet.create return an internal identifier, not
* an object of styles.
*
* If you want to type the style prop of a function,
* consider using ImageStyleProp.
*
* A reasonable usage of this type is for helper functions that return an
* object of styles to pass to an Image that can't be precomputed with
* StyleSheet.create.
*/
export type ImageStyle = ____ImageStyle_Internal;
/**
* WARNING: You probably shouldn't be using this type. This type is an object
* with all possible style keys and their values. Note that this isn't
* a safe way to type a style prop for a component as results from
* StyleSheet.create return an internal identifier, not an object of styles.
*
* If you want to type the style prop of a function, consider using
* ViewStyleProp, TextStyleProp, or ImageStyleProp.
*
* This should only be used by very core utilities that operate on an object
* containing any possible style value.
*/
export type DangerouslyImpreciseStyle = ____DangerouslyImpreciseStyle_Internal;
export default StyleSheet;

View File

@@ -0,0 +1,188 @@
/**
* 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
* @format
*/
export * as default from './StyleSheetExports';
import type {
____ColorValue_Internal,
____DangerouslyImpreciseStyle_Internal,
____DangerouslyImpreciseStyleProp_Internal,
____FontVariant_Internal,
____ImageStyle_Internal,
____ImageStyleProp_Internal,
____TextStyle_Internal,
____TextStyleProp_Internal,
____TransformStyle_Internal,
____ViewStyle_Internal,
____ViewStyleProp_Internal,
NativeColorValue,
} from './StyleSheetTypes';
export type {
BoxShadowValue,
FilterFunction,
NativeColorValue,
StyleProp,
} from './StyleSheetTypes';
export type StyleSheetProperties = {
hairlineWidth: number,
flatten<T: string>(style: T): T,
};
/**
* This type should be used as the type for anything that is a color. It is
* most useful when using DynamicColorIOS which can be a string or a dynamic
* color object.
*
* type props = {backgroundColor: ColorValue};
*/
export type ColorValue = ____ColorValue_Internal;
/**
* Expose the opaque type for NativeColorValue.
* @deprecated Use NativeColorValue instead.
*/
export type OpaqueColorValue = NativeColorValue;
/**
* This type is an object of the properties related to transforms.
*/
export type TransformsStyle = ____TransformStyle_Internal;
/**
* This type holds possible values for the `fontVariant` style property.
*/
export type FontVariant = ____FontVariant_Internal;
/**
* This type should be used as the type for a prop that is passed through
* to a <View>'s `style` prop. This ensures call sites of the component
* can't pass styles that View doesn't support such as `fontSize`.`
*
* type Props = {style: ViewStyleProp}
* const MyComponent = (props: Props) => <View style={props.style} />
*/
export type ViewStyleProp = ____ViewStyleProp_Internal;
/**
* This type should be used as the type for a prop that is passed through
* to a <Text>'s `style` prop. This ensures call sites of the component
* can't pass styles that Text doesn't support such as `resizeMode`.`
*
* type Props = {style: TextStyleProp}
* const MyComponent = (props: Props) => <Text style={props.style} />
*/
export type TextStyleProp = ____TextStyleProp_Internal;
/**
* This type should be used as the type for a prop that is passed through
* to an <Image>'s `style` prop. This ensures call sites of the component
* can't pass styles that Image doesn't support such as `fontSize`.`
*
* type Props = {style: ImageStyleProp}
* const MyComponent = (props: Props) => <Image style={props.style} />
*/
export type ImageStyleProp = ____ImageStyleProp_Internal;
/**
* WARNING: You probably shouldn't be using this type. This type
* is similar to the ones above except it allows styles that are accepted
* by all of View, Text, or Image. It is therefore very unsafe to pass this
* through to an underlying component. Using this is almost always a mistake
* and using one of the other more restrictive types is likely the right choice.
*/
export type DangerouslyImpreciseStyleProp =
____DangerouslyImpreciseStyleProp_Internal;
/**
* Utility type for getting the values for specific style keys.
*
* The following is bad because position is more restrictive than 'string':
* ```
* type Props = {position: string};
* ```
*
* You should use the following instead:
*
* ```
* type Props = {position: TypeForStyleKey<'position'>};
* ```
*
* This will correctly give you the type 'absolute' | 'relative'
*/
export type TypeForStyleKey<
+key: $Keys<____DangerouslyImpreciseStyle_Internal>,
> = ____DangerouslyImpreciseStyle_Internal[key];
/**
* This type is an object of the different possible style
* properties that can be specified for View.
*
* Note that this isn't a safe way to type a style prop for a component as
* results from StyleSheet.create return an internal identifier, not
* an object of styles.
*
* If you want to type the style prop of a function,
* consider using ViewStyleProp.
*
* A reasonable usage of this type is for helper functions that return an
* object of styles to pass to a View that can't be precomputed with
* StyleSheet.create.
*/
export type ViewStyle = ____ViewStyle_Internal;
/**
* This type is an object of the different possible style
* properties that can be specified for Text.
*
* Note that this isn't a safe way to type a style prop for a component as
* results from StyleSheet.create return an internal identifier, not
* an object of styles.
*
* If you want to type the style prop of a function,
* consider using TextStyleProp.
*
* A reasonable usage of this type is for helper functions that return an
* object of styles to pass to a Text that can't be precomputed with
* StyleSheet.create.
*/
export type TextStyle = ____TextStyle_Internal;
/**
* This type is an object of the different possible style
* properties that can be specified for Image.
*
* Note that this isn't a safe way to type a style prop for a component as
* results from StyleSheet.create return an internal identifier, not
* an object of styles.
*
* If you want to type the style prop of a function,
* consider using ImageStyleProp.
*
* A reasonable usage of this type is for helper functions that return an
* object of styles to pass to an Image that can't be precomputed with
* StyleSheet.create.
*/
export type ImageStyle = ____ImageStyle_Internal;
/**
* WARNING: You probably shouldn't be using this type. This type is an object
* with all possible style keys and their values. Note that this isn't
* a safe way to type a style prop for a component as results from
* StyleSheet.create return an internal identifier, not an object of styles.
*
* If you want to type the style prop of a function, consider using
* ViewStyleProp, TextStyleProp, or ImageStyleProp.
*
* This should only be used by very core utilities that operate on an object
* containing any possible style value.
*/
export type DangerouslyImpreciseStyle = ____DangerouslyImpreciseStyle_Internal;

View File

@@ -0,0 +1,204 @@
/**
* 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
* @format
*/
'use strict';
import type {____Styles_Internal} from './StyleSheetTypes';
import composeStyles from '../../src/private/styles/composeStyles';
import flatten from './flattenStyle';
const ReactNativeStyleAttributes =
require('../Components/View/ReactNativeStyleAttributes').default;
const PixelRatio = require('../Utilities/PixelRatio').default;
let hairlineWidth: number = PixelRatio.roundToNearestPixel(0.4);
if (hairlineWidth === 0) {
hairlineWidth = 1 / PixelRatio.get();
}
const absoluteFill = {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
};
if (__DEV__) {
Object.freeze(absoluteFill);
}
/**
* A StyleSheet is an abstraction similar to CSS StyleSheets
*
* Create a new StyleSheet:
*
* ```
* const styles = StyleSheet.create({
* container: {
* borderRadius: 4,
* borderWidth: 0.5,
* borderColor: '#d6d7da',
* },
* title: {
* fontSize: 19,
* fontWeight: 'bold',
* },
* activeTitle: {
* color: 'red',
* },
* });
* ```
*
* Use a StyleSheet:
*
* ```
* <View style={styles.container}>
* <Text style={[styles.title, this.props.isActive && styles.activeTitle]} />
* </View>
* ```
*
* Code quality:
*
* - By moving styles away from the render function, you're making the code
* easier to understand.
* - Naming the styles is a good way to add meaning to the low level components
* in the render function, and encourage reuse.
* - In most IDEs, using `StyleSheet.create()` will offer static type checking
* and suggestions to help you write valid styles.
*
*/
export default {
/**
* This is defined as the width of a thin line on the platform. It can be
* used as the thickness of a border or division between two elements.
* Example:
* ```
* {
* borderBottomColor: '#bbb',
* borderBottomWidth: StyleSheet.hairlineWidth
* }
* ```
*
* This constant will always be a round number of pixels (so a line defined
* by it look crisp) and will try to match the standard width of a thin line
* on the underlying platform. However, you should not rely on it being a
* constant size, because on different platforms and screen densities its
* value may be calculated differently.
*
* A line with hairline width may not be visible if your simulator is downscaled.
*/
hairlineWidth,
/**
* A very common pattern is to create overlays with position absolute and zero positioning,
* so `absoluteFill` can be used for convenience and to reduce duplication of these repeated
* styles.
*/
absoluteFill,
/**
* Sometimes you may want `absoluteFill` but with a couple tweaks - `absoluteFillObject` can be
* used to create a customized entry in a `StyleSheet`, e.g.:
*
* const styles = StyleSheet.create({
* wrapper: {
* ...StyleSheet.absoluteFillObject,
* top: 10,
* backgroundColor: 'transparent',
* },
* });
*/
absoluteFillObject: absoluteFill,
/**
* Combines two styles such that `style2` will override any styles in `style1`.
* If either style is falsy, the other one is returned without allocating an
* array, saving allocations and maintaining reference equality for
* PureComponent checks.
*/
compose: composeStyles,
/**
* Flattens an array of style objects, into one aggregated style object.
*
* Example:
* ```
* const styles = StyleSheet.create({
* listItem: {
* flex: 1,
* fontSize: 16,
* color: 'white'
* },
* selectedListItem: {
* color: 'green'
* }
* });
*
* StyleSheet.flatten([styles.listItem, styles.selectedListItem])
* // returns { flex: 1, fontSize: 16, color: 'green' }
* ```
*/
flatten,
/**
* WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
* not be reliably announced. The whole thing might be deleted, who knows? Use
* at your own risk.
*
* Sets a function to use to pre-process a style property value. This is used
* internally to process color and transform values. You should not use this
* unless you really know what you are doing and have exhausted other options.
*/
setStyleAttributePreprocessor(
property: string,
process: (nextProp: mixed) => mixed,
) {
let value;
if (ReactNativeStyleAttributes[property] === true) {
value = {process};
} else if (typeof ReactNativeStyleAttributes[property] === 'object') {
value = {...ReactNativeStyleAttributes[property], process};
} else {
console.error(`${property} is not a valid style attribute`);
return;
}
if (
__DEV__ &&
typeof value.process === 'function' &&
typeof ReactNativeStyleAttributes[property]?.process === 'function' &&
value.process !== ReactNativeStyleAttributes[property]?.process
) {
console.warn(`Overwriting ${property} style attribute preprocessor`);
}
ReactNativeStyleAttributes[property] = value;
},
/**
* An identity function for creating style sheets.
*/
// $FlowFixMe[unsupported-variance-annotation]
create<+S: ____Styles_Internal>(obj: S): $ReadOnly<S> {
// TODO: This should return S as the return type. But first,
// we need to codemod all the callsites that are typing this
// return value as a number (even though it was opaque).
if (__DEV__) {
for (const key in obj) {
if (obj[key]) {
Object.freeze(obj[key]);
}
}
}
return obj;
},
};

View File

@@ -0,0 +1,105 @@
/**
* 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
* @format
*/
import type {____Styles_Internal} from './StyleSheetTypes';
import composeStyles from '../../src/private/styles/composeStyles';
import flattenStyle from './flattenStyle';
type AbsoluteFillStyle = $ReadOnly<{
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
}>;
/**
* This is defined as the width of a thin line on the platform. It can be
* used as the thickness of a border or division between two elements.
* Example:
* ```
* {
* borderBottomColor: '#bbb',
* borderBottomWidth: StyleSheet.hairlineWidth
* }
* ```
*
* This constant will always be a round number of pixels (so a line defined
* by it look crisp) and will try to match the standard width of a thin line
* on the underlying platform. However, you should not rely on it being a
* constant size, because on different platforms and screen densities its
* value may be calculated differently.
*/
declare export const hairlineWidth: number;
/**
* A very common pattern is to create overlays with position absolute and zero positioning,
* so `absoluteFill` can be used for convenience and to reduce duplication of these repeated
* styles.
*/
declare export const absoluteFill: AbsoluteFillStyle;
/**
* @deprecated Use `StyleSheet.absoluteFill`.
*/
declare export const absoluteFillObject: AbsoluteFillStyle;
/**
* Combines two styles such that style2 will override any styles in style1.
* If either style is falsy, the other one is returned without allocating
* an array, saving allocations and maintaining reference equality for
* PureComponent checks.
*/
declare export const compose: typeof composeStyles;
/**
* Flattens an array of style objects, into one aggregated style object.
*
* Example:
* ```
* const styles = StyleSheet.create({
* listItem: {
* flex: 1,
* fontSize: 16,
* color: 'white'
* },
* selectedListItem: {
* color: 'green'
* }
* });
*
* StyleSheet.flatten([styles.listItem, styles.selectedListItem])
* // returns { flex: 1, fontSize: 16, color: 'green' }
* ```
*/
declare export const flatten: typeof flattenStyle;
/**
* WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
* not be reliably announced. The whole thing might be deleted, who knows? Use
* at your own risk.
*
* Sets a function to use to pre-process a style property value. This is used
* internally to process color and transform values. You should not use this
* unless you really know what you are doing and have exhausted other options.
*/
declare export const setStyleAttributePreprocessor: (
property: string,
process: (nextProp: any) => any,
) => void;
/**
* An identity function for creating style sheets.
*/
// $FlowFixMe[unsupported-variance-annotation]
declare export const create: <+S: ____Styles_Internal>(
obj: S & ____Styles_Internal,
) => $ReadOnly<S>;

View File

@@ -0,0 +1,573 @@
/**
* 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.
*
* @format
*/
import {Animated} from '../Animated/Animated';
import {ImageResizeMode} from '../Image/ImageResizeMode';
import {ColorValue} from './StyleSheet';
type FlexAlignType =
| 'flex-start'
| 'flex-end'
| 'center'
| 'stretch'
| 'baseline';
export type DimensionValue =
| number
| 'auto'
| `${number}%`
| Animated.AnimatedNode
| null;
type AnimatableNumericValue = number | Animated.AnimatedNode;
type AnimatableStringValue = string | Animated.AnimatedNode;
export type CursorValue = 'auto' | 'pointer';
/**
* Flex Prop Types
* @see https://reactnative.dev/docs/flexbox
* @see https://reactnative.dev/docs/layout-props
*/
export interface FlexStyle {
alignContent?:
| 'flex-start'
| 'flex-end'
| 'center'
| 'stretch'
| 'space-between'
| 'space-around'
| 'space-evenly'
| undefined;
alignItems?: FlexAlignType | undefined;
alignSelf?: 'auto' | FlexAlignType | undefined;
aspectRatio?: number | string | undefined;
borderBottomWidth?: number | undefined;
borderEndWidth?: number | undefined;
borderLeftWidth?: number | undefined;
borderRightWidth?: number | undefined;
borderStartWidth?: number | undefined;
borderTopWidth?: number | undefined;
borderWidth?: number | undefined;
bottom?: DimensionValue | undefined;
boxSizing?: 'border-box' | 'content-box' | undefined;
display?: 'none' | 'flex' | 'contents' | undefined;
end?: DimensionValue | undefined;
flex?: number | undefined;
flexBasis?: DimensionValue | undefined;
flexDirection?:
| 'row'
| 'column'
| 'row-reverse'
| 'column-reverse'
| undefined;
rowGap?: number | string | undefined;
gap?: number | string | undefined;
columnGap?: number | string | undefined;
flexGrow?: number | undefined;
flexShrink?: number | undefined;
flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse' | undefined;
height?: DimensionValue | undefined;
justifyContent?:
| 'flex-start'
| 'flex-end'
| 'center'
| 'space-between'
| 'space-around'
| 'space-evenly'
| undefined;
left?: DimensionValue | undefined;
margin?: DimensionValue | undefined;
marginBottom?: DimensionValue | undefined;
marginEnd?: DimensionValue | undefined;
marginHorizontal?: DimensionValue | undefined;
marginLeft?: DimensionValue | undefined;
marginRight?: DimensionValue | undefined;
marginStart?: DimensionValue | undefined;
marginTop?: DimensionValue | undefined;
marginVertical?: DimensionValue | undefined;
maxHeight?: DimensionValue | undefined;
maxWidth?: DimensionValue | undefined;
minHeight?: DimensionValue | undefined;
minWidth?: DimensionValue | undefined;
overflow?: 'visible' | 'hidden' | 'scroll' | undefined;
padding?: DimensionValue | undefined;
paddingBottom?: DimensionValue | undefined;
paddingEnd?: DimensionValue | undefined;
paddingHorizontal?: DimensionValue | undefined;
paddingLeft?: DimensionValue | undefined;
paddingRight?: DimensionValue | undefined;
paddingStart?: DimensionValue | undefined;
paddingTop?: DimensionValue | undefined;
paddingVertical?: DimensionValue | undefined;
position?: 'absolute' | 'relative' | 'static' | undefined;
right?: DimensionValue | undefined;
start?: DimensionValue | undefined;
top?: DimensionValue | undefined;
width?: DimensionValue | undefined;
zIndex?: number | undefined;
direction?: 'inherit' | 'ltr' | 'rtl' | undefined;
/**
* Equivalent to `top`, `bottom`, `right` and `left`
*/
inset?: DimensionValue | undefined;
/**
* Equivalent to `top`, `bottom`
*/
insetBlock?: DimensionValue | undefined;
/**
* Equivalent to `bottom`
*/
insetBlockEnd?: DimensionValue | undefined;
/**
* Equivalent to `top`
*/
insetBlockStart?: DimensionValue | undefined;
/**
* Equivalent to `right` and `left`
*/
insetInline?: DimensionValue | undefined;
/**
* Equivalent to `right` or `left`
*/
insetInlineEnd?: DimensionValue | undefined;
/**
* Equivalent to `right` or `left`
*/
insetInlineStart?: DimensionValue | undefined;
/**
* Equivalent to `marginVertical`
*/
marginBlock?: DimensionValue | undefined;
/**
* Equivalent to `marginBottom`
*/
marginBlockEnd?: DimensionValue | undefined;
/**
* Equivalent to `marginTop`
*/
marginBlockStart?: DimensionValue | undefined;
/**
* Equivalent to `marginHorizontal`
*/
marginInline?: DimensionValue | undefined;
/**
* Equivalent to `marginEnd`
*/
marginInlineEnd?: DimensionValue | undefined;
/**
* Equivalent to `marginStart`
*/
marginInlineStart?: DimensionValue | undefined;
/**
* Equivalent to `paddingVertical`
*/
paddingBlock?: DimensionValue | undefined;
/**
* Equivalent to `paddingBottom`
*/
paddingBlockEnd?: DimensionValue | undefined;
/**
* Equivalent to `paddingTop`
*/
paddingBlockStart?: DimensionValue | undefined;
/**
* Equivalent to `paddingHorizontal`
*/
paddingInline?: DimensionValue | undefined;
/**
* Equivalent to `paddingEnd`
*/
paddingInlineEnd?: DimensionValue | undefined;
/**
* Equivalent to `paddingStart`
*/
paddingInlineStart?: DimensionValue | undefined;
}
export interface ShadowStyleIOS {
shadowColor?: ColorValue | undefined;
shadowOffset?: Readonly<{width: number; height: number}> | undefined;
shadowOpacity?: AnimatableNumericValue | undefined;
shadowRadius?: number | undefined;
}
interface PerspectiveTransform {
perspective: AnimatableNumericValue;
}
interface RotateTransform {
rotate: AnimatableStringValue;
}
interface RotateXTransform {
rotateX: AnimatableStringValue;
}
interface RotateYTransform {
rotateY: AnimatableStringValue;
}
interface RotateZTransform {
rotateZ: AnimatableStringValue;
}
interface ScaleTransform {
scale: AnimatableNumericValue;
}
interface ScaleXTransform {
scaleX: AnimatableNumericValue;
}
interface ScaleYTransform {
scaleY: AnimatableNumericValue;
}
interface TranslateXTransform {
translateX: AnimatableNumericValue | `${number}%`;
}
interface TranslateYTransform {
translateY: AnimatableNumericValue | `${number}%`;
}
interface SkewXTransform {
skewX: AnimatableStringValue;
}
interface SkewYTransform {
skewY: AnimatableStringValue;
}
interface MatrixTransform {
matrix: AnimatableNumericValue[];
}
type MaximumOneOf<T, K extends keyof T = keyof T> = K extends keyof T
? {[P in K]: T[K]} & {[P in Exclude<keyof T, K>]?: never}
: never;
export interface TransformsStyle {
transform?:
| Readonly<
MaximumOneOf<
PerspectiveTransform &
RotateTransform &
RotateXTransform &
RotateYTransform &
RotateZTransform &
ScaleTransform &
ScaleXTransform &
ScaleYTransform &
TranslateXTransform &
TranslateYTransform &
SkewXTransform &
SkewYTransform &
MatrixTransform
>[]
>
| string
| undefined;
transformOrigin?: Array<string | number> | string | undefined;
/**
* @deprecated Use matrix in transform prop instead.
*/
transformMatrix?: Array<number> | undefined;
/**
* @deprecated Use rotate in transform prop instead.
*/
rotation?: AnimatableNumericValue | undefined;
/**
* @deprecated Use scaleX in transform prop instead.
*/
scaleX?: AnimatableNumericValue | undefined;
/**
* @deprecated Use scaleY in transform prop instead.
*/
scaleY?: AnimatableNumericValue | undefined;
/**
* @deprecated Use translateX in transform prop instead.
*/
translateX?: AnimatableNumericValue | undefined;
/**
* @deprecated Use translateY in transform prop instead.
*/
translateY?: AnimatableNumericValue | undefined;
}
export type FilterFunction =
| {brightness: number | string}
| {blur: number | string}
| {contrast: number | string}
| {grayscale: number | string}
| {hueRotate: number | string}
| {invert: number | string}
| {opacity: number | string}
| {saturate: number | string}
| {sepia: number | string}
| {dropShadow: DropShadowValue | string};
export type DropShadowValue = {
offsetX: number | string;
offsetY: number | string;
standardDeviation?: number | string | undefined;
color?: ColorValue | number | undefined;
};
export type BoxShadowValue = {
offsetX: number | string;
offsetY: number | string;
color?: string | undefined;
blurRadius?: ColorValue | number | undefined;
spreadDistance?: number | string | undefined;
inset?: boolean | undefined;
};
export type BlendMode =
| 'normal'
| 'multiply'
| 'screen'
| 'overlay'
| 'darken'
| 'lighten'
| 'color-dodge'
| 'color-burn'
| 'hard-light'
| 'soft-light'
| 'difference'
| 'exclusion'
| 'hue'
| 'saturation'
| 'color'
| 'luminosity';
export type GradientValue = {
type: 'linear-gradient';
// Angle or direction enums
direction?: string | undefined;
colorStops: ReadonlyArray<{
color: ColorValue | null;
positions?: ReadonlyArray<string> | undefined;
}>;
};
/**
* @see https://reactnative.dev/docs/view#style
*/
export interface ViewStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
backfaceVisibility?: 'visible' | 'hidden' | undefined;
backgroundColor?: ColorValue | undefined;
borderBlockColor?: ColorValue | undefined;
borderBlockEndColor?: ColorValue | undefined;
borderBlockStartColor?: ColorValue | undefined;
borderBottomColor?: ColorValue | undefined;
borderBottomEndRadius?: AnimatableNumericValue | string | undefined;
borderBottomLeftRadius?: AnimatableNumericValue | string | undefined;
borderBottomRightRadius?: AnimatableNumericValue | string | undefined;
borderBottomStartRadius?: AnimatableNumericValue | string | undefined;
borderColor?: ColorValue | undefined;
/**
* On iOS 13+, it is possible to change the corner curve of borders.
* @platform ios
*/
borderCurve?: 'circular' | 'continuous' | undefined;
borderEndColor?: ColorValue | undefined;
borderEndEndRadius?: AnimatableNumericValue | string | undefined;
borderEndStartRadius?: AnimatableNumericValue | string | undefined;
borderLeftColor?: ColorValue | undefined;
borderRadius?: AnimatableNumericValue | string | undefined;
borderRightColor?: ColorValue | undefined;
borderStartColor?: ColorValue | undefined;
borderStartEndRadius?: AnimatableNumericValue | string | undefined;
borderStartStartRadius?: AnimatableNumericValue | string | undefined;
borderStyle?: 'solid' | 'dotted' | 'dashed' | undefined;
borderTopColor?: ColorValue | undefined;
borderTopEndRadius?: AnimatableNumericValue | string | undefined;
borderTopLeftRadius?: AnimatableNumericValue | string | undefined;
borderTopRightRadius?: AnimatableNumericValue | string | undefined;
borderTopStartRadius?: AnimatableNumericValue | string | undefined;
outlineColor?: ColorValue | undefined;
outlineOffset?: AnimatableNumericValue | undefined;
outlineStyle?: 'solid' | 'dotted' | 'dashed' | undefined;
outlineWidth?: AnimatableNumericValue | undefined;
opacity?: AnimatableNumericValue | undefined;
/**
* Sets the elevation of a view, using Android's underlying
* [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation).
* This adds a drop shadow to the item and affects z-order for overlapping views.
* Only supported on Android 5.0+, has no effect on earlier versions.
*
* @platform android
*/
elevation?: number | undefined;
/**
* Controls whether the View can be the target of touch events.
*/
pointerEvents?: 'box-none' | 'none' | 'box-only' | 'auto' | undefined;
isolation?: 'auto' | 'isolate' | undefined;
cursor?: CursorValue | undefined;
boxShadow?: ReadonlyArray<BoxShadowValue> | string | undefined;
filter?: ReadonlyArray<FilterFunction> | string | undefined;
mixBlendMode?: BlendMode | undefined;
experimental_backgroundImage?:
| ReadonlyArray<GradientValue>
| string
| undefined;
}
export type FontVariant =
| 'small-caps'
| 'oldstyle-nums'
| 'lining-nums'
| 'tabular-nums'
| 'common-ligatures'
| 'no-common-ligatures'
| 'discretionary-ligatures'
| 'no-discretionary-ligatures'
| 'historical-ligatures'
| 'no-historical-ligatures'
| 'contextual'
| 'no-contextual'
| 'proportional-nums'
| 'stylistic-one'
| 'stylistic-two'
| 'stylistic-three'
| 'stylistic-four'
| 'stylistic-five'
| 'stylistic-six'
| 'stylistic-seven'
| 'stylistic-eight'
| 'stylistic-nine'
| 'stylistic-ten'
| 'stylistic-eleven'
| 'stylistic-twelve'
| 'stylistic-thirteen'
| 'stylistic-fourteen'
| 'stylistic-fifteen'
| 'stylistic-sixteen'
| 'stylistic-seventeen'
| 'stylistic-eighteen'
| 'stylistic-nineteen'
| 'stylistic-twenty';
export interface TextStyleIOS extends ViewStyle {
fontVariant?: FontVariant[] | undefined;
textDecorationColor?: ColorValue | undefined;
textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed' | undefined;
writingDirection?: 'auto' | 'ltr' | 'rtl' | undefined;
}
export interface TextStyleAndroid extends ViewStyle {
textAlignVertical?: 'auto' | 'top' | 'bottom' | 'center' | undefined;
verticalAlign?: 'auto' | 'top' | 'bottom' | 'middle' | undefined;
includeFontPadding?: boolean | undefined;
}
// @see https://reactnative.dev/docs/text#style
export interface TextStyle extends TextStyleIOS, TextStyleAndroid, ViewStyle {
color?: ColorValue | undefined;
fontFamily?: string | undefined;
fontSize?: number | undefined;
fontStyle?: 'normal' | 'italic' | undefined;
/**
* Specifies font weight. The values 'normal' and 'bold' are supported
* for most fonts. Not all fonts have a variant for each of the numeric
* values, in that case the closest one is chosen.
*/
fontWeight?:
| 'normal'
| 'bold'
| '100'
| '200'
| '300'
| '400'
| '500'
| '600'
| '700'
| '800'
| '900'
| 100
| 200
| 300
| 400
| 500
| 600
| 700
| 800
| 900
| 'ultralight'
| 'thin'
| 'light'
| 'medium'
| 'regular'
| 'semibold'
| 'condensedBold'
| 'condensed'
| 'heavy'
| 'black'
| undefined;
letterSpacing?: number | undefined;
lineHeight?: number | undefined;
textAlign?: 'auto' | 'left' | 'right' | 'center' | 'justify' | undefined;
textDecorationLine?:
| 'none'
| 'underline'
| 'line-through'
| 'underline line-through'
| undefined;
textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed' | undefined;
textDecorationColor?: ColorValue | undefined;
textShadowColor?: ColorValue | undefined;
textShadowOffset?: {width: number; height: number} | undefined;
textShadowRadius?: number | undefined;
textTransform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase' | undefined;
userSelect?: 'auto' | 'none' | 'text' | 'contain' | 'all' | undefined;
}
/**
* Image style
* @see https://reactnative.dev/docs/image#style
*/
export interface ImageStyle extends FlexStyle, ShadowStyleIOS, TransformsStyle {
resizeMode?: ImageResizeMode | undefined;
backfaceVisibility?: 'visible' | 'hidden' | undefined;
borderBottomLeftRadius?: AnimatableNumericValue | string | undefined;
borderBottomRightRadius?: AnimatableNumericValue | string | undefined;
backgroundColor?: ColorValue | undefined;
borderColor?: ColorValue | undefined;
borderRadius?: AnimatableNumericValue | string | undefined;
borderTopLeftRadius?: AnimatableNumericValue | string | undefined;
borderTopRightRadius?: AnimatableNumericValue | string | undefined;
overflow?: 'visible' | 'hidden' | undefined;
overlayColor?: ColorValue | undefined;
tintColor?: ColorValue | undefined;
opacity?: AnimatableNumericValue | undefined;
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none' | undefined;
cursor?: CursorValue | undefined;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
/**
* 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
*/
'use strict';
import type AnimatedNode from '../Animated/nodes/AnimatedNode';
import type {
____DangerouslyImpreciseAnimatedStyleProp_Internal,
____FlattenStyleProp_Internal,
} from './StyleSheetTypes';
type NonAnimatedNodeObject<TStyleProp> = TStyleProp extends AnimatedNode
? empty
: TStyleProp;
function flattenStyle<
TStyleProp: ____DangerouslyImpreciseAnimatedStyleProp_Internal,
>(
style: ?TStyleProp,
// $FlowFixMe[underconstrained-implicit-instantiation]
): ?NonAnimatedNodeObject<____FlattenStyleProp_Internal<TStyleProp>> {
if (style === null || typeof style !== 'object') {
return undefined;
}
if (!Array.isArray(style)) {
// $FlowFixMe[incompatible-type]
return style;
}
const result: {[string]: $FlowFixMe} = {};
for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
// $FlowFixMe[underconstrained-implicit-instantiation]
const computedStyle = flattenStyle(style[i]);
if (computedStyle) {
// $FlowFixMe[invalid-in-rhs]
for (const key in computedStyle) {
// $FlowFixMe[incompatible-use]
// $FlowFixMe[invalid-computed-prop]
// $FlowFixMe[prop-missing]
result[key] = computedStyle[key];
}
}
}
// $FlowFixMe[incompatible-type]
return result;
}
export default flattenStyle;

View File

@@ -0,0 +1,34 @@
/**
* 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
*/
/* eslint no-bitwise: 0 */
import type {ProcessedColorValue} from './processColor';
import type {ColorValue} from './StyleSheet';
import _normalizeColor from '@react-native/normalize-colors';
function normalizeColor(
color: ?(ColorValue | ProcessedColorValue),
): ?ProcessedColorValue {
if (typeof color === 'object' && color != null) {
const {normalizeColorObject} = require('./PlatformColorValueTypes');
const normalizedColor = normalizeColorObject(color);
if (normalizedColor != null) {
return normalizedColor;
}
}
if (typeof color === 'string' || typeof color === 'number') {
return _normalizeColor(color);
}
}
export default normalizeColor;

View File

@@ -0,0 +1,15 @@
/**
* 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
* @format
*/
export type ____DangerouslyImpreciseStyle_InternalOverrides = $ReadOnly<{}>;
export type ____ImageStyle_InternalOverrides = $ReadOnly<{}>;
export type ____ShadowStyle_InternalOverrides = $ReadOnly<{}>;
export type ____TextStyle_InternalOverrides = $ReadOnly<{}>;
export type ____ViewStyle_InternalOverrides = $ReadOnly<{}>;

View File

@@ -0,0 +1,97 @@
/**
* 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
*/
import type AnimatedNode from '../../Animated/nodes/AnimatedNode';
// Helper types to enforce that a single key is used in a transform object
// after generating a TypeScript definition file from the Flow types.
// $FlowExpectedError[unclear-type]
type KeysOfUnion<T> = T extends any ? $Keys<T> : empty;
// $FlowExpectedError[unclear-type]
type ValueOfUnion<T, K> = T extends any
? K extends $Keys<T>
? T[K]
: empty
: empty;
type MergeUnion<T> = {
[K in KeysOfUnion<T>]?: ValueOfUnion<T, K>,
};
type MaximumOneOf<T: {...}> = $Values<{
[K in keyof T]: $Exact<{
[P in keyof T]?: P extends K ? T[P] : empty,
}>,
}>;
export type ____TransformStyle_Internal = $ReadOnly<{
/**
* `transform` accepts an array of transformation objects. Each object specifies
* the property that will be transformed as the key, and the value to use in the
* transformation. Objects should not be combined. Use a single key/value pair
* per object.
*
* The rotate transformations require a string so that the transform may be
* expressed in degrees (deg) or radians (rad). For example:
*
* `transform([{ rotateX: '45deg' }, { rotateZ: '0.785398rad' }])`
*
* The skew transformations require a string so that the transform may be
* expressed in degrees (deg). For example:
*
* `transform([{ skewX: '45deg' }])`
*/
transform?:
| $ReadOnlyArray<
$ReadOnly<
MaximumOneOf<
MergeUnion<
| {+perspective: number | AnimatedNode}
| {+rotate: string | AnimatedNode}
| {+rotateX: string | AnimatedNode}
| {+rotateY: string | AnimatedNode}
| {+rotateZ: string | AnimatedNode}
| {+scale: number | AnimatedNode}
| {+scaleX: number | AnimatedNode}
| {+scaleY: number | AnimatedNode}
| {+translateX: number | string | AnimatedNode}
| {+translateY: number | string | AnimatedNode}
| {
+translate:
| [
number | string | AnimatedNode,
number | string | AnimatedNode,
]
| AnimatedNode,
}
| {+skewX: string | AnimatedNode}
| {+skewY: string | AnimatedNode}
// TODO: what is the actual type it expects?
| {
+matrix: $ReadOnlyArray<number | AnimatedNode> | AnimatedNode,
},
>,
>,
>,
>
| string,
/**
* `transformOrigin` accepts an array with 3 elements - each element either being
* a number, or a string of a number ending with `%`. The last element cannot be
* a percentage, so must be a number.
*
* E.g. transformOrigin: ['30%', '80%', 15]
*
* Alternatively accepts a string of the CSS syntax. You must use `%` or `px`.
*
* E.g. transformOrigin: '30% 80% 15px'
*/
transformOrigin?:
| [string | number, string | number, string | number]
| string,
}>;

View File

@@ -0,0 +1,65 @@
/**
* 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
*/
'use strict';
const invariant = require('invariant');
function processAspectRatio(aspectRatio?: number | string): ?number {
if (typeof aspectRatio === 'number') {
return aspectRatio;
}
if (typeof aspectRatio !== 'string') {
if (__DEV__) {
invariant(
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
!aspectRatio,
'aspectRatio must either be a number, a ratio string or `auto`. You passed: %s',
aspectRatio,
);
}
return;
}
const matches = aspectRatio.split('/').map(s => s.trim());
if (matches.includes('auto')) {
if (__DEV__) {
invariant(
matches.length,
'aspectRatio does not support `auto <ratio>`. You passed: %s',
aspectRatio,
);
}
return;
}
const hasNonNumericValues = matches.some(n => Number.isNaN(Number(n)));
if (__DEV__) {
invariant(
!hasNonNumericValues && (matches.length === 1 || matches.length === 2),
'aspectRatio must either be a number, a ratio string or `auto`. You passed: %s',
aspectRatio,
);
}
if (hasNonNumericValues) {
return;
}
if (matches.length === 2) {
return Number(matches[0]) / Number(matches[1]);
}
return Number(matches[0]);
}
export default processAspectRatio;

View File

@@ -0,0 +1,819 @@
/**
* 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
*/
'use strict';
import type {ProcessedColorValue} from './processColor';
import type {
BackgroundImageValue,
RadialGradientPosition,
RadialGradientShape,
RadialGradientSize,
} from './StyleSheetTypes';
const processColor = require('./processColor').default;
// Linear Gradient
const LINEAR_GRADIENT_DIRECTION_REGEX =
/^to\s+(?:top|bottom|left|right)(?:\s+(?:top|bottom|left|right))?/i;
const LINEAR_GRADIENT_ANGLE_UNIT_REGEX =
/^([+-]?\d*\.?\d+)(deg|grad|rad|turn)$/i;
const LINEAR_GRADIENT_DEFAULT_DIRECTION: LinearGradientDirection = {
type: 'angle',
value: 180,
};
type LinearGradientDirection =
| {type: 'angle', value: number}
| {type: 'keyword', value: string};
type LinearGradientBackgroundImage = {
type: 'linear-gradient',
direction: LinearGradientDirection,
colorStops: $ReadOnlyArray<{
color: ColorStopColor,
position: ColorStopPosition,
}>,
};
// Radial Gradient
const DEFAULT_RADIAL_SHAPE = 'ellipse';
const DEFAULT_RADIAL_SIZE = 'farthest-corner';
// center
const DEFAULT_RADIAL_POSITION: RadialGradientPosition = {
top: '50%',
left: '50%',
};
type RadialGradientBackgroundImage = {
type: 'radial-gradient',
shape: RadialGradientShape,
size: RadialGradientSize,
position: RadialGradientPosition,
colorStops: $ReadOnlyArray<{
color: ColorStopColor,
position: ColorStopPosition,
}>,
};
// null color indicate that the transition hint syntax is used. e.g. red, 20%, blue
type ColorStopColor = ProcessedColorValue | null;
// percentage or pixel value
type ColorStopPosition = number | string | null;
type ParsedBackgroundImageValue =
| LinearGradientBackgroundImage
| RadialGradientBackgroundImage;
export default function processBackgroundImage(
backgroundImage: ?($ReadOnlyArray<BackgroundImageValue> | string),
): $ReadOnlyArray<ParsedBackgroundImageValue> {
let result: $ReadOnlyArray<ParsedBackgroundImageValue> = [];
if (backgroundImage == null) {
return result;
}
if (typeof backgroundImage === 'string') {
result = parseBackgroundImageCSSString(backgroundImage.replace(/\n/g, ' '));
} else if (Array.isArray(backgroundImage)) {
for (const bgImage of backgroundImage) {
const processedColorStops = processColorStops(bgImage);
if (processedColorStops == null) {
// If a color stop is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
if (bgImage.type === 'linear-gradient') {
let direction: LinearGradientDirection =
LINEAR_GRADIENT_DEFAULT_DIRECTION;
const bgDirection =
bgImage.direction != null ? bgImage.direction.toLowerCase() : null;
if (bgDirection != null) {
if (LINEAR_GRADIENT_ANGLE_UNIT_REGEX.test(bgDirection)) {
const parsedAngle = getAngleInDegrees(bgDirection);
if (parsedAngle != null) {
direction = {
type: 'angle',
value: parsedAngle,
};
} else {
// If an angle is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
} else if (LINEAR_GRADIENT_DIRECTION_REGEX.test(bgDirection)) {
const parsedDirection = getDirectionForKeyword(bgDirection);
if (parsedDirection != null) {
direction = parsedDirection;
} else {
// If a direction is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
} else {
// If a direction is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
}
result = result.concat({
type: 'linear-gradient',
direction,
colorStops: processedColorStops,
});
} else if (bgImage.type === 'radial-gradient') {
let shape: RadialGradientShape = DEFAULT_RADIAL_SHAPE;
let size: RadialGradientSize = DEFAULT_RADIAL_SIZE;
let position: RadialGradientPosition = {...DEFAULT_RADIAL_POSITION};
if (bgImage.shape != null) {
if (bgImage.shape === 'circle' || bgImage.shape === 'ellipse') {
shape = bgImage.shape;
} else {
// If the shape is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
}
if (bgImage.size != null) {
if (
typeof bgImage.size === 'string' &&
(bgImage.size === 'closest-side' ||
bgImage.size === 'closest-corner' ||
bgImage.size === 'farthest-side' ||
bgImage.size === 'farthest-corner')
) {
size = bgImage.size;
} else if (
typeof bgImage.size === 'object' &&
bgImage.size.x != null &&
bgImage.size.y != null
) {
size = {
x: bgImage.size.x,
y: bgImage.size.y,
};
} else {
// If the size is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
}
if (bgImage.position != null) {
position = bgImage.position;
}
result = result.concat({
type: 'radial-gradient',
shape,
size,
position,
colorStops: processedColorStops,
});
}
}
}
return result;
}
function processColorStops(bgImage: BackgroundImageValue): $ReadOnlyArray<{
color: ColorStopColor,
position: ColorStopPosition,
}> | null {
const processedColorStops: Array<{
color: ColorStopColor,
position: ColorStopPosition,
}> = [];
for (let index = 0; index < bgImage.colorStops.length; index++) {
const colorStop = bgImage.colorStops[index];
const positions = colorStop.positions;
// Color transition hint syntax (red, 20%, blue)
if (
colorStop.color == null &&
Array.isArray(positions) &&
positions.length === 1
) {
const position = positions[0];
if (
typeof position === 'number' ||
(typeof position === 'string' && position.endsWith('%'))
) {
processedColorStops.push({
color: null,
position,
});
} else {
// If a position is invalid, return null and do not apply gradient. Same as web.
return null;
}
} else {
const processedColor = processColor(colorStop.color);
if (processedColor == null) {
// If a color is invalid, return null and do not apply gradient. Same as web.
return null;
}
if (positions != null && positions.length > 0) {
for (const position of positions) {
if (
typeof position === 'number' ||
(typeof position === 'string' && position.endsWith('%'))
) {
processedColorStops.push({
color: processedColor,
position,
});
} else {
// If a position is invalid, return null and do not apply gradient. Same as web.
return null;
}
}
} else {
processedColorStops.push({
color: processedColor,
position: null,
});
}
}
}
return processedColorStops;
}
function parseBackgroundImageCSSString(
cssString: string,
): $ReadOnlyArray<ParsedBackgroundImageValue> {
const gradients = [];
const bgImageStrings = splitGradients(cssString);
for (const bgImageString of bgImageStrings) {
const bgImage = bgImageString.toLowerCase();
const gradientRegex = /^(linear|radial)-gradient\(((?:\([^)]*\)|[^()])*)\)/;
const match = gradientRegex.exec(bgImage);
if (match) {
const [, type, gradientContent] = match;
const isRadial = type.toLowerCase() === 'radial';
const gradient = isRadial
? parseRadialGradientCSSString(gradientContent)
: parseLinearGradientCSSString(gradientContent);
if (gradient != null) {
gradients.push(gradient);
}
}
}
return gradients;
}
function parseRadialGradientCSSString(
gradientContent: string,
): RadialGradientBackgroundImage | null {
let shape: RadialGradientShape = DEFAULT_RADIAL_SHAPE;
let size: RadialGradientSize = DEFAULT_RADIAL_SIZE;
let position: RadialGradientPosition = {...DEFAULT_RADIAL_POSITION};
// split the content by commas, but not if inside parentheses (for color values)
const parts = gradientContent.split(/,(?![^(]*\))/);
// first part may contain shape, size, and position
// [ <radial-shape> || <radial-size> ]? [ at <position> ]?
const firstPartStr = parts[0].trim();
const remainingParts = [...parts];
let hasShapeSizeOrPositionString = false;
let hasExplicitSingleSize = false;
let hasExplicitShape = false;
const firstPartTokens = firstPartStr.split(/\s+/);
// firstPartTokens is the shape, size, and position
while (firstPartTokens.length > 0) {
let token = firstPartTokens.shift();
if (token == null) {
continue;
}
let tokenTrimmed = token.toLowerCase().trim();
if (tokenTrimmed === 'circle' || tokenTrimmed === 'ellipse') {
shape = tokenTrimmed === 'circle' ? 'circle' : 'ellipse';
hasShapeSizeOrPositionString = true;
hasExplicitShape = true;
} else if (
tokenTrimmed === 'closest-corner' ||
tokenTrimmed === 'farthest-corner' ||
tokenTrimmed === 'closest-side' ||
tokenTrimmed === 'farthest-side'
) {
size = tokenTrimmed;
hasShapeSizeOrPositionString = true;
} else if (tokenTrimmed.endsWith('px') || tokenTrimmed.endsWith('%')) {
let sizeX = getPositionFromCSSValue(tokenTrimmed);
if (sizeX == null) {
// If a size is invalid, return null and do not apply any gradient. Same as web.
return null;
}
if (typeof sizeX === 'number' && sizeX < 0) {
// If a size is invalid, return null and do not apply any gradient. Same as web.
return null;
}
hasShapeSizeOrPositionString = true;
size = {x: sizeX, y: sizeX};
token = firstPartTokens.shift();
if (token == null) {
hasExplicitSingleSize = true;
continue;
}
tokenTrimmed = token.toLowerCase().trim();
if (tokenTrimmed.endsWith('px') || tokenTrimmed.endsWith('%')) {
const sizeY = getPositionFromCSSValue(tokenTrimmed);
if (sizeY == null) {
// If a size is invalid, return null and do not apply any gradient. Same as web.
return null;
}
if (typeof sizeY === 'number' && sizeY < 0) {
// If a size is invalid, return null and do not apply any gradient. Same as web.
return null;
}
size = {x: sizeX, y: sizeY};
} else {
hasExplicitSingleSize = true;
}
} else if (tokenTrimmed === 'at') {
let top: string | number;
let left: string | number;
let right: string | number;
let bottom: string | number;
hasShapeSizeOrPositionString = true;
if (firstPartTokens.length === 0) {
// If 'at' is not followed by a position, return null and do not apply any gradient. Same as web.
return null;
}
// 1. [ left | center | right | top | bottom | <length-percentage> ]
if (firstPartTokens.length === 1) {
token = firstPartTokens.shift();
if (token == null) {
// If 'at' is not followed by a position, return null and do not apply any gradient. Same as web.
return null;
}
tokenTrimmed = token.toLowerCase().trim();
if (tokenTrimmed === 'left') {
left = '0%';
top = '50%';
} else if (tokenTrimmed === 'center') {
left = '50%';
top = '50%';
} else if (tokenTrimmed === 'right') {
left = '100%';
top = '50%';
} else if (tokenTrimmed === 'top') {
left = '50%';
top = '0%';
} else if (tokenTrimmed === 'bottom') {
left = '50%';
top = '100%';
} else if (tokenTrimmed.endsWith('px') || tokenTrimmed.endsWith('%')) {
const value = getPositionFromCSSValue(tokenTrimmed);
if (value == null) {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
left = value;
top = '50%';
}
}
if (firstPartTokens.length === 2) {
const t1 = firstPartTokens.shift();
const t2 = firstPartTokens.shift();
if (t1 == null || t2 == null) {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
const token1 = t1.toLowerCase().trim();
const token2 = t2.toLowerCase().trim();
// 2. [ left | center | right ] && [ top | center | bottom ]
const horizontalPositions = ['left', 'center', 'right'];
const verticalPositions = ['top', 'center', 'bottom'];
if (
horizontalPositions.includes(token1) &&
verticalPositions.includes(token2)
) {
left =
token1 === 'left' ? '0%' : token1 === 'center' ? '50%' : '100%';
top = token2 === 'top' ? '0%' : token2 === 'center' ? '50%' : '100%';
} else if (
verticalPositions.includes(token1) &&
horizontalPositions.includes(token2)
) {
left =
token2 === 'left' ? '0%' : token2 === 'center' ? '50%' : '100%';
top = token1 === 'top' ? '0%' : token1 === 'center' ? '50%' : '100%';
}
// 3. [ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ]
else {
if (token1 === 'left') {
left = '0%';
} else if (token1 === 'center') {
left = '50%';
} else if (token1 === 'right') {
left = '100%';
} else if (token1.endsWith('px') || token1.endsWith('%')) {
const value = getPositionFromCSSValue(token1);
if (value == null) {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
left = value;
} else {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
if (token2 === 'top') {
top = '0%';
} else if (token2 === 'center') {
top = '50%';
} else if (token2 === 'bottom') {
top = '100%';
} else if (token2.endsWith('px') || token2.endsWith('%')) {
const value = getPositionFromCSSValue(token2);
if (value == null) {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
top = value;
} else {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
}
}
// 4. [ [ left | right ] <length-percentage> ] && [ [ top | bottom ] <length-percentage> ]
if (firstPartTokens.length === 4) {
const t1 = firstPartTokens.shift();
const t2 = firstPartTokens.shift();
const t3 = firstPartTokens.shift();
const t4 = firstPartTokens.shift();
if (t1 == null || t2 == null || t3 == null || t4 == null) {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
const token1 = t1.toLowerCase().trim();
const token2 = t2.toLowerCase().trim();
const token3 = t3.toLowerCase().trim();
const token4 = t4.toLowerCase().trim();
const keyword1 = token1;
const value1 = getPositionFromCSSValue(token2);
const keyword2 = token3;
const value2 = getPositionFromCSSValue(token4);
if (value1 == null || value2 == null) {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
if (keyword1 === 'left') {
left = value1;
} else if (keyword1 === 'right') {
right = value1;
} else if (keyword1 === 'top') {
top = value1;
} else if (keyword1 === 'bottom') {
bottom = value1;
} else {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
if (keyword2 === 'left') {
left = value2;
} else if (keyword2 === 'right') {
right = value2;
} else if (keyword2 === 'top') {
top = value2;
} else if (keyword2 === 'bottom') {
bottom = value2;
} else {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
}
if (top != null && left != null) {
position = {
top,
left,
};
} else if (bottom != null && right != null) {
position = {
bottom,
right,
};
} else if (top != null && right != null) {
position = {
top,
right,
};
} else if (bottom != null && left != null) {
position = {
bottom,
left,
};
} else {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
// 'at' comes at the end of first part of radial gradient syntax;
break;
}
// if there is no shape, size, or position string found in first token, break
// if might be a color stop
if (!hasShapeSizeOrPositionString) {
break;
}
}
if (hasShapeSizeOrPositionString) {
remainingParts.shift();
if (!hasExplicitShape && hasExplicitSingleSize) {
shape = 'circle';
}
if (hasExplicitSingleSize && hasExplicitShape && shape === 'ellipse') {
// If a single size is explicitly set and the shape is an ellipse, return null and do not apply any gradient. Same as web.
return null;
}
}
const colorStops = parseColorStopsCSSString(remainingParts);
if (colorStops == null) {
// If color stops are invalid, return null and do not apply any gradient. Same as web.
return null;
}
return {
type: 'radial-gradient',
shape,
size,
position,
colorStops,
};
}
function parseLinearGradientCSSString(
gradientContent: string,
): LinearGradientBackgroundImage | null {
const parts = gradientContent.split(',');
let direction: LinearGradientDirection = LINEAR_GRADIENT_DEFAULT_DIRECTION;
const trimmedDirection = parts[0].trim().toLowerCase();
if (LINEAR_GRADIENT_ANGLE_UNIT_REGEX.test(trimmedDirection)) {
const parsedAngle = getAngleInDegrees(trimmedDirection);
if (parsedAngle != null) {
direction = {
type: 'angle',
value: parsedAngle,
};
parts.shift();
} else {
// If an angle is invalid, return null and do not apply any gradient. Same as web.
return null;
}
} else if (LINEAR_GRADIENT_DIRECTION_REGEX.test(trimmedDirection)) {
const parsedDirection = getDirectionForKeyword(trimmedDirection);
if (parsedDirection != null) {
direction = parsedDirection;
parts.shift();
} else {
// If a direction is invalid, return null and do not apply any gradient. Same as web.
return null;
}
}
const colorStops = parseColorStopsCSSString(parts);
if (colorStops == null) {
// If a color stop is invalid, return null and do not apply any gradient. Same as web.
return null;
}
return {
type: 'linear-gradient',
direction,
colorStops,
};
}
function parseColorStopsCSSString(parts: Array<string>): Array<{
color: ColorStopColor,
position: ColorStopPosition,
}> | null {
const colorStopsString = parts.join(',');
const colorStops: Array<{
color: ColorStopColor,
position: ColorStopPosition,
}> = [];
// split by comma, but not if it's inside a parentheses. e.g. red, rgba(0, 0, 0, 0.5), green => ["red", "rgba(0, 0, 0, 0.5)", "green"]
const stops = colorStopsString.split(/,(?![^(]*\))/);
let prevStop = null;
for (let i = 0; i < stops.length; i++) {
const stop = stops[i];
const trimmedStop = stop.trim().toLowerCase();
// Match function like pattern or single words
const colorStopParts = trimmedStop.match(/\S+\([^)]*\)|\S+/g);
if (colorStopParts == null) {
// If a color stop is invalid, return null and do not apply any gradient. Same as web.
return null;
}
// Case 1: [color, position, position]
if (colorStopParts.length === 3) {
const color = colorStopParts[0];
const position1 = getPositionFromCSSValue(colorStopParts[1]);
const position2 = getPositionFromCSSValue(colorStopParts[2]);
const processedColor = processColor(color);
if (processedColor == null) {
// If a color is invalid, return null and do not apply any gradient. Same as web.
return null;
}
if (position1 == null || position2 == null) {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
colorStops.push({
color: processedColor,
position: position1,
});
colorStops.push({
color: processedColor,
position: position2,
});
}
// Case 2: [color, position]
else if (colorStopParts.length === 2) {
const color = colorStopParts[0];
const position = getPositionFromCSSValue(colorStopParts[1]);
const processedColor = processColor(color);
if (processedColor == null) {
// If a color is invalid, return null and do not apply any gradient. Same as web.
return null;
}
if (position == null) {
// If a position is invalid, return null and do not apply any gradient. Same as web.
return null;
}
colorStops.push({
color: processedColor,
position,
});
}
// Case 3: [color]
// Case 4: [position] => transition hint syntax
else if (colorStopParts.length === 1) {
const position = getPositionFromCSSValue(colorStopParts[0]);
if (position != null) {
// handle invalid transition hint syntax. transition hint syntax must have color before and after the position. e.g. red, 20%, blue
if (
(prevStop != null &&
prevStop.length === 1 &&
getPositionFromCSSValue(prevStop[0]) != null) ||
i === stops.length - 1 ||
i === 0
) {
// If the last stop is a transition hint syntax, return null and do not apply any gradient. Same as web.
return null;
}
colorStops.push({
color: null,
position,
});
} else {
const processedColor = processColor(colorStopParts[0]);
if (processedColor == null) {
// If a color is invalid, return null and do not apply any gradient. Same as web.
return null;
}
colorStops.push({
color: processedColor,
position: null,
});
}
} else {
// If a color stop is invalid, return null and do not apply any gradient. Same as web.
return null;
}
prevStop = colorStopParts;
}
return colorStops;
}
function getDirectionForKeyword(direction?: string): ?LinearGradientDirection {
if (direction == null) {
return null;
}
// Remove extra whitespace
const normalized = direction.replace(/\s+/g, ' ').toLowerCase();
switch (normalized) {
case 'to top':
return {type: 'angle', value: 0};
case 'to right':
return {type: 'angle', value: 90};
case 'to bottom':
return {type: 'angle', value: 180};
case 'to left':
return {type: 'angle', value: 270};
case 'to top right':
case 'to right top':
return {type: 'keyword', value: 'to top right'};
case 'to bottom right':
case 'to right bottom':
return {type: 'keyword', value: 'to bottom right'};
case 'to top left':
case 'to left top':
return {type: 'keyword', value: 'to top left'};
case 'to bottom left':
case 'to left bottom':
return {type: 'keyword', value: 'to bottom left'};
default:
return null;
}
}
function getAngleInDegrees(angle?: string): ?number {
if (angle == null) {
return null;
}
const match = angle.match(LINEAR_GRADIENT_ANGLE_UNIT_REGEX);
if (!match) {
return null;
}
const [, value, unit] = match;
const numericValue = parseFloat(value);
switch (unit) {
case 'deg':
return numericValue;
case 'grad':
return numericValue * 0.9; // 1 grad = 0.9 degrees
case 'rad':
return (numericValue * 180) / Math.PI;
case 'turn':
return numericValue * 360; // 1 turn = 360 degrees
default:
return null;
}
}
function getPositionFromCSSValue(position: string) {
if (position.endsWith('px')) {
return parseFloat(position);
}
if (position.endsWith('%')) {
return position;
}
}
function splitGradients(input: string) {
const result = [];
let current = '';
let depth = 0;
for (let i = 0; i < input.length; i++) {
const char = input[i];
if (char === '(') {
depth++;
} else if (char === ')') {
depth--;
} else if (char === ',' && depth === 0) {
result.push(current.trim());
current = '';
continue;
}
current += char;
}
if (current.trim() !== '') {
result.push(current.trim());
}
return result;
}

View File

@@ -0,0 +1,284 @@
/**
* 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
*/
'use strict';
import type {BackgroundPositionValue} from './StyleSheetTypes';
export default function processBackgroundPosition(
backgroundPosition: ?($ReadOnlyArray<BackgroundPositionValue> | string),
): $ReadOnlyArray<BackgroundPositionValue> {
let result: $ReadOnlyArray<BackgroundPositionValue> = [];
if (backgroundPosition == null) {
return [];
}
if (typeof backgroundPosition === 'string') {
result = parseBackgroundPositionCSSString(
backgroundPosition.replace(/\n/g, ' '),
);
} else if (Array.isArray(backgroundPosition)) {
result = backgroundPosition;
}
return result;
}
// https://www.w3.org/TR/css-backgrounds-3/#typedef-bg-position
const parseBackgroundPositionCSSString = (
backgroundPosition: string,
): $ReadOnlyArray<BackgroundPositionValue> => {
const result: Array<BackgroundPositionValue> = [];
const positions = backgroundPosition.split(',').map(s => s.trim());
for (const position of positions) {
let top: string | number;
let left: string | number;
let right: string | number;
let bottom: string | number;
const parts = position.split(/\s+/).filter(p => p.length > 0);
// 1. Single value syntax [ left | center | right | top | bottom | <length-percentage> ]
if (parts.length === 1) {
const t1 = parts[0];
if (t1 == null) {
return [];
}
const token1 = t1.toLowerCase().trim();
if (token1 === 'left') {
left = '0%';
top = '50%';
} else if (token1 === 'center') {
left = '50%';
top = '50%';
} else if (token1 === 'right') {
left = '100%';
top = '50%';
} else if (token1 === 'top') {
left = '50%';
top = '0%';
} else if (token1 === 'bottom') {
left = '50%';
top = '100%';
} else if (isValidPosition(token1)) {
const value = getPositionFromCSSValue(token1);
if (value == null) {
return [];
}
left = value;
top = '50%';
}
}
// 2. Two value syntax [ left | center | right | <length-percentage> ] [ top | center | bottom | <length-percentage> ]
if (parts.length === 2) {
const t1 = parts[0];
const t2 = parts[1];
if (t1 == null || t2 == null) {
return [];
}
const token1 = t1.toLowerCase().trim();
if (token1 === 'left') {
left = '0%';
} else if (token1 === 'center') {
left = '50%';
} else if (token1 === 'right') {
left = '100%';
} else if (token1 === 'top') {
top = '0%';
} else if (token1 === 'bottom') {
top = '100%';
} else if (isValidPosition(token1)) {
const value = getPositionFromCSSValue(token1);
if (value == null) {
return [];
}
left = value;
}
const token2 = t2.toLowerCase().trim();
if (token2 === 'top') {
top = '0%';
} else if (token2 === 'center') {
top = '50%';
} else if (token2 === 'bottom') {
top = '100%';
} else if (token2 === 'left') {
left = '0%';
} else if (token2 === 'right') {
left = '100%';
} else if (isValidPosition(token2)) {
const value = getPositionFromCSSValue(token2);
if (value == null) {
return [];
}
top = value;
}
}
// 3. Three value syntax [ center | [ left | right ] <length-percentage>? ] && [ center | [ top | bottom ] <length-percentage>? ]
if (parts.length === 3) {
const t1 = parts[0];
const t2 = parts[1];
const t3 = parts[2];
if (t1 == null || t2 == null || t3 == null) {
return [];
}
const token1 = t1.toLowerCase().trim();
const token2 = t2.toLowerCase().trim();
const token3 = t3.toLowerCase().trim();
// e.g. center top 40%
if (token1 === 'center') {
left = '50%';
const value = getPositionFromCSSValue(token3);
if (value == null) {
return [];
}
if (token2 === 'top') {
top = value;
} else if (token2 === 'bottom') {
bottom = value;
} else {
return [];
}
}
// e.g. left 40% center
else if (token3 === 'center') {
top = '50%';
const value = getPositionFromCSSValue(token2);
if (value == null) {
return [];
}
if (token1 === 'left') {
left = value;
} else if (token1 === 'right') {
right = value;
} else {
return [];
}
}
// e.g. left 40% top, left top 10%
else {
const tokens = [token1, token2, token3];
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (isValidPosition(token)) {
const value = getPositionFromCSSValue(token);
if (value == null) {
return [];
}
const previousToken = tokens[i - 1];
if (previousToken === 'left') {
left = value;
} else if (previousToken === 'right') {
right = value;
} else if (previousToken === 'top') {
top = value;
} else if (previousToken === 'bottom') {
bottom = value;
}
} else {
if (token === 'left') {
left = '0%';
} else if (token === 'right') {
right = '0%';
} else if (token === 'top') {
top = '0%';
} else if (token === 'bottom') {
bottom = '0%';
} else {
return [];
}
}
}
}
}
// 4. Four value syntax [ center | [ left | right ] <length-percentage>? ] && [ center | [ top | bottom ] <length-percentage>? ]
if (parts.length === 4) {
const t1 = parts.shift();
const t2 = parts.shift();
const t3 = parts.shift();
const t4 = parts.shift();
if (t1 == null || t2 == null || t3 == null || t4 == null) {
return [];
}
const token1 = t1.toLowerCase().trim();
const token2 = t2.toLowerCase().trim();
const token3 = t3.toLowerCase().trim();
const token4 = t4.toLowerCase().trim();
const keyword1 = token1;
const value1 = getPositionFromCSSValue(token2);
const keyword2 = token3;
const value2 = getPositionFromCSSValue(token4);
if (value1 == null || value2 == null) {
return [];
}
if (keyword1 === 'left') {
left = value1;
} else if (keyword1 === 'right') {
right = value1;
}
if (keyword2 === 'top') {
top = value2;
} else if (keyword2 === 'bottom') {
bottom = value2;
}
}
if (top != null && left != null) {
result.push({
top,
left,
});
} else if (bottom != null && right != null) {
result.push({
bottom,
right,
});
} else if (top != null && right != null) {
result.push({
top,
right,
});
} else if (bottom != null && left != null) {
result.push({
bottom,
left,
});
} else {
return [];
}
}
return result;
};
function getPositionFromCSSValue(position: string) {
if (position.endsWith('px')) {
return parseFloat(position);
}
if (position.endsWith('%')) {
return position;
}
// CSS length allows 0 as a valid value
if (position === '0') {
return 0;
}
}
function isValidPosition(position: string) {
if (position.endsWith('px') || position.endsWith('%') || position === '0') {
return true;
}
return false;
}

View File

@@ -0,0 +1,105 @@
/**
* 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
*/
'use strict';
import type {
BackgroundRepeatKeyword,
BackgroundRepeatValue,
} from './StyleSheetTypes';
function isBackgroundRepeatKeyword(
value: string,
): value is BackgroundRepeatKeyword {
return (
value === 'repeat' ||
value === 'space' ||
value === 'round' ||
value === 'no-repeat'
);
}
export default function processBackgroundRepeat(
backgroundRepeat: ?($ReadOnlyArray<BackgroundRepeatValue> | string),
): $ReadOnlyArray<BackgroundRepeatValue> {
let result: $ReadOnlyArray<BackgroundRepeatValue> = [];
if (backgroundRepeat == null) {
return [];
}
if (Array.isArray(backgroundRepeat)) {
return backgroundRepeat;
}
if (typeof backgroundRepeat === 'string') {
result = parseBackgroundRepeatCSSString(
backgroundRepeat.replace(/\n/g, ' '),
);
}
return result;
}
// https://www.w3.org/TR/css-backgrounds-3/#typedef-repeat-style
function parseBackgroundRepeatCSSString(
backgroundRepeat: string,
): $ReadOnlyArray<BackgroundRepeatValue> {
const result: Array<BackgroundRepeatValue> = [];
const bgRepeatArray = backgroundRepeat.split(',').map(s => s.trim());
for (const bgRepeat of bgRepeatArray) {
if (bgRepeat.length === 0) {
return [];
}
const parts = bgRepeat.split(/\s+/).filter(p => p.length > 0);
if (parts.length === 1) {
const part1 = parts[0];
if (part1 == null) {
return [];
}
const token1 = part1.toLowerCase();
if (token1 === 'repeat-x') {
result.push({x: 'repeat', y: 'no-repeat'});
} else if (token1 === 'repeat-y') {
result.push({x: 'no-repeat', y: 'repeat'});
} else if (token1 === 'repeat') {
result.push({x: 'repeat', y: 'repeat'});
} else if (token1 === 'space') {
result.push({x: 'space', y: 'space'});
} else if (token1 === 'round') {
result.push({x: 'round', y: 'round'});
} else if (token1 === 'no-repeat') {
result.push({x: 'no-repeat', y: 'no-repeat'});
} else {
return [];
}
} else if (parts.length === 2) {
const part1 = parts[0];
const part2 = parts[1];
if (part1 == null || part2 == null) {
return [];
}
const token1 = part1.toLowerCase();
const token2 = part2.toLowerCase();
if (
isBackgroundRepeatKeyword(token1) &&
isBackgroundRepeatKeyword(token2)
) {
result.push({x: token1, y: token2});
} else {
return [];
}
}
}
return result;
}

View File

@@ -0,0 +1,104 @@
/**
* 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
*/
'use strict';
import type {BackgroundSizeValue} from './StyleSheetTypes';
export default function processBackgroundSize(
backgroundSize: ?($ReadOnlyArray<BackgroundSizeValue> | string),
): $ReadOnlyArray<BackgroundSizeValue> {
let result: $ReadOnlyArray<BackgroundSizeValue> = [];
if (backgroundSize == null) {
// If the size is invalid, return an empty array and do not apply any background size. Same as web.
return [];
}
if (typeof backgroundSize === 'string') {
result = parseBackgroundSizeCSSString(backgroundSize.replace(/\n/g, ' '));
} else if (Array.isArray(backgroundSize)) {
result = backgroundSize;
}
return result;
}
// https://www.w3.org/TR/css-backgrounds-3/#typedef-bg-size
// <bg-size> = [ <length-percentage [0,∞]> | auto ]{1,2} | cover | contain
function parseBackgroundSizeCSSString(
backgroundSize: string,
): $ReadOnlyArray<BackgroundSizeValue> {
const result: Array<BackgroundSizeValue> = [];
const sizes = backgroundSize.split(',').map(s => s.trim());
for (const size of sizes) {
if (size.length === 0) {
return [];
}
const parts = size.split(/\s+/).filter(p => p.length > 0);
if (parts.length === 2) {
const x = getValidLengthPercentageSizeOrNull(parts[0].toLowerCase());
const y = getValidLengthPercentageSizeOrNull(parts[1].toLowerCase());
if (x != null && y != null) {
result.push({
x,
y,
});
} else {
return [];
}
} else if (parts.length === 1) {
const part = parts[0].toLowerCase();
if (part === 'cover' || part === 'contain') {
result.push(part);
} else {
const x = getValidLengthPercentageSizeOrNull(parts[0].toLowerCase());
if (x != null) {
result.push({
x,
y: 'auto',
});
} else {
return [];
}
}
}
}
return result;
}
// [ <length-percentage [0,∞]> | auto ]
function getValidLengthPercentageSizeOrNull(size: ?string) {
if (size == null) {
return null;
}
if (size.endsWith('px')) {
const num = parseFloat(size);
if (!Number.isNaN(num) && num >= 0) {
return num;
}
}
if (size.endsWith('%')) {
if (parseFloat(size) >= 0) {
return size;
}
}
if (size === 'auto') {
return size;
}
return null;
}

View File

@@ -0,0 +1,212 @@
/**
* 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
*/
import type {ProcessedColorValue} from './processColor';
import type {BoxShadowValue} from './StyleSheetTypes';
import processColor from './processColor';
export type ParsedBoxShadow = {
offsetX: number,
offsetY: number,
color?: ProcessedColorValue,
blurRadius?: number,
spreadDistance?: number,
inset?: boolean,
};
export default function processBoxShadow(
rawBoxShadows: ?($ReadOnlyArray<BoxShadowValue> | string),
): Array<ParsedBoxShadow> {
const result: Array<ParsedBoxShadow> = [];
if (rawBoxShadows == null) {
return result;
}
const boxShadowList =
typeof rawBoxShadows === 'string'
? parseBoxShadowString(rawBoxShadows.replace(/\n/g, ' '))
: rawBoxShadows;
for (const rawBoxShadow of boxShadowList) {
const parsedBoxShadow: ParsedBoxShadow = {
offsetX: 0,
offsetY: 0,
};
let value;
for (const arg in rawBoxShadow) {
switch (arg) {
case 'offsetX':
value =
typeof rawBoxShadow.offsetX === 'string'
? parseLength(rawBoxShadow.offsetX)
: rawBoxShadow.offsetX;
if (value == null) {
return [];
}
parsedBoxShadow.offsetX = value;
break;
case 'offsetY':
value =
typeof rawBoxShadow.offsetY === 'string'
? parseLength(rawBoxShadow.offsetY)
: rawBoxShadow.offsetY;
if (value == null) {
return [];
}
parsedBoxShadow.offsetY = value;
break;
case 'spreadDistance':
value =
typeof rawBoxShadow.spreadDistance === 'string'
? parseLength(rawBoxShadow.spreadDistance)
: rawBoxShadow.spreadDistance;
if (value == null) {
return [];
}
parsedBoxShadow.spreadDistance = value;
break;
case 'blurRadius':
value =
typeof rawBoxShadow.blurRadius === 'string'
? parseLength(rawBoxShadow.blurRadius)
: rawBoxShadow.blurRadius;
if (value == null || value < 0) {
return [];
}
parsedBoxShadow.blurRadius = value;
break;
case 'color':
const color = processColor(rawBoxShadow.color);
if (color == null) {
return [];
}
parsedBoxShadow.color = color;
break;
case 'inset':
parsedBoxShadow.inset = rawBoxShadow.inset;
}
}
result.push(parsedBoxShadow);
}
return result;
}
function parseBoxShadowString(rawBoxShadows: string): Array<BoxShadowValue> {
let result: Array<BoxShadowValue> = [];
for (const rawBoxShadow of rawBoxShadows
.split(/,(?![^()]*\))/) // split by comma that is not in parenthesis
.map(bS => bS.trim())
.filter(bS => bS !== '')) {
const boxShadow: BoxShadowValue = {
offsetX: 0,
offsetY: 0,
};
let offsetX: number | string;
let offsetY: number | string;
let keywordDetectedAfterLength = false;
let lengthCount = 0;
// split rawBoxShadow string by all whitespaces that are not in parenthesis
const args = rawBoxShadow.split(/\s+(?![^(]*\))/);
for (const arg of args) {
const processedColor = processColor(arg);
if (processedColor != null) {
if (boxShadow.color != null) {
return [];
}
if (offsetX != null) {
keywordDetectedAfterLength = true;
}
boxShadow.color = arg;
continue;
}
if (arg === 'inset') {
if (boxShadow.inset != null) {
return [];
}
if (offsetX != null) {
keywordDetectedAfterLength = true;
}
boxShadow.inset = true;
continue;
}
switch (lengthCount) {
case 0:
offsetX = arg;
lengthCount++;
break;
case 1:
if (keywordDetectedAfterLength) {
return [];
}
offsetY = arg;
lengthCount++;
break;
case 2:
if (keywordDetectedAfterLength) {
return [];
}
boxShadow.blurRadius = arg;
lengthCount++;
break;
case 3:
if (keywordDetectedAfterLength) {
return [];
}
boxShadow.spreadDistance = arg;
lengthCount++;
break;
default:
return [];
}
}
if (offsetX == null || offsetY == null) {
return [];
}
boxShadow.offsetX = offsetX;
boxShadow.offsetY = offsetY;
result.push(boxShadow);
}
return result;
}
function parseLength(length: string): ?number {
// matches on args with units like "1.5 5% -80deg"
const argsWithUnitsRegex = /([+-]?\d*(\.\d+)?)([\w\W]+)?/g;
const match = argsWithUnitsRegex.exec(length);
if (!match || Number.isNaN(match[1])) {
return null;
}
if (match[3] != null && match[3] !== 'px') {
return null;
}
if (match[3] == null && match[1] !== '0') {
return null;
}
return Number(match[1]);
}

View File

@@ -0,0 +1,16 @@
/**
* 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.
*
* @format
*/
import {ColorValue, OpaqueColorValue} from './StyleSheet';
export type ProcessedColorValue = number | OpaqueColorValue;
export function processColor(
color?: number | ColorValue,
): ProcessedColorValue | null | undefined;

View File

@@ -0,0 +1,59 @@
/**
* 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
*/
'use strict';
import type {ColorValue, NativeColorValue} from './StyleSheet';
const Platform = require('../Utilities/Platform').default;
const normalizeColor = require('./normalizeColor').default;
export type ProcessedColorValue = number | NativeColorValue;
/* eslint no-bitwise: 0 */
function processColor(color?: ?(number | ColorValue)): ?ProcessedColorValue {
if (color === undefined || color === null) {
return color;
}
let normalizedColor = normalizeColor(color);
if (normalizedColor === null || normalizedColor === undefined) {
return undefined;
}
if (typeof normalizedColor === 'object') {
const processColorObject =
require('./PlatformColorValueTypes').processColorObject;
const processedColorObj = processColorObject(normalizedColor);
if (processedColorObj != null) {
return processedColorObj;
}
}
if (typeof normalizedColor !== 'number') {
return null;
}
// Converts 0xrrggbbaa into 0xaarrggbb
normalizedColor = ((normalizedColor << 24) | (normalizedColor >>> 8)) >>> 0;
if (Platform.OS === 'android') {
// Android use 32 bit *signed* integer to represent the color
// We utilize the fact that bitwise operations in JS also operates on
// signed 32 bit integers, so that we can use those to convert from
// *unsigned* to *signed* 32bit int that way.
normalizedColor = normalizedColor | 0x0;
}
return normalizedColor;
}
export default processColor;

View File

@@ -0,0 +1,35 @@
/**
* 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
*/
'use strict';
import type {ColorValue} from './StyleSheet';
import processColor, {type ProcessedColorValue} from './processColor';
const TRANSPARENT = 0; // rgba(0, 0, 0, 0)
function processColorArray(
colors: ?$ReadOnlyArray<ColorValue>,
): ?$ReadOnlyArray<ProcessedColorValue> {
return colors == null ? null : colors.map(processColorElement);
}
function processColorElement(color: ColorValue): ProcessedColorValue {
const value = processColor(color);
// For invalid colors, fallback to transparent.
if (value == null) {
console.error('Invalid value in color array:', color);
return TRANSPARENT;
}
return value;
}
export default processColorArray;

View File

@@ -0,0 +1,324 @@
/**
* 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
* @format strict-local
*/
'use strict';
import type {ColorValue} from './StyleSheet';
import type {DropShadowValue, FilterFunction} from './StyleSheetTypes';
import processColor from './processColor';
type ParsedFilter =
| {brightness: number}
| {blur: number}
| {contrast: number}
| {grayscale: number}
| {hueRotate: number}
| {invert: number}
| {opacity: number}
| {saturate: number}
| {sepia: number}
| {dropShadow: ParsedDropShadow};
type ParsedDropShadow = {
offsetX: number,
offsetY: number,
standardDeviation?: number,
color?: ColorValue,
};
export default function processFilter(
filter: ?($ReadOnlyArray<FilterFunction> | string),
): $ReadOnlyArray<ParsedFilter> {
let result: Array<ParsedFilter> = [];
if (filter == null) {
return result;
}
if (typeof filter === 'string') {
filter = filter.replace(/\n/g, ' ');
// matches on functions with args and nested functions like "drop-shadow(10 10 10 rgba(0, 0, 0, 1))"
const regex = /([\w-]+)\(([^()]*|\([^()]*\)|[^()]*\([^()]*\)[^()]*)\)/g;
let matches;
while ((matches = regex.exec(filter))) {
let filterName = matches[1].toLowerCase();
if (filterName === 'drop-shadow') {
const dropShadow = parseDropShadow(matches[2]);
if (dropShadow != null) {
result.push({dropShadow});
} else {
return [];
}
} else {
const camelizedName =
filterName === 'drop-shadow'
? 'dropShadow'
: filterName === 'hue-rotate'
? 'hueRotate'
: filterName;
const amount = _getFilterAmount(camelizedName, matches[2]);
if (amount != null) {
const filterFunction = {};
// $FlowFixMe[prop-missing] The key will be the correct one but flow can't see that.
filterFunction[camelizedName] = amount;
// $FlowFixMe[incompatible-type] The key will be the correct one but flow can't see that.
result.push(filterFunction);
} else {
// If any primitive is invalid then apply none of the filters. This is how
// web works and makes it clear that something is wrong becuase no
// graphical effects are happening.
return [];
}
}
}
} else if (Array.isArray(filter)) {
for (const filterFunction of filter) {
const [filterName, filterValue] = Object.entries(filterFunction)[0];
if (filterName === 'dropShadow') {
// $FlowFixMe[incompatible-type]
const dropShadow = parseDropShadow(filterValue);
if (dropShadow == null) {
return [];
}
result.push({dropShadow});
} else {
const amount = _getFilterAmount(filterName, filterValue);
if (amount != null) {
const resultObject = {};
// $FlowFixMe[prop-missing]
resultObject[filterName] = amount;
// $FlowFixMe[incompatible-type]
result.push(resultObject);
} else {
// If any primitive is invalid then apply none of the filters. This is how
// web works and makes it clear that something is wrong becuase no
// graphical effects are happening.
return [];
}
}
}
} else {
throw new TypeError(`${typeof filter} filter is not a string or array`);
}
return result;
}
function _getFilterAmount(filterName: string, filterArgs: mixed): ?number {
let filterArgAsNumber: number;
let unit: string;
if (typeof filterArgs === 'string') {
// matches on args with units like "1.5 5% -80deg"
const argsWithUnitsRegex = new RegExp(/([+-]?\d*(\.\d+)?)([a-zA-Z%]+)?/g);
const match = argsWithUnitsRegex.exec(filterArgs);
if (!match || isNaN(Number(match[1]))) {
return undefined;
}
filterArgAsNumber = Number(match[1]);
unit = match[3];
} else if (typeof filterArgs === 'number') {
filterArgAsNumber = filterArgs;
} else {
return undefined;
}
switch (filterName) {
// Hue rotate takes some angle that can have a unit and can be
// negative. Additionally, 0 with no unit is allowed.
case 'hueRotate':
if (filterArgAsNumber === 0) {
return 0;
}
if (unit !== 'deg' && unit !== 'rad') {
return undefined;
}
return unit === 'rad'
? (180 * filterArgAsNumber) / Math.PI
: filterArgAsNumber;
// blur takes any positive CSS length that is not a percent. In RN
// we currently only have DIPs, so we are not parsing units here.
case 'blur':
if ((unit && unit !== 'px') || filterArgAsNumber < 0) {
return undefined;
}
return filterArgAsNumber;
// All other filters except take a non negative number or percentage. There
// are no units associated with this value and percentage numbers map 1-to-1
// to a non-percentage number (e.g. 50% == 0.5).
case 'brightness':
case 'contrast':
case 'grayscale':
case 'invert':
case 'opacity':
case 'saturate':
case 'sepia':
if ((unit && unit !== '%' && unit !== 'px') || filterArgAsNumber < 0) {
return undefined;
}
if (unit === '%') {
filterArgAsNumber /= 100;
}
return filterArgAsNumber;
default:
return undefined;
}
}
function parseDropShadow(
rawDropShadow: string | DropShadowValue,
): ?ParsedDropShadow {
const dropShadow =
typeof rawDropShadow === 'string'
? parseDropShadowString(rawDropShadow)
: rawDropShadow;
const parsedDropShadow: ParsedDropShadow = {
offsetX: 0,
offsetY: 0,
};
let offsetX: number;
let offsetY: number;
for (const arg in dropShadow) {
let value;
switch (arg) {
case 'offsetX':
value =
typeof dropShadow.offsetX === 'string'
? parseLength(dropShadow.offsetX)
: dropShadow.offsetX;
if (value == null) {
return null;
}
offsetX = value;
break;
case 'offsetY':
value =
typeof dropShadow.offsetY === 'string'
? parseLength(dropShadow.offsetY)
: dropShadow.offsetY;
if (value == null) {
return null;
}
offsetY = value;
break;
case 'standardDeviation':
value =
typeof dropShadow.standardDeviation === 'string'
? parseLength(dropShadow.standardDeviation)
: dropShadow.standardDeviation;
if (value == null || value < 0) {
return null;
}
parsedDropShadow.standardDeviation = value;
break;
case 'color':
const color = processColor(dropShadow.color);
if (color == null) {
return null;
}
parsedDropShadow.color = color;
break;
default:
return null;
}
}
if (offsetX == null || offsetY == null) {
return null;
}
parsedDropShadow.offsetX = offsetX;
parsedDropShadow.offsetY = offsetY;
return parsedDropShadow;
}
function parseDropShadowString(rawDropShadow: string): ?DropShadowValue {
const dropShadow: DropShadowValue = {
offsetX: 0,
offsetY: 0,
};
let offsetX: string;
let offsetY: string;
let lengthCount = 0;
let keywordDetectedAfterLength = false;
// split args by all whitespaces that are not in parenthesis
for (const arg of rawDropShadow.split(/\s+(?![^(]*\))/)) {
const processedColor = processColor(arg);
if (processedColor != null) {
if (dropShadow.color != null) {
return null;
}
if (offsetX != null) {
keywordDetectedAfterLength = true;
}
dropShadow.color = arg;
continue;
}
switch (lengthCount) {
case 0:
offsetX = arg;
lengthCount++;
break;
case 1:
if (keywordDetectedAfterLength) {
return null;
}
offsetY = arg;
lengthCount++;
break;
case 2:
if (keywordDetectedAfterLength) {
return null;
}
dropShadow.standardDeviation = arg;
lengthCount++;
break;
default:
return null;
}
}
if (offsetX == null || offsetY == null) {
return null;
}
dropShadow.offsetX = offsetX;
dropShadow.offsetY = offsetY;
return dropShadow;
}
function parseLength(length: string): ?number {
// matches on args with units like "1.5 5% -80deg"
const argsWithUnitsRegex = /([+-]?\d*(\.\d+)?)([\w\W]+)?/g;
const match = argsWithUnitsRegex.exec(length);
if (!match || Number.isNaN(match[1])) {
return null;
}
if (match[3] != null && match[3] !== 'px') {
return null;
}
if (match[3] == null && match[1] !== '0') {
return null;
}
return Number(match[1]);
}

View File

@@ -0,0 +1,30 @@
/**
* 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
*/
'use strict';
import type {____FontVariantArray_Internal} from './StyleSheetTypes';
function processFontVariant(
fontVariant: ____FontVariantArray_Internal | string,
): ?____FontVariantArray_Internal {
if (Array.isArray(fontVariant)) {
return fontVariant;
}
// $FlowFixMe[incompatible-type]
const match: ?____FontVariantArray_Internal = fontVariant
.split(' ')
.filter(Boolean);
return match;
}
export default processFontVariant;

View File

@@ -0,0 +1,269 @@
/**
* 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
* @format
*/
'use strict';
const stringifySafe = require('../Utilities/stringifySafe').default;
const invariant = require('invariant');
/**
* Generate a transform matrix based on the provided transforms, and use that
* within the style object instead.
*
* This allows us to provide an API that is similar to CSS, where transforms may
* be applied in an arbitrary order, and yet have a universal, singular
* interface to native code.
*/
function processTransform(
transform: Array<Object> | string,
): Array<Object> | Array<number> {
if (typeof transform === 'string') {
const regex = new RegExp(/(\w+)\(([^)]+)\)/g);
const transformArray: Array<Object> = [];
let matches;
while ((matches = regex.exec(transform))) {
const {key, value} = _getKeyAndValueFromCSSTransform(
matches[1],
matches[2],
);
if (value !== undefined) {
transformArray.push({[key]: value});
}
}
transform = transformArray;
}
if (__DEV__) {
_validateTransforms(transform);
}
return transform;
}
const _getKeyAndValueFromCSSTransform: (
key: string,
args: string,
) => {key: string, value?: Array<string | number> | number | string} = (
key,
args,
) => {
const argsWithUnitsRegex = new RegExp(/([+-]?\d+(\.\d+)?)([a-zA-Z]+|%)?/g);
switch (key) {
case 'matrix':
return {key, value: args.match(/[+-]?\d+(\.\d+)?/g)?.map(Number)};
case 'translate':
case 'translate3d':
const parsedArgs = [];
let missingUnitOfMeasurement = false;
let matches;
while ((matches = argsWithUnitsRegex.exec(args))) {
const value = Number(matches[1]);
const unitOfMeasurement = matches[3];
if (value !== 0 && !unitOfMeasurement) {
missingUnitOfMeasurement = true;
}
if (unitOfMeasurement === '%') {
parsedArgs.push(`${value}%`);
} else {
parsedArgs.push(value);
}
}
if (__DEV__) {
invariant(
!missingUnitOfMeasurement,
`Transform with key ${key} must have units unless the provided value is 0, found %s`,
`${key}(${args})`,
);
if (key === 'translate') {
invariant(
parsedArgs?.length === 1 || parsedArgs?.length === 2,
'Transform with key translate must be an string with 1 or 2 parameters, found %s: %s',
parsedArgs?.length,
`${key}(${args})`,
);
} else {
invariant(
parsedArgs?.length === 3,
'Transform with key translate3d must be an string with 3 parameters, found %s: %s',
parsedArgs?.length,
`${key}(${args})`,
);
}
}
if (parsedArgs?.length === 1) {
parsedArgs.push(0);
}
return {key: 'translate', value: parsedArgs};
case 'translateX':
case 'translateY':
case 'perspective':
const argMatches = argsWithUnitsRegex.exec(args);
if (!argMatches?.length) {
return {key, value: undefined};
}
const value = Number(argMatches[1]);
const unitOfMeasurement = argMatches[3];
if (__DEV__) {
invariant(
value === 0 || unitOfMeasurement,
`Transform with key ${key} must have units unless the provided value is 0, found %s`,
`${key}(${args})`,
);
}
return {key, value};
default:
return {key, value: !isNaN(args) ? Number(args) : args};
}
};
function _validateTransforms(transform: Array<Object>): void {
transform.forEach(transformation => {
const keys = Object.keys(transformation);
invariant(
keys.length === 1,
'You must specify exactly one property per transform object. Passed properties: %s',
stringifySafe(transformation),
);
const key = keys[0];
const value = transformation[key];
if (key === 'matrix' && transform.length > 1) {
console.error(
'When using a matrix transform, you must specify exactly one transform object. Passed transform: ' +
stringifySafe(transform),
);
}
_validateTransform(key, value, transformation);
});
}
function _validateTransform(
key: string,
value: any | number | string,
transformation: any,
) {
invariant(
!value.getValue,
'You passed an Animated.Value to a normal component. ' +
'You need to wrap that component in an Animated. For example, ' +
'replace <View /> by <Animated.View />.',
);
const multivalueTransforms = ['matrix', 'translate'];
if (multivalueTransforms.indexOf(key) !== -1) {
invariant(
Array.isArray(value),
'Transform with key of %s must have an array as the value: %s',
key,
stringifySafe(transformation),
);
}
switch (key) {
case 'matrix':
invariant(
value.length === 9 || value.length === 16,
'Matrix transform must have a length of 9 (2d) or 16 (3d). ' +
'Provided matrix has a length of %s: %s',
/* $FlowFixMe[prop-missing] (>=0.84.0 site=react_native_fb) This
* comment suppresses an error found when Flow v0.84 was deployed. To
* see the error, delete this comment and run Flow. */
value.length,
stringifySafe(transformation),
);
break;
case 'translate':
invariant(
value.length === 2 || value.length === 3,
'Transform with key translate must be an array of length 2 or 3, found %s: %s',
/* $FlowFixMe[prop-missing] (>=0.84.0 site=react_native_fb) This
* comment suppresses an error found when Flow v0.84 was deployed. To
* see the error, delete this comment and run Flow. */
value.length,
stringifySafe(transformation),
);
break;
case 'rotateX':
case 'rotateY':
case 'rotateZ':
case 'rotate':
case 'skewX':
case 'skewY':
invariant(
typeof value === 'string',
'Transform with key of "%s" must be a string: %s',
key,
stringifySafe(transformation),
);
invariant(
value.indexOf('deg') > -1 || value.indexOf('rad') > -1,
'Rotate transform must be expressed in degrees (deg) or radians ' +
'(rad): %s',
stringifySafe(transformation),
);
break;
case 'perspective':
invariant(
typeof value === 'number',
'Transform with key of "%s" must be a number: %s',
key,
stringifySafe(transformation),
);
invariant(
value !== 0,
'Transform with key of "%s" cannot be zero: %s',
key,
stringifySafe(transformation),
);
break;
case 'translateX':
case 'translateY':
invariant(
typeof value === 'number' ||
(typeof value === 'string' && value.endsWith('%')),
'Transform with key of "%s" must be number or a percentage. Passed value: %s.',
key,
stringifySafe(transformation),
);
break;
case 'scale':
case 'scaleX':
case 'scaleY':
invariant(
typeof value === 'number',
'Transform with key of "%s" must be a number: %s',
key,
stringifySafe(transformation),
);
break;
default:
invariant(
false,
'Invalid transform %s: %s',
key,
stringifySafe(transformation),
);
}
}
export default processTransform;

View File

@@ -0,0 +1,136 @@
/**
* 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
* @format
*/
import invariant from 'invariant';
const INDEX_X = 0;
const INDEX_Y = 1;
const INDEX_Z = 2;
/* eslint-disable no-labels */
export default function processTransformOrigin(
transformOrigin: Array<string | number> | string,
): Array<string | number> {
if (typeof transformOrigin === 'string') {
const transformOriginString = transformOrigin;
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;
outer: while ((matches = regex.exec(transformOriginString))) {
let nextIndex = index + 1;
const value = matches[0];
const valueLower = value.toLowerCase();
switch (valueLower) {
case 'left':
case 'right': {
invariant(
index === INDEX_X,
'Transform-origin %s can only be used for x-position',
value,
);
transformOriginArray[INDEX_X] = valueLower === 'left' ? 0 : '100%';
break;
}
case 'top':
case 'bottom': {
invariant(
index !== INDEX_Z,
'Transform-origin %s can only be used for y-position',
value,
);
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 outer;
}
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:
invariant(
false,
'Could not parse transform-origin: %s',
transformOriginString,
);
}
nextIndex = INDEX_Z;
}
break;
}
case 'center': {
invariant(
index !== INDEX_Z,
'Transform-origin value %s cannot be used for z-position',
value,
);
transformOriginArray[index] = '50%';
break;
}
default: {
if (value.endsWith('%')) {
transformOriginArray[index] = value;
} else {
transformOriginArray[index] = parseFloat(value); // Remove `px`
}
break;
}
}
index = nextIndex;
}
transformOrigin = transformOriginArray;
}
if (__DEV__) {
_validateTransformOrigin(transformOrigin);
}
return transformOrigin;
}
function _validateTransformOrigin(transformOrigin: Array<string | number>) {
invariant(
transformOrigin.length === 3,
'Transform origin must have exactly 3 values.',
);
const [x, y, z] = transformOrigin;
invariant(
typeof x === 'number' || (typeof x === 'string' && x.endsWith('%')),
'Transform origin x-position must be a number. Passed value: %s.',
x,
);
invariant(
typeof y === 'number' || (typeof y === 'string' && y.endsWith('%')),
'Transform origin y-position must be a number. Passed value: %s.',
y,
);
invariant(
typeof z === 'number',
'Transform origin z-position must be a number. Passed value: %s.',
z,
);
}

View File

@@ -0,0 +1,31 @@
/**
* 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
* @format
*/
/* eslint no-bitwise: 0 */
'use strict';
/**
* number should be a color processed by `normalizeColor`
* alpha should be number between 0 and 1
*/
function setNormalizedColorAlpha(input: number, alpha: number): number {
if (alpha < 0) {
alpha = 0;
} else if (alpha > 1) {
alpha = 1;
}
alpha = Math.round(alpha * 255);
// magic bitshift guarantees we return an unsigned int
return ((input & 0xffffff00) | alpha) >>> 0;
}
export default setNormalizedColorAlpha;

View File

@@ -0,0 +1,72 @@
/**
* 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
*/
import type {____ViewStyle_Internal} from './StyleSheetTypes';
export default function splitLayoutProps(props: ?____ViewStyle_Internal): {
outer: ?____ViewStyle_Internal,
inner: ?____ViewStyle_Internal,
} {
let outer: ?____ViewStyle_Internal = null;
let inner: ?____ViewStyle_Internal = null;
if (props != null) {
// $FlowFixMe[incompatible-exact] Will contain a subset of keys from `props`.
outer = {};
// $FlowFixMe[incompatible-exact] Will contain a subset of keys from `props`.
inner = {};
for (const prop of Object.keys(props)) {
switch (prop) {
case 'margin':
case 'marginHorizontal':
case 'marginVertical':
case 'marginBottom':
case 'marginTop':
case 'marginLeft':
case 'marginRight':
case 'flex':
case 'flexGrow':
case 'flexShrink':
case 'flexBasis':
case 'alignSelf':
case 'height':
case 'minHeight':
case 'maxHeight':
case 'width':
case 'minWidth':
case 'maxWidth':
case 'position':
case 'left':
case 'right':
case 'bottom':
case 'top':
case 'transform':
case 'transformOrigin':
case 'rowGap':
case 'columnGap':
case 'gap':
// $FlowFixMe[cannot-write]
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
outer[prop] = props[prop];
break;
default:
// $FlowFixMe[cannot-write]
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
inner[prop] = props[prop];
break;
}
}
}
return {outer, inner};
}