feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 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.
*
* @format
* @flow strict
*/
'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.
*
* @format
* @flow strict-local
*/
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-return]
* 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-cast]
* 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.
*
* @format
* @flow strict-local
*/
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-return] 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-return]
* 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');
// 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,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.
*
* @format
* @flow strict-local
*/
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.
*
* @format
* @flow strict-local
*/
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,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.
*
* @format
* @flow strict-local
*/
import type {ColorValue} from './StyleSheet';
export type DynamicColorIOSTuple = {
light: ColorValue,
dark: ColorValue,
highContrastLight?: ColorValue,
highContrastDark?: ColorValue,
};
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.
*
* @format
* @flow strict
*/
'use strict';
export type PointProp = $ReadOnly<{
x: number,
y: number,
...
}>;

View File

@@ -0,0 +1,26 @@
/**
* 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
* @flow strict
*/
export type Rect = $ReadOnly<{|
bottom?: ?number,
left?: ?number,
right?: ?number,
top?: ?number,
|}>;
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,150 @@
/**
* 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>> {}
/** Keep a brand of 'T' so that calls to `StyleSheet.flatten` can take `RegisteredStyle<T>` and return `T`. */
type RegisteredStyle<T> = number & {__registeredStyleBrand: T};
export type StyleProp<T> =
| T
| RegisteredStyle<T>
| RecursiveArray<T | RegisteredStyle<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;
}
/**
* 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',
* },
* });
*/
export const absoluteFillObject: AbsoluteFillStyle;
/**
* 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: RegisteredStyle<AbsoluteFillStyle>;
}

View File

@@ -0,0 +1,357 @@
/**
* 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 {
____ColorValue_Internal,
____DangerouslyImpreciseStyle_Internal,
____DangerouslyImpreciseStyleProp_Internal,
____ImageStyle_Internal,
____ImageStyleProp_Internal,
____Styles_Internal,
____TextStyle_Internal,
____TextStyleProp_Internal,
____ViewStyle_Internal,
____ViewStyleProp_Internal,
} from './StyleSheetTypes';
const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleAttributes');
const PixelRatio = require('../Utilities/PixelRatio').default;
const flatten = require('./flattenStyle');
export type {NativeColorValue} 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;
/**
* 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>,
> = $ElementType<____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;
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.
*
*/
module.exports = {
/**
* 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: (absoluteFill: any), // TODO: This should be updated after we fix downstream Flow sites.
/**
* 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<T: DangerouslyImpreciseStyleProp>(
style1: ?T,
style2: ?T,
): ?T | $ReadOnlyArray<T> {
if (style1 != null && style2 != null) {
return ([style1, style2]: $ReadOnlyArray<T>);
} else {
return style1 != null ? style1 : style2;
}
},
/**
* 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,410 @@
/**
* 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;
display?: 'none' | 'flex' | undefined;
end?: DimensionValue | undefined;
flex?: number | undefined;
flexBasis?: DimensionValue | undefined;
flexDirection?:
| 'row'
| 'column'
| 'row-reverse'
| 'column-reverse'
| undefined;
rowGap?: number | undefined;
gap?: number | undefined;
columnGap?: number | 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;
/**
* @platform ios
*/
direction?: 'inherit' | 'ltr' | 'rtl' | 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?:
| 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;
}
/**
* @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 | undefined;
borderBottomLeftRadius?: AnimatableNumericValue | undefined;
borderBottomRightRadius?: AnimatableNumericValue | undefined;
borderBottomStartRadius?: AnimatableNumericValue | 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 | undefined;
borderEndStartRadius?: AnimatableNumericValue | undefined;
borderLeftColor?: ColorValue | undefined;
borderRadius?: AnimatableNumericValue | undefined;
borderRightColor?: ColorValue | undefined;
borderStartColor?: ColorValue | undefined;
borderStartEndRadius?: AnimatableNumericValue | undefined;
borderStartStartRadius?: AnimatableNumericValue | undefined;
borderStyle?: 'solid' | 'dotted' | 'dashed' | undefined;
borderTopColor?: ColorValue | undefined;
borderTopEndRadius?: AnimatableNumericValue | undefined;
borderTopLeftRadius?: AnimatableNumericValue | undefined;
borderTopRightRadius?: AnimatableNumericValue | undefined;
borderTopStartRadius?: 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;
cursor?: CursorValue | 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 | undefined;
borderBottomRightRadius?: AnimatableNumericValue | undefined;
backgroundColor?: ColorValue | undefined;
borderColor?: ColorValue | undefined;
borderRadius?: AnimatableNumericValue | undefined;
borderTopLeftRadius?: AnimatableNumericValue | undefined;
borderTopRightRadius?: AnimatableNumericValue | undefined;
overflow?: 'visible' | 'hidden' | undefined;
overlayColor?: ColorValue | undefined;
tintColor?: ColorValue | undefined;
opacity?: AnimatableNumericValue | undefined;
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | undefined;
cursor?: CursorValue | undefined;
}

View File

@@ -0,0 +1,925 @@
/**
* 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 {
____DangerouslyImpreciseStyle_InternalOverrides,
____ImageStyle_InternalOverrides,
____ShadowStyle_InternalOverrides,
____TextStyle_InternalOverrides,
____ViewStyle_InternalOverrides,
} from './private/_StyleSheetTypesOverrides';
import type {____TransformStyle_Internal} from './private/_TransformStyle';
declare export opaque type NativeColorValue;
export type ____ColorValue_Internal = null | string | number | NativeColorValue;
export type ColorArrayValue = null | $ReadOnlyArray<____ColorValue_Internal>;
export type PointValue = {
x: number,
y: number,
};
export type EdgeInsetsValue = {
top: number,
left: number,
right: number,
bottom: number,
};
export type DimensionValue = number | string | 'auto' | AnimatedNode | null;
export type AnimatableNumericValue = number | AnimatedNode;
export type CursorValue = 'auto' | 'pointer';
/**
* React Native's layout system is based on Flexbox and is powered both
* on iOS and Android by an open source project called `Yoga`:
* https://github.com/facebook/yoga
*
* The implementation in Yoga is slightly different from what the
* Flexbox spec defines - for example, we chose more sensible default
* values. Since our layout docs are generated from the comments in this
* file, please keep a brief comment describing each prop type.
*
* These properties are a subset of our styles that are consumed by the layout
* algorithm and affect the positioning and sizing of views.
*/
type ____LayoutStyle_Internal = $ReadOnly<{
/** `display` sets the display type of this component.
*
* It works similarly to `display` in CSS, but only support 'flex' and 'none'.
* 'flex' is the default.
*/
display?: 'none' | 'flex',
/** `width` sets the width of this component.
*
* It works similarly to `width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/width for more details.
*/
width?: DimensionValue,
/** `height` sets the height of this component.
*
* It works similarly to `height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/height for more details.
*/
height?: DimensionValue,
/** `bottom` is the number of logical pixels to offset the bottom edge of
* this component.
*
* It works similarly to `bottom` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/bottom
* for more details of how `bottom` affects layout.
*/
bottom?: DimensionValue,
/**
* When the direction is `ltr`, `end` is equivalent to `right`.
* When the direction is `rtl`, `end` is equivalent to `left`.
*
* This style takes precedence over the `left` and `right` styles.
*/
end?: DimensionValue,
/** `left` is the number of logical pixels to offset the left edge of
* this component.
*
* It works similarly to `left` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/left
* for more details of how `left` affects layout.
*/
left?: DimensionValue,
/** `right` is the number of logical pixels to offset the right edge of
* this component.
*
* It works similarly to `right` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/right
* for more details of how `right` affects layout.
*/
right?: DimensionValue,
/**
* When the direction is `ltr`, `start` is equivalent to `left`.
* When the direction is `rtl`, `start` is equivalent to `right`.
*
* This style takes precedence over the `left`, `right`, and `end` styles.
*/
start?: DimensionValue,
/** `top` is the number of logical pixels to offset the top edge of
* this component.
*
* It works similarly to `top` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/top
* for more details of how `top` affects layout.
*/
top?: DimensionValue,
/** `inset` is a shorthand that corresponds to the top, right, bottom, and/or left properties.
*
* It works similarly to `inset` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset
* for more details of how `inset` affects layout.
*/
inset?: DimensionValue,
/** `insetBlock` is a shorthand that corresponds to the `insetBlockStart` and `insetBlockEnd` properties.
*
* It works similarly to `inset-block` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block
* for more details of how `inset-block` affects layout.
*/
insetBlock?: DimensionValue,
/** `insetBlockEnd` is a logical property that sets the length that an
* element is offset in the block direction from its ending edge.
*
* It works similarly to `inset-block-end` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block-end
* for more details of how `inset-block-end` affects layout.
*/
insetBlockEnd?: DimensionValue,
/** `insetBlockStart` is a logical property that sets the length that an
* element is offset in the block direction from its starting edge.
*
* It works similarly to `inset-block-start` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block-start
* for more details of how `inset-block-start` affects layout.
*/
insetBlockStart?: DimensionValue,
/** `insetInline` is a shorthand that corresponds to the `insetInlineStart` and `insetInlineEnd` properties.
*
* It works similarly to `inset-inline` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline
* for more details of how `inset-inline` affects layout.
*/
insetInline?: DimensionValue,
/** `insetInlineEnd` is a logical property that sets the length that an
* element is offset in the starting inline direction.
*
* It works similarly to `inset-inline-end` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline-end
* for more details of how `inset-inline-end` affects layout.
*/
insetInlineEnd?: DimensionValue,
/** `insetInlineStart` is a logical property that sets the length that an
* element is offset in the starting inline direction.
*
* It works similarly to `inset-inline-start` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline-start
* for more details of how `inset-inline-start` affects layout.
*/
insetInlineStart?: DimensionValue,
/** `minWidth` is the minimum width for this component, in logical pixels.
*
* It works similarly to `min-width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/min-width
* for more details.
*/
minWidth?: DimensionValue,
/** `maxWidth` is the maximum width for this component, in logical pixels.
*
* It works similarly to `max-width` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/max-width
* for more details.
*/
maxWidth?: DimensionValue,
/** `minHeight` is the minimum height for this component, in logical pixels.
*
* It works similarly to `min-height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/min-height
* for more details.
*/
minHeight?: DimensionValue,
/** `maxHeight` is the maximum height for this component, in logical pixels.
*
* It works similarly to `max-height` in CSS, but in React Native you
* must use points or percentages. Ems and other units are not supported.
*
* See https://developer.mozilla.org/en-US/docs/Web/CSS/max-height
* for more details.
*/
maxHeight?: DimensionValue,
/** Setting `margin` has the same effect as setting each of
* `marginTop`, `marginLeft`, `marginBottom`, and `marginRight`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin
* for more details.
*/
margin?: DimensionValue,
/** Setting `marginBlock` has the same effect as setting both
* `marginTop` and `marginBottom`.
*/
marginBlock?: DimensionValue,
/** `marginBlockEnd` works like `margin-block-end`in CSS. Because React
* Native doesn not support `writing-mode` this is always mapped to
* `margin-bottom`. See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-block-end
* for more details.
*/
marginBlockEnd?: DimensionValue,
/** `marginBlockEnd` works like `margin-block-end`in CSS. Because React
* Native doesn not support `writing-mode` this is always mapped to
* `margin-top`. See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-block-end
* for more details.
*/
marginBlockStart?: DimensionValue,
/** `marginBottom` works like `margin-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-block-start
* for more details.
*/
marginBottom?: DimensionValue,
/**
* When direction is `ltr`, `marginEnd` is equivalent to `marginRight`.
* When direction is `rtl`, `marginEnd` is equivalent to `marginLeft`.
*/
marginEnd?: DimensionValue,
/** Setting `marginHorizontal` has the same effect as setting
* both `marginLeft` and `marginRight`.
*/
marginHorizontal?: DimensionValue,
/** Setting `marginInline` has the same effect as setting
* both `marginLeft` and `marginRight`.
*/
marginInline?: DimensionValue,
/**
* When direction is `ltr`, `marginInlineEnd` is equivalent to `marginRight`.
* When direction is `rtl`, `marginInlineEnd` is equivalent to `marginLeft`.
*/
marginInlineEnd?: DimensionValue,
/**
* When direction is `ltr`, `marginInlineStart` is equivalent to `marginLeft`.
* When direction is `rtl`, `marginInlineStart` is equivalent to `marginRight`.
*/
marginInlineStart?: DimensionValue,
/** `marginLeft` works like `margin-left` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left
* for more details.
*/
marginLeft?: DimensionValue,
/** `marginRight` works like `margin-right` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right
* for more details.
*/
marginRight?: DimensionValue,
/**
* When direction is `ltr`, `marginStart` is equivalent to `marginLeft`.
* When direction is `rtl`, `marginStart` is equivalent to `marginRight`.
*/
marginStart?: DimensionValue,
/** `marginTop` works like `margin-top` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top
* for more details.
*/
marginTop?: DimensionValue,
/** Setting `marginVertical` has the same effect as setting both
* `marginTop` and `marginBottom`.
*/
marginVertical?: DimensionValue,
/** Setting `padding` has the same effect as setting each of
* `paddingTop`, `paddingBottom`, `paddingLeft`, and `paddingRight`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding
* for more details.
*/
padding?: DimensionValue,
/** Setting `paddingBlock` is like setting both of
* `paddingTop` and `paddingBottom`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-block
* for more details.
*/
paddingBlock?: DimensionValue,
/** `paddingBlockEnd` works like `padding-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-block-end
* for more details.
*/
paddingBlockEnd?: DimensionValue,
/** `paddingBlockStart` works like `padding-top` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-block-start
* for more details.
*/
paddingBlockStart?: DimensionValue,
/** `paddingBottom` works like `padding-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom
* for more details.
*/
paddingBottom?: DimensionValue,
/**
* When direction is `ltr`, `paddingEnd` is equivalent to `paddingRight`.
* When direction is `rtl`, `paddingEnd` is equivalent to `paddingLeft`.
*/
paddingEnd?: DimensionValue,
/** Setting `paddingHorizontal` is like setting both of
* `paddingLeft` and `paddingRight`.
*/
paddingHorizontal?: DimensionValue,
/** Setting `paddingInline` is like setting both of
* `paddingLeft` and `paddingRight`.
*/
paddingInline?: DimensionValue,
/**
* When direction is `ltr`, `paddingInlineEnd` is equivalent to `paddingRight`.
* When direction is `rtl`, `paddingInlineEnd` is equivalent to `paddingLeft`.
*/
paddingInlineEnd?: DimensionValue,
/**
* When direction is `ltr`, `paddingInlineStart` is equivalent to `paddingLeft`.
* When direction is `rtl`, `paddingInlineStart` is equivalent to `paddingRight`.
*/
paddingInlineStart?: DimensionValue,
/** `paddingLeft` works like `padding-left` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-left
* for more details.
*/
paddingLeft?: DimensionValue,
/** `paddingRight` works like `padding-right` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-right
* for more details.
*/
paddingRight?: DimensionValue,
/**
* When direction is `ltr`, `paddingStart` is equivalent to `paddingLeft`.
* When direction is `rtl`, `paddingStart` is equivalent to `paddingRight`.
*/
paddingStart?: DimensionValue,
/** `paddingTop` works like `padding-top` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top
* for more details.
*/
paddingTop?: DimensionValue,
/** Setting `paddingVertical` is like setting both of
* `paddingTop` and `paddingBottom`.
*/
paddingVertical?: DimensionValue,
/** `borderWidth` works like `border-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-width
* for more details.
*/
borderWidth?: number,
/** `borderBottomWidth` works like `border-bottom-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-bottom-width
* for more details.
*/
borderBottomWidth?: number,
/**
* When direction is `ltr`, `borderEndWidth` is equivalent to `borderRightWidth`.
* When direction is `rtl`, `borderEndWidth` is equivalent to `borderLeftWidth`.
*/
borderEndWidth?: number,
/** `borderLeftWidth` works like `border-left-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-left-width
* for more details.
*/
borderLeftWidth?: number,
/** `borderRightWidth` works like `border-right-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-right-width
* for more details.
*/
borderRightWidth?: number,
/**
* When direction is `ltr`, `borderStartWidth` is equivalent to `borderLeftWidth`.
* When direction is `rtl`, `borderStartWidth` is equivalent to `borderRightWidth`.
*/
borderStartWidth?: number,
/** `borderTopWidth` works like `border-top-width` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-top-width
* for more details.
*/
borderTopWidth?: number,
/** `position` in React Native is similar to regular CSS, but
* everything is set to `relative` by default.
*
* If you want to position a child using specific numbers of logical
* pixels relative to its parent, set the child to have `absolute`
* position.
*
* If you want to position a child relative to something
* that is not its parent, set the child to have `absolute` position and the
* nodes between to have `static` position.
*
* Note that `static` is only available on the new renderer.
*
* See https://github.com/facebook/yoga
* for more details on how `position` differs between React Native
* and CSS.
*/
position?: 'absolute' | 'relative' | 'static',
/** `flexDirection` controls which directions children of a container go.
* `row` goes left to right, `column` goes top to bottom, and you may
* be able to guess what the other two do. It works like `flex-direction`
* in CSS, except the default is `column`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction
* for more details.
*/
flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse',
/** `flexWrap` controls whether children can wrap around after they
* hit the end of a flex container.
* It works like `flex-wrap` in CSS (default: nowrap).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap
* for more details.
*/
flexWrap?: 'wrap' | 'nowrap' | 'wrap-reverse',
/** `justifyContent` aligns children in the main direction.
* For example, if children are flowing vertically, `justifyContent`
* controls how they align vertically.
* It works like `justify-content` in CSS (default: flex-start).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content
* for more details.
*/
justifyContent?:
| 'flex-start'
| 'flex-end'
| 'center'
| 'space-between'
| 'space-around'
| 'space-evenly',
/** `alignItems` aligns children in the cross direction.
* For example, if children are flowing vertically, `alignItems`
* controls how they align horizontally.
* It works like `align-items` in CSS (default: stretch).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-items
* for more details.
*/
alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline',
/** `alignSelf` controls how a child aligns in the cross direction,
* overriding the `alignItems` of the parent. It works like `align-self`
* in CSS (default: auto).
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-self
* for more details.
*/
alignSelf?:
| 'auto'
| 'flex-start'
| 'flex-end'
| 'center'
| 'stretch'
| 'baseline',
/** `alignContent` controls how rows align in the cross direction,
* overriding the `alignContent` of the parent.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/align-content
* for more details.
*/
alignContent?:
| 'flex-start'
| 'flex-end'
| 'center'
| 'stretch'
| 'space-between'
| 'space-around'
| 'space-evenly',
/** `overflow` controls how children are measured and displayed.
* `overflow: hidden` causes views to be clipped while `overflow: scroll`
* causes views to be measured independently of their parents main axis.
* It works like `overflow` in CSS (default: visible).
* See https://developer.mozilla.org/en/docs/Web/CSS/overflow
* for more details.
* `overflow: visible` only works on iOS. On Android, all views will clip
* their children.
*/
overflow?: 'visible' | 'hidden' | 'scroll',
/** In React Native `flex` does not work the same way that it does in CSS.
* `flex` is a number rather than a string, and it works
* according to the `Yoga` library
* at https://github.com/facebook/yoga
*
* When `flex` is a positive number, it makes the component flexible
* and it will be sized proportional to its flex value. So a
* component with `flex` set to 2 will take twice the space as a
* component with `flex` set to 1.
*
* When `flex` is 0, the component is sized according to `width`
* and `height` and it is inflexible.
*
* When `flex` is -1, the component is normally sized according
* `width` and `height`. However, if there's not enough space,
* the component will shrink to its `minWidth` and `minHeight`.
*
* flexGrow, flexShrink, and flexBasis work the same as in CSS.
*/
flex?: number,
flexGrow?: number,
flexShrink?: number,
flexBasis?: number | string,
/**
* Aspect ratio control the size of the undefined dimension of a node.
*
* - On a node with a set width/height aspect ratio control the size of the unset dimension
* - On a node with a set flex basis aspect ratio controls the size of the node in the cross axis
* if unset
* - On a node with a measure function aspect ratio works as though the measure function measures
* the flex basis
* - On a node with flex grow/shrink aspect ratio controls the size of the node in the cross axis
* if unset
* - Aspect ratio takes min/max dimensions into account
*
* Supports a number or a ratio, e.g.:
* - aspectRatio: '1 / 1'
* - aspectRatio: '1'
* - aspectRatio: '1'
*/
aspectRatio?: number | string,
/** `zIndex` controls which components display on top of others.
* Normally, you don't use `zIndex`. Components render according to
* their order in the document tree, so later components draw over
* earlier ones. `zIndex` may be useful if you have animations or custom
* modal interfaces where you don't want this behavior.
*
* It works like the CSS `z-index` property - components with a larger
* `zIndex` will render on top. Think of the z-direction like it's
* pointing from the phone into your eyeball.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/z-index for
* more details.
*/
zIndex?: number,
/** `direction` specifies the directional flow of the user interface.
* The default is `inherit`, except for root node which will have
* value based on the current locale.
* See https://yogalayout.dev/docs/layout-direction
* for more details.
* @platform ios
*/
direction?: 'inherit' | 'ltr' | 'rtl',
/**
* In React Native, gap works the same way it does in CSS.
* If there are two or more children in a container, they will be separated from each other
* by the value of the gap - but the children will not be separated from the edges of their parent container.
* For horizontal gaps, use columnGap, for vertical gaps, use rowGap, and to apply both at the same time, it's gap.
* When align-content or justify-content are set to space-between or space-around, the separation
* between children may be larger than the gap value.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/gap for more details.
*/
rowGap?: number,
columnGap?: number,
gap?: number,
}>;
/**
* These props can be used to dynamically generate shadows on views, images, text, etc.
*
* Because they are dynamically generated, they may cause performance regressions. Static
* shadow image asset may be a better way to go for optimal performance.
*
* Shadow-related properties are not fully supported on Android.
* To add a drop shadow to a view use the [`elevation` property](docs/viewstyleproptypes.html#elevation) (Android 5.0+).
* To customize the color use the [`shadowColor` property](docs/shadow-props.html#shadowColor) (Android 9.0+).
*/
export type ____ShadowStyle_InternalCore = $ReadOnly<{
/**
* Sets the drop shadow color
* @platform ios
*/
shadowColor?: ____ColorValue_Internal,
/**
* Sets the drop shadow offset
* @platform ios
*/
shadowOffset?: $ReadOnly<{
width?: number,
height?: number,
}>,
/**
* Sets the drop shadow opacity (multiplied by the color's alpha component)
* @platform ios
*/
shadowOpacity?: AnimatableNumericValue,
/**
* Sets the drop shadow blur radius
* @platform ios
*/
shadowRadius?: number,
}>;
export type ____ShadowStyle_Internal = $ReadOnly<{
...____ShadowStyle_InternalCore,
...____ShadowStyle_InternalOverrides,
}>;
export type ____ViewStyle_InternalCore = $ReadOnly<{
...$Exact<____LayoutStyle_Internal>,
...$Exact<____ShadowStyle_Internal>,
...$Exact<____TransformStyle_Internal>,
backfaceVisibility?: 'visible' | 'hidden',
backgroundColor?: ____ColorValue_Internal,
borderColor?: ____ColorValue_Internal,
borderCurve?: 'circular' | 'continuous',
borderBottomColor?: ____ColorValue_Internal,
borderEndColor?: ____ColorValue_Internal,
borderLeftColor?: ____ColorValue_Internal,
borderRightColor?: ____ColorValue_Internal,
borderStartColor?: ____ColorValue_Internal,
borderTopColor?: ____ColorValue_Internal,
borderBlockColor?: ____ColorValue_Internal,
borderBlockEndColor?: ____ColorValue_Internal,
borderBlockStartColor?: ____ColorValue_Internal,
borderRadius?: AnimatableNumericValue,
borderBottomEndRadius?: AnimatableNumericValue,
borderBottomLeftRadius?: AnimatableNumericValue,
borderBottomRightRadius?: AnimatableNumericValue,
borderBottomStartRadius?: AnimatableNumericValue,
borderEndEndRadius?: AnimatableNumericValue,
borderEndStartRadius?: AnimatableNumericValue,
borderStartEndRadius?: AnimatableNumericValue,
borderStartStartRadius?: AnimatableNumericValue,
borderTopEndRadius?: AnimatableNumericValue,
borderTopLeftRadius?: AnimatableNumericValue,
borderTopRightRadius?: AnimatableNumericValue,
borderTopStartRadius?: AnimatableNumericValue,
borderStyle?: 'solid' | 'dotted' | 'dashed',
borderWidth?: AnimatableNumericValue,
borderBottomWidth?: AnimatableNumericValue,
borderEndWidth?: AnimatableNumericValue,
borderLeftWidth?: AnimatableNumericValue,
borderRightWidth?: AnimatableNumericValue,
borderStartWidth?: AnimatableNumericValue,
borderTopWidth?: AnimatableNumericValue,
opacity?: AnimatableNumericValue,
elevation?: number,
pointerEvents?: 'auto' | 'none' | 'box-none' | 'box-only',
cursor?: CursorValue,
}>;
export type ____ViewStyle_Internal = $ReadOnly<{
...____ViewStyle_InternalCore,
...____ViewStyle_InternalOverrides,
}>;
export type FontStyleType = {
fontFamily: string,
fontWeight: ____FontWeight_Internal,
};
export type FontStyleMap = {
ultraLight: FontStyleType,
thin: FontStyleType,
light: FontStyleType,
regular: FontStyleType,
medium: FontStyleType,
semibold: FontStyleType,
bold: FontStyleType,
heavy: FontStyleType,
black: FontStyleType,
};
export type ____FontWeight_Internal =
| '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';
export type ____FontVariantArray_Internal = $ReadOnlyArray<
| '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 type ____TextStyle_InternalCore = $ReadOnly<{
...$Exact<____ViewStyle_Internal>,
color?: ____ColorValue_Internal,
fontFamily?: string,
fontSize?: number,
fontStyle?: 'normal' | 'italic',
fontWeight?: ____FontWeight_Internal,
fontVariant?: ____FontVariantArray_Internal | string,
textShadowOffset?: $ReadOnly<{
width: number,
height: number,
}>,
textShadowRadius?: number,
textShadowColor?: ____ColorValue_Internal,
letterSpacing?: number,
lineHeight?: number,
textAlign?: 'auto' | 'left' | 'right' | 'center' | 'justify',
textAlignVertical?: 'auto' | 'top' | 'bottom' | 'center',
includeFontPadding?: boolean,
textDecorationLine?:
| 'none'
| 'underline'
| 'line-through'
| 'underline line-through',
textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed',
textDecorationColor?: ____ColorValue_Internal,
textTransform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase',
userSelect?: 'auto' | 'text' | 'none' | 'contain' | 'all',
verticalAlign?: 'auto' | 'top' | 'bottom' | 'middle',
writingDirection?: 'auto' | 'ltr' | 'rtl',
}>;
export type ____TextStyle_Internal = $ReadOnly<{
...____TextStyle_InternalCore,
...____TextStyle_InternalOverrides,
}>;
export type ____ImageStyle_InternalCore = $ReadOnly<{
...$Exact<____ViewStyle_Internal>,
resizeMode?: 'contain' | 'cover' | 'stretch' | 'center' | 'repeat',
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down',
tintColor?: ____ColorValue_Internal,
overlayColor?: string,
}>;
export type ____ImageStyle_Internal = $ReadOnly<{
...____ImageStyle_InternalCore,
...____ImageStyle_InternalOverrides,
}>;
export type ____DangerouslyImpreciseStyle_InternalCore = $ReadOnly<{
...$Exact<____TextStyle_Internal>,
resizeMode?: 'contain' | 'cover' | 'stretch' | 'center' | 'repeat',
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down',
tintColor?: ____ColorValue_Internal,
overlayColor?: string,
}>;
export type ____DangerouslyImpreciseStyle_Internal = $ReadOnly<{
...____DangerouslyImpreciseStyle_InternalCore,
...____DangerouslyImpreciseStyle_InternalOverrides,
...
}>;
type GenericStyleProp<+T> =
| null
| void
| T
| false
| ''
| $ReadOnlyArray<GenericStyleProp<T>>;
export type ____DangerouslyImpreciseStyleProp_Internal = GenericStyleProp<
Partial<____DangerouslyImpreciseStyle_Internal>,
>;
export type ____ViewStyleProp_Internal = GenericStyleProp<
$ReadOnly<Partial<____ViewStyle_Internal>>,
>;
export type ____TextStyleProp_Internal = GenericStyleProp<
$ReadOnly<Partial<____TextStyle_Internal>>,
>;
export type ____ImageStyleProp_Internal = GenericStyleProp<
$ReadOnly<Partial<____ImageStyle_Internal>>,
>;
export type ____Styles_Internal = {
// $FlowFixMe[incompatible-exact]
// $FlowFixMe[incompatible-type]
+[key: string]: Partial<____DangerouslyImpreciseStyle_Internal>,
...
};
export type ____FlattenStyleProp_Internal<
+TStyleProp: GenericStyleProp<mixed>,
> = TStyleProp extends null | void | false | ''
? empty
: TStyleProp extends $ReadOnlyArray<infer V>
? ____FlattenStyleProp_Internal<V>
: TStyleProp;

View File

@@ -0,0 +1,58 @@
/**
* 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 {ImageStyleProp, TextStyleProp} from '../StyleSheet';
const StyleSheet = require('../StyleSheet');
const imageStyle = {tintColor: 'rgb(0, 0, 0)'};
const textStyle = {color: 'rgb(0, 0, 0)'};
module.exports = {
testGoodCompose() {
(StyleSheet.compose(imageStyle, imageStyle): ImageStyleProp);
(StyleSheet.compose(textStyle, textStyle): TextStyleProp);
(StyleSheet.compose(null, null): TextStyleProp);
(StyleSheet.compose(textStyle, null): TextStyleProp);
(StyleSheet.compose(
textStyle,
Math.random() < 0.5 ? textStyle : null,
): TextStyleProp);
(StyleSheet.compose([textStyle], null): TextStyleProp);
(StyleSheet.compose([textStyle], null): TextStyleProp);
(StyleSheet.compose([textStyle], [textStyle]): TextStyleProp);
},
testBadCompose() {
// $FlowExpectedError - Incompatible type.
(StyleSheet.compose(textStyle, textStyle): ImageStyleProp);
// $FlowExpectedError - Incompatible type.
(StyleSheet.compose(
// $FlowExpectedError - Incompatible type.
[textStyle],
null,
): ImageStyleProp);
// $FlowExpectedError - Incompatible type.
(StyleSheet.compose(
Math.random() < 0.5 ? textStyle : null,
null,
): ImageStyleProp);
},
};

View File

@@ -0,0 +1,45 @@
/**
* 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 {DangerouslyImpreciseStyleProp} from './StyleSheet';
import type {____FlattenStyleProp_Internal} from './StyleSheetTypes';
function flattenStyle<TStyleProp: DangerouslyImpreciseStyleProp>(
style: ?TStyleProp,
// $FlowFixMe[underconstrained-implicit-instantiation]
): ?____FlattenStyleProp_Internal<TStyleProp> {
if (style === null || typeof style !== 'object') {
return undefined;
}
if (!Array.isArray(style)) {
// $FlowFixMe[incompatible-return]
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]
result[key] = computedStyle[key];
}
}
}
// $FlowFixMe[incompatible-return]
return result;
}
module.exports = 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.
*
* @format
* @flow strict-local
*/
/* 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);
}
}
module.exports = 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,69 @@
/**
* 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';
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<
| {|+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 | AnimatedNode|}
| {|+translateY: number | AnimatedNode|}
| {|
+translate:
| [number | AnimatedNode, number | 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,63 @@
/**
* 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
* @flow strict-local
*/
'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(
!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]);
}
module.exports = processAspectRatio;

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.
*
* @format
* @flow strict-local
*/
'use strict';
import type {ColorValue, NativeColorValue} from './StyleSheet';
const Platform = require('../Utilities/Platform');
const normalizeColor = require('./normalizeColor');
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.
*
* @format
* @flow strict-local
*/
'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;
}
module.exports = processColorArray;

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.
*
* @format
* @flow strict-local
*/
'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;
}
module.exports = processFontVariant;

View File

@@ -0,0 +1,279 @@
/**
* 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
* @flow
*/
'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);
let 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
| $TEMPORARY$string<'matrix'>
| $TEMPORARY$string<'perspective'>
| $TEMPORARY$string<'rotate'>
| $TEMPORARY$string<'rotateX'>
| $TEMPORARY$string<'rotateY'>
| $TEMPORARY$string<'rotateZ'>
| $TEMPORARY$string<'scale'>
| $TEMPORARY$string<'scaleX'>
| $TEMPORARY$string<'scaleY'>
| $TEMPORARY$string<'skewX'>
| $TEMPORARY$string<'skewY'>
| $TEMPORARY$string<'translate'>
| $TEMPORARY$string<'translate3d'>
| $TEMPORARY$string<'translateX'>
| $TEMPORARY$string<'translateY'>,
args: string,
) => {key: string, value?: 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;
}
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];
_validateTransform(key, value, transformation);
});
}
function _validateTransform(
key:
| string
| $TEMPORARY$string<'matrix'>
| $TEMPORARY$string<'perspective'>
| $TEMPORARY$string<'rotate'>
| $TEMPORARY$string<'rotateX'>
| $TEMPORARY$string<'rotateY'>
| $TEMPORARY$string<'rotateZ'>
| $TEMPORARY$string<'scale'>
| $TEMPORARY$string<'scaleX'>
| $TEMPORARY$string<'scaleY'>
| $TEMPORARY$string<'skewX'>
| $TEMPORARY$string<'skewY'>
| $TEMPORARY$string<'translate'>
| $TEMPORARY$string<'translateX'>
| $TEMPORARY$string<'translateY'>,
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':
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),
);
}
}
module.exports = 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.
*
* @format
* @flow
*/
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.
*
* @format
* @flow
*/
/* 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;
}
module.exports = 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) {
// $FlowIgnore[incompatible-exact] Will contain a subset of keys from `props`.
outer = {};
// $FlowIgnore[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};
}