From 382c823ccde779e8254ffd750075a01d0cb03c6a Mon Sep 17 00:00:00 2001 From: Keith Melmon Date: Mon, 12 Aug 2019 21:08:14 -0700 Subject: [PATCH 1/3] merge --- vnext/Playground/Playground/index.windows.js | 48 ++ .../runWindows/utils/commandWithProgress.js | 103 ++++ .../Libraries/AppTheme/AppTheme.windows.ts | 72 +++ .../AccessibilityInfo.windows.ts | 62 +++ .../Libraries/Components/Button.windows.js | 127 +++++ .../Components/CheckBox/CheckBox.windows.tsx | 69 +++ .../DatePicker/DatePicker.windows.tsx | 89 +++ .../DatePicker/DatePickerIOS.windows.js | 8 + .../DatePickerAndroid.windows.js | 8 + .../DatePickerMacOS.windows.js | 8 + .../DrawerLayoutAndroid.windows.js | 8 + .../Components/Flyout/Flyout.windows.tsx | 75 +++ .../Components/Glyph/Glyph.windows.tsx | 30 + .../Keyboard/KeyboardExt.windows.tsx | 38 ++ .../MaskedView/MaskedViewIOS.windows.js | 8 + .../Navigation/NavigatorIOS.windows.js | 8 + .../Components/Picker/Picke.windows.uwp.tsx | 125 +++++ .../Picker/PickerAndroid.windows.js | 8 + .../Components/Picker/PickerIOS.windows.js | 8 + .../Components/Picker/PickerWindows.tsx | 25 + .../Components/Popup/Popup.windows.tsx | 70 +++ .../ProgressBarAndroid.windows.js | 8 + .../ProgressViewIOS.windows.js | 8 + .../SafeAreaView/SafeAreaView.windows.js | 10 + .../SegmentedControlIOS.windows.js | 8 + .../StatusBar/StatusBarIOS.windows.js | 8 + .../Components/Switch/Switch.windows.tsx | 118 ++++ .../Components/Switch/SwitchProps.ts | 32 ++ .../Components/TabBarIOS/TabBarIOS.windows.js | 8 + .../TabBarIOS/TabBarItemIOS.windows.js | 8 + .../Components/TextInput/TextInput.windows.js | 464 ++++++++++++++++ .../TextInput/TextInputState.windows.js | 100 ++++ .../TimePickerAndroid.windows.js | 8 + .../ToastAndroid/ToastAndroid.windows.js | 8 + .../ToolbarAndroid/ToolbarAndroid.windows.js | 8 + .../Touchable/TouchableHighlight.windows.js | 514 ++++++++++++++++++ .../TouchableNativeFeedback.windows.tsx | 52 ++ .../Touchable/TouchableOpacity.windows.js | 391 +++++++++++++ .../TouchableWithoutFeedback.windows.js | 350 ++++++++++++ .../View/PlatformViewPropTypes.windows.js | 6 + .../View/ViewAccessibility.windows.js | 162 ++++++ .../Components/View/ViewWindows.windows.tsx | 18 + .../ViewPager/ViewPagerAndroid.windows.js | 8 + .../Components/WebView/WebView.windows.js | 214 ++++++++ .../DeprecatedViewAccessibility.windows.js | 81 +++ vnext/src/Libraries/Image/Image.windows.js | 182 +++++++ 46 files changed, 3771 insertions(+) create mode 100644 vnext/Playground/Playground/index.windows.js create mode 100644 vnext/local-cli/runWindows/utils/commandWithProgress.js create mode 100644 vnext/src/Libraries/AppTheme/AppTheme.windows.ts create mode 100644 vnext/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.windows.ts create mode 100644 vnext/src/Libraries/Components/Button.windows.js create mode 100644 vnext/src/Libraries/Components/CheckBox/CheckBox.windows.tsx create mode 100644 vnext/src/Libraries/Components/DatePicker/DatePicker.windows.tsx create mode 100644 vnext/src/Libraries/Components/DatePicker/DatePickerIOS.windows.js create mode 100644 vnext/src/Libraries/Components/DatePickerAndroid/DatePickerAndroid.windows.js create mode 100644 vnext/src/Libraries/Components/DatePickerMacOS/DatePickerMacOS.windows.js create mode 100644 vnext/src/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.windows.js create mode 100644 vnext/src/Libraries/Components/Flyout/Flyout.windows.tsx create mode 100644 vnext/src/Libraries/Components/Glyph/Glyph.windows.tsx create mode 100644 vnext/src/Libraries/Components/Keyboard/KeyboardExt.windows.tsx create mode 100644 vnext/src/Libraries/Components/MaskedView/MaskedViewIOS.windows.js create mode 100644 vnext/src/Libraries/Components/Navigation/NavigatorIOS.windows.js create mode 100644 vnext/src/Libraries/Components/Picker/Picke.windows.uwp.tsx create mode 100644 vnext/src/Libraries/Components/Picker/PickerAndroid.windows.js create mode 100644 vnext/src/Libraries/Components/Picker/PickerIOS.windows.js create mode 100644 vnext/src/Libraries/Components/Picker/PickerWindows.tsx create mode 100644 vnext/src/Libraries/Components/Popup/Popup.windows.tsx create mode 100644 vnext/src/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.windows.js create mode 100644 vnext/src/Libraries/Components/ProgressViewIOS/ProgressViewIOS.windows.js create mode 100644 vnext/src/Libraries/Components/SafeAreaView/SafeAreaView.windows.js create mode 100644 vnext/src/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.windows.js create mode 100644 vnext/src/Libraries/Components/StatusBar/StatusBarIOS.windows.js create mode 100644 vnext/src/Libraries/Components/Switch/Switch.windows.tsx create mode 100644 vnext/src/Libraries/Components/Switch/SwitchProps.ts create mode 100644 vnext/src/Libraries/Components/TabBarIOS/TabBarIOS.windows.js create mode 100644 vnext/src/Libraries/Components/TabBarIOS/TabBarItemIOS.windows.js create mode 100644 vnext/src/Libraries/Components/TextInput/TextInput.windows.js create mode 100644 vnext/src/Libraries/Components/TextInput/TextInputState.windows.js create mode 100644 vnext/src/Libraries/Components/TimePickerAndroid/TimePickerAndroid.windows.js create mode 100644 vnext/src/Libraries/Components/ToastAndroid/ToastAndroid.windows.js create mode 100644 vnext/src/Libraries/Components/ToolbarAndroid/ToolbarAndroid.windows.js create mode 100644 vnext/src/Libraries/Components/Touchable/TouchableHighlight.windows.js create mode 100644 vnext/src/Libraries/Components/Touchable/TouchableNativeFeedback.windows.tsx create mode 100644 vnext/src/Libraries/Components/Touchable/TouchableOpacity.windows.js create mode 100644 vnext/src/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js create mode 100644 vnext/src/Libraries/Components/View/PlatformViewPropTypes.windows.js create mode 100644 vnext/src/Libraries/Components/View/ViewAccessibility.windows.js create mode 100644 vnext/src/Libraries/Components/View/ViewWindows.windows.tsx create mode 100644 vnext/src/Libraries/Components/ViewPager/ViewPagerAndroid.windows.js create mode 100644 vnext/src/Libraries/Components/WebView/WebView.windows.js create mode 100644 vnext/src/Libraries/DeprecatedPropTypes/DeprecatedViewAccessibility.windows.js create mode 100644 vnext/src/Libraries/Image/Image.windows.js diff --git a/vnext/Playground/Playground/index.windows.js b/vnext/Playground/Playground/index.windows.js new file mode 100644 index 00000000000..08f6708fcfb --- /dev/null +++ b/vnext/Playground/Playground/index.windows.js @@ -0,0 +1,48 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + */ + +import React, { Component } from 'react'; +import { + AppRegistry, + StyleSheet, + Text, + View, +} from 'react-native'; + +class Playground extends Component { + render() { + return ( + + + Welcome to React Native! + + + To get started, edit index.windows.js + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, + instructions: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, + }, +}); + +AppRegistry.registerComponent('Playground', () => Playground); diff --git a/vnext/local-cli/runWindows/utils/commandWithProgress.js b/vnext/local-cli/runWindows/utils/commandWithProgress.js new file mode 100644 index 00000000000..13d56de9e27 --- /dev/null +++ b/vnext/local-cli/runWindows/utils/commandWithProgress.js @@ -0,0 +1,103 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +// @ts-check + +const child_process = require('child_process'); +const ora = require('ora'); +const spinners = require('cli-spinners'); +const chalk = require('chalk'); + +function setSpinnerText(spinner, prefix, text) { + text = prefix + spinnerString(text); + spinner.text = text.length > 45 ? text.slice(0, 110) + '...' : text; +} + +function spinnerString(msg) { + msg = msg.trim(); + const lastLineIndex = msg.lastIndexOf('\n'); + if (lastLineIndex > 0) { + msg = msg.slice(lastLineIndex + 1); + } + msg = msg.trim(); + return msg; +} + +function newSpinner(text) { + const options = {text: text, indent: 1}; + + // ora turns off emoji rendering by default on windows, since the default terminal + // doesn't render them. Turn them back on when running in the new windows terminal + if (process.env.WT_SESSION) { + options.spinner = spinners.dots; + } + return ora(options).start(); +} + +function commandWithProgress(spinner, taskDoingName, command, args, verbose) { + return new Promise(function(resolve, reject) { + const spawnOptions = verbose ? {stdio: 'inherit'} : {}; + + if (verbose) { + spinner.stop(); + } + + const cp = child_process.spawn(command, args, spawnOptions); + + if (!verbose) { + cp.stdout.on('data', chunk => { + const text = chunk.toString(); + setSpinnerText(spinner, taskDoingName + ': ', text); + }); + cp.stderr.on('data', chunk => { + const text = chunk.toString(); + if (verbose) { + console.error(chalk.red(text)); + } + setSpinnerText(spinner, taskDoingName + ': ERROR: ', text); + }); + } + cp.on('error', e => { + if (verbose) { + console.error(chalk.red(e.toString())); + } + spinner.fail(e.toString()); + reject(e); + }).on('close', function(code) { + if (code === 0) { + spinner.succeed(taskDoingName); + resolve(); + } else { + spinner.fail(); + reject(); + } + }); + }); +} + +function newError(text) { + newSpinner(text).fail(text); +} + +function newWarn(text) { + newSpinner(text).warn(text); +} + +function newSuccess(text) { + newSpinner(text).succeed(text); +} + +function newInfo(text) { + newSpinner(text).info(text); +} + +module.exports = { + commandWithProgress, + newError, + newInfo, + newSpinner, + newSuccess, + newWarn, +}; diff --git a/vnext/src/Libraries/AppTheme/AppTheme.windows.ts b/vnext/src/Libraries/AppTheme/AppTheme.windows.ts new file mode 100644 index 00000000000..1ca9c91bfe9 --- /dev/null +++ b/vnext/src/Libraries/AppTheme/AppTheme.windows.ts @@ -0,0 +1,72 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import {NativeEventEmitter, NativeModules} from 'react-native'; +const MissingNativeEventEmitterShim = require('MissingNativeEventEmitterShim'); +import { + AppThemeTypes, + IHighContrastColors, + IHighContrastChangedEvent, +} from './AppThemeTypes'; + +const NativeAppTheme = NativeModules.RTCAppTheme; + +class AppThemeModule extends NativeEventEmitter { + public isAvailable: boolean; + private _isHighContrast: boolean; + private _currentTheme: AppThemeTypes; + private _highContrastColors: IHighContrastColors; + + constructor() { + super(NativeAppTheme); + this.isAvailable = true; + + this._highContrastColors = NativeAppTheme.initialHighContrastColors; + this._isHighContrast = NativeAppTheme.initialHighContrast; + this.addListener( + 'highContrastChanged', + (nativeEvent: IHighContrastChangedEvent) => { + this._isHighContrast = nativeEvent.isHighContrast; + this._highContrastColors = nativeEvent.highContrastColors; + }, + ); + + this._currentTheme = NativeAppTheme.initialAppTheme; + this.addListener( + 'appThemeChanged', + ({currentTheme}: {currentTheme: AppThemeTypes}) => { + this._currentTheme = currentTheme; + }, + ); + } + + get currentTheme(): AppThemeTypes { + return this._currentTheme; + } + + get isHighContrast(): boolean { + return this._isHighContrast; + } + + get currentHighContrastColors(): IHighContrastColors { + return this._highContrastColors; + } +} + +// This module depends on the native `RCTAppTheme` module. If you don't include it, +// `AppTheme.isAvailable` will return `false`, and any method calls will throw. +class MissingNativeAppThemeShim extends MissingNativeEventEmitterShim { + public isAvailable = false; + public currentTheme = ''; + public isHighContrast = false; + public currentHighContrastColors = {} as IHighContrastColors; +} + +export const AppTheme = NativeAppTheme + ? new AppThemeModule() + : new MissingNativeAppThemeShim(); +export default AppTheme; diff --git a/vnext/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.windows.ts b/vnext/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.windows.ts new file mode 100644 index 00000000000..e1db1db8424 --- /dev/null +++ b/vnext/src/Libraries/Components/AccessibilityInfo/AccessibilityInfo.windows.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import { + NativeEventEmitter as NativeEventEmitter_TypeCarrier, + EmitterSubscription, +} from 'react-native'; +const RCTDeviceEventEmitter: typeof NativeEventEmitter_TypeCarrier = require('RCTDeviceEventEmitter'); + +const TOUCH_EXPLORATION_EVENT = 'touchExplorationDidChange'; + +type ChangeEventName = 'change'; + +const _subscriptions = new Map(); + +const AccessibilityInfo = { + fetch: (): Promise => { + // tslint:disable-next-line no-any + return new Promise( + ( + _resolve: (value?: boolean | PromiseLike) => void, + _reject: (reason?: any) => void, + ) => { + // TODO Hx: Implement this module. + return false; + }, + ); + }, + + addEventListener: ( + eventName: ChangeEventName, + handler: ((enabled: boolean) => void), + ) => { + // TODO Hx: Implement this module. + const listener = RCTDeviceEventEmitter.addListener( + TOUCH_EXPLORATION_EVENT, + (enabledInner: boolean) => { + handler(enabledInner); + }, + ); + _subscriptions.set(handler, listener); + }, + + removeEventListener: ( + eventName: ChangeEventName, + handler: Function, + ): void => { + // TODO Hx: Implement this module. + const listener = _subscriptions.get(handler); + if (!listener) { + return; + } + listener.remove(); + _subscriptions.delete(handler); + }, +}; + +export = AccessibilityInfo; diff --git a/vnext/src/Libraries/Components/Button.windows.js b/vnext/src/Libraries/Components/Button.windows.js new file mode 100644 index 00000000000..1dfb915571f --- /dev/null +++ b/vnext/src/Libraries/Components/Button.windows.js @@ -0,0 +1,127 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + * @flow + */ + +'use strict'; + +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const Text = require('Text'); +const TouchableHighlight = require('TouchableHighlight'); // [TODO(windows ISS) +const View = require('View'); + +const invariant = require('invariant'); + +import type {PressEvent} from 'CoreEventTypes'; + +type ButtonProps = $ReadOnly<{| + /** + * Text to display inside the button + */ + title: string, + + /** + * Handler to be called when the user taps the button + */ + onPress: (event?: PressEvent) => mixed, + + /** + * Color of the text (iOS), or background color of the button (Android) + */ + color?: ?string, + + /** + * TV preferred focus (see documentation for the View component). + */ + hasTVPreferredFocus?: ?boolean, + + /** + * Text to display for blindness accessibility features + */ + accessibilityLabel?: ?string, + /** + * Hint text to display blindness accessibility features + */ + accessibilityHint?: ?string, // TODO(OSS Candidate ISS#2710739) + /** + * If true, disable all interactions for this component. + */ + disabled?: ?boolean, + + /** + * Used to locate this view in end-to-end tests. + */ + testID?: ?string, +|}>; + +class Button extends React.Component { + render() { + const { + accessibilityLabel, + accessibilityHint, + color, + onPress, + title, + hasTVPreferredFocus, + disabled, + testID, + } = this.props; + const buttonStyles = [styles.button]; + const textStyles = [styles.text]; + if (color) { + buttonStyles.push({backgroundColor: color}); + } + const accessibilityStates = []; + if (disabled) { + buttonStyles.push(styles.buttonDisabled); + textStyles.push(styles.textDisabled); + accessibilityStates.push('disabled'); + } + invariant( + typeof title === 'string', + 'The title prop of a Button must be a string', + ); + const formattedTitle = title; + return ( + + + + {formattedTitle} + + + + ); + } +} + +const styles = StyleSheet.create({ + button: { + backgroundColor: '#2196F3', + borderRadius: 2, + }, + text: { + textAlign: 'center', + padding: 8, + color: 'white', + fontWeight: '500', + }, + buttonDisabled: { + backgroundColor: '#dfdfdf', + }, + textDisabled: { + color: '#a1a1a1', + }, +}); + +module.exports = Button; diff --git a/vnext/src/Libraries/Components/CheckBox/CheckBox.windows.tsx b/vnext/src/Libraries/Components/CheckBox/CheckBox.windows.tsx new file mode 100644 index 00000000000..08642fc10ae --- /dev/null +++ b/vnext/src/Libraries/Components/CheckBox/CheckBox.windows.tsx @@ -0,0 +1,69 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import {StyleSheet, requireNativeComponent} from 'react-native'; +import {ICheckBoxProps, ICheckBoxChangeEvent} from './CheckBoxProps'; + +const styles = StyleSheet.create({ + rctCheckBox: { + height: 32, + width: 32, + }, +}); + +const RCTCheckBox = requireNativeComponent('RCTCheckBox'); + +/** + * Renders a boolean input. + * + * This is a controlled component that requires an `onValueChange` callback that + * updates the `checked` prop in order for the component to reflect user actions. + * If the `checked` prop is not updated, the component will continue to render + * the supplied `checked` prop instead of the expected result of any user actions. + * + * @keyword checkbox + * @keyword toggle + */ +export class CheckBox extends React.Component { + // tslint:disable-next-line:no-any + private _rctCheckBox: any; + + public constructor(props: ICheckBoxProps) { + super(props); + this._rctCheckBox = React.createRef(); + } + + public render(): JSX.Element { + const props = {...this.props}; + props.onStartShouldSetResponder = () => true; + props.onResponderTerminationRequest = () => false; + props.style = [styles.rctCheckBox, this.props.style]; + + return ( + + ); + } + + private _setRef = (checkBox: CheckBox /*RCTCheckBox*/) => { + this._rctCheckBox = checkBox; + }; + + private _onChange = (event: ICheckBoxChangeEvent) => { + if (this._rctCheckBox) { + this._rctCheckBox.setNativeProps({checked: this.props.checked}); + } + + // Change the props after the native props are set in case the props + // change removes the component + this.props.onChange && this.props.onChange(event); + this.props.onValueChange && + this.props.onValueChange(event.nativeEvent.value); + }; +} + +export default CheckBox; diff --git a/vnext/src/Libraries/Components/DatePicker/DatePicker.windows.tsx b/vnext/src/Libraries/Components/DatePicker/DatePicker.windows.tsx new file mode 100644 index 00000000000..e32f5157ed5 --- /dev/null +++ b/vnext/src/Libraries/Components/DatePicker/DatePicker.windows.tsx @@ -0,0 +1,89 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import {requireNativeComponent, StyleSheet} from 'react-native'; +import {IDatePickerProps, IDatePickerChangeEvent} from './DatePickerProps'; +import * as React from 'react'; + +const styles = StyleSheet.create({ + rctDatePicker: { + height: 32, + width: 150, + }, +}); + +const RCTDatePicker = requireNativeComponent('RCTDatePicker'); + +export class DatePicker extends React.Component { + public static defaultProps: IDatePickerProps = { + dateFormat: 'dayofweek day month', + }; + + // tslint:disable-next-line:no-any + private _rctDatePicker: any; + + public constructor(props: IDatePickerProps) { + super(props); + this._rctDatePicker = React.createRef(); + } + public render(): JSX.Element { + const props = { + dayOfWeekFormat: this.props.dayOfWeekFormat, + dateFormat: this.props.dateFormat, + firstDayOfWeek: this.props.firstDayOfWeek, + maxDate: this.props.maxDate ? this.props.maxDate.getTime() : undefined, // time in milliseconds + minDate: this.props.minDate ? this.props.minDate.getTime() : undefined, // time in milliseconds + onChange: this.props.onChange, + placeholderText: this.props.placeholderText, + selectedDate: this.props.selectedDate + ? this.props.selectedDate.getTime() + : undefined, // time in milliseconds + style: [styles.rctDatePicker, this.props.style], + }; + + // The Date object returns timezone in minutes. Convert that to seconds + // and multiply by -1 so that the offset can be added to GMT time to get + // the correct value on the native side. + const timeZoneOffsetInSeconds = this.props.timeZoneOffsetInSeconds + ? this.props.timeZoneOffsetInSeconds + : this.props.selectedDate + ? -1 * this.props.selectedDate.getTimezoneOffset() * 60 + : undefined; + + return ( + + ); + } + + private _setRef = (datepicker: DatePicker /*RCTDatePicker*/) => { + this._rctDatePicker = datepicker; + }; + + private _onChange = (event: IDatePickerChangeEvent) => { + if (this.props.selectedDate) { + const propsTimeStamp = this.props.selectedDate.getTime(); + if (this._rctDatePicker) { + this._rctDatePicker.setNativeProps({selectedDate: propsTimeStamp}); + } + } + + // Change the props after the native props are set in case the props + // change removes the component + this.props.onChange && this.props.onChange(event); + + const nativeTimeStamp = event.nativeEvent.newDate; + this.props.onDateChange && + this.props.onDateChange(new Date(+nativeTimeStamp)); // Added the '+' operator to convert string to number + }; +} + +export default DatePicker; diff --git a/vnext/src/Libraries/Components/DatePicker/DatePickerIOS.windows.js b/vnext/src/Libraries/Components/DatePicker/DatePickerIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/DatePicker/DatePickerIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/DatePickerAndroid/DatePickerAndroid.windows.js b/vnext/src/Libraries/Components/DatePickerAndroid/DatePickerAndroid.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/DatePickerAndroid/DatePickerAndroid.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/DatePickerMacOS/DatePickerMacOS.windows.js b/vnext/src/Libraries/Components/DatePickerMacOS/DatePickerMacOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/DatePickerMacOS/DatePickerMacOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.windows.js b/vnext/src/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/Flyout/Flyout.windows.tsx b/vnext/src/Libraries/Components/Flyout/Flyout.windows.tsx new file mode 100644 index 00000000000..534083b855c --- /dev/null +++ b/vnext/src/Libraries/Components/Flyout/Flyout.windows.tsx @@ -0,0 +1,75 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import {findNodeHandle, requireNativeComponent, StyleSheet} from 'react-native'; +import {IFlyoutProps} from './FlyoutProps'; + +const styles = StyleSheet.create({ + rctFlyout: { + position: 'absolute', + }, +}); + +export interface IFlyoutTargetState { + target?: number | null; + targetRef?: React.ReactNode; +} + +const RCTFlyout = requireNativeComponent('RCTFlyout'); + +/** + * Renders a flyout component. + * + * This is a controlled component that requires an `onDismiss` callback that + * updates the `isOpen` prop in order for the component to reflect user actions. + * + * @keyword flyout + */ +export class Flyout extends React.Component { + public static getDerivedStateFromProps( + nextProps: IFlyoutProps, + prevState: IFlyoutTargetState, + ): IFlyoutTargetState { + // Check if we're given a new target property; we need to resolve it to a node handle before render + if (prevState.targetRef !== nextProps.target) { + // Map the 'target' property to a node tag to use in the native layer + /* tslint:disable-next-line no-any */ + const newtarget: number | null = findNodeHandle(nextProps.target as + | null + | number + | React.Component + | React.ComponentClass); + + return { + target: newtarget, + targetRef: nextProps.target, + }; + } + + return prevState; + } + + constructor(props: IFlyoutProps) { + super(props); + this.state = {target: undefined, targetRef: null}; + } + + public render(): JSX.Element { + const props = {...this.props}; + + return ( + + ); + } +} + +export default Flyout; diff --git a/vnext/src/Libraries/Components/Glyph/Glyph.windows.tsx b/vnext/src/Libraries/Components/Glyph/Glyph.windows.tsx new file mode 100644 index 00000000000..40c142e7879 --- /dev/null +++ b/vnext/src/Libraries/Components/Glyph/Glyph.windows.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import {requireNativeComponent} from 'react-native'; +import {GlyphProps} from './GlyphProps'; + +const RCTGlyph = requireNativeComponent('PLYIcon'); + +/** + * Glyph is a wrapper for the Xaml Glyph control + * + * This control is used to render Glyphs from a Font file, which might be + * used similar to SVG to have vector based images that also have the + * ability to be rendered with a specific color + */ +export class Glyph extends React.PureComponent { + public render(): JSX.Element { + const props = {...this.props}; + props.style = this.props.style; + + return ; + } +} + +export default Glyph; diff --git a/vnext/src/Libraries/Components/Keyboard/KeyboardExt.windows.tsx b/vnext/src/Libraries/Components/Keyboard/KeyboardExt.windows.tsx new file mode 100644 index 00000000000..36c1679581d --- /dev/null +++ b/vnext/src/Libraries/Components/Keyboard/KeyboardExt.windows.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import {IKeyboardProps} from './KeyboardExtProps'; + +export const supportKeyboard =

( + WrappedComponent: React.ComponentType

, +) => { + interface IForwardRefProps { + // tslint:disable-next-line:no-any + forwardedRef?: React.Ref; + } + + // children is used to avoid error: Property 'children' does not exist on type 'IntrinsicAttributes & ViewProps & + // IKeyboardProps & RefAttributes + // tslint:disable-next-line:no-any + type PropsWithoutForwardedRef = P & IKeyboardProps & {children?: any}; + type PropsWithForwardedRef = PropsWithoutForwardedRef & IForwardRefProps; + + class SupportKeyboard extends React.Component { + public render(): JSX.Element { + const {forwardedRef, ...rest} = this.props; + return ; + } + } + + // tslint:disable-next-line:no-any + return React.forwardRef( + (props: PropsWithoutForwardedRef, ref: React.Ref) => { + return ; + }, + ); +}; diff --git a/vnext/src/Libraries/Components/MaskedView/MaskedViewIOS.windows.js b/vnext/src/Libraries/Components/MaskedView/MaskedViewIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/MaskedView/MaskedViewIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/Navigation/NavigatorIOS.windows.js b/vnext/src/Libraries/Components/Navigation/NavigatorIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/Navigation/NavigatorIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/Picker/Picke.windows.uwp.tsx b/vnext/src/Libraries/Components/Picker/Picke.windows.uwp.tsx new file mode 100644 index 00000000000..57d2df11f42 --- /dev/null +++ b/vnext/src/Libraries/Components/Picker/Picke.windows.uwp.tsx @@ -0,0 +1,125 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import {requireNativeComponent, processColor} from 'react-native'; +import { + IPickerItemProps, + IPickerProps, + IPickerChangeEvent, +} from './PickerProps'; + +const RCTPicker = requireNativeComponent('RCTPicker'); + +class PickerItem extends React.Component { + public render(): JSX.Element | null { + return null; + } +} + +export interface IPickerItemData { + label: string; + // tslint:disable-next-line:no-any + value?: any; + textColor?: number; +} + +// tslint:disable-next-line:interface-name +interface State { + selectedIndex: number; + items: IPickerItemData[]; +} + +type PickerPropsWithChildren = Readonly<{children?: React.ReactNode}> & + Readonly; + +/** + * Picker is a controlled component, which expects the selectedValue prop to be updated + * whenever selection changes, or selection will revert to the prop selectedValue + * + * when using editable=true, onValueChange can be called with a selectedValue of null & + * Index of -1, and text will be provided. + * To maintain the text in the controlled component, props should reflect + * that state by specifying selectedValue of null and specify the text property. + */ +export class Picker extends React.Component { + public static Item = PickerItem; + + // tslint:disable-next-line:no-any + private _rctPicker: any; + + public static getDerivedStateFromProps( + props: PickerPropsWithChildren, + ): State { + let selectedIndex = -1; + const items: IPickerItemData[] = []; + React.Children.toArray(props.children).forEach( + (c: React.ReactNode, index: number) => { + const child = (c as unknown) as PickerItem; + if (child.props.value === props.selectedValue) { + selectedIndex = index; + } + items.push({ + value: child.props.value, + label: child.props.label, + textColor: processColor(child.props.color), + }); + }, + ); + return {selectedIndex, items}; + } + + public constructor(props: IPickerProps) { + super(props); + this._rctPicker = React.createRef(); + this.state = { + selectedIndex: 0, + items: [], + }; + } + + public render(): JSX.Element { + const props = {...this.props}; + props.onStartShouldSetResponder = () => true; + props.onResponderTerminationRequest = () => false; + props.style = this.props.style; + + return ( + + {this.props.children} + + ); + } + + private _setRef = (comboBox: Picker /*RCTPicker*/) => { + this._rctPicker = comboBox; + }; + + private _onChange = (event: IPickerChangeEvent) => { + if (this._rctPicker) { + this._rctPicker.setNativeProps({ + selectedIndex: this.state.selectedIndex, + text: this.props.text, + }); + } + + this.props.onChange && this.props.onChange(event); + this.props.onValueChange && + this.props.onValueChange( + event.nativeEvent.value, + event.nativeEvent.itemIndex, + event.nativeEvent.text, + ); + }; +} + +export default Picker; diff --git a/vnext/src/Libraries/Components/Picker/PickerAndroid.windows.js b/vnext/src/Libraries/Components/Picker/PickerAndroid.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/Picker/PickerAndroid.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/Picker/PickerIOS.windows.js b/vnext/src/Libraries/Components/Picker/PickerIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/Picker/PickerIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/Picker/PickerWindows.tsx b/vnext/src/Libraries/Components/Picker/PickerWindows.tsx new file mode 100644 index 00000000000..c9b9f0fdcbd --- /dev/null +++ b/vnext/src/Libraries/Components/Picker/PickerWindows.tsx @@ -0,0 +1,25 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import {IPickerItemProps, IPickerProps} from './PickerProps'; + +class PickerItem extends React.Component { + public render(): JSX.Element | null { + return null; + } +} + +export class Picker extends React.Component { + public static Item = PickerItem; + + public render(): JSX.Element | null { + return null; + } +} + +export default Picker; diff --git a/vnext/src/Libraries/Components/Popup/Popup.windows.tsx b/vnext/src/Libraries/Components/Popup/Popup.windows.tsx new file mode 100644 index 00000000000..819135feee5 --- /dev/null +++ b/vnext/src/Libraries/Components/Popup/Popup.windows.tsx @@ -0,0 +1,70 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import {findNodeHandle, requireNativeComponent, StyleSheet} from 'react-native'; +import {IPopupProps} from './PopupProps'; + +const styles = StyleSheet.create({ + rctPopup: { + position: 'absolute', + }, +}); + +export interface IPopupTargetState { + target?: number | null; + targetRef?: React.ReactNode; +} + +const RCTPopup = requireNativeComponent('RCTPopup'); + +/** + * Renders a popup component. + * + * This is a controlled component that requires an `onDismiss` callback that + * updates the `isOpen` prop in order for the component to reflect user actions. + * + * @keyword popup + */ +export class Popup extends React.Component { + public static getDerivedStateFromProps( + nextProps: IPopupProps, + prevState: IPopupTargetState, + ): IPopupTargetState { + // Check if we're given a new target property; we need to resolve it to a node handle before render + if (prevState.targetRef !== nextProps.target) { + // Map the 'target' property to a node tag to use in the native layer + /* tslint:disable-next-line no-any */ + const newTarget: number | null = findNodeHandle(nextProps.target as + | null + | number + | React.Component + | React.ComponentClass); + + return { + target: newTarget, + targetRef: nextProps.target, + }; + } + + return prevState; + } + + constructor(props: IPopupProps) { + super(props); + this.state = {target: undefined, targetRef: null}; + } + + public render(): JSX.Element { + const props = {...this.props}; + props.style = [styles.rctPopup, this.props.style]; + + return ; + } +} + +export default Popup; diff --git a/vnext/src/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.windows.js b/vnext/src/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/ProgressViewIOS/ProgressViewIOS.windows.js b/vnext/src/Libraries/Components/ProgressViewIOS/ProgressViewIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/ProgressViewIOS/ProgressViewIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/SafeAreaView/SafeAreaView.windows.js b/vnext/src/Libraries/Components/SafeAreaView/SafeAreaView.windows.js new file mode 100644 index 00000000000..2c2949022a9 --- /dev/null +++ b/vnext/src/Libraries/Components/SafeAreaView/SafeAreaView.windows.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @flow + */ +'use strict'; + +module.exports = require('View'); diff --git a/vnext/src/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.windows.js b/vnext/src/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/StatusBar/StatusBarIOS.windows.js b/vnext/src/Libraries/Components/StatusBar/StatusBarIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/StatusBar/StatusBarIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/Switch/Switch.windows.tsx b/vnext/src/Libraries/Components/Switch/Switch.windows.tsx new file mode 100644 index 00000000000..c4708622e12 --- /dev/null +++ b/vnext/src/Libraries/Components/Switch/Switch.windows.tsx @@ -0,0 +1,118 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import {StyleSheet, requireNativeComponent} from 'react-native'; +import {ISwitchProps, ISwitchChangeEvent} from './SwitchProps'; + +const styles = StyleSheet.create({ + rctSwitch: {height: 31, width: 51}, +}); + +const RCTSwitch = requireNativeComponent('RCTSwitch'); + +/** + * A visual toggle between two mutually exclusive states. + * + * This is a controlled component that requires an `onValueChange` callback that + * updates the `value` prop in order for the component to reflect user actions. + * If the `value` prop is not updated, the component will continue to render the + * supplied `value` prop instead of the expected result of any user actions. + * + * @keyword switch + * @keyword toggle + */ +class Switch extends React.Component { + // tslint:disable-next-line:no-any + private _rctSwitch: any; + private _lastNativeValue: boolean; + + public constructor(props: ISwitchProps) { + super(props); + this._rctSwitch = React.createRef(); + this._lastNativeValue = false; + } + + public render(): JSX.Element { + const props = {...this.props}; + let _thumbColor = this.props.thumbColor; + let _trackColorForFalse = this.props.trackColor + ? this.props.trackColor.false + : undefined; + let _trackColorForTrue = this.props.trackColor + ? this.props.trackColor.true + : undefined; + + if (this.props.thumbTintColor !== undefined) { + _thumbColor = this.props.thumbTintColor; + if (__DEV__) { + console.warn( + 'Switch: `thumbTintColor` is deprecated, use `thumbColor` instead.', + ); + } + } + + if (this.props.tintColor !== undefined) { + _trackColorForFalse = this.props.tintColor; + if (__DEV__) { + console.warn( + 'Switch: `tintColor` is deprecated, use `trackColor` instead.', + ); + } + } + + if (this.props.onTintColor !== undefined) { + _thumbColor = this.props.onTintColor; + if (__DEV__) { + console.warn( + 'Switch: `onTintColor` is deprecated, use `trackColor` instead.', + ); + } + } + + props.onResponderTerminationRequest = () => false; + props.onStartShouldSetResponder = () => true; + props.style = [styles.rctSwitch, this.props.style]; + props.value = this.props.value === true; + props.accessibilityRole = this.props.accessibilityRole + ? this.props.accessibilityRole + : 'button'; + props.thumbTintColor = _thumbColor; + props.tintColor = _trackColorForFalse; + props.onTintColor = _trackColorForTrue; + + return ( + + ); + } + + componentDidUpdate = () => { + // This is necessary in case native updates the switch and JS decides + // that the update should be ignored and we should stick with the value + // that we have in JS. + const value = this.props.value === true; + + if (this._lastNativeValue !== value && this._rctSwitch) { + this._rctSwitch.setNativeProps({value}); + } + }; + + private _setRef = (switchRef: Switch /*RCTSwitch*/) => { + this._rctSwitch = switchRef; + }; + + private _onChange = (event: ISwitchChangeEvent) => { + this.props.onChange && this.props.onChange(event); + this.props.onValueChange && + this.props.onValueChange(event.nativeEvent.value); + + this._lastNativeValue = event.nativeEvent.value; + this.forceUpdate(); + }; +} + +module.exports = Switch; diff --git a/vnext/src/Libraries/Components/Switch/SwitchProps.ts b/vnext/src/Libraries/Components/Switch/SwitchProps.ts new file mode 100644 index 00000000000..2297cb28e28 --- /dev/null +++ b/vnext/src/Libraries/Components/Switch/SwitchProps.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ + +import {ViewProps} from 'react-native'; + +export interface ISwitchTrackColorProps { + false?: string; + true?: string; +} + +export interface ISwitchProps extends ViewProps { + disabled?: boolean; + value?: boolean; + thumbColor?: string; + trackColor?: ISwitchTrackColorProps; + onChange?: (event: ISwitchChangeEvent) => void; + onValueChange?: (value: boolean) => void; + + // deprecated + thumbTintColor?: string; + tintColor?: string; + onTintColor?: string; +} + +export interface ISwitchChangeEvent { + nativeEvent: { + value: boolean; + }; +} diff --git a/vnext/src/Libraries/Components/TabBarIOS/TabBarIOS.windows.js b/vnext/src/Libraries/Components/TabBarIOS/TabBarIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/TabBarIOS/TabBarIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/TabBarIOS/TabBarItemIOS.windows.js b/vnext/src/Libraries/Components/TabBarIOS/TabBarItemIOS.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/TabBarIOS/TabBarItemIOS.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/TextInput/TextInput.windows.js b/vnext/src/Libraries/Components/TextInput/TextInput.windows.js new file mode 100644 index 00000000000..5a6936df5d7 --- /dev/null +++ b/vnext/src/Libraries/Components/TextInput/TextInput.windows.js @@ -0,0 +1,464 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * Portions copyright for react-native-windows: + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + * @flow + */ +'use strict'; + +const EventEmitter = require('EventEmitter'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const React = require('React'); +const createReactClass = require('create-react-class'); +const PropTypes = require('prop-types'); +const ReactNative = require('ReactNative'); +const StyleSheet = require('StyleSheet'); +const TextAncestor = require('TextAncestor'); +const TextInputState = require('TextInputState'); +/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error + * found when Flow v0.54 was deployed. To see the error delete this comment and + * run Flow. */ +const TimerMixin = require('react-timer-mixin'); +import type {SyntheticEvent, ScrollEvent} from 'CoreEventTypes'; +import type {PressEvent} from 'CoreEventTypes'; + +const requireNativeComponent = require('requireNativeComponent'); + +var RCTTextInput = requireNativeComponent('RCTTextInput'); // TODO(windows ISS) + +export type ChangeEvent = SyntheticEvent< + $ReadOnly<{| + eventCount: number, + target: number, + text: string, + |}>, +>; + +export type TextInputEvent = SyntheticEvent< + $ReadOnly<{| + eventCount: number, + previousText: string, + range: $ReadOnly<{| + start: number, + end: number, + |}>, + target: number, + text: string, + |}>, +>; + +export type ContentSizeChangeEvent = SyntheticEvent< + $ReadOnly<{| + target: number, + contentSize: $ReadOnly<{| + width: number, + height: number, + |}>, + |}>, +>; + +type TargetEvent = SyntheticEvent< + $ReadOnly<{| + target: number, + |}>, +>; + +export type BlurEvent = TargetEvent; +export type FocusEvent = TargetEvent; + +type Selection = $ReadOnly<{| + start: number, + end: number, +|}>; + +export type SelectionChangeEvent = SyntheticEvent< + $ReadOnly<{| + selection: Selection, + target: number, + |}>, +>; + +export type KeyPressEvent = SyntheticEvent< + $ReadOnly<{| + key: string, + target?: ?number, + eventCount?: ?number, + |}>, +>; + +export type EditingEvent = SyntheticEvent< + $ReadOnly<{| + eventCount: number, + text: string, + target: number, + |}>, +>; + +/** + * A foundational component for inputting text into the app via a + * keyboard. Props provide configurability for several features, such as + * auto-correction, auto-capitalization, placeholder text, and different keyboard + * types, such as a numeric keypad. + * + * The simplest use case is to plop down a `TextInput` and subscribe to the + * `onChangeText` events to read the user input. There are also other events, + * such as `onFocus` that can be subscribed to. A simple + * example: + * + * ```ReactNativeWebPlayer + * import React, { Component } from 'react'; + * import { AppRegistry, TextInput } from 'react-native'; + * + * export default class UselessTextInput extends Component { + * constructor(props) { + * super(props); + * this.state = { text: 'Useless Placeholder' }; + * } + * + * render() { + * return ( + * this.setState({text})} + * value={this.state.text} + * /> + * ); + * } + * } + * + * // skip this line if using Create React Native App + * AppRegistry.registerComponent('AwesomeProject', () => UselessTextInput); + * ``` + * + * Two methods exposed via the native element are .focus() and .blur() that + * will focus or blur the TextInput programmatically. + * + * Note that some props are only available with `multiline={true/false}`. + * Additionally, border styles that apply to only one side of the element + * (e.g., `borderBottomColor`, `borderLeftWidth`, etc.) will not be applied if + * `multiline=false`. To achieve the same effect, you can wrap your `TextInput` + * in a `View`: + * + * ```ReactNativeWebPlayer + * import React, { Component } from 'react'; + * import { AppRegistry, View, TextInput } from 'react-native'; + * + * class UselessTextInput extends Component { + * render() { + * return ( + * + * ); + * } + * } + * + * export default class UselessTextInputMultiline extends Component { + * constructor(props) { + * super(props); + * this.state = { + * text: 'Useless Multiline Placeholder', + * }; + * } + * + * // If you type something in the text box that is a color, the background will change to that + * // color. + * render() { + * return ( + * + * this.setState({text})} + * value={this.state.text} + * /> + * + * ); + * } + * } + * + * // skip these lines if using Create React Native App + * AppRegistry.registerComponent( + * 'AwesomeProject', + * () => UselessTextInputMultiline + * ); + * ``` + * + * `TextInput` has by default a border at the bottom of its view. This border + * has its padding set by the background image provided by the system, and it + * cannot be changed. Solutions to avoid this is to either not set height + * explicitly, case in which the system will take care of displaying the border + * in the correct position, or to not display the border by setting + * `underlineColorAndroid` to transparent. + * + */ + +const TextInput = createReactClass({ + displayName: 'TextInput', + statics: { + State: { + currentlyFocusedField: TextInputState.currentlyFocusedField, + focusTextInput: TextInputState.focusTextInput, + blurTextInput: TextInputState.blurTextInput, + }, + }, + + getDefaultProps(): Object { + return { + allowFontScaling: true, + }; + }, + /** + * `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We + * make `this` look like an actual native component class. + */ + mixins: [NativeMethodsMixin, TimerMixin], + + /** + * Returns `true` if the input is currently focused; `false` otherwise. + */ + isFocused: function(): boolean { + return ( + TextInputState.currentlyFocusedField() === + ReactNative.findNodeHandle(this._inputRef) + ); + }, + + /** + * Returns the native `TextView` node. + */ + getTextViewHandle: function(): any { + // TODO(OSS Candidate ISS#2710739) + return ReactNative.findNodeHandle(this._inputRef); + }, + + _inputRef: (undefined: any), + _focusSubscription: (undefined: ?Function), + _lastNativeText: (undefined: ?string), + _lastNativeSelection: (undefined: ?Selection), + _rafId: (null: ?AnimationFrameID), + + componentDidMount: function() { + this._lastNativeText = this.props.value; + const tag = ReactNative.findNodeHandle(this._inputRef); + if (tag != null) { + // tag is null only in unit tests + TextInputState.registerInput(tag); + } + + if (this.context.focusEmitter) { + this._focusSubscription = this.context.focusEmitter.addListener( + 'focus', + el => { + if (this === el) { + this._rafId = requestAnimationFrame(this.focus); + } else if (this.isFocused()) { + this.blur(); + } + }, + ); + if (this.props.autoFocus) { + this.context.onFocusRequested(this); + } + } else { + if (this.props.autoFocus) { + this._rafId = requestAnimationFrame(this.focus); + } + } + }, + + componentWillUnmount: function() { + this._focusSubscription && this._focusSubscription.remove(); + if (this.isFocused()) { + this.blur(); + } + if (this._rafId != null) { + cancelAnimationFrame(this._rafId); + } + }, + + contextTypes: { + onFocusRequested: PropTypes.func, + focusEmitter: PropTypes.instanceOf(EventEmitter), + }, + + /** + * Removes all text from the `TextInput`. + */ + clear: function() { + this.setNativeProps({text: ''}); + }, + + render: function() { + var props = Object.assign({}, this.props); + props.style = [styles.input, this.props.style]; + + return ( + + + + ); + }, + + _getText: function(): ?string { + return typeof this.props.value === 'string' + ? this.props.value + : typeof this.props.defaultValue === 'string' + ? this.props.defaultValue + : ''; + }, + + _setNativeRef: function(ref: any) { + this._inputRef = ref; + }, + + _onFocus: function(event: FocusEvent) { + // [TODO(android ISS) + // Set the focused TextInput field info in TextInputState. + // Delaying this to onFocus native event ensures that - + // 1. The state is updated only after the native code completes setting focus on the view + // 2. In case the focus is moving from one TextInput(A) to another TextInput(B), the state of + // A needs to be updated (blurred) before info about B is updated in TestInputState. + TextInputState.setFocusedTextInput( + ReactNative.findNodeHandle(this._inputRef), + ); // ]TODO(android ISS) + if (this.props.onFocus) { + this.props.onFocus(event); + } + }, + + _onPress: function(event: PressEvent) { + if (this.props.editable || this.props.editable === undefined) { + this.focus(); + } + }, + + _onChange: function(event: ChangeEvent) { + // Make sure to fire the mostRecentEventCount first so it is already set on + // native when the text value is set. + if (this._inputRef && this._inputRef.setNativeProps) { + this._inputRef.setNativeProps({ + mostRecentEventCount: event.nativeEvent.eventCount, + }); + } + + const text = event.nativeEvent.text; + this.props.onChange && this.props.onChange(event); + this.props.onChangeText && this.props.onChangeText(text); + + if (!this._inputRef) { + // calling `this.props.onChange` or `this.props.onChangeText` + // may clean up the input itself. Exits here. + return; + } + + this._lastNativeText = text; + this.forceUpdate(); + }, + + _onSelectionChange: function(event: SelectionChangeEvent) { + this.props.onSelectionChange && this.props.onSelectionChange(event); + + if (!this._inputRef) { + // calling `this.props.onSelectionChange` + // may clean up the input itself. Exits here. + return; + } + + this._lastNativeSelection = event.nativeEvent.selection; + + if (this.props.selection) { + this.forceUpdate(); + } + }, + + componentDidUpdate: function() { + // This is necessary in case native updates the text and JS decides + // that the update should be ignored and we should stick with the value + // that we have in JS. + const nativeProps = {}; + + if ( + this._lastNativeText !== this.props.value && + typeof this.props.value === 'string' + ) { + nativeProps.text = this.props.value; + } + + // Selection is also a controlled prop, if the native value doesn't match + // JS, update to the JS value. + const {selection} = this.props; + if ( + this._lastNativeSelection && + selection && + (this._lastNativeSelection.start !== selection.start || + this._lastNativeSelection.end !== selection.end) + ) { + nativeProps.selection = this.props.selection; + } + + if ( + Object.keys(nativeProps).length > 0 && + this._inputRef && + this._inputRef.setNativeProps + ) { + this._inputRef.setNativeProps(nativeProps); + } + }, + + _onBlur: function(event: Event) { + // [TODO(android ISS) + // Set the focused TextInput field info in TextInputState. + // Delaying this to onBlur native event ensures that - + // 1. The state is updated only after the native code completes clearing focus on the view + // 2. In case the focus is moving from one TextInput(A) to another TextInput(B), the state of + // A needs to be updated (blurred) before info about B is updated in TestInputState. + TextInputState.clearFocusedTextInput( + ReactNative.findNodeHandle(this._inputRef), + ); // ]TODO(android ISS) + if (this.props.onBlur) { + this.props.onBlur(event); + } + }, + + _onTextInput: function(event: TextInputEvent) { + this.props.onTextInput && this.props.onTextInput(event); + }, + + _onScroll: function(event: ScrollEvent) { + this.props.onScroll && this.props.onScroll(event); + }, +}); + +const styles = StyleSheet.create({ + multilineInput: { + // This default top inset makes RCTMultilineTextInputView seem as close as possible + // to single-line RCTSinglelineTextInputView defaults, using the system defaults + // of font size 17 and a height of 31 points. + paddingTop: 5, + }, +}); + +module.exports = TextInput; diff --git a/vnext/src/Libraries/Components/TextInput/TextInputState.windows.js b/vnext/src/Libraries/Components/TextInput/TextInputState.windows.js new file mode 100644 index 00000000000..50e6d48a0dd --- /dev/null +++ b/vnext/src/Libraries/Components/TextInput/TextInputState.windows.js @@ -0,0 +1,100 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * Portions copyright for react-native-windows: + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + * @flow + * + * This class is responsible for coordinating the "focused" + * state for TextInputs. All calls relating to the keyboard + * should be funneled through here. + */ + +'use strict'; + +const UIManager = require('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) { + UIManager.focus(textFieldID); + } +} + +/** + * @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; + UIManager.blur(textFieldID); + } +} + +/** [TODO(android ISS) + * @param {number} TextInputID id of the text field that has received focus + * Should be called after the view has received focus and fired the onFocus event + * noop if the focused text field is same + */ +function setFocusedTextInput(textFieldID: ?number) { + if (currentlyFocusedID !== textFieldID && textFieldID !== null) { + currentlyFocusedID = textFieldID; + } +} + +/** + * @param {number} TextInputID id of the text field whose focus has to be cleared + * Should be called after the view has cleared focus and fired the onFocus event + * noop if the focused text field is not same + */ +function clearFocusedTextInput(textFieldID: ?number) { + if (currentlyFocusedID === textFieldID && textFieldID !== null) { + currentlyFocusedID = null; + } +} // ]TODO(android ISS) + +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, + setFocusedTextInput, // TODO(android ISS) + clearFocusedTextInput, // TODO(android ISS) + focusTextInput, + blurTextInput, + registerInput, + unregisterInput, + isTextInput, +}; diff --git a/vnext/src/Libraries/Components/TimePickerAndroid/TimePickerAndroid.windows.js b/vnext/src/Libraries/Components/TimePickerAndroid/TimePickerAndroid.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/TimePickerAndroid/TimePickerAndroid.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/ToastAndroid/ToastAndroid.windows.js b/vnext/src/Libraries/Components/ToastAndroid/ToastAndroid.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/ToastAndroid/ToastAndroid.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/ToolbarAndroid/ToolbarAndroid.windows.js b/vnext/src/Libraries/Components/ToolbarAndroid/ToolbarAndroid.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/ToolbarAndroid/ToolbarAndroid.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/Touchable/TouchableHighlight.windows.js b/vnext/src/Libraries/Components/Touchable/TouchableHighlight.windows.js new file mode 100644 index 00000000000..5c519323841 --- /dev/null +++ b/vnext/src/Libraries/Components/Touchable/TouchableHighlight.windows.js @@ -0,0 +1,514 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ +'use strict'; + +const DeprecatedColorPropType = require('DeprecatedColorPropType'); +const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const Platform = require('Platform'); +const PropTypes = require('prop-types'); +const React = require('React'); +const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +const StyleSheet = require('StyleSheet'); +const Touchable = require('Touchable'); +const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); +const View = require('View'); + +const createReactClass = require('create-react-class'); +const ensurePositiveDelayProps = require('ensurePositiveDelayProps'); + +import type {PressEvent} from 'CoreEventTypes'; +import type {ViewStyleProp} from 'StyleSheet'; +import type {ColorValue} from 'StyleSheetTypes'; +import type {Props as TouchableWithoutFeedbackProps} from 'TouchableWithoutFeedback'; +import type {TVParallaxPropertiesType} from 'TVViewPropTypes'; + +const DEFAULT_PROPS = { + activeOpacity: 0.85, + delayPressOut: 100, + underlayColor: 'black', +}; + +const handledNativeKeyboardEvents = [ + {code: 'Space'}, + {code: 'Enter'}, + {code: 'GamepadA'}, +]; + +const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; + +type IOSProps = $ReadOnly<{| + hasTVPreferredFocus?: ?boolean, + tvParallaxProperties?: ?TVParallaxPropertiesType, +|}>; + +type Props = $ReadOnly<{| + ...TouchableWithoutFeedbackProps, + ...IOSProps, + + activeOpacity?: ?number, + underlayColor?: ?ColorValue, + style?: ?ViewStyleProp, + onShowUnderlay?: ?() => void, + onHideUnderlay?: ?() => void, + testOnly_pressed?: ?boolean, +|}>; + +/** + * A wrapper for making views respond properly to touches. + * On press down, the opacity of the wrapped view is decreased, which allows + * the underlay color to show through, darkening or tinting the view. + * + * The underlay comes from wrapping the child in a new View, which can affect + * layout, and sometimes cause unwanted visual artifacts if not used correctly, + * for example if the backgroundColor of the wrapped view isn't explicitly set + * to an opaque color. + * + * TouchableHighlight must have one child (not zero or more than one). + * If you wish to have several child components, wrap them in a View. + * + * Example: + * + * ``` + * renderButton: function() { + * return ( + * + * + * + * ); + * }, + * ``` + * + * + * ### Example + * + * ```ReactNativeWebPlayer + * import React, { Component } from 'react' + * import { + * AppRegistry, + * StyleSheet, + * TouchableHighlight, + * Text, + * View, + * } from 'react-native' + * + * class App extends Component { + * constructor(props) { + * super(props) + * this.state = { count: 0 } + * } + * + * onPress = () => { + * this.setState({ + * count: this.state.count+1 + * }) + * } + * + * render() { + * return ( + * + * + * Touch Here + * + * + * + * { this.state.count !== 0 ? this.state.count: null} + * + * + * + * ) + * } + * } + * + * const styles = StyleSheet.create({ + * container: { + * flex: 1, + * justifyContent: 'center', + * paddingHorizontal: 10 + * }, + * button: { + * alignItems: 'center', + * backgroundColor: '#DDDDDD', + * padding: 10 + * }, + * countContainer: { + * alignItems: 'center', + * padding: 10 + * }, + * countText: { + * color: '#FF00FF' + * } + * }) + * + * AppRegistry.registerComponent('App', () => App) + * ``` + * + */ + +const TouchableHighlight = ((createReactClass({ + displayName: 'TouchableHighlight', + propTypes: { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + ...TouchableWithoutFeedback.propTypes, + /** + * Determines what the opacity of the wrapped view should be when touch is + * active. + */ + activeOpacity: PropTypes.number, + /** + * The color of the underlay that will show through when the touch is + * active. + */ + underlayColor: DeprecatedColorPropType, + /** + * Style to apply to the container/underlay. Most commonly used to make sure + * rounded corners match the wrapped component. + */ + style: DeprecatedViewPropTypes.style, + /** + * Called immediately after the underlay is shown + */ + onShowUnderlay: PropTypes.func, + /** + * Called immediately after the underlay is hidden + */ + onHideUnderlay: PropTypes.func, + /** + * *(Apple TV only)* TV preferred focus (see documentation for the View component). + * + * @platform ios + */ + hasTVPreferredFocus: PropTypes.bool, + /** + * Apple TV parallax effects + */ + tvParallaxProperties: PropTypes.object, + /** + * Handy for snapshot tests. + */ + testOnly_pressed: PropTypes.bool, + }, + + mixins: [NativeMethodsMixin, Touchable.Mixin.withoutDefaultFocusAndBlur], + + getDefaultProps: () => DEFAULT_PROPS, + + getInitialState: function() { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + this._isMounted = false; + if (this.props.testOnly_pressed) { + return { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + ...this.touchableGetInitialState(), + extraChildStyle: { + opacity: this.props.activeOpacity, + }, + extraUnderlayStyle: { + backgroundColor: this.props.underlayColor, + }, + }; + } else { + return { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + ...this.touchableGetInitialState(), + extraChildStyle: null, + extraUnderlayStyle: null, + }; + } + }, + + componentDidMount: function() { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + this._isMounted = true; + ensurePositiveDelayProps(this.props); + }, + + componentWillUnmount: function() { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + this._isMounted = false; + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + clearTimeout(this._hideTimeout); + }, + + UNSAFE_componentWillReceiveProps: function(nextProps) { + ensurePositiveDelayProps(nextProps); + }, + + viewConfig: { + uiViewClassName: 'RCTView', + validAttributes: ReactNativeViewAttributes.RCTView, + }, + + /** + * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are + * defined on your component. + */ + touchableHandleActivePressIn: function(e: PressEvent) { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + clearTimeout(this._hideTimeout); + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + this._hideTimeout = null; + this._showUnderlay(); + this.props.onPressIn && this.props.onPressIn(e); + }, + + touchableHandleActivePressOut: function(e: PressEvent) { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + if (!this._hideTimeout) { + this._hideUnderlay(); + } + this.props.onPressOut && this.props.onPressOut(e); + }, + + touchableHandleFocus: function(e: Event) { + if (Platform.isTV) { + this._showUnderlay(); + } + this.props.onFocus && this.props.onFocus(e); + }, + + touchableHandleBlur: function(e: Event) { + if (Platform.isTV) { + this._hideUnderlay(); + } + this.props.onBlur && this.props.onBlur(e); + }, + + touchableHandlePress: function(e: PressEvent) { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + clearTimeout(this._hideTimeout); + if (!Platform.isTV) { + this._showUnderlay(); + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + this._hideTimeout = setTimeout( + this._hideUnderlay, + this.props.delayPressOut, + ); + } + this.props.onPress && this.props.onPress(e); + }, + + touchableHandleLongPress: function(e: PressEvent) { + this.props.onLongPress && this.props.onLongPress(e); + }, + + touchableGetPressRectOffset: function() { + return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET; + }, + + touchableGetHitSlop: function() { + return this.props.hitSlop; + }, + + touchableGetHighlightDelayMS: function() { + return this.props.delayPressIn; + }, + + touchableGetLongPressDelayMS: function() { + return this.props.delayLongPress; + }, + + touchableGetPressOutDelayMS: function() { + return this.props.delayPressOut; + }, + + _showUnderlay: function() { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + if (!this._isMounted || !this._hasPressHandler()) { + return; + } + this.setState({ + extraChildStyle: { + opacity: this.props.activeOpacity, + }, + extraUnderlayStyle: { + backgroundColor: this.props.underlayColor, + }, + }); + this.props.onShowUnderlay && this.props.onShowUnderlay(); + }, + + _hideUnderlay: function() { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + clearTimeout(this._hideTimeout); + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + this._hideTimeout = null; + if (this.props.testOnly_pressed) { + return; + } + if (this._hasPressHandler()) { + this.setState({ + extraChildStyle: null, + extraUnderlayStyle: null, + }); + this.props.onHideUnderlay && this.props.onHideUnderlay(); + } + }, + + _hasPressHandler: function() { + return !!( + this.props.onPress || + this.props.onPressIn || + this.props.onPressOut || + this.props.onLongPress + ); + }, + + _onKeyUp: function(ev) { + if ( + (ev.nativeEvent.code === 'Space' || + ev.nativeEvent.code === 'Enter' || + ev.nativeEvent.code === 'GamepadA') && + !this.props.disabled + ) { + this.touchableHandlePress(); + } + }, + + _onKeyDown: function(ev) { + if ( + (ev.nativeEvent.code === 'Space' || + ev.nativeEvent.code === 'Enter' || + ev.nativeEvent.code === 'GamepadA') && + !this.props.disabled + ) { + this.touchableHandleActivePressIn(); + } + }, + + render: function() { + const child = React.Children.only(this.props.children); + return ( + =0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} + onResponderTerminationRequest={ + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses + * an error found when Flow v0.89 was deployed. To see the error, + * delete this comment and run Flow. */ + this.touchableHandleResponderTerminationRequest + } + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onResponderGrant={this.touchableHandleResponderGrant} + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onResponderMove={this.touchableHandleResponderMove} + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onResponderRelease={this.touchableHandleResponderRelease} + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onResponderTerminate={this.touchableHandleResponderTerminate} + tooltip={this.props.tooltip} // TODO(macOS/win ISS#2323203) + clickable={ + this.props.clickable !== false && this.props.onPress !== undefined + } // TODO(android ISS) + onClick={this.touchableHandlePress} // TODO(android ISS) + onMouseEnter={this.props.onMouseEnter} // [TODO(macOS/win ISS#2323203) + onMouseLeave={this.props.onMouseLeave} + keyDownEvents={handledNativeKeyboardEvents} + keyUpEvents={handledNativeKeyboardEvents} + onDragEnter={this.props.onDragEnter} + onDragLeave={this.props.onDragLeave} + onDrop={this.props.onDrop} + draggedTypes={this.props.draggedTypes} // ]TODO(macOS/win ISS#2323203) + nativeID={this.props.nativeID} + testID={this.props.testID}> + {React.cloneElement(child, { + style: StyleSheet.compose( + child.props.style, + this.state.extraChildStyle, + ), + })} + {Touchable.renderDebugView({ + color: 'green', + hitSlop: this.props.hitSlop, + })} + + ); + }, +}): any): React.ComponentType); + +module.exports = TouchableHighlight; diff --git a/vnext/src/Libraries/Components/Touchable/TouchableNativeFeedback.windows.tsx b/vnext/src/Libraries/Components/Touchable/TouchableNativeFeedback.windows.tsx new file mode 100644 index 00000000000..c8a10fe02a4 --- /dev/null +++ b/vnext/src/Libraries/Components/Touchable/TouchableNativeFeedback.windows.tsx @@ -0,0 +1,52 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +import * as React from 'react'; +import { + StyleSheet, + View, + Text, + TouchableNativeFeedbackProps, +} from 'react-native'; + +const styles = StyleSheet.create({ + container: { + height: 100, + width: 300, + backgroundColor: '#ffbcbc', + borderWidth: 1, + borderColor: 'red', + alignItems: 'center', + justifyContent: 'center', + margin: 10, + }, + info: { + color: '#333333', + margin: 20, + }, +}); + +class DummyTouchableNativeFeedback extends React.Component< + TouchableNativeFeedbackProps +> { + public static SelectableBackground = () => ({}); + public static SelectableBackgroundBorderless = () => ({}); + public static Ripple = () => ({}); + public static canUseNativeForeground = () => false; + + public render(): JSX.Element { + return ( + + + TouchableNativeFeedback is not supported on this platform! + + + ); + } +} + +export default DummyTouchableNativeFeedback; diff --git a/vnext/src/Libraries/Components/Touchable/TouchableOpacity.windows.js b/vnext/src/Libraries/Components/Touchable/TouchableOpacity.windows.js new file mode 100644 index 00000000000..54cb8aa33da --- /dev/null +++ b/vnext/src/Libraries/Components/Touchable/TouchableOpacity.windows.js @@ -0,0 +1,391 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 Animated = require('Animated'); +const Easing = require('Easing'); +const NativeMethodsMixin = require('NativeMethodsMixin'); +const Platform = require('Platform'); +const React = require('React'); +const PropTypes = require('prop-types'); +const Touchable = require('Touchable'); +const TouchableWithoutFeedback = require('TouchableWithoutFeedback'); + +const createReactClass = require('create-react-class'); +const ensurePositiveDelayProps = require('ensurePositiveDelayProps'); +const flattenStyle = require('flattenStyle'); + +import type {Props as TouchableWithoutFeedbackProps} from 'TouchableWithoutFeedback'; +import type {ViewStyleProp} from 'StyleSheet'; +import type {TVParallaxPropertiesType} from 'TVViewPropTypes'; +import type {PressEvent} from 'CoreEventTypes'; + +const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; + +const handledNativeKeyboardEvents = [ + {code: 'Space'}, + {code: 'Enter'}, + {code: 'GamepadA'}, +]; + +type TVProps = $ReadOnly<{| + hasTVPreferredFocus?: ?boolean, + tvParallaxProperties?: ?TVParallaxPropertiesType, +|}>; + +type Props = $ReadOnly<{| + ...TouchableWithoutFeedbackProps, + ...TVProps, + activeOpacity?: ?number, + style?: ?ViewStyleProp, +|}>; + +/** + * A wrapper for making views respond properly to touches. + * On press down, the opacity of the wrapped view is decreased, dimming it. + * + * Opacity is controlled by wrapping the children in an Animated.View, which is + * added to the view hiearchy. Be aware that this can affect layout. + * + * Example: + * + * ``` + * renderButton: function() { + * return ( + * + * + * + * ); + * }, + * ``` + * ### Example + * + * ```ReactNativeWebPlayer + * import React, { Component } from 'react' + * import { + * AppRegistry, + * StyleSheet, + * TouchableOpacity, + * Text, + * View, + * } from 'react-native' + * + * class App extends Component { + * constructor(props) { + * super(props) + * this.state = { count: 0 } + * } + * + * onPress = () => { + * this.setState({ + * count: this.state.count+1 + * }) + * } + * + * render() { + * return ( + * + * + * Touch Here + * + * + * + * { this.state.count !== 0 ? this.state.count: null} + * + * + * + * ) + * } + * } + * + * const styles = StyleSheet.create({ + * container: { + * flex: 1, + * justifyContent: 'center', + * paddingHorizontal: 10 + * }, + * button: { + * alignItems: 'center', + * backgroundColor: '#DDDDDD', + * padding: 10 + * }, + * countContainer: { + * alignItems: 'center', + * padding: 10 + * }, + * countText: { + * color: '#FF00FF' + * } + * }) + * + * AppRegistry.registerComponent('App', () => App) + * ``` + * + */ +const TouchableOpacity = ((createReactClass({ + displayName: 'TouchableOpacity', + mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur, NativeMethodsMixin], + + propTypes: { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + ...TouchableWithoutFeedback.propTypes, + /** + * Determines what the opacity of the wrapped view should be when touch is + * active. Defaults to 0.2. + */ + activeOpacity: PropTypes.number, + /** + * TV preferred focus (see documentation for the View component). + */ + hasTVPreferredFocus: PropTypes.bool, + /** + * Apple TV parallax effects + */ + tvParallaxProperties: PropTypes.object, + }, + + getDefaultProps: function() { + return { + activeOpacity: 0.2, + }; + }, + + getInitialState: function() { + return { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + ...this.touchableGetInitialState(), + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + anim: new Animated.Value(this._getChildStyleOpacityWithDefault()), + }; + }, + + componentDidMount: function() { + ensurePositiveDelayProps(this.props); + }, + + UNSAFE_componentWillReceiveProps: function(nextProps) { + ensurePositiveDelayProps(nextProps); + }, + + componentDidUpdate: function(prevProps, prevState) { + if (this.props.disabled !== prevProps.disabled) { + this._opacityInactive(250); + } + }, + + /** + * Animate the touchable to a new opacity. + */ + setOpacityTo: function(value: number, duration: number) { + Animated.timing(this.state.anim, { + toValue: value, + duration: duration, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true, + }).start(); + }, + + /** + * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are + * defined on your component. + */ + touchableHandleActivePressIn: function(e: PressEvent) { + if (e.dispatchConfig.registrationName === 'onResponderGrant') { + this._opacityActive(0); + } else { + this._opacityActive(150); + } + this.props.onPressIn && this.props.onPressIn(e); + }, + + touchableHandleActivePressOut: function(e: PressEvent) { + this._opacityInactive(250); + this.props.onPressOut && this.props.onPressOut(e); + }, + + touchableHandleFocus: function(e: Event) { + if (Platform.isTV) { + this._opacityActive(150); + } + this.props.onFocus && this.props.onFocus(e); + }, + + touchableHandleBlur: function(e: Event) { + if (Platform.isTV) { + this._opacityInactive(250); + } + this.props.onBlur && this.props.onBlur(e); + }, + + touchableHandlePress: function(e: PressEvent) { + this.props.onPress && this.props.onPress(e); + }, + + touchableHandleLongPress: function(e: PressEvent) { + this.props.onLongPress && this.props.onLongPress(e); + }, + + touchableGetPressRectOffset: function() { + return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET; + }, + + touchableGetHitSlop: function() { + return this.props.hitSlop; + }, + + touchableGetHighlightDelayMS: function() { + return this.props.delayPressIn || 0; + }, + + touchableGetLongPressDelayMS: function() { + return this.props.delayLongPress === 0 + ? 0 + : this.props.delayLongPress || 500; + }, + + touchableGetPressOutDelayMS: function() { + return this.props.delayPressOut; + }, + + _opacityActive: function(duration: number) { + this.setOpacityTo(this.props.activeOpacity, duration); + }, + + _opacityInactive: function(duration: number) { + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ + this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration); + }, + + _getChildStyleOpacityWithDefault: function() { + const childStyle = flattenStyle(this.props.style) || {}; + return childStyle.opacity == null ? 1 : childStyle.opacity; + }, + + _onKeyUp: function(ev) { + if ( + (ev.nativeEvent.code === 'Space' || + ev.nativeEvent.code === 'Enter' || + ev.nativeEvent.code === 'GamepadA') && + !this.props.disabled + ) { + this.touchableHandlePress(); + } + }, + + _onKeyDown: function(ev) { + if ( + (ev.nativeEvent.code === 'Space' || + ev.nativeEvent.code === 'Enter' || + ev.nativeEvent.code === 'GamepadA') && + !this.props.disabled + ) { + this.touchableHandleActivePressIn(); + } + }, + + render: function() { + return ( + =0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} + onResponderTerminationRequest={ + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses + * an error found when Flow v0.89 was deployed. To see the error, + * delete this comment and run Flow. */ + this.touchableHandleResponderTerminationRequest + } + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onResponderGrant={this.touchableHandleResponderGrant} + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onResponderMove={this.touchableHandleResponderMove} + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onResponderRelease={this.touchableHandleResponderRelease} + /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete + * this comment and run Flow. */ + onResponderTerminate={this.touchableHandleResponderTerminate} + tooltip={this.props.tooltip} // TODO(macOS/win ISS#2323203) + clickable={ + this.props.clickable !== false && this.props.onPress !== undefined + } // TODO(android ISS) + onClick={this.touchableHandlePress} // TODO(android ISS) + onMouseEnter={this.props.onMouseEnter} // [TODO(macOS ISS#2323203) + onMouseLeave={this.props.onMouseLeave} + keyDownEvents={handledNativeKeyboardEvents} + keyUpEvents={handledNativeKeyboardEvents} + onDragEnter={this.props.onDragEnter} + onDragLeave={this.props.onDragLeave} + onDrop={this.props.onDrop} + draggedTypes={this.props.draggedTypes}> + {this.props.children} + {Touchable.renderDebugView({ + color: 'cyan', + hitSlop: this.props.hitSlop, + })} + + ); + }, +}): any): React.ComponentType); + +module.exports = TouchableOpacity; diff --git a/vnext/src/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js b/vnext/src/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js new file mode 100644 index 00000000000..14e948cdfa0 --- /dev/null +++ b/vnext/src/Libraries/Components/Touchable/TouchableWithoutFeedback.windows.js @@ -0,0 +1,350 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 DeprecatedEdgeInsetsPropType = require('DeprecatedEdgeInsetsPropType'); +const React = require('React'); +const PropTypes = require('prop-types'); +const Touchable = require('Touchable'); +const View = require('View'); + +const createReactClass = require('create-react-class'); +const ensurePositiveDelayProps = require('ensurePositiveDelayProps'); + +const { + DeprecatedAccessibilityComponentTypes, + DeprecatedAccessibilityRoles, + DeprecatedAccessibilityStates, + DeprecatedAccessibilityTraits, +} = require('DeprecatedViewAccessibility'); + +import type {SyntheticEvent, LayoutEvent, PressEvent} from 'CoreEventTypes'; +import type {EdgeInsetsProp} from 'EdgeInsetsPropType'; +import type { + AccessibilityComponentType, + AccessibilityRole, + AccessibilityStates, + AccessibilityTraits, +} from 'ViewAccessibility'; + +type TargetEvent = SyntheticEvent< + $ReadOnly<{| + target: number, + |}>, +>; + +type BlurEvent = TargetEvent; +type FocusEvent = TargetEvent; + +const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; + +export type Props = $ReadOnly<{| + accessible?: ?boolean, + accessibilityComponentType?: ?AccessibilityComponentType, + accessibilityLabel?: ?Stringish, + accessibilityHint?: ?Stringish, + accessibilityIgnoresInvertColors?: ?boolean, + accessibilityRole?: ?AccessibilityRole, + accessibilityStates?: ?AccessibilityStates, + accessibilityTraits?: ?AccessibilityTraits, + accessibilityPosInSet?: ?number, // https://github.com/ReactWindows/discussions-and-proposals/blob/harinik-accessibility/proposals/0000-accessibilityapis-lists.md + accessibilitySetSize?: ?number, // https://github.com/ReactWindows/discussions-and-proposals/blob/harinik-accessibility/proposals/0000-accessibilityapis-lists.md + children?: ?React.Node, + delayLongPress?: ?number, + delayPressIn?: ?number, + delayPressOut?: ?number, + disabled?: ?boolean, + hitSlop?: ?EdgeInsetsProp, + nativeID?: ?string, + onBlur?: ?(e: BlurEvent) => void, + onFocus?: ?(e: FocusEvent) => void, + onLayout?: ?(event: LayoutEvent) => mixed, + onLongPress?: ?(event: PressEvent) => mixed, + onPress?: ?(event: PressEvent) => mixed, + onPressIn?: ?(event: PressEvent) => mixed, + onPressOut?: ?(event: PressEvent) => mixed, + onAccessibilityTap?: ?Function, // TODO(OSS Candidate ISS#2710739) + acceptsKeyboardFocus?: ?boolean, // [TODO(macOS ISS#2323203) + onMouseEnter?: ?Function, + onMouseLeave?: ?Function, + onDragEnter?: ?Function, + onMouseLeave?: ?Function, + onDragEnter?: ?Function, + onDragLeave?: ?Function, + onDrop?: ?Function, + pressRetentionOffset?: ?EdgeInsetsProp, + rejectResponderTermination?: ?boolean, + testID?: ?string, +|}>; + +/** + * Do not use unless you have a very good reason. All elements that + * respond to press should have a visual feedback when touched. + * + * TouchableWithoutFeedback supports only one child. + * If you wish to have several child components, wrap them in a View. + */ +const TouchableWithoutFeedback = ((createReactClass({ + displayName: 'TouchableWithoutFeedback', + mixins: [Touchable.Mixin], + + propTypes: { + accessible: PropTypes.bool, + accessibilityLabel: PropTypes.node, + accessibilityHint: PropTypes.string, + accessibilityComponentType: PropTypes.oneOf( + DeprecatedAccessibilityComponentTypes, + ), + accessibilityRole: PropTypes.oneOf(DeprecatedAccessibilityRoles), + accessibilityStates: PropTypes.arrayOf( + PropTypes.oneOf(DeprecatedAccessibilityStates), + ), + accessibilityTraits: PropTypes.oneOfType([ + PropTypes.oneOf(DeprecatedAccessibilityTraits), + PropTypes.arrayOf(PropTypes.oneOf(DeprecatedAccessibilityTraits)), + ]), + accessibilityPosInSet: PropTypes.number, // https://github.com/ReactWindows/discussions-and-proposals/blob/harinik-accessibility/proposals/0000-accessibilityapis-lists.md + accessibilitySetSize: PropTypes.number, // https://github.com/ReactWindows/discussions-and-proposals/blob/harinik-accessibility/proposals/0000-accessibilityapis-lists.md + onAccessibilityTap: PropTypes.func, // TODO(OSS Candidate ISS#2710739) + tabIndex: PropTypes.number, // TODO(macOS/win ISS#2323203) + + /** + * When `accessible` is true (which is the default) this may be called when + * the OS-specific concept of "focus" occurs. Some platforms may not have + * the concept of focus. + */ + onFocus: PropTypes.func, + /** + * When `accessible` is true (which is the default) this may be called when + * the OS-specific concept of "blur" occurs, meaning the element lost focus. + * Some platforms may not have the concept of blur. + */ + onBlur: PropTypes.func, + /** + * If true, disable all interactions for this component. + */ + disabled: PropTypes.bool, + /** + * Called when the mouse enters the touchable element + */ + onMouseEnter: PropTypes.func, // TODO(macOS ISS#2323203) + /** + * Called when the mouse exits the touchable element + */ + onMouseLeave: PropTypes.func, // TODO(macOS ISS#2323203) + /** + * Fired when a dragged element enters a valid drop target + */ + onDragEnter: PropTypes.func, // TODO(macOS ISS#2323203) + /** + * Fired when a dragged element leaves a valid drop target + */ + onDragLeave: PropTypes.func, // TODO(macOS ISS#2323203) + /** + * Fired when an element is dropped on a valid drop target + */ + onDrop: PropTypes.func, // TODO(macOS ISS#2323203) + tooltip: PropTypes.string, // TODO(macOS/win ISS#2323203) + /** + * Called when the touch is released, but not if cancelled (e.g. by a scroll + * that steals the responder lock). + */ + onPress: PropTypes.func, + /** + * Called as soon as the touchable element is pressed and invoked even before onPress. + * This can be useful when making network requests. + */ + onPressIn: PropTypes.func, + /** + * Called as soon as the touch is released even before onPress. + */ + onPressOut: PropTypes.func, + /** + * Invoked on mount and layout changes with + * + * `{nativeEvent: {layout: {x, y, width, height}}}` + */ + onLayout: PropTypes.func, + + onLongPress: PropTypes.func, + + nativeID: PropTypes.string, + testID: PropTypes.string, + + /** + * Delay in ms, from the start of the touch, before onPressIn is called. + */ + delayPressIn: PropTypes.number, + /** + * Delay in ms, from the release of the touch, before onPressOut is called. + */ + delayPressOut: PropTypes.number, + /** + * Delay in ms, from onPressIn, before onLongPress is called. + */ + delayLongPress: PropTypes.number, + /** + * When the scroll view is disabled, this defines how far your touch may + * move off of the button, before deactivating the button. Once deactivated, + * try moving it back and you'll see that the button is once again + * reactivated! Move it back and forth several times while the scroll view + * is disabled. Ensure you pass in a constant to reduce memory allocations. + */ + pressRetentionOffset: DeprecatedEdgeInsetsPropType, + /** + * This defines how far your touch can start away from the button. This is + * added to `pressRetentionOffset` when moving off of the button. + * ** NOTE ** + * The touch area never extends past the parent view bounds and the Z-index + * of sibling views always takes precedence if a touch hits two overlapping + * views. + */ + hitSlop: DeprecatedEdgeInsetsPropType, + }, + + getInitialState: function() { + return this.touchableGetInitialState(); + }, + + componentDidMount: function() { + ensurePositiveDelayProps(this.props); + }, + + UNSAFE_componentWillReceiveProps: function(nextProps: Object) { + ensurePositiveDelayProps(nextProps); + }, + + /** + * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are + * defined on your component. + */ + touchableHandlePress: function(e: PressEvent) { + this.props.onPress && this.props.onPress(e); + }, + + touchableHandleActivePressIn: function(e: PressEvent) { + this.props.onPressIn && this.props.onPressIn(e); + }, + + touchableHandleActivePressOut: function(e: PressEvent) { + this.props.onPressOut && this.props.onPressOut(e); + }, + + touchableHandleLongPress: function(e: PressEvent) { + this.props.onLongPress && this.props.onLongPress(e); + }, + + touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET { + // $FlowFixMe Invalid prop usage + return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET; + }, + + touchableGetHitSlop: function(): ?Object { + return this.props.hitSlop; + }, + + touchableGetHighlightDelayMS: function(): number { + return this.props.delayPressIn || 0; + }, + + touchableGetLongPressDelayMS: function(): number { + return this.props.delayLongPress === 0 + ? 0 + : this.props.delayLongPress || 500; + }, + + touchableGetPressOutDelayMS: function(): number { + return this.props.delayPressOut || 0; + }, + + _onKeyUp: function(ev) { + if ( + (ev.nativeEvent.code === 'Space' || + ev.nativeEvent.code === 'Enter' || + ev.nativeEvent.code === 'GamepadA') && + !this.props.disabled + ) { + this.touchableHandlePress(); + } + }, + + _onKeyDown: function(ev) { + if ( + (ev.nativeEvent.code === 'Space' || + ev.nativeEvent.code === 'Enter' || + ev.nativeEvent.code === 'GamepadA') && + !this.props.disabled + ) { + this.touchableHandleActivePressIn(); + } + }, + + render: function(): React.Element { + // Note(avik): remove dynamic typecast once Flow has been upgraded + // $FlowFixMe(>=0.41.0) + const child = React.Children.only(this.props.children); + let children = child.props.children; + if (Touchable.TOUCH_TARGET_DEBUG && child.type === View) { + children = React.Children.toArray(children); + children.push( + Touchable.renderDebugView({color: 'red', hitSlop: this.props.hitSlop}), + ); + } + return (React: any).cloneElement(child, { + onKeyUp: this._onKeyUp, + onKeyDown: this._onKeyDown, + onFocus: this.props.onFocus, + onBlur: this.props.onBlur, + accessible: this.props.accessible !== false, + accessibilityLabel: this.props.accessibilityLabel, + accessibilityHint: this.props.accessibilityHint, + accessibilityComponentType: this.props.accessibilityComponentType, + accessibilityRole: this.props.accessibilityRole, + accessibilityStates: this.props.accessibilityStates, + accessibilityTraits: this.props.accessibilityTraits, + + accessibilityPosInSet: this.props.accessibilityPosInSet, // https://github.com/ReactWindows/discussions-and-proposals/blob/harinik-accessibility/proposals/0000-accessibilityapis-lists.md + accessibilitySetSize: this.props.accessibilitySetSize, // https://github.com/ReactWindows/discussions-and-proposals/blob/harinik-accessibility/proposals/0000-accessibilityapis-lists.md + + onAccessibilityTap: this.props.onAccessibilityTap, // TODO(OSS Candidate ISS#2710739) + acceptsKeyboardFocus: + (this.props.acceptsKeyboardFocus === undefined || + this.props.acceptsKeyboardFocus) && + !this.props.disabled, // TODO(macOS ISS#2323203) + enableFocusRing: + this.props.enableFocusRing === true && !this.props.disabled, // TODO(macOS ISS#2323203) + tabIndex: this.props.tabIndex, // TODO(win ISS#2323203) + nativeID: this.props.nativeID, + testID: this.props.testID, + onLayout: this.props.onLayout, + hitSlop: this.props.hitSlop, + onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, + onResponderTerminationRequest: this + .touchableHandleResponderTerminationRequest, + onResponderGrant: this.touchableHandleResponderGrant, + onResponderMove: this.touchableHandleResponderMove, + onResponderRelease: this.touchableHandleResponderRelease, + onResponderTerminate: this.touchableHandleResponderTerminate, + tooltip: this.props.tooltip, // TODO(macOS/win ISS#2323203) + clickable: + this.props.clickable !== false && this.props.onPress !== undefined, // TODO(android ISS) + onClick: this.touchableHandlePress, // TODO(android ISS) + onMouseEnter: this.props.onMouseEnter, // [TODO(macOS ISS#2323203) + onMouseLeave: this.props.onMouseLeave, // [TODO(macOS ISS#2323203) + onDragEnter: this.props.onDragEnter, // [TODO(macOS ISS#2323203) + onDragLeave: this.props.onDragLeave, // [TODO(macOS ISS#2323203) + onDrop: this.props.onDrop, // [TODO(macOS ISS#2323203) + children, + }); + }, +}): any): React.ComponentType); + +module.exports = TouchableWithoutFeedback; diff --git a/vnext/src/Libraries/Components/View/PlatformViewPropTypes.windows.js b/vnext/src/Libraries/Components/View/PlatformViewPropTypes.windows.js new file mode 100644 index 00000000000..6b44f29d9ff --- /dev/null +++ b/vnext/src/Libraries/Components/View/PlatformViewPropTypes.windows.js @@ -0,0 +1,6 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +module.export = {}; diff --git a/vnext/src/Libraries/Components/View/ViewAccessibility.windows.js b/vnext/src/Libraries/Components/View/ViewAccessibility.windows.js new file mode 100644 index 00000000000..f7d89a47c93 --- /dev/null +++ b/vnext/src/Libraries/Components/View/ViewAccessibility.windows.js @@ -0,0 +1,162 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 AccessibilityTrait = + | 'none' + | 'button' + | 'link' + | 'header' + | 'search' + | 'image' + | 'selected' + | 'plays' + | 'key' + | 'text' + | 'summary' + | 'disabled' + | 'frequentUpdates' + | 'startsMedia' + | 'adjustable' + | 'allowsDirectInteraction' + | 'pageTurn' // [TODO(macOS ISS#2323203) + | 'group' + | 'list'; // ]TODO(macOS ISS#2323203) + +export type AccessibilityTraits = + | AccessibilityTrait + | $ReadOnlyArray; + +export type AccessibilityComponentType = + | 'none' + | 'button' + | 'radiobutton_checked' + | 'radiobutton_unchecked'; + +// [TODO(android ISS) +export type AccessibilityNodeInfoProp = { + clickable: boolean, +}; // ]TODO(android ISS) + +// This must be kept in sync with the AccessibilityRolesMask in RCTViewManager.m +export type AccessibilityRole = + | 'none' + | 'button' + | 'link' + | 'search' + | 'image' + | 'keyboardkey' + | 'text' + | 'adjustable' + | 'imagebutton' + | 'header' + | 'summary' + | 'alert' + | 'checkbox' + | 'combobox' + | 'menu' + | 'menubar' + | 'menuitem' + | 'progressbar' + | 'radio' + | 'radiogroup' + | 'scrollbar' + | 'spinbutton' + | 'switch' + | 'tab' + | 'tablist' + | 'timer' + | 'toolbar' + | 'list' // RNW-only + | 'listitem'; // RNW-only + +export type AccessibilityState = + | 'selected' + | 'disabled' + | 'checked' + | 'unchecked' + | 'busy' + | 'expanded' + | 'collapsed'; + +export type AccessibilityStates = + | AccessibilityState + | $ReadOnlyArray; + +module.exports = { + AccessibilityTraits: [ + 'none', + 'button', + 'link', + 'header', + 'search', + 'image', + 'selected', + 'plays', + 'key', + 'text', + 'summary', + 'disabled', + 'frequentUpdates', + 'startsMedia', + 'adjustable', + 'allowsDirectInteraction', + 'pageTurn', + 'group', // [TODO(macOS ISS#2323203) + 'list', // ]TODO(macOS ISS#2323203) + ], + AccessibilityComponentTypes: [ + 'none', + 'button', + 'radiobutton_checked', + 'radiobutton_unchecked', + ], + AccessibilityRoles: [ + 'none', + 'button', + 'link', + 'search', + 'image', + 'keyboardkey', + 'text', + 'adjustable', + 'imagebutton', + 'header', + 'summary', + 'alert', + 'checkbox', + 'combobox', + 'menu', + 'menubar', + 'menuitem', + 'progressbar', + 'radio', + 'radiogroup', + 'scrollbar', + 'spinbutton', + 'switch', + 'tab', + 'tablist', + 'timer', + 'toolbar', + 'list', // RNW-only + 'listitem', // RNW-only + ], + AccessibilityStates: [ + 'selected', + 'disabled', + 'checked', + 'unchecked', + 'busy', + 'expanded', + 'collapsed', + ], +}; diff --git a/vnext/src/Libraries/Components/View/ViewWindows.windows.tsx b/vnext/src/Libraries/Components/View/ViewWindows.windows.tsx new file mode 100644 index 00000000000..dee732f2afa --- /dev/null +++ b/vnext/src/Libraries/Components/View/ViewWindows.windows.tsx @@ -0,0 +1,18 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ + +import {IViewWindowsProps} from './ViewWindowsProps'; +import * as React from 'react'; +import {View} from 'react-native'; + +// tslint:disable-next-line:no-any +export const ViewWindows = React.forwardRef( + (props: IViewWindowsProps, ref: React.Ref) => ( + + ), +); + +export default ViewWindows; diff --git a/vnext/src/Libraries/Components/ViewPager/ViewPagerAndroid.windows.js b/vnext/src/Libraries/Components/ViewPager/ViewPagerAndroid.windows.js new file mode 100644 index 00000000000..1a86a7a9072 --- /dev/null +++ b/vnext/src/Libraries/Components/ViewPager/ViewPagerAndroid.windows.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/vnext/src/Libraries/Components/WebView/WebView.windows.js b/vnext/src/Libraries/Components/WebView/WebView.windows.js new file mode 100644 index 00000000000..ffecd12e519 --- /dev/null +++ b/vnext/src/Libraries/Components/WebView/WebView.windows.js @@ -0,0 +1,214 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * Portions copyright for react-native-windows: + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + * @flow + */ + +'use strict'; + +var React = require('React'); +var ReactNative = require('ReactNative'); +var StyleSheet = require('StyleSheet'); +var UIManager = require('UIManager'); +var View = require('View'); + +var keyMirror = require('fbjs/lib/keyMirror'); +var requireNativeComponent = require('requireNativeComponent'); +var resolveAssetSource = require('resolveAssetSource'); + +const createReactClass = require('create-react-class'); + +var RCT_WEBVIEW_REF = 'webview'; + +var WebViewState = keyMirror({ + IDLE: null, + LOADING: null, + ERROR: null, +}); + +/** + * Renders a native WebView. + */ +var WebView = createReactClass({ + getInitialState: function() { + return { + viewState: WebViewState.IDLE, + lastErrorEvent: null, + startInLoadingState: true, + }; + }, + + getDefaultProps: function() { + return { + javaScriptEnabled: true, + }; + }, + + componentWillMount: function() { + if (this.props.startInLoadingState) { + this.setState({viewState: WebViewState.LOADING}); + } + }, + + render: function() { + var otherView = null; + + if (this.state.viewState === WebViewState.LOADING) { + otherView = this.props.renderLoading && this.props.renderLoading(); + } else if (this.state.viewState === WebViewState.ERROR) { + var errorEvent = this.state.lastErrorEvent; + otherView = + this.props.renderError && + this.props.renderError( + errorEvent.domain, + errorEvent.code, + errorEvent.description, + ); + } else if (this.state.viewState !== WebViewState.IDLE) { + console.error( + 'RCTWebView invalid state encountered: ' + this.state.loading, + ); + } + + var webViewStyles = [styles.container, this.props.style]; + if ( + this.state.viewState === WebViewState.LOADING || + this.state.viewState === WebViewState.ERROR + ) { + // if we're in either LOADING or ERROR states, don't show the webView + webViewStyles.push(styles.hidden); + } + + var source = this.props.source || {}; + if (this.props.html) { + source.html = this.props.html; + } else if (this.props.url) { + source.uri = this.props.url; + } + + var webView = ( + + ); + + return ( + + {webView} + {otherView} + + ); + }, + + goForward: function() { + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebView.Commands.goForward, + null, + ); + }, + + goBack: function() { + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebView.Commands.goBack, + null, + ); + }, + + reload: function() { + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebView.Commands.reload, + null, + ); + }, + + /** + * We return an event with a bunch of fields including: + * url, title, loading, canGoBack, canGoForward + */ + updateNavigationState: function(event) { + if (this.props.onNavigationStateChange) { + this.props.onNavigationStateChange(event.nativeEvent); + } + }, + + getWebViewHandle: function() { + // eslint-disable-next-line react/no-string-refs + return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); + }, + + onLoadingStart: function(event) { + var onLoadStart = this.props.onLoadStart; + onLoadStart && onLoadStart(event); + this.updateNavigationState(event); + }, + + onLoadingError: function(event) { + event.persist(); // persist this event because we need to store it + var {onError, onLoadEnd} = this.props; + onError && onError(event); + onLoadEnd && onLoadEnd(event); + console.error('Encountered an error loading page', event.nativeEvent); + + this.setState({ + lastErrorEvent: event.nativeEvent, + viewState: WebViewState.ERROR, + }); + }, + + onLoadingFinish: function(event) { + var {onLoad, onLoadEnd} = this.props; + onLoad && onLoad(event); + onLoadEnd && onLoadEnd(event); + this.setState({ + viewState: WebViewState.IDLE, + }); + this.updateNavigationState(event); + }, +}); + +var RCTWebView = requireNativeComponent('RCTWebView', WebView); + +var styles = StyleSheet.create({ + container: { + flex: 1, + }, + hidden: { + height: 0, + flex: 0, // disable 'flex:1' when hiding a View + }, + loadingView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + loadingProgressBar: { + height: 20, + }, +}); + +module.exports = WebView; diff --git a/vnext/src/Libraries/DeprecatedPropTypes/DeprecatedViewAccessibility.windows.js b/vnext/src/Libraries/DeprecatedPropTypes/DeprecatedViewAccessibility.windows.js new file mode 100644 index 00000000000..e31f83a3bf4 --- /dev/null +++ b/vnext/src/Libraries/DeprecatedPropTypes/DeprecatedViewAccessibility.windows.js @@ -0,0 +1,81 @@ +/** + * Copyright (c) Facebook, Inc. and its 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'; + +module.exports = { + DeprecatedAccessibilityTraits: [ + 'none', + 'button', + 'link', + 'header', + 'search', + 'image', + 'selected', + 'plays', + 'key', + 'text', + 'summary', + 'disabled', + 'frequentUpdates', + 'startsMedia', + 'adjustable', + 'allowsDirectInteraction', + 'pageTurn', + ], + DeprecatedAccessibilityComponentTypes: [ + 'none', + 'button', + 'radiobutton_checked', + 'radiobutton_unchecked', + ], + // This must be kept in sync with the AccessibilityRolesMask in RCTViewManager.m + DeprecatedAccessibilityRoles: [ + 'none', + 'button', + 'link', + 'search', + 'image', + 'keyboardkey', + 'text', + 'adjustable', + 'imagebutton', + 'header', + 'summary', + 'alert', + 'checkbox', + 'combobox', + 'menu', + 'menubar', + 'menuitem', + 'progressbar', + 'radio', + 'radiogroup', + 'scrollbar', + 'spinbutton', + 'switch', + 'tab', + 'tablist', + 'timer', + 'toolbar', + 'list', // RNW-only + 'listitem', // RNW-only + ], + // This must be kept in sync with the AccessibilityStatesMask in RCTViewManager.m + DeprecatedAccessibilityStates: [ + 'selected', + 'disabled', + 'checked', + 'unchecked', + 'busy', + 'expanded', + 'collapsed', + ], +}; diff --git a/vnext/src/Libraries/Image/Image.windows.js b/vnext/src/Libraries/Image/Image.windows.js new file mode 100644 index 00000000000..96d7b98f3a6 --- /dev/null +++ b/vnext/src/Libraries/Image/Image.windows.js @@ -0,0 +1,182 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * + * @flow + * @format + */ +'use strict'; + +const DeprecatedImagePropType = require('DeprecatedImagePropType'); +const NativeModules = require('NativeModules'); +const React = require('React'); +const ReactNative = require('ReactNative'); // eslint-disable-line no-unused-vars +const StyleSheet = require('StyleSheet'); + +const flattenStyle = require('flattenStyle'); +const requireNativeComponent = require('requireNativeComponent'); +const resolveAssetSource = require('resolveAssetSource'); + +const ImageViewManager = NativeModules.ImageViewManager; + +const RCTImageView = requireNativeComponent('RCTImageView'); + +import type {ImageProps as ImagePropsType} from 'ImageProps'; + +import type {ImageStyleProp} from 'StyleSheet'; + +function getSize( + uri: string, + success: (width: number, height: number) => void, + failure?: (error: any) => void, +) { + ImageViewManager.getSize( + uri, + success, + failure || + function() { + console.warn('Failed to get size for image: ' + uri); + }, + ); +} + +function prefetch(url: string) { + return ImageViewManager.prefetchImage(url); +} + +async function queryCache( + urls: Array, +): Promise> { + return await ImageViewManager.queryCache(urls); +} + +declare class ImageComponentType extends ReactNative.NativeComponent< + ImagePropsType, +> { + static getSize: typeof getSize; + static prefetch: typeof prefetch; + static queryCache: typeof queryCache; + static resolveAssetSource: typeof resolveAssetSource; + static propTypes: typeof DeprecatedImagePropType; +} + +/** + * A React component for displaying different types of images, + * including network images, static resources, temporary local images, and + * images from local disk, such as the camera roll. + * + * See https://facebook.github.io/react-native/docs/image.html + */ +let Image = ( + props: ImagePropsType, + forwardedRef: ?React.Ref<'RCTImageView'>, +) => { + const source = resolveAssetSource(props.source) || { + uri: undefined, + width: undefined, + height: undefined, + }; + + let sources; + let style: ImageStyleProp; + if (Array.isArray(source)) { + // $FlowFixMe flattenStyle is not strong enough + style = flattenStyle([styles.base, props.style]) || {}; + sources = source; + } else { + const {width, height, uri} = source; + // $FlowFixMe flattenStyle is not strong enough + style = flattenStyle([{width, height}, styles.base, props.style]) || {}; + sources = [source]; + + if (uri === '') { + console.warn('source.uri should not be an empty string'); + } + } + + const resizeMode = props.resizeMode || style.resizeMode || 'cover'; + const tintColor = style.tintColor; + + if (props.src != null) { + console.warn( + 'The component requires a `source` property rather than `src`.', + ); + } + + if (props.children != null) { + throw new Error( + 'The component cannot contain children. If you want to render content on top of the image, consider using the component or absolute positioning.', + ); + } + + return ( + + ); +}; + +Image = React.forwardRef(Image); +Image.displayName = 'Image'; + +/** + * Retrieve the width and height (in pixels) of an image prior to displaying it. + * + * See https://facebook.github.io/react-native/docs/image.html#getsize + */ +/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ +Image.getSize = getSize; + +/** + * Prefetches a remote image for later use by downloading it to the disk + * cache. + * + * See https://facebook.github.io/react-native/docs/image.html#prefetch + */ +/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ +Image.prefetch = prefetch; + +/** + * Performs cache interrogation. + * + * See https://facebook.github.io/react-native/docs/image.html#querycache + */ +/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ +Image.queryCache = queryCache; + +/** + * Resolves an asset reference into an object. + * + * See https://facebook.github.io/react-native/docs/image.html#resolveassetsource + */ +/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ +Image.resolveAssetSource = resolveAssetSource; + +/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ +Image.propTypes = DeprecatedImagePropType; + +const styles = StyleSheet.create({ + base: { + overflow: 'hidden', + }, +}); + +/* $FlowFixMe(>=0.89.0 site=react_native_ios_fb) This comment suppresses an + * error found when Flow v0.89 was deployed. To see the error, delete this + * comment and run Flow. */ +module.exports = (Image: Class); From fc6d271706cbf6e4d58baef7dab2b1c02cd0fc1c Mon Sep 17 00:00:00 2001 From: Keith Melmon Date: Wed, 16 Oct 2019 14:54:09 -0700 Subject: [PATCH 2/3] exclude Generated Files --- vnext/local-cli/generator-windows/templates/_gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vnext/local-cli/generator-windows/templates/_gitignore b/vnext/local-cli/generator-windows/templates/_gitignore index b897ae155a4..4ea0c7b5a35 100644 --- a/vnext/local-cli/generator-windows/templates/_gitignore +++ b/vnext/local-cli/generator-windows/templates/_gitignore @@ -86,3 +86,7 @@ packages/ *.DotSettings .vs/ *project.lock.json + +#Files generated by the VS build +**/Generated Files/** + From 113e8aefea6e4d7bbb5786b190de42e5cee23d60 Mon Sep 17 00:00:00 2001 From: Keith Melmon Date: Wed, 16 Oct 2019 14:54:29 -0700 Subject: [PATCH 3/3] Change files --- ...eact-native-windows-2019-10-16-14-54-29-gitignore.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 change/react-native-windows-2019-10-16-14-54-29-gitignore.json diff --git a/change/react-native-windows-2019-10-16-14-54-29-gitignore.json b/change/react-native-windows-2019-10-16-14-54-29-gitignore.json new file mode 100644 index 00000000000..84d021f879f --- /dev/null +++ b/change/react-native-windows-2019-10-16-14-54-29-gitignore.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "exclude Generated Files", + "packageName": "react-native-windows", + "email": "kmelmon@microsoft.com", + "commit": "fc6d271706cbf6e4d58baef7dab2b1c02cd0fc1c", + "date": "2019-10-16T21:54:29.329Z" +} \ No newline at end of file