diff --git a/change/@office-iss-react-native-win32-2019-12-20-11-19-05-win32defork.json b/change/@office-iss-react-native-win32-2019-12-20-11-19-05-win32defork.json new file mode 100644 index 00000000000..1dbfdc2b4de --- /dev/null +++ b/change/@office-iss-react-native-win32-2019-12-20-11-19-05-win32defork.json @@ -0,0 +1,8 @@ +{ + "type": "patch", + "comment": "Remove remaining need for fork of RN for win32 JS", + "packageName": "@office-iss/react-native-win32", + "email": "acoates@microsoft.com", + "commit": "b17624cd44d682c73db5ae17a3d10c8bed07c5f8", + "date": "2019-12-20T19:19:05.418Z" +} \ No newline at end of file diff --git a/change/react-native-windows-2019-12-20-15-20-03-win32defork.json b/change/react-native-windows-2019-12-20-15-20-03-win32defork.json new file mode 100644 index 00000000000..2462198aaf3 --- /dev/null +++ b/change/react-native-windows-2019-12-20-15-20-03-win32defork.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Fix RNTester bundle (#2728)", + "packageName": "react-native-windows", + "email": "acoates@microsoft.com", + "commit": "65167fb5e52f042879d00891e89ed951bdda84bc", + "date": "2019-12-20T23:20:03.026Z" +} \ No newline at end of file diff --git a/packages/react-native-win32/package.json b/packages/react-native-win32/package.json index cf799519039..7b621c54546 100644 --- a/packages/react-native-win32/package.json +++ b/packages/react-native-win32/package.json @@ -12,10 +12,10 @@ "lint": "just-scripts eslint", "lint:fix": "eslint ./**/*.js ./**/*.ts? --fix", "watch": "tsc -w", - "bundle": "just-scripts prepareBundle && react-native bundle --platform win32 --entry-file RNTester.js --bundle-output dist/win32/dev/index.win32.bundle --assets-dest dist/win32/dev", - "run-win32": "rex-win32 --bundle RNTester.win32 --component RNTesterApp --basePath ./dist/win32/dev", - "run-win32-devmain": "rex-win32 --bundle RNTester.win32 --component RNTesterApp --basePath ./dist/win32/dev --useDevMain", - "run-win32-dev-web": "rex-win32 --bundle RNTester.win32 --component RNTesterApp --basePath ./dist/win32/dev --useWebDebugger" + "bundle": "just-scripts prepareBundle && react-native bundle --platform win32 --entry-file RNTester.js --bundle-output dist/win32/dev/RNTester.bundle --assets-dest dist/win32/dev", + "run-win32": "rex-win32 --bundle RNTester --component RNTesterApp --basePath ./dist/win32/dev", + "run-win32-devmain": "rex-win32 --bundle RNTester --component RNTesterApp --basePath ./dist/win32/dev --useDevMain", + "run-win32-dev-web": "rex-win32 --bundle RNTester --component RNTesterApp --basePath ./dist/win32/dev --useWebDebugger" }, "dependencies": { "@babel/runtime": "^7.4.0", diff --git a/packages/react-native-win32/src/Libraries/Components/TextInput/TextInputState.win32.js b/packages/react-native-win32/src/Libraries/Components/TextInput/TextInputState.win32.js new file mode 100644 index 00000000000..b3e51f25042 --- /dev/null +++ b/packages/react-native-win32/src/Libraries/Components/TextInput/TextInputState.win32.js @@ -0,0 +1,101 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * @format + * @flow strict-local + */ + +'use strict'; + +const Platform = require('../../Utilities/Platform'); +const UIManager = require('../../ReactNative/UIManager'); + +let currentlyFocusedID: ?number = null; +const inputs = new Set(); + +/** + * Returns the ID of the currently focused text field, if one exists + * If no text field is focused it returns null + */ +function currentlyFocusedField(): ?number { + return currentlyFocusedID; +} + +/** + * @param {number} TextInputID id of the text field to focus + * Focuses the specified text field + * noop if the text field was already focused + */ +function focusTextInput(textFieldID: ?number) { + if (currentlyFocusedID !== textFieldID && textFieldID !== null) { + currentlyFocusedID = textFieldID; + if (Platform.OS === 'ios') { + UIManager.focus(textFieldID); + } else if (Platform.OS === 'android') { + UIManager.dispatchViewManagerCommand( + textFieldID, + UIManager.getViewManagerConfig('AndroidTextInput').Commands + .focusTextInput, + null, + ); + // [Win32 + } else if (Platform.OS === 'win32') { + UIManager.dispatchViewManagerCommand( + textFieldID, + UIManager.RCTView.Commands.focus, + null, + ); + } + // Win32] + } +} + +/** + * @param {number} textFieldID id of the text field to unfocus + * Unfocuses the specified text field + * noop if it wasn't focused + */ +function blurTextInput(textFieldID: ?number) { + if (currentlyFocusedID === textFieldID && textFieldID !== null) { + currentlyFocusedID = null; + if (Platform.OS === 'ios') { + UIManager.blur(textFieldID); + } else if (Platform.OS === 'android') { + UIManager.dispatchViewManagerCommand( + textFieldID, + UIManager.getViewManagerConfig('AndroidTextInput').Commands + .blurTextInput, + null, + ); + // [Win32 + } else if (Platform.OS === 'win32') { + UIManager.dispatchViewManagerCommand( + textFieldID, + UIManager.RCTView.Commands.blur, + null, + ); + } + // Win32] + } +} + +function registerInput(textFieldID: number) { + inputs.add(textFieldID); +} + +function unregisterInput(textFieldID: number) { + inputs.delete(textFieldID); +} + +function isTextInput(textFieldID: number) { + return inputs.has(textFieldID); +} + +module.exports = { + currentlyFocusedField, + focusTextInput, + blurTextInput, + registerInput, + unregisterInput, + isTextInput, +}; diff --git a/packages/react-native-win32/src/Libraries/StyleSheet/StyleSheet.win32.js b/packages/react-native-win32/src/Libraries/StyleSheet/StyleSheet.win32.js new file mode 100644 index 00000000000..ac176b8d8ec --- /dev/null +++ b/packages/react-native-win32/src/Libraries/StyleSheet/StyleSheet.win32.js @@ -0,0 +1,362 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * @flow + * @format + */ +'use strict'; + +const PixelRatio = require('../Utilities/PixelRatio'); +const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleAttributes'); +const StyleSheetValidation = require('./StyleSheetValidation'); +const Platform = require('../Utilities/Platform'); // [Win32] + +const flatten = require('./flattenStyle'); + +import type { + ____Styles_Internal, + ____DangerouslyImpreciseStyle_Internal, + ____DangerouslyImpreciseStyleProp_Internal, + ____ViewStyle_Internal, + ____ViewStyleProp_Internal, + ____TextStyle_Internal, + ____TextStyleProp_Internal, + ____ImageStyle_Internal, + ____ImageStyleProp_Internal, +} from './StyleSheetTypes'; + +/** + * This type should be used as the type for a prop that is passed through + * to a '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) => + */ +export type ViewStyleProp = ____ViewStyleProp_Internal; + +/** + * This type should be used as the type for a prop that is passed through + * to a '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) => + */ +export type TextStyleProp = ____TextStyleProp_Internal; + +/** + * This type should be used as the type for a prop that is passed through + * to an '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) => + */ +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 = + Platform.OS === 'win32' ? 0.5 : PixelRatio.roundToNearestPixel(0.4); // TODO(windows ISS) - Avoid calls to PixelRatio - needs multi window support +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: + * + * ``` + * + * + * + * ``` + * + * 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. + * + * Performance: + * + * - Making a stylesheet from a style object makes it possible to refer to it + * by ID instead of creating a new style object every time. + * - It also allows to send the style only once through the bridge. All + * subsequent uses are going to refer an id (not implemented yet). + */ +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( + style1: ?T, + style2: ?T, + ): ?T | $ReadOnlyArray { + if (style1 != null && style2 != null) { + return ([style1, style2]: $ReadOnlyArray); + } else { + return style1 != null ? style1 : style2; + } + }, + + /** + * Flattens an array of style objects, into one aggregated style object. + * Alternatively, this method can be used to lookup IDs, returned by + * StyleSheet.register. + * + * > **NOTE**: Exercise caution as abusing this can tax you in terms of + * > optimizations. + * > + * > IDs enable optimizations through the bridge and memory in general. Refering + * > to style objects directly will deprive you of these optimizations. + * + * 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' } + * ``` + * Alternative use: + * ``` + * StyleSheet.flatten(styles.listItem); + * // return { flex: 1, fontSize: 16, color: 'white' } + * // Simply styles.listItem would return its ID (number) + * ``` + * This method internally uses `StyleSheetRegistry.getStyleByID(style)` + * to resolve style objects represented by IDs. Thus, an array of style + * objects (instances of StyleSheet.create), are individually resolved to, + * their respective objects, merged as one and then returned. This also explains + * the alternative use. + */ + 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 = {}; + } else if (typeof ReactNativeStyleAttributes[property] === 'object') { + value = ReactNativeStyleAttributes[property]; + } else { + console.error(`${property} is not a valid style attribute`); + return; + } + + if (__DEV__ && typeof value.process === 'function') { + console.warn(`Overwriting ${property} style attribute preprocessor`); + } + + ReactNativeStyleAttributes[property] = {...value, process}; + }, + + /** + * Creates a StyleSheet style reference from the given object. + */ + create<+S: ____Styles_Internal>(obj: S): $ObjMap any> { + // 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) { + StyleSheetValidation.validateStyle(key, obj); + if (obj[key]) { + Object.freeze(obj[key]); + } + } + } + return obj; + }, +}; diff --git a/packages/react-native-win32/src/Libraries/YellowBox/UI/YellowBoxList.win32.js b/packages/react-native-win32/src/Libraries/YellowBox/UI/YellowBoxList.win32.js new file mode 100644 index 00000000000..9740b9d90c0 --- /dev/null +++ b/packages/react-native-win32/src/Libraries/YellowBox/UI/YellowBoxList.win32.js @@ -0,0 +1,144 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const Dimensions = require('../../Utilities/Dimensions'); +const React = require('react'); +const FlatList = require('../../Lists/FlatList'); +const SafeAreaView = require('../../Components/SafeAreaView/SafeAreaView'); +const StyleSheet = require('../../StyleSheet/StyleSheet'); +const View = require('../../Components/View/View'); +const YellowBoxButton = require('./YellowBoxButton'); +const YellowBoxInspector = require('./YellowBoxInspector'); +const YellowBoxListRow = require('./YellowBoxListRow'); +const YellowBoxStyle = require('./YellowBoxStyle'); +const Platform = require('../../Utilities/Platform'); // TODO(windows ISS) + +import type {Category} from '../Data/YellowBoxCategory'; +import type {Registry} from '../Data/YellowBoxRegistry'; + +type Props = $ReadOnly<{| + onDismiss: (category: Category) => void, + onDismissAll: () => void, + registry: Registry, +|}>; + +type State = {| + selectedCategory: ?Category, +|}; + +const VIEWPORT_RATIO = 0.5; +const MAX_ITEMS = + Platform.OS === 'win32' // [Win32 - Avoid calls to Dimensions since we need multi window support + ? 3 + : Math.floor( + (Dimensions.get('window').height * VIEWPORT_RATIO) / + (YellowBoxListRow.GUTTER + YellowBoxListRow.HEIGHT), + ); // ]Win32 + +class YellowBoxList extends React.Component { + state = { + selectedCategory: null, + }; + + render(): React.Node { + const selectedWarnings = + this.state.selectedCategory == null + ? null + : this.props.registry.get(this.state.selectedCategory); + + if (selectedWarnings != null) { + return ( + + + + ); + } + + const items = []; + for (const [category, warnings] of this.props.registry) { + items.unshift({category, warnings}); + } + + const listStyle = { + width: Platform.OS === 'win32' ? '85%' : undefined, // [Win32 - Percentage not currently supported on win32] + height: + // Additional `0.5` so the (N + 1)th row can peek into view. + Math.min(items.length, MAX_ITEMS + 0.5) * + (YellowBoxListRow.GUTTER + YellowBoxListRow.HEIGHT), + }; + + return items.length === 0 ? null : ( + + + + + item.category} + renderItem={({item}) => ( + + )} + scrollEnabled={items.length > MAX_ITEMS} + scrollsToTop={false} + style={listStyle} + /> + + + ); + } + + _handleInspectorDismiss = () => { + const category = this.state.selectedCategory; + if (category == null) { + return; + } + this.setState({selectedCategory: null}, () => { + this.props.onDismiss(category); + }); + }; + + _handleInspectorMinimize = () => { + this.setState({selectedCategory: null}); + }; + + _handleRowPress = (category: Category) => { + this.setState({selectedCategory: category}); + }; +} + +const styles = StyleSheet.create({ + list: { + bottom: 0, + position: 'absolute', + width: '100%', + }, + dismissAll: { + bottom: Platform.OS === 'win32' ? 0 : '100%', // [Win32 - Percentage not currently supported on win32] + flexDirection: 'row', + justifyContent: 'flex-end', + paddingBottom: 4, + paddingEnd: 4, + position: 'absolute', + width: '100%', + }, + safeArea: { + backgroundColor: YellowBoxStyle.getBackgroundColor(0.95), + marginTop: StyleSheet.hairlineWidth, + }, +}); + +module.exports = YellowBoxList; diff --git a/vnext/src/RNTester/WindowsBrushExample.windows.tsx b/vnext/src/RNTester/js/WindowsBrushExample.windows.tsx similarity index 100% rename from vnext/src/RNTester/WindowsBrushExample.windows.tsx rename to vnext/src/RNTester/js/WindowsBrushExample.windows.tsx