Compare commits

...

25 Commits

Author SHA1 Message Date
Nicolas Gallagher
54597edbaf 0.0.18 2016-03-15 17:27:42 -07:00
Nicolas Gallagher
fc31287566 [fix] remove -webkit-tap-highlight-color 2016-03-15 17:26:29 -07:00
Nicolas Gallagher
21cc8f47ba Update documentation 2016-03-15 17:14:44 -07:00
Nicolas Gallagher
bf7beb4102 Update dependencies 2016-03-15 14:25:27 -07:00
Nicolas Gallagher
127d103c0a Fix lint issues 2016-03-15 14:19:46 -07:00
Nicolas Gallagher
ae6132af56 Update library exports 2016-03-15 14:19:29 -07:00
Nicolas Gallagher
3c4d7655db [fix] Touchable: adapt RN touchables for Web 2016-03-15 14:18:08 -07:00
Nicolas Gallagher
190966f411 [fix] ScrollView: based on ScrollResponder 2016-03-15 14:07:45 -07:00
Nicolas Gallagher
8d5ecb84d5 [add] ScrollResponder: adjust to work on Web 2016-03-15 13:49:03 -07:00
Nicolas Gallagher
b4a9177ce3 [add] dismissKeyboard module 2016-03-15 13:44:50 -07:00
Nicolas Gallagher
ad4a6c5be7 [fix] View: guard against browser flexbox bugs
There are various flexbox bugs that can cause views to grow beyond the
vertical or horizontal bounds of their container. The width and height
styles avoid most of them manifesting.
2016-03-15 13:43:25 -07:00
Nicolas Gallagher
5f795dfc6c Clean up View's event normalization code 2016-03-15 13:43:11 -07:00
Nicolas Gallagher
949cb75894 [add] TextInput: 'clear' method on instance 2016-03-15 13:38:54 -07:00
Nicolas Gallagher
2e1914080f [add] TextInputState 2016-03-15 13:38:28 -07:00
Nicolas Gallagher
49e9e0ab5b [add] StyleSheet: new prop types 2016-03-15 13:32:52 -07:00
Nicolas Gallagher
ee4c544957 [fix] StyleSheet: support for 'transform' and 'transformMatrix'
Fix #46
2016-03-15 13:32:02 -07:00
Nicolas Gallagher
56549cf794 [add] NativeMethodsMixin: 'measureInWindow' support 2016-03-15 13:24:36 -07:00
Nicolas Gallagher
e6811b2134 [fix] ResponderEventPlugin: touch or mouse dependencies
**Problem**

Browsers tend to fire both touch and mouse events when touch is
supported. This causes touchables to fire responder callbacks twice.
For example, 'TouchableOpacity' will animate the opacity twice in
response to a touch.

**Response**

If a browser supports touch, don't include the mouse events as
dependencies of the responder events. This is not ideal, as some devices
support both touch and mouse interactions. This will need to be
revisited.
2016-03-15 13:19:43 -07:00
Nicolas Gallagher
d8b7dcc60f Add 'locationX/Y' to recorded Responder events 2016-03-15 13:19:24 -07:00
Nicolas Gallagher
62a08f09ab [fix] ResponderEventPlugin: add missing 'responderRelease' dependencies 2016-03-15 13:17:25 -07:00
Nicolas Gallagher
3e7cd1a001 [add] UIManager: 'blur', 'focus' and 'measureInWindow' 2016-03-15 13:14:06 -07:00
Nicolas Gallagher
8441755d61 [fix] 'window' value in 'Dimension' 2016-03-15 11:35:36 -07:00
Nicolas Gallagher
ba9fa2a7a0 [add] upstream 'merge' module 2016-03-15 11:34:09 -07:00
Nicolas Gallagher
e26edfb9ea Move NativeMethodsDecorator 2016-03-14 23:25:58 -07:00
Nicolas Gallagher
9a8a9ad209 Use 'module.exports' over 'export default'
The use of CommonJS require in RN modules makes it simpler to use
CommonJS exports everywhere.
2016-03-14 23:21:12 -07:00
75 changed files with 2627 additions and 646 deletions

View File

@@ -2,12 +2,29 @@
[![Build Status][travis-image]][travis-url]
[![npm version][npm-image]][npm-url]
![gzipped size](https://img.shields.io/badge/gzipped-~36.7k-blue.svg)
![gzipped size](https://img.shields.io/badge/gzipped-~41.0k-blue.svg)
[React Native][react-native-url] components and APIs for the Web.
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
## Overview
"React Native for Web" is a project to bring React Native's building blocks and
touch handling to the Web.
React Native provides a foundational layer to support interoperable,
zero-configuration React component development. This is missing from React's
web ecosystem where OSS components rely on inline styles (usually without
vendor prefixes), or require build tool configuration. This project allows
components built upon React Native to be run on the Web, and it manages all
component styling out-of-the-box.
For example, the [`View`](docs/apis/View.md) component makes it easy to build
cross-browser layouts with flexbox, such as stacked and nested boxes with
margin and padding. And the [`StyleSheet`](docs/guides/style.md) API converts
styles defined in JavaScript into "Atomic CSS".
## Quick start
To install in your app:
@@ -18,17 +35,6 @@ npm install --save react@0.14 react-dom@0.14 react-native-web
Read the [Client and Server rendering](docs/guides/rendering.md) guide.
## Overview
This is a web implementation of React Native components and APIs. The React
Native components are good web application building blocks, and provide a common
foundation for component libraries.
For example, the [`View`](docs/apis/View.md) component makes it easy to build
common layouts with flexbox, such as stacked and nested boxes with margin and
padding. And the [`StyleSheet`](docs/guides/style.md) API converts styles
defined in JavaScript to "atomic" CSS.
## Examples
Demos:
@@ -37,7 +43,7 @@ Demos:
* [TicTacToe](http://codepen.io/necolas/full/eJaLZd/)
* [2048](http://codepen.io/necolas/full/wMVvxj/)
Example:
Sample:
```js
import React, { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
@@ -53,10 +59,6 @@ const App = () => (
</Card>
)
// App registration and rendering
AppRegistry.registerComponent('MyApp', () => App)
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
// Styles
const styles = StyleSheet.create({
card: {
@@ -73,6 +75,10 @@ const styles = StyleSheet.create({
width: 40
}
})
// App registration and rendering
AppRegistry.registerComponent('MyApp', () => App)
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
```
## Documentation
@@ -96,8 +102,8 @@ Exported modules:
* [`ScrollView`](docs/components/ScrollView.md)
* [`Text`](docs/components/Text.md)
* [`TextInput`](docs/components/TextInput.md)
* [`TouchableHighlight`](docs/components/TouchableHighlight.md)
* [`TouchableOpacity`](docs/components/TouchableOpacity.md)
* [`TouchableHighlight`](http://facebook.github.io/react-native/releases/0.22/docs/touchablehighlight.html) (mirrors React Native)
* [`TouchableOpacity`](http://facebook.github.io/react-native/releases/0.22/docs/touchableopacity.html) (mirrors React Native)
* [`TouchableWithoutFeedback`](docs/components/TouchableWithoutFeedback.md)
* [`View`](docs/components/View.md)
* APIs

View File

@@ -58,20 +58,22 @@ could be an http address or a base64 encoded image.
**style**: style
+ ...[View#style](View.md)
Defaults:
```js
{
alignSelf: 'flex-start',
backgroundColor: 'transparent'
}
```
+ `resizeMode`
**testID**: string
Used to locate a view in end-to-end tests.
## Properties
static **resizeMode**: Object
Example usage:
```
<Image resizeMode={Image.resizeMode.contain} />
```
## Examples
```js

View File

@@ -1,13 +1,13 @@
# ScrollView
Scrollable `View` for use with bounded height, either by setting the height of
the view directly (discouraged) or by bounding the height of ancestor views.
A scrollable `View` that provides itegration with the touch-locking "responder"
system. `ScrollView`'s must have a bounded height: either set the height of the
view directly (discouraged) or make sure all parent views have bounded height
(e.g., transfer `{ flex: 1}` down the view stack).
## Props
**children**: any
Child content.
[...View props](./View.md)
**contentContainerStyle**: style
@@ -19,11 +19,34 @@ all of the child views.
When true, the scroll view's children are arranged horizontally in a row
instead of vertically in a column.
**keyboardDismissMode**: oneOf('none', 'on-drag') = 'none'
Determines whether the keyboard gets dismissed in response to a scroll drag.
* `none` (the default), drags do not dismiss the keyboard.
* `on-drag`, the keyboard is dismissed when a drag begins.
* `interactive` (not supported on web; same as `none`)
**onContentSizeChange**: function
TODO
Called when scrollable content view of the `ScrollView` changes. It's
implemented using the `onLayout` handler attached to the content container
which this `ScrollView` renders.
**onScroll**: function
Fires at most once per frame during scrolling. The frequency of the events can
be contolled using the `scrollEventThrottle` prop.
**refreshControl**: element
TODO
A [RefreshControl](../RefreshControl) component, used to provide
pull-to-refresh functionality for the `ScrollView`.
**scrollEnabled**: bool = true
When false, the content does not scroll.
@@ -36,9 +59,26 @@ tracking the scroll position, but can lead to scroll performance problems. The
default value is `0`, which means the scroll event will be sent only once each
time the view is scrolled.
**style**: style
## Instance methods
+ ...[View#style](View.md)
**getInnerViewNode()**: any
Returns a reference to the underlying content container DOM node within the `ScrollView`.
**getScrollableNode()**: any
Returns a reference to the underlying scrollable DOM node.
**getScrollResponder()**: Component
Returns a reference to the underlying scroll responder, which supports
operations like `scrollTo`. All `ScrollView`-like components should implement
this method so that they can be composed while providing access to the
underlying scroll responder's methods.
**scrollTo(options: { x: number = 0; y: number = 0; animated: boolean = true })**
Scrolls to a given `x`, `y` offset (animation is not currently supported).
## Examples
@@ -50,7 +90,7 @@ export default class ScrollViewExample extends Component {
constructor(props, context) {
super(props, context)
this.state = {
items: Array.from({ length: 20 }).map((_, i) => ({ id: i }))
items: Array.from(new Array(20)).map((_, i) => ({ id: i }))
}
}

View File

@@ -147,6 +147,20 @@ Read about how [React form
components](https://facebook.github.io/react/docs/forms.html) work. To prevent
user edits to the value set `editable={false}`.
## Instance methods
**blur()**
Blur the underlying DOM input.
**clear()**
Clear the text from the underlying DOM input.
**focus()**
Focus the underlying DOM input.
## Examples
```js

View File

@@ -1,100 +0,0 @@
# Touchable
A wrapper for making views respond to mouse, keyboard, and touch presses. On
press in, the touchable area can display a highlight color, and the opacity of
the wrapped view can be decreased.
This component combines the various `Touchable*` components from React Native.
Unsupported React Native props:
`accessibilityComponentType` (android) use `accessibilityRole`,
`accessibilityTraits` (ios) use `accessibilityRole`,
`onHideUnderlay` use `onPressOut`,
`onShowUnderlay` use `onPressIn`,
`underlayColor` use `activeUnderlayColor`
## Props
**accessibilityLabel**: string
Overrides the text that's read by the screen reader when the user interacts
with the element.
(web) **accessibilityRole**: oneOf(roles) = 'button'
Allows assistive technologies to present and support interaction with the view
in a manner that is consistent with user expectations for similar views of that
type. For example, marking a touchable view with an `accessibilityRole` of
`button`. (This is implemented using [ARIA roles](http://www.w3.org/TR/wai-aria/roles#role_definitions)).
Note: Avoid changing `accessibilityRole` values over time or after user
actions. Generally, accessibility APIs do not provide a means of notifying
assistive technologies of a `role` value change.
**accessible**: bool = true
When `false`, the view is hidden from screenreaders.
**activeOpacity**: number = 0.8
Sets the opacity of the child view when `onPressIn` is called. The opacity is
reset when `onPressOut` is called.
(web) **activeUnderlayColor**: string = 'black'
Sets the color of the background highlight when `onPressIn` is called. The
highlight is removed when `onPressOut` is called.
**children**: element
A single child element.
**delayLongPress**: number = 500
Delay in ms, from `onPressIn`, before `onLongPress` is called.
**delayPressIn**: number = 0
(TODO)
Delay in ms, from the start of the touch, before `onPressIn` is called.
**delayPressOut**: number = 100
(TODO)
Delay in ms, from the release of the touch, before `onPressOut` is called.
**onLayout**: function
(TODO)
**onLongPress**: function
**onPress**: function
**onPressIn**: function
**onPressOut**: function
**style**: style
+ ...[View#style](View.md)
## Examples
```js
import React, { Component, PropTypes, Touchable } from 'react-native'
export default class Example extends Component {
static propTypes = {}
static defaultProps = {}
render() {
return (
<Touchable />
)
}
}
```

View File

@@ -0,0 +1,70 @@
# TouchableWithoutFeedback
Do not use unless you have a very good reason. All the elements that respond to
press should have a visual feedback when touched. This is one of the primary
reason a "web" app doesn't feel "native".
**NOTE: `TouchableWithoutFeedback` supports only one child**. If you wish to have
several child components, wrap them in a View.
## Props
**accessibilityLabel**: string
Overrides the text that's read by the screen reader when the user interacts
with the element.
(web) **accessibilityRole**: oneOf(roles) = 'button'
Allows assistive technologies to present and support interaction with the view
**accessible**: bool = true
When `false`, the view is hidden from screenreaders.
**delayLongPress**: number
Delay in ms, from `onPressIn`, before `onLongPress` is called.
**delayPressIn**: number
Delay in ms, from the start of the touch, before `onPressIn` is called.
**delayPressOut**: number
Delay in ms, from the release of the touch, before `onPressOut` is called.
**disabled**: bool
If true, disable all interactions for this component.
**hitSlop**: `{top: number, left: number, bottom: number, right: number}`
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.
**onLayout**: function
Invoked on mount and layout changes with.
`{nativeEvent: {layout: {x, y, width, height}}}`
**onLongPress**: function
**onPress**: function
Called when the touch is released, but not if cancelled (e.g. by a scroll that steals the responder lock).
**onPressIn**: function
**onPressOut**: function
**pressRetentionOffset**: `{top: number, left: number, bottom: number, right: 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.

View File

@@ -4,21 +4,13 @@
style, layout with flexbox, and accessibility controls. It can be nested
inside another `View` and has 0-to-many children of any type.
Also, refer to React Native's documentation about the [Gesture Responder
System](http://facebook.github.io/react-native/releases/0.22/docs/gesture-responder-system.html).
Unsupported React Native props:
`accessibilityComponentType` (android) use `accessibilityRole`,
`accessibilityTraits` (ios) use `accessibilityRole`,
`collapsable` (android),
`importantForAccessibility` (android),
`needsOffscreenAlphaCompositing` (android),
`onAccessibilityTap`,
`onMagicTap`,
`onMoveShouldSetResponder`,
`onResponder*`,
`onStartShouldSetResponder`,
`onStartShouldSetResponderCapture`
`removeClippedSubviews` (ios),
`renderToHardwareTextureAndroid` (android),
`shouldRasterizeIOS` (ios)
`hitSlop`,
`onMagicTap`
## Props
@@ -58,6 +50,29 @@ implemented using `aria-hidden`.)
(TODO)
**onMoveShouldSetResponder**: function
**onMoveShouldSetResponderCapture**: function
**onResponderGrant**: function
For most touch interactions, you'll simply want to wrap your component in
`TouchableHighlight` or `TouchableOpacity`.
**onResponderMove**: function
**onResponderReject**: function
**onResponderRelease**: function
**onResponderTerminate**: function
**onResponderTerminationRequest**: function
**onStartShouldSetResponder**: function
**onStartShouldSetResponderCapture**: function
**pointerEvents**: oneOf('auto', 'box-only', 'box-none', 'none') = 'auto'
Configure the `pointerEvents` of the view. The enhanced `pointerEvents` modes
@@ -136,6 +151,7 @@ from `style`.
+ `right`
+ `top`
+ `transform`
+ `transformMatrix`
+ `userSelect`
+ `visibility`
+ `width`

View File

@@ -18,45 +18,53 @@ module.exports = {
## Client-side rendering
Rendering without using the `AppRegistry`:
```js
import React from 'react-native'
// DOM render
React.render(<div />, document.getElementById('react-app'))
// Server render
React.renderToString(<div />)
React.renderToStaticMarkup(<div />)
```
Rendering using the `AppRegistry`:
```
// App.js
import React, { AppRegistry } from 'react-native'
// component that renders the app
const AppContainer = (props) => { /* ... */ }
export default AppContainer
```
```js
// client.js
import React, { AppRegistry } from 'react-native'
import MyApp from './MyApp'
import App from './App'
// register the app
AppRegistry.registerApp('MyApp', () => MyApp)
// registers the app
AppRegistry.registerComponent('App', () => App)
// mount the app within the `rootTag` and run it
AppRegistry.runApplication('MyApp', { initialProps, rootTag: document.getElementById('react-root') })
// DOM render
React.render(<div />, document.getElementById('sidebar-app'))
// Server render
React.renderToString(<div />)
// mounts and runs the app within the `rootTag` DOM node
AppRegistry.runApplication('App', { initialProps, rootTag: document.getElementById('react-app') })
```
## Server-side rendering
Pre-rendering React apps on the server is a key feature for Web applications.
React Native for Web extends `AppRegistry` to provide support for server-side
rendering.
```js
// server.js
// AppShell.js
import React, { AppRegistry } from 'react-native'
import MyApp from './MyApp'
import React from 'react-native'
// register the app
AppRegistry.registerApp('MyApp', () => MyApp)
// prerender the app
const { html, style } = AppRegistry.prerenderApplication('MyApp', { initialProps })
// construct full page markup
const HtmlShell = (html, style) => (
const AppShell = (html, style) => (
<html>
<head>
<meta charSet="utf-8" />
@@ -64,10 +72,26 @@ const HtmlShell = (html, style) => (
{style}
</head>
<body>
<div id="react-root" dangerouslySetInnerHTML={{ __html: html }} />
<div id="react-app" dangerouslySetInnerHTML={{ __html: html }} />
</body>
</html>
)
React.renderToStaticMarkup(<HtmlShell html={html} style={style} />)
export default AppShell
```
```js
// server.js
import React, { AppRegistry } from 'react-native'
import App from './App'
import AppShell from './AppShell'
// registers the app
AppRegistry.registerComponent('App', () => App)
// prerenders the app
const { html, style } = AppRegistry.prerenderApplication('App', { initialProps })
// renders the full-page markup
const renderedApplicationHTML = React.renderToString(<AppShell html={html} style={style} />)
```

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.0.17",
"version": "0.0.18",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
@@ -10,17 +10,17 @@
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"build:umd": "webpack --config config/webpack.config.js --sort-assets-by --progress",
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config examples src",
"lint": "eslint config src",
"prepublish": "npm run build && npm run build:umd",
"test": "karma start config/karma.config.js",
"test:watch": "npm run test -- --no-single-run"
},
"dependencies": {
"fbjs": "^0.7.2",
"inline-style-prefix-all": "^1.0.2",
"lodash.debounce": "^3.1.1",
"react-tappable": "^0.7.1",
"react-textarea-autosize": "^3.1.0"
"inline-style-prefix-all": "^1.0.3",
"lodash.debounce": "^4.0.3",
"react-textarea-autosize": "^3.1.0",
"react-timer-mixin": "^0.13.3"
},
"devDependencies": {
"babel-cli": "^6.3.17",

View File

@@ -11,7 +11,7 @@ import Image from '../../components/Image'
import Text from '../../components/Text'
import View from '../../components/View'
export default {
module.exports = {
...AnimatedImplementation,
View: AnimatedImplementation.createAnimatedComponent(View),
Text: AnimatedImplementation.createAnimatedComponent(Text),

View File

@@ -4,7 +4,7 @@ import ReactDOM from 'react-dom'
import StyleSheet from '../StyleSheet'
import View from '../../components/View'
export default class ReactNativeApp extends Component {
class ReactNativeApp extends Component {
static propTypes = {
initialProps: PropTypes.object,
rootComponent: PropTypes.any.isRequired,
@@ -45,3 +45,5 @@ const styles = StyleSheet.create({
bottom: 0
}
})
module.exports = ReactNativeApp

View File

@@ -24,7 +24,7 @@ type AppConfig = {
/**
* `AppRegistry` is the JS entry point to running all React Native apps.
*/
export default class AppRegistry {
class AppRegistry {
static getAppKeys(): Array<string> {
return Object.keys(runnables)
}
@@ -88,3 +88,5 @@ export default class AppRegistry {
ReactDOM.unmountComponentAtNode(rootTag)
}
}
module.exports = AppRegistry

View File

@@ -3,7 +3,7 @@ import invariant from 'fbjs/lib/invariant'
const listeners = {}
const eventTypes = [ 'change' ]
export default class AppState {
class AppState {
static get currentState() {
switch (document.visibilityState) {
case 'hidden':
@@ -27,3 +27,5 @@ export default class AppState {
delete listeners[handler]
}
}
module.exports = AppState

View File

@@ -12,7 +12,7 @@ const mergeLocalStorageItem = (key, value) => {
window.localStorage.setItem(key, nextValue)
}
export default class AsyncStorage {
class AsyncStorage {
/**
* Erases *all* AsyncStorage for the domain.
*/
@@ -157,3 +157,5 @@ export default class AsyncStorage {
})
}
}
module.exports = AsyncStorage

View File

@@ -17,15 +17,17 @@ const dimensions = {
},
window: {
fontScale: 1,
get height() { return document.documentElement.clientHeight },
get height() { return window.innerHeight },
scale: window.devicePixelRatio || 1,
get width() { return document.documentElement.clientWidth }
get width() { return window.innerWidth }
}
}
export default class Dimensions {
class Dimensions {
static get(dimension: string): Object {
invariant(dimensions[dimension], 'No dimension set for key ' + dimension)
return dimensions[dimension]
}
}
module.exports = Dimensions

View File

@@ -45,4 +45,4 @@ const InteractionManager = {
addListener: () => {}
}
export default InteractionManager
module.exports = InteractionManager

View File

@@ -76,4 +76,4 @@ const NetInfo = {
}
}
export default NetInfo
module.exports = NetInfo

View File

@@ -18,9 +18,11 @@ const {
topTouchStart
} = EventConstants.topLevelTypes
const endDependencies = [ topMouseUp, topTouchCancel, topTouchEnd ]
const moveDependencies = [ topMouseMove, topTouchMove ]
const startDependencies = [ topMouseDown, topTouchStart ]
const supportsTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch
const endDependencies = supportsTouch ? [ topTouchCancel, topTouchEnd ] : [ topMouseUp ]
const moveDependencies = supportsTouch ? [ topTouchMove ] : [ topMouseMove ]
const startDependencies = supportsTouch ? [ topTouchStart ] : [ topMouseDown ]
/**
* Setup ResponderEventPlugin dependencies
@@ -28,7 +30,7 @@ const startDependencies = [ topMouseDown, topTouchStart ]
ResponderEventPlugin.eventTypes.responderMove.dependencies = moveDependencies
ResponderEventPlugin.eventTypes.responderEnd.dependencies = endDependencies
ResponderEventPlugin.eventTypes.responderStart.dependencies = startDependencies
ResponderEventPlugin.eventTypes.responderRelease.dependencies = []
ResponderEventPlugin.eventTypes.responderRelease.dependencies = endDependencies
ResponderEventPlugin.eventTypes.responderTerminationRequest.dependencies = []
ResponderEventPlugin.eventTypes.responderGrant.dependencies = []
ResponderEventPlugin.eventTypes.responderReject.dependencies = []
@@ -42,7 +44,7 @@ const originalRecordTouchTrack = ResponderTouchHistoryStore.recordTouchTrack
ResponderTouchHistoryStore.recordTouchTrack = (topLevelType, nativeEvent) => {
// Filter out mouse-move events when the mouse button is not down
if ((topLevelType === 'topMouseMove') && !ResponderTouchHistoryStore.touchHistory.touchBank.length) {
if ((topLevelType === topMouseMove) && !ResponderTouchHistoryStore.touchHistory.touchBank.length) {
return
}
originalRecordTouchTrack.call(ResponderTouchHistoryStore, topLevelType, normalizeNativeEvent(nativeEvent))

View File

@@ -2,10 +2,16 @@
const normalizeTouches = (touches = []) => Array.prototype.slice.call(touches).map((touch) => {
const identifier = touch.identifier > 20 ? (touch.identifier % 20) : touch.identifier
const rect = touch.target && touch.target.getBoundingClientRect()
const locationX = touch.pageX - rect.left
const locationY = touch.pageY - rect.top
return {
clientX: touch.clientX,
clientY: touch.clientY,
force: touch.force,
locationX: locationX,
locationY: locationY,
identifier: identifier,
pageX: touch.pageX,
pageY: touch.pageY,
@@ -41,9 +47,8 @@ function normalizeTouchEvent(nativeEvent) {
event.identifier = changedTouches[0].identifier
event.pageX = changedTouches[0].pageX
event.pageY = changedTouches[0].pageY
const rect = changedTouches[0].target.getBoundingClientRect()
event.locationX = changedTouches[0].pageX - rect.left
event.locationY = changedTouches[0].pageY - rect.top
event.locationX = changedTouches[0].locationX
event.locationY = changedTouches[0].locationY
}
return event
@@ -54,6 +59,8 @@ function normalizeMouseEvent(nativeEvent) {
clientX: nativeEvent.clientX,
clientY: nativeEvent.clientY,
force: nativeEvent.force,
locationX: nativeEvent.clientX,
locationY: nativeEvent.clientY,
identifier: 0,
pageX: nativeEvent.pageX,
pageY: nativeEvent.pageY,
@@ -81,4 +88,4 @@ function normalizeNativeEvent(nativeEvent) {
return mouse ? normalizeMouseEvent(nativeEvent) : normalizeTouchEvent(nativeEvent)
}
export default normalizeNativeEvent
module.exports = normalizeNativeEvent

View File

@@ -11,7 +11,7 @@ import Dimensions from '../Dimensions'
/**
* PixelRatio gives access to the device pixel density.
*/
export default class PixelRatio {
class PixelRatio {
/**
* Returns the device pixel density.
*/
@@ -45,3 +45,5 @@ export default class PixelRatio {
return Math.round(layoutSize * ratio) / ratio
}
}
module.exports = PixelRatio

View File

@@ -5,4 +5,4 @@ const Platform = {
userAgent: canUseDOM ? window.navigator.userAgent : ''
}
export default Platform
module.exports = Platform

View File

@@ -22,4 +22,4 @@ const BorderPropTypes = {
borderLeftStyle: BorderStylePropType
}
export default BorderPropTypes
module.exports = BorderPropTypes

View File

@@ -59,4 +59,4 @@ var colorPropType = function(isRequired, props, propName, componentName, locatio
var ColorPropType = colorPropType.bind(null, false /* isRequired */);
ColorPropType.isRequired = colorPropType.bind(null, true /* isRequired */);
export default ColorPropType
module.exports = ColorPropType

View File

@@ -0,0 +1,26 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule EdgeInsetsPropType
* @flow
*/
'use strict';
var PropTypes = require('react').PropTypes;
var createStrictShapeTypeChecker = require('./createStrictShapeTypeChecker');
var EdgeInsetsPropType = createStrictShapeTypeChecker({
top: PropTypes.number,
left: PropTypes.number,
bottom: PropTypes.number,
right: PropTypes.number,
});
module.exports = EdgeInsetsPropType;

View File

@@ -51,4 +51,4 @@ const LayoutPropTypes = {
top: numberOrString
}
export default LayoutPropTypes
module.exports = LayoutPropTypes

View File

@@ -0,0 +1,24 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule PointPropType
* @flow
*/
'use strict';
var PropTypes = require('react').PropTypes;
var createStrictShapeTypeChecker = require('./createStrictShapeTypeChecker');
var PointPropType = createStrictShapeTypeChecker({
x: PropTypes.number,
y: PropTypes.number,
});
module.exports = PointPropType;

View File

@@ -1,7 +1,7 @@
import prefixAll from 'inline-style-prefix-all'
import hyphenate from './hyphenate'
export default class Store {
class Store {
constructor(
initialState:Object = {},
options:Object = { obfuscateClassNames: false }
@@ -95,3 +95,5 @@ export default class Store {
}
}
}
module.exports = Store

View File

@@ -8,7 +8,7 @@
import createStrictShapeTypeChecker from './createStrictShapeTypeChecker'
import flattenStyle from './flattenStyle'
export default function StyleSheetPropType(shape) {
module.exports = function StyleSheetPropType(shape) {
const shapePropType = createStrictShapeTypeChecker(shape)
return function (props, propName, componentName, location?) {
let newProps = props

View File

@@ -8,14 +8,15 @@
import prefixAll from 'inline-style-prefix-all'
import flattenStyle from './flattenStyle'
import processTransform from './processTransform'
export default class StyleSheetRegistry {
class StyleSheetRegistry {
static registerStyle(style: Object, store): number {
if (process.env.NODE_ENV !== 'production') {
Object.freeze(style)
}
const normalizedStyle = flattenStyle(style)
const normalizedStyle = processTransform(flattenStyle(style))
Object.keys(normalizedStyle).forEach((prop) => {
// add each declaration to the store
store.set(prop, normalizedStyle[prop])
@@ -26,7 +27,7 @@ export default class StyleSheetRegistry {
let _className
let _style = {}
const classList = []
const normalizedStyle = flattenStyle(style)
const normalizedStyle = processTransform(flattenStyle(style))
for (const prop in normalizedStyle) {
let styleClass = store.get(prop, normalizedStyle[prop])
@@ -44,3 +45,5 @@ export default class StyleSheetRegistry {
return { className: _className, style: _style }
}
}
module.exports = StyleSheetRegistry

View File

@@ -12,7 +12,7 @@ import TextStylePropTypes from '../../components/Text/TextStylePropTypes'
import ViewStylePropTypes from '../../components/View/ViewStylePropTypes'
import invariant from 'fbjs/lib/invariant'
export default class StyleSheetValidation {
class StyleSheetValidation {
static validateStyleProp(prop, style, caller) {
if (process.env.NODE_ENV !== 'production') {
if (allStylePropTypes[prop] === undefined) {
@@ -66,3 +66,5 @@ StyleSheetValidation.addValidStylePropTypes({
listStyle: PropTypes.string,
verticalAlign: PropTypes.string
})
module.exports = StyleSheetValidation

View File

@@ -38,10 +38,12 @@ const TransformPropTypes = {
PropTypes.shape({ skewX: numberOrString }),
PropTypes.shape({ skewY: numberOrString }),
PropTypes.shape({ translateX: numberOrString }),
PropTypes.shape({ translateY: numberOrString })
PropTypes.shape({ translateY: numberOrString }),
PropTypes.shape({ translateZ: numberOrString }),
PropTypes.shape({ translate3d: PropTypes.string })
])
),
transformMatrix: TransformMatrixPropType
}
export default TransformPropTypes
module.exports = TransformPropTypes

View File

@@ -12,7 +12,7 @@
import invariant from 'fbjs/lib/invariant'
import ReactPropTypeLocationNames from 'react/lib/ReactPropTypeLocationNames'
export default function createStrictShapeTypeChecker(shapeTypes) {
module.exports = function createStrictShapeTypeChecker(shapeTypes) {
function checkType(isRequired, props, propName, componentName, location?) {
if (!props[propName]) {
if (isRequired) {

View File

@@ -56,4 +56,4 @@ const expandStyle = (style) => {
}, {})
}
export default expandStyle
module.exports = expandStyle

View File

@@ -8,7 +8,7 @@
import invariant from 'fbjs/lib/invariant'
import expandStyle from './expandStyle'
export default function flattenStyle(style): ?Object {
module.exports = function flattenStyle(style): ?Object {
if (!style) {
return undefined
}

View File

@@ -1 +1 @@
export default (string) => (string.replace(/([A-Z])/g, '-$1').toLowerCase()).replace(/^ms-/, '-ms-')
module.exports = (string) => (string.replace(/([A-Z])/g, '-$1').toLowerCase()).replace(/^ms-/, '-ms-')

View File

@@ -65,7 +65,7 @@ const resolve = ({ style = {} }) => {
return StyleSheetRegistry.getStyleAsNativeProps(style, store)
}
export default {
module.exports = {
_destroy,
_renderToString,
create,

View File

@@ -30,4 +30,4 @@ const normalizeValue = (property, value) => {
return value
}
export default normalizeValue
module.exports = normalizeValue

View File

@@ -3,11 +3,10 @@
*/
export const resetCSS =
`/* React Native Web */
html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
html {font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}
body {margin:0}
button::-moz-focus-inner, input::-moz-focus-inner {border:0;padding:0}
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {-webkit-appearance:none}
ol,ul,li {list-style:none}`
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {-webkit-appearance:none}`
/**
* Custom pointer event styles

View File

@@ -0,0 +1,24 @@
// { scale: 2 } => 'scale(2)'
const mapTransform = (transform) => {
var key = Object.keys(transform)[0]
return `${key}(${transform[key]})`
}
// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'
const convertTransformMatrix = (transformMatrix) => {
var matrix = transformMatrix.join(',')
return `matrix3d(${matrix})`
}
const processTransform = (style) => {
if (style) {
if (style.transform) {
style.transform = style.transform.map(mapTransform).join(' ')
} else if (style.transformMatrix) {
style.transformMatrix = convertTransformMatrix(style.transformMatrix)
}
}
return style
}
module.exports = processTransform

View File

@@ -73,8 +73,28 @@ suite('apis/UIManager', () => {
})
})
suite('measureInWindow', () => {
test('provides correct layout to callback', () => {
const node = createNode({ height: '10px', width: '10px' })
const middle = createNode({ padding: '20px' })
const context = createNode({ padding: '20px' })
middle.appendChild(node)
context.appendChild(middle)
document.body.appendChild(context)
UIManager.measureInWindow(node, (x, y, width, height) => {
assert.equal(x, 40)
assert.equal(y, 40)
assert.equal(width, 10)
assert.equal(height, 10)
})
document.body.removeChild(context)
})
})
suite('updateView', () => {
test('adds new className to existing className', () => {
test('add new className to existing className', () => {
const node = createNode()
node.className = 'existing'
const props = { className: 'extra' }
@@ -89,7 +109,20 @@ suite('apis/UIManager', () => {
assert.equal(node.getAttribute('style'), 'color: red; opacity: 0;')
})
test('sets attribute values', () => {
test('replaces input and textarea text', () => {
const node = createNode()
node.value = 'initial'
const textProp = { text: 'expected-text' }
const valueProp = { value: 'expected-value' }
UIManager.updateView(node, textProp)
assert.equal(node.value, 'expected-text')
UIManager.updateView(node, valueProp)
assert.equal(node.value, 'expected-value')
})
test('sets other attribute values', () => {
const node = createNode()
const props = { 'aria-level': '4', 'data-of-type': 'string' }
UIManager.updateView(node, props)

View File

@@ -1,6 +1,8 @@
import CSSPropertyOperations from 'react/lib/CSSPropertyOperations'
import flattenStyle from '../StyleSheet/flattenStyle'
import processTransform from '../StyleSheet/processTransform'
const measureAll = (node, callback, relativeToNativeNode) => {
const _measureLayout = (node, relativeToNativeNode, callback) => {
const relativeNode = relativeToNativeNode || node.parentNode
const relativeRect = relativeNode.getBoundingClientRect()
const { height, left, top, width } = node.getBoundingClientRect()
@@ -10,26 +12,55 @@ const measureAll = (node, callback, relativeToNativeNode) => {
}
const UIManager = {
blur(node) {
try { node.blur() } catch (err) {}
},
focus(node) {
try { node.focus() } catch (err) {}
},
measure(node, callback) {
measureAll(node, callback)
_measureLayout(node, null, callback)
},
measureInWindow(node, callback) {
const { height, left, top, width } = node.getBoundingClientRect()
callback(left, top, width, height)
},
measureLayout(node, relativeToNativeNode, onFail, onSuccess) {
measureAll(node, (x, y, width, height) => onSuccess(x, y, width, height), relativeToNativeNode)
const relativeTo = relativeToNativeNode || node.parentNode
_measureLayout(node, relativeTo, onSuccess)
},
updateView(node, props) {
for (const prop in props) {
let nativeProp
const value = props[prop]
if (prop === 'style') {
CSSPropertyOperations.setValueForStyles(node, value)
} else if (prop === 'className') {
node.classList.add(value)
} else {
node.setAttribute(prop, value)
switch (prop) {
case 'style':
// convert styles to DOM-styles
CSSPropertyOperations.setValueForStyles(node, processTransform(flattenStyle(value)))
break
case 'class':
case 'className':
nativeProp = 'class'
// prevent class names managed by React Native from being replaced
const className = node.getAttribute(nativeProp) + ' ' + value
node.setAttribute(nativeProp, className)
break
case 'text':
case 'value':
// native platforms use `text` prop to replace text input value
node.value = value
break
default:
node.setAttribute(prop, value)
}
}
}
}
export default UIManager
module.exports = UIManager

View File

@@ -1,4 +1,4 @@
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../../apis/StyleSheet'
@@ -20,7 +20,7 @@ const keyframeEffects = [
]
@NativeMethodsDecorator
export default class ActivityIndicator extends Component {
class ActivityIndicator extends Component {
static propTypes = {
animating: PropTypes.bool,
color: PropTypes.string,
@@ -107,3 +107,5 @@ const indicatorStyles = StyleSheet.create({
height: 36
}
})
module.exports = ActivityIndicator

View File

@@ -1,4 +1,4 @@
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
@@ -19,7 +19,7 @@ const roleComponents = {
}
@NativeMethodsDecorator
export default class CoreComponent extends Component {
class CoreComponent extends Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
@@ -64,3 +64,5 @@ export default class CoreComponent extends Component {
)
}
}
module.exports = CoreComponent

View File

@@ -7,4 +7,4 @@ const ImageResizeMode = keyMirror({
stretch: null
})
export default ImageResizeMode
module.exports = ImageResizeMode

View File

@@ -6,7 +6,7 @@ import ImageResizeMode from './ImageResizeMode'
const hiddenOrVisible = PropTypes.oneOf([ 'hidden', 'visible' ])
export default {
module.exports = {
...LayoutPropTypes,
...TransformPropTypes,
backfaceVisibility: hiddenOrVisible,

View File

@@ -1,5 +1,5 @@
/* global window */
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import resolveAssetSource from './resolveAssetSource'
import CoreComponent from '../CoreComponent'
import ImageResizeMode from './ImageResizeMode'
@@ -23,7 +23,7 @@ const ImageSourcePropType = PropTypes.oneOfType([
])
@NativeMethodsDecorator
export default class Image extends Component {
class Image extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessible: CoreComponent.propTypes.accessible,
@@ -217,3 +217,5 @@ const resizeModeStyles = StyleSheet.create({
backgroundSize: '100% 100%'
}
})
module.exports = Image

View File

@@ -2,4 +2,4 @@ function resolveAssetSource(source) {
return ((typeof source === 'object') ? source.uri : source) || null
}
export default resolveAssetSource
module.exports = resolveAssetSource

View File

@@ -1,9 +1,9 @@
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import React, { Component, PropTypes } from 'react'
import ScrollView from '../ScrollView'
@NativeMethodsDecorator
export default class ListView extends Component {
class ListView extends Component {
static propTypes = {
children: PropTypes.any,
style: ScrollView.propTypes.style
@@ -19,3 +19,5 @@ export default class ListView extends Component {
)
}
}
module.exports = ListView

View File

@@ -19,7 +19,7 @@ let lastUsedTag = 0
/**
* A container that renders all the modals on top of everything else in the application.
*/
export default class Portal extends Component {
class Portal extends Component {
static propTypes = {
onModalVisibilityChanged: PropTypes.func.isRequired
};
@@ -154,3 +154,5 @@ const styles = StyleSheet.create({
bottom: 0
}
})
module.exports = Portal

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import debounce from 'lodash.debounce'
import React, { Component, PropTypes } from 'react'
import View from '../View'
/**
* Encapsulates the Web-specific scroll throttling and disabling logic
*/
export default class ScrollViewBase extends Component {
static propTypes = {
...View.propTypes,
onScroll: PropTypes.func,
onTouchMove: PropTypes.func,
onWheel: PropTypes.func,
scrollEnabled: PropTypes.bool,
scrollEventThrottle: PropTypes.number
};
static defaultProps = {
scrollEnabled: true
};
constructor(props) {
super(props)
this._debouncedOnScrollEnd = debounce(this._handleScrollEnd, 100)
this._handlePreventableScrollEvent = this._handlePreventableScrollEvent.bind(this)
this._handleScroll = this._handleScroll.bind(this)
this._state = { isScrolling: false }
}
_handlePreventableScrollEvent(handler) {
return (e) => {
if (!this.props.scrollEnabled) {
e.preventDefault()
} else {
if (handler) handler(e)
}
}
}
_handleScroll(e) {
const { scrollEventThrottle } = this.props
// A scroll happened, so the scroll bumps the debounce.
this._debouncedOnScrollEnd(e)
if (this._state.isScrolling) {
// Scroll last tick may have changed, check if we need to notify
if (this._shouldEmitScrollEvent(this._state.scrollLastTick, scrollEventThrottle)) {
this._handleScrollTick(e)
}
} else {
// Weren't scrolling, so we must have just started
this._handleScrollStart(e)
}
}
_handleScrollStart(e) {
this._state.isScrolling = true
this._state.scrollLastTick = Date.now()
}
_handleScrollTick(e) {
const { onScroll } = this.props
this._state.scrollLastTick = Date.now()
if (onScroll) onScroll(e)
}
_handleScrollEnd(e) {
const { onScroll } = this.props
this._state.isScrolling = false
if (onScroll) onScroll(e)
}
_shouldEmitScrollEvent(lastTick, eventThrottle) {
const timeSinceLastTick = Date.now() - lastTick
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle))
}
render() {
return (
<View
{...this.props}
onScroll={this._handleScroll}
onTouchMove={this._handlePreventableScrollEvent(this.props.onTouchMove)}
onWheel={this._handlePreventableScrollEvent(this.props.onWheel)}
/>
)
}
}

View File

@@ -1,130 +1,227 @@
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
import debounce from 'lodash.debounce'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import View from '../View'
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
@NativeMethodsDecorator
export default class ScrollView extends Component {
static propTypes = {
children: PropTypes.any,
contentContainerStyle: View.propTypes.style,
import dismissKeyboard from '../../modules/dismissKeyboard'
import invariant from 'fbjs/lib/invariant'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import ScrollResponder from '../../modules/ScrollResponder'
import ScrollViewBase from './ScrollViewBase'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import View from '../View'
import ViewStylePropTypes from '../View/ViewStylePropTypes'
const INNERVIEW = 'InnerScrollView'
const SCROLLVIEW = 'ScrollView'
const ScrollView = React.createClass({
propTypes: {
...View.propTypes,
children: View.propTypes.children,
contentContainerStyle: StyleSheetPropType(ViewStylePropTypes),
horizontal: PropTypes.bool,
keyboardDismissMode: PropTypes.oneOf([ 'none', 'interactive', 'on-drag' ]),
onContentSizeChange: PropTypes.func,
onScroll: PropTypes.func,
refreshControl: PropTypes.element,
scrollEnabled: PropTypes.bool,
scrollEventThrottle: PropTypes.number,
style: View.propTypes.style
};
style: StyleSheetPropType(ViewStylePropTypes)
},
static defaultProps = {
contentContainerStyle: {},
horizontal: false,
scrollEnabled: true,
scrollEventThrottle: 0,
style: {}
};
mixins: [ScrollResponder.Mixin],
constructor(...args) {
super(...args)
this._debouncedOnScrollEnd = debounce(this._onScrollEnd, 100)
this.state = {
isScrolling: false
}
}
getInitialState() {
return this.scrollResponderMixinGetInitialState()
},
_onScroll(e) {
const { scrollEventThrottle } = this.props
const { isScrolling, scrollLastTick } = this.state
setNativeProps(props: Object) {
this.refs[SCROLLVIEW].setNativeProps(props)
},
// A scroll happened, so the scroll bumps the debounce.
this._debouncedOnScrollEnd(e)
/**
* Returns a reference to the underlying scroll responder, which supports
* operations like `scrollTo`. All ScrollView-like components should
* implement this method so that they can be composed while providing access
* to the underlying scroll responder's methods.
*/
getScrollResponder(): Component {
return this
},
if (isScrolling) {
// Scroll last tick may have changed, check if we need to notify
if (this._shouldEmitScrollEvent(scrollLastTick, scrollEventThrottle)) {
this._onScrollTick(e)
}
getScrollableNode(): any {
return ReactDOM.findDOMNode(this.refs[SCROLLVIEW])
},
getInnerViewNode(): any {
return ReactDOM.findDOMNode(this.refs[INNERVIEW])
},
/**
* Scrolls to a given x, y offset, either immediately or with a smooth animation.
* Syntax:
*
* scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})
*
* Note: The weird argument signature is due to the fact that, for historical reasons,
* the function also accepts separate arguments as as alternative to the options object.
* This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
*/
scrollTo(
y?: number | { x?: number, y?: number, animated?: boolean },
x?: number,
animated?: boolean
) {
if (typeof y === 'number') {
console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.')
} else {
// Weren't scrolling, so we must have just started
this._onScrollStart(e)
({x, y, animated} = y || {})
}
}
_onScrollStart() {
this.setState({
isScrolling: true,
scrollLastTick: Date.now()
})
}
this.getScrollResponder().scrollResponderScrollTo({x: x || 0, y: y || 0, animated: animated !== false})
},
_onScrollTick(e) {
const { onScroll } = this.props
this.setState({
scrollLastTick: Date.now()
})
if (onScroll) onScroll(e)
}
/**
* Deprecated, do not use.
*/
scrollWithoutAnimationTo(y: number = 0, x: number = 0) {
console.warn('`scrollWithoutAnimationTo` is deprecated. Use `scrollTo` instead')
this.scrollTo({x, y, animated: false})
},
_onScrollEnd(e) {
const { onScroll } = this.props
this.setState({
isScrolling: false
})
if (onScroll) onScroll(e)
}
handleScroll(e: Object) {
if (process.env.NODE_ENV !== 'production') {
if (this.props.onScroll && !this.props.scrollEventThrottle) {
console.log(
'You specified `onScroll` on a <ScrollView> but not ' +
'`scrollEventThrottle`. You will only receive one event. ' +
'Using `16` you get all the events but be aware that it may ' +
'cause frame drops, use a bigger number if you don\'t need as ' +
'much precision.'
)
}
}
_shouldEmitScrollEvent(lastTick, eventThrottle) {
const timeSinceLastTick = Date.now() - lastTick
return (eventThrottle > 0 && timeSinceLastTick >= (1000 / eventThrottle))
}
if (this.props.keyboardDismissMode === 'on-drag') {
dismissKeyboard()
}
_maybePreventScroll(e) {
const { scrollEnabled } = this.props
if (!scrollEnabled) e.preventDefault()
}
this.scrollResponderHandleScroll(e)
},
_handleContentOnLayout(e: Object) {
const { width, height } = e.nativeEvent.layout
this.props.onContentSizeChange && this.props.onContentSizeChange(width, height)
},
render() {
const {
children,
contentContainerStyle,
horizontal,
style
} = this.props
const scrollViewStyle = [
styles.base,
this.props.horizontal && styles.baseHorizontal
]
const contentContainerStyle = [
styles.contentContainer,
this.props.horizontal && styles.contentContainerHorizontal,
this.props.contentContainerStyle
]
if (process.env.NODE_ENV !== 'production' && this.props.style) {
const style = StyleSheet.flatten(this.props.style)
const childLayoutProps = ['alignItems', 'justifyContent'].filter((prop) => style && style[prop] !== undefined)
invariant(
childLayoutProps.length === 0,
'ScrollView child layout (' + JSON.stringify(childLayoutProps) +
') must be applied through the contentContainerStyle prop.'
)
}
let contentSizeChangeProps = {}
if (this.props.onContentSizeChange) {
contentSizeChangeProps = {
onLayout: this._handleContentOnLayout
}
}
const contentContainer = (
<View
{...contentSizeChangeProps}
children={this.props.children}
collapsable={false}
ref={INNERVIEW}
style={contentContainerStyle}
/>
)
const props = {
...this.props,
style: [scrollViewStyle, this.props.style],
onTouchStart: this.scrollResponderHandleTouchStart,
onTouchMove: this.scrollResponderHandleTouchMove,
onTouchEnd: this.scrollResponderHandleTouchEnd,
onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag,
onScrollEndDrag: this.scrollResponderHandleScrollEndDrag,
onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin,
onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd,
onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder,
onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture,
onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder,
onScroll: this.handleScroll,
onResponderGrant: this.scrollResponderHandleResponderGrant,
onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest,
onResponderTerminate: this.scrollResponderHandleTerminate,
onResponderRelease: this.scrollResponderHandleResponderRelease,
onResponderReject: this.scrollResponderHandleResponderReject
}
const ScrollViewClass = ScrollViewBase
invariant(
ScrollViewClass !== undefined,
'ScrollViewClass must not be undefined'
)
var refreshControl = this.props.refreshControl
if (refreshControl) {
return React.cloneElement(
refreshControl,
{ style: props.style },
<ScrollViewClass {...props} ref={SCROLLVIEW} style={styles.base}>
{contentContainer}
</ScrollViewClass>
)
}
return (
<View
onScroll={(e) => this._onScroll(e)}
onTouchMove={(e) => this._maybePreventScroll(e)}
onWheel={(e) => this._maybePreventScroll(e)}
style={[
styles.initial,
style
]}
>
{children ? (
<View
children={children}
style={[
styles.initialContentContainer,
contentContainerStyle,
horizontal && styles.row
]}
/>
) : null}
</View>
<ScrollViewClass {...props} ref={SCROLLVIEW} style={props.style}>
{contentContainer}
</ScrollViewClass>
)
}
}
})
const styles = StyleSheet.create({
initial: {
base: {
flex: 1,
overflow: 'auto'
overflowX: 'hidden',
overflowY: 'auto'
},
initialContentContainer: {
baseHorizontal: {
overflowX: 'auto',
overflowY: 'hidden'
},
contentContainer: {
flex: 1
},
row: {
contentContainerHorizontal: {
flexDirection: 'row'
}
})
module.exports = ScrollView

View File

@@ -23,7 +23,7 @@ import React, { Component, PropTypes } from 'react'
* Typically, you will not need to use this component and should opt for normal
* React reconciliation.
*/
export default class StaticContainer extends Component {
class StaticContainer extends Component {
static propTypes = {
children: PropTypes.any.isRequired,
shouldUpdate: PropTypes.bool.isRequired
@@ -38,3 +38,5 @@ export default class StaticContainer extends Component {
return (child === null || child === false) ? null : React.Children.only(child)
}
}
module.exports = StaticContainer

View File

@@ -22,7 +22,7 @@ import { Component, PropTypes } from 'react'
* React reconciliation.
*/
export default class StaticRenderer extends Component {
class StaticRenderer extends Component {
static propTypes = {
render: PropTypes.func.isRequired,
shouldUpdate: PropTypes.bool.isRequired
@@ -36,3 +36,5 @@ export default class StaticRenderer extends Component {
return this.props.render()
}
}
module.exports = StaticRenderer

View File

@@ -5,7 +5,7 @@ import ViewStylePropTypes from '../View/ViewStylePropTypes'
const { number, oneOf, oneOfType, string } = PropTypes
const numberOrString = oneOfType([ number, string ])
export default {
module.exports = {
...ViewStylePropTypes,
color: ColorPropType,
fontFamily: string,

View File

@@ -1,4 +1,4 @@
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import CoreComponent from '../CoreComponent'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
@@ -6,7 +6,7 @@ import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import TextStylePropTypes from './TextStylePropTypes'
@NativeMethodsDecorator
export default class Text extends Component {
class Text extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
@@ -66,3 +66,5 @@ const styles = StyleSheet.create({
whiteSpace: 'nowrap'
}
})
module.exports = Text

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* @flow
*/
import UIManager from '../../apis/UIManager'
/**
* This class is responsible for coordinating the "focused"
* state for TextInputs. All calls relating to the keyboard
* should be funneled through here
*/
const TextInputState = {
/**
* Internal state
*/
_currentlyFocusedNode: (null: ?Object),
/**
* Returns the ID of the currently focused text field, if one exists
* If no text field is focused it returns null
*/
currentlyFocusedField(): ?Object {
return this._currentlyFocusedNode
},
/**
* @param {Object} TextInputID id of the text field to focus
* Focuses the specified text field
* noop if the text field was already focused
*/
focusTextInput(textFieldNode: ?Object) {
if (this._currentlyFocusedNode !== textFieldNode && textFieldNode !== null) {
this._currentlyFocusedNode = textFieldNode
UIManager.focus(textFieldNode)
}
},
/**
* @param {Object} textFieldNode id of the text field to focus
* Unfocuses the specified text field
* noop if it wasn't focused
*/
blurTextInput(textFieldNode: ?Object) {
if (this._currentlyFocusedNode === textFieldNode && textFieldNode !== null) {
this._currentlyFocusedNode = null
UIManager.blur(textFieldNode)
}
}
}
module.exports = TextInputState

View File

@@ -1,14 +1,15 @@
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import CoreComponent from '../CoreComponent'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../../apis/StyleSheet'
import Text from '../Text'
import TextareaAutosize from 'react-textarea-autosize'
import TextInputState from './TextInputState'
import View from '../View'
@NativeMethodsDecorator
export default class TextInput extends Component {
class TextInput extends Component {
static propTypes = {
...View.propTypes,
autoComplete: PropTypes.bool,
@@ -50,11 +51,15 @@ export default class TextInput extends Component {
}
blur() {
this.refs.input.blur()
TextInputState.blurTextInput(ReactDOM.findDOMNode(this.refs.input))
}
clear() {
this.setNativeProps({ text: '' })
}
focus() {
this.refs.input.focus()
TextInputState.focusTextInput(ReactDOM.findDOMNode(this.refs.input))
}
setNativeProps(props) {
@@ -63,27 +68,34 @@ export default class TextInput extends Component {
_onBlur(e) {
const { onBlur } = this.props
const value = e.target.value
this.setState({ showPlaceholder: value === '' })
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
this.blur()
if (onBlur) onBlur(e)
}
_onChange(e) {
const { onChange, onChangeText } = this.props
const value = e.target.value
this.setState({ showPlaceholder: value === '' })
if (onChangeText) onChangeText(value)
const text = e.target.value
this.setState({ showPlaceholder: text === '' })
if (onChange) onChange(e)
if (onChangeText) onChangeText(text)
if (!this.refs.input) {
// calling `this.props.onChange` or `this.props.onChangeText`
// may clean up the input itself. Exits here.
return
}
}
_onFocus(e) {
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
const node = ReactDOM.findDOMNode(this.refs.input)
const value = e.target.value
if (clearTextOnFocus) node.value = ''
if (selectTextOnFocus) node.select()
this.setState({ showPlaceholder: value === '' })
const text = e.target.value
this.focus()
if (onFocus) onFocus(e)
if (clearTextOnFocus) this.clear()
if (selectTextOnFocus) node.select()
this.setState({ showPlaceholder: text === '' })
}
_onSelectionChange(e) {
@@ -229,3 +241,5 @@ const styles = StyleSheet.create({
whiteSpace: 'pre'
}
})
module.exports = TextInput

View File

@@ -340,7 +340,7 @@ var TouchableMixin = {
* Must return true to start the process of `Touchable`.
*/
touchableHandleStartShouldSetResponder: function() {
return true;
return !this.props.disabled;
},
/**
@@ -558,10 +558,10 @@ var TouchableMixin = {
* @sideeffects
* @private
*/
_remeasureMetricsOnActivation: function() {
_remeasureMetricsOnActivation: function(e) {
/* @edit begin */
UIManager.measure(
this.state.touchable.responderID,
e.nativeEvent.target,
this._handleQueryLayout
);
/* @edit end */
@@ -603,18 +603,22 @@ var TouchableMixin = {
* @sideeffects
*/
_receiveSignal: function(signal, e) {
var responderID = this.state.touchable.responderID;
var curState = this.state.touchable.touchState;
var nextState = Transitions[curState] && Transitions[curState][signal];
if (!responderID && signal === Signals.RESPONDER_RELEASE) {
return;
}
if (!nextState) {
throw new Error(
'Unrecognized signal `' + signal + '` or state `' + curState +
'` for Touchable responder `' + this.state.touchable.responderID + '`'
'` for Touchable responder `' + responderID + '`'
);
}
if (nextState === States.ERROR) {
throw new Error(
'Touchable cannot transition from `' + curState + '` to `' + signal +
'` for responder `' + this.state.touchable.responderID + '`'
'` for responder `' + responderID + '`'
);
}
if (curState !== nextState) {
@@ -672,7 +676,7 @@ var TouchableMixin = {
}
if (!IsActive[curState] && IsActive[nextState]) {
this._remeasureMetricsOnActivation();
this._remeasureMetricsOnActivation(e);
}
if (IsPressingIn[curState] && signal === Signals.LONG_PRESS_DETECTED) {

View File

@@ -0,0 +1,164 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule TouchableBounce
* @flow
*/
'use strict';
var Animated = require('../../apis/Animated');
var EdgeInsetsPropType = require('../../apis/StyleSheet/EdgeInsetsPropType');
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
var React = require('react');
var StyleSheet = require('../../apis/StyleSheet');
var Touchable = require('./Touchable');
type Event = Object;
type State = {
animationID: ?number;
scale: Animated.Value;
};
var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
/**
* Example of using the `TouchableMixin` to play well with other responder
* locking views including `ScrollView`. `TouchableMixin` provides touchable
* hooks (`this.touchableHandle*`) that we forward events to. In turn,
* `TouchableMixin` expects us to implement some abstract methods to handle
* interesting interactions such as `handleTouchablePress`.
*/
var TouchableBounce = React.createClass({
mixins: [Touchable.Mixin, NativeMethodsMixin],
propTypes: {
onPress: React.PropTypes.func,
onPressIn: React.PropTypes.func,
onPressOut: React.PropTypes.func,
// The function passed takes a callback to start the animation which should
// be run after this onPress handler is done. You can use this (for example)
// to update UI before starting the animation.
onPressWithCompletion: React.PropTypes.func,
// the function passed is called after the animation is complete
onPressAnimationComplete: React.PropTypes.func,
/**
* 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: EdgeInsetsPropType,
/**
* 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: EdgeInsetsPropType,
},
getInitialState: function(): State {
return {
...this.touchableGetInitialState(),
scale: new Animated.Value(1),
};
},
bounceTo: function(
value: number,
velocity: number,
bounciness: number,
callback?: ?Function
) {
Animated.spring(this.state.scale, {
toValue: value,
velocity,
bounciness,
}).start(callback);
},
/**
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
* defined on your component.
*/
touchableHandleActivePressIn: function(e: Event) {
this.bounceTo(0.93, 0.1, 0);
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
this.bounceTo(1, 0.4, 0);
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandlePress: function(e: Event) {
var onPressWithCompletion = this.props.onPressWithCompletion;
if (onPressWithCompletion) {
onPressWithCompletion(() => {
this.state.scale.setValue(0.93);
this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
});
return;
}
this.bounceTo(1, 10, 10, this.props.onPressAnimationComplete);
this.props.onPress && this.props.onPress(e);
},
touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
},
touchableGetHitSlop: function(): ?Object {
return this.props.hitSlop;
},
touchableGetHighlightDelayMS: function(): number {
return 0;
},
render: function(): ReactElement {
const scaleTransform = [{ scale: this.state.scale }];
const propsTransform = this.props.style.transform;
const transform = propsTransform && Array.isArray(propsTransform) ? propsTransform.concat(scaleTransform) : scaleTransform;
return (
<Animated.View
accessible={true}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole || 'button'}
testID={this.props.testID}
hitSlop={this.props.hitSlop}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
onResponderGrant={this.touchableHandleResponderGrant}
onResponderMove={this.touchableHandleResponderMove}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
style={[styles.root, this.props.style, { transform }]}
tabIndex='0'
>
{this.props.children}
</Animated.View>
);
}
});
const styles = StyleSheet.create({
root: {
cursor: 'pointer',
userSelect: 'none'
}
});
module.exports = TouchableBounce;

View File

@@ -0,0 +1,278 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule TouchableHighlight
* @noflow
*/
'use strict';
// Note (avik): add @flow when Flow supports spread properties in propTypes
var ColorPropType = require('../../apis/StyleSheet/ColorPropType');
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
var React = require('react');
var StyleSheet = require('../../apis/StyleSheet');
var TimerMixin = require('react-timer-mixin');
var Touchable = require('./Touchable');
var TouchableWithoutFeedback = require('./TouchableWithoutFeedback');
var View = require('../View');
var ensureComponentIsNative = require('./ensureComponentIsNative');
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
var keyOf = require('fbjs/lib/keyOf');
var merge = require('../../modules/merge');
type Event = Object;
var DEFAULT_PROPS = {
activeOpacity: 0.8,
underlayColor: 'black',
};
var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
/**
* 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 adding a view to the view hierarchy, which can 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.
*
* Example:
*
* ```
* renderButton: function() {
* return (
* <TouchableHighlight onPress={this._onPressButton}>
* <Image
* style={styles.button}
* source={require('image!myButton')}
* />
* </TouchableHighlight>
* );
* },
* ```
* > **NOTE**: TouchableHighlight supports only one child
* >
* > If you wish to have several child components, wrap them in a View.
*/
var TouchableHighlight = React.createClass({
propTypes: {
...TouchableWithoutFeedback.propTypes,
/**
* Determines what the opacity of the wrapped view should be when touch is
* active.
*/
activeOpacity: React.PropTypes.number,
/**
* The color of the underlay that will show through when the touch is
* active.
*/
underlayColor: ColorPropType,
style: View.propTypes.style,
/**
* Called immediately after the underlay is shown
*/
onShowUnderlay: React.PropTypes.func,
/**
* Called immediately after the underlay is hidden
*/
onHideUnderlay: React.PropTypes.func,
},
mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin],
getDefaultProps: () => DEFAULT_PROPS,
// Performance optimization to avoid constantly re-generating these objects.
computeSyntheticState: function(props) {
return {
activeProps: {
style: {
opacity: props.activeOpacity,
}
},
activeUnderlayProps: {
style: {
backgroundColor: props.underlayColor,
}
},
underlayStyle: [
INACTIVE_UNDERLAY_PROPS.style
]
};
},
getInitialState: function() {
return merge(this.touchableGetInitialState(), this.computeSyntheticState(this.props))
},
componentDidMount: function() {
ensurePositiveDelayProps(this.props);
ensureComponentIsNative(this.refs[CHILD_REF]);
},
componentDidUpdate: function() {
ensureComponentIsNative(this.refs[CHILD_REF]);
},
componentWillReceiveProps: function(nextProps) {
ensurePositiveDelayProps(nextProps);
if (nextProps.activeOpacity !== this.props.activeOpacity ||
nextProps.underlayColor !== this.props.underlayColor ||
nextProps.style !== this.props.style) {
this.setState(this.computeSyntheticState(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: Event) {
this.clearTimeout(this._hideTimeout);
this._hideTimeout = null;
this._showUnderlay();
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
if (!this._hideTimeout) {
this._hideUnderlay();
}
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandlePress: function(e: Event) {
this.clearTimeout(this._hideTimeout);
this._showUnderlay();
this._hideTimeout = this.setTimeout(this._hideUnderlay,
this.props.delayPressOut || 100);
this.props.onPress && this.props.onPress(e);
},
touchableHandleLongPress: function(e: Event) {
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() {
if (!this.isMounted() || !this._hasPressHandler()) {
return;
}
this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps);
this.refs[CHILD_REF].setNativeProps(this.state.activeProps);
this.props.onShowUnderlay && this.props.onShowUnderlay();
},
_hideUnderlay: function() {
this.clearTimeout(this._hideTimeout);
this._hideTimeout = null;
if (this._hasPressHandler() && this.refs[UNDERLAY_REF]) {
this.refs[CHILD_REF].setNativeProps(INACTIVE_CHILD_PROPS);
this.refs[UNDERLAY_REF].setNativeProps({
...INACTIVE_UNDERLAY_PROPS,
style: this.state.underlayStyle,
});
this.props.onHideUnderlay && this.props.onHideUnderlay();
}
},
_hasPressHandler: function() {
return !!(
this.props.onPress ||
this.props.onPressIn ||
this.props.onPressOut ||
this.props.onLongPress
);
},
_onKeyEnter(e, callback) {
var ENTER = 13
if (e.keyCode === ENTER) {
callback && callback(e)
}
},
render: function() {
return (
<View
accessible={true}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole || this.props.accessibilityTraits || 'button'}
ref={UNDERLAY_REF}
style={[styles.root, this.state.underlayStyle, this.props.style]}
onLayout={this.props.onLayout}
hitSlop={this.props.hitSlop}
onKeyDown={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressIn) }}
onKeyPress={(e) => { this._onKeyEnter(e, this.touchableHandlePress) }}
onKeyUp={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressOut) }}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
onResponderGrant={this.touchableHandleResponderGrant}
onResponderMove={this.touchableHandleResponderMove}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
tabIndex='0'
testID={this.props.testID}>
{React.cloneElement(
React.Children.only(this.props.children),
{
ref: CHILD_REF,
}
)}
</View>
);
}
});
var CHILD_REF = keyOf({childRef: null});
var UNDERLAY_REF = keyOf({underlayRef: null});
var INACTIVE_CHILD_PROPS = {
style: StyleSheet.create({x: {opacity: 1.0}}).x,
};
var INACTIVE_UNDERLAY_PROPS = {
style: {backgroundColor: null}
};
var styles = StyleSheet.create({
root: {
cursor: 'pointer',
userSelect: 'none'
}
});
module.exports = TouchableHighlight;

View File

@@ -0,0 +1,200 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule TouchableOpacity
* @noflow
*/
'use strict';
// Note (avik): add @flow when Flow supports spread properties in propTypes
var Animated = require('../../apis/Animated');
var NativeMethodsMixin = require('../../modules/NativeMethodsMixin');
var React = require('react');
var StyleSheet = require('../../apis/StyleSheet');
var TimerMixin = require('react-timer-mixin');
var Touchable = require('./Touchable');
var TouchableWithoutFeedback = require('./TouchableWithoutFeedback');
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
var flattenStyle = require('../../apis/StyleSheet/flattenStyle');
type Event = Object;
var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
/**
* A wrapper for making views respond properly to touches.
* On press down, the opacity of the wrapped view is decreased, dimming it.
* This is done without actually changing the view hierarchy, and in general is
* easy to add to an app without weird side-effects.
*
* Example:
*
* ```
* renderButton: function() {
* return (
* <TouchableOpacity onPress={this._onPressButton}>
* <Image
* style={styles.button}
* source={require('image!myButton')}
* />
* </TouchableOpacity>
* );
* },
* ```
*/
var TouchableOpacity = React.createClass({
mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin],
propTypes: {
...TouchableWithoutFeedback.propTypes,
/**
* Determines what the opacity of the wrapped view should be when touch is
* active.
*/
activeOpacity: React.PropTypes.number,
},
getDefaultProps: function() {
return {
activeOpacity: 0.2,
};
},
getInitialState: function() {
return {
...this.touchableGetInitialState(),
anim: new Animated.Value(1),
};
},
componentDidMount: function() {
ensurePositiveDelayProps(this.props);
},
componentWillReceiveProps: function(nextProps) {
ensurePositiveDelayProps(nextProps);
},
setOpacityTo: function(value) {
Animated.timing(
this.state.anim,
{toValue: value, duration: 150}
).start();
},
/**
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
* defined on your component.
*/
touchableHandleActivePressIn: function(e: Event) {
this.clearTimeout(this._hideTimeout);
this._hideTimeout = null;
this._opacityActive();
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
if (!this._hideTimeout) {
this._opacityInactive();
}
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandlePress: function(e: Event) {
this.clearTimeout(this._hideTimeout);
this._opacityActive();
this._hideTimeout = this.setTimeout(
this._opacityInactive,
this.props.delayPressOut || 100
);
this.props.onPress && this.props.onPress(e);
},
touchableHandleLongPress: function(e: Event) {
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() {
this.setOpacityTo(this.props.activeOpacity);
},
_opacityInactive: function() {
this.clearTimeout(this._hideTimeout);
this._hideTimeout = null;
var childStyle = flattenStyle(this.props.style) || {};
this.setOpacityTo(
childStyle.opacity === undefined ? 1 : childStyle.opacity
);
},
_onKeyEnter(e, callback) {
var ENTER = 13
if (e.keyCode === ENTER) {
callback && callback(e)
}
},
render: function() {
return (
<Animated.View
accessible={true}
accessibilityLabel={this.props.accessibilityLabel}
accessibilityRole={this.props.accessibilityRole || 'button'}
style={[styles.root, this.props.style, {opacity: this.state.anim}]}
testID={this.props.testID}
onLayout={this.props.onLayout}
hitSlop={this.props.hitSlop}
onKeyDown={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressIn) }}
onKeyPress={(e) => { this._onKeyEnter(e, this.touchableHandlePress) }}
onKeyUp={(e) => { this._onKeyEnter(e, this.touchableHandleActivePressOut) }}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
onResponderGrant={this.touchableHandleResponderGrant}
onResponderMove={this.touchableHandleResponderMove}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
tabIndex='0'
>
{this.props.children}
</Animated.View>
);
},
});
var styles = StyleSheet.create({
root: {
cursor: 'pointer',
userSelect: 'none'
}
});
module.exports = TouchableOpacity;

View File

@@ -0,0 +1,166 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule TouchableWithoutFeedback
* @flow
*/
'use strict';
var EdgeInsetsPropType = require('../../apis/StyleSheet/EdgeInsetsPropType');
var React = require('react');
var TimerMixin = require('react-timer-mixin');
var Touchable = require('./Touchable');
var View = require('../View');
var ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
type Event = Object;
var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
/**
* Do not use unless you have a very good reason. All the elements that
* respond to press should have a visual feedback when touched. This is
* one of the primary reason a "web" app doesn't feel "native".
*
* > **NOTE**: TouchableWithoutFeedback supports only one child
* >
* > If you wish to have several child components, wrap them in a View.
*/
var TouchableWithoutFeedback = React.createClass({
mixins: [TimerMixin, Touchable.Mixin],
propTypes: {
accessible: React.PropTypes.bool,
accessibilityLabel: View.propTypes.accessibilityLabel,
accessibilityRole: View.propTypes.accessibilityRole,
/**
* If true, disable all interactions for this component.
*/
disabled: React.PropTypes.bool,
/**
* Called when the touch is released, but not if cancelled (e.g. by a scroll
* that steals the responder lock).
*/
onPress: React.PropTypes.func,
onPressIn: React.PropTypes.func,
onPressOut: React.PropTypes.func,
/**
* Invoked on mount and layout changes with
*
* `{nativeEvent: {layout: {x, y, width, height}}}`
*/
onLayout: React.PropTypes.func,
onLongPress: React.PropTypes.func,
/**
* Delay in ms, from the start of the touch, before onPressIn is called.
*/
delayPressIn: React.PropTypes.number,
/**
* Delay in ms, from the release of the touch, before onPressOut is called.
*/
delayPressOut: React.PropTypes.number,
/**
* Delay in ms, from onPressIn, before onLongPress is called.
*/
delayLongPress: React.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: EdgeInsetsPropType,
/**
* 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: EdgeInsetsPropType,
},
getInitialState: function() {
return this.touchableGetInitialState();
},
componentDidMount: function() {
ensurePositiveDelayProps(this.props);
},
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: Event) {
this.props.onPress && this.props.onPress(e);
},
touchableHandleActivePressIn: function(e: Event) {
this.props.onPressIn && this.props.onPressIn(e);
},
touchableHandleActivePressOut: function(e: Event) {
this.props.onPressOut && this.props.onPressOut(e);
},
touchableHandleLongPress: function(e: Event) {
this.props.onLongPress && this.props.onLongPress(e);
},
touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
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;
},
render: function(): ReactElement {
// Note(avik): remove dynamic typecast once Flow has been upgraded
return (React: any).cloneElement(React.children.only(this.props.children), {
accessible: this.props.accessible !== false,
accessibilityLabel: this.props.accessibilityLabel,
accessibilityRole: this.props.accessibilityRole,
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,
tabIndex: '0'
});
}
});
module.exports = TouchableWithoutFeedback;

View File

@@ -1,35 +1,5 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import Touchable from '../'
const children = <span style={{}}>children</span>
const requiredProps = { children }
suite('components/Touchable', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<Touchable {...requiredProps} accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'accessibilityRole'
const result = utils.shallowRender(<Touchable {...requiredProps} accessibilityRole={accessibilityRole} />)
assert.equal(result.props.accessibilityRole, accessibilityRole)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<Touchable {...requiredProps} accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const result = utils.shallowRender(<Touchable {...requiredProps} />)
assert.deepEqual(result.props.children, <span style={[{}, false]}>children</span>)
})
test.skip('NO TEST COVERAGE', () => {})
})

View File

@@ -0,0 +1,25 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ensureComponentIsNative
* @flow
*/
'use strict';
var invariant = require('fbjs/lib/invariant');
var ensureComponentIsNative = function(component: any) {
invariant(
component && typeof component.setNativeProps === 'function',
'Touchable child must either be native or forward setNativeProps to a ' +
'native component'
);
};
module.exports = ensureComponentIsNative;

View File

@@ -0,0 +1,25 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ensurePositiveDelayProps
* @flow
*/
'use strict';
var invariant = require('fbjs/lib/invariant');
var ensurePositiveDelayProps = function(props: any) {
invariant(
!(props.delayPressIn < 0 || props.delayPressOut < 0 ||
props.delayLongPress < 0),
'Touchable components cannot have negative delay properties'
);
};
module.exports = ensurePositiveDelayProps;

View File

@@ -1,131 +0,0 @@
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import Tappable from 'react-tappable'
import View from '../View'
export default class Touchable extends Component {
constructor(props, context) {
super(props, context)
this.state = {
isActive: false
}
this._onLongPress = this._onLongPress.bind(this)
this._onPress = this._onPress.bind(this)
this._onPressIn = this._onPressIn.bind(this)
this._onPressOut = this._onPressOut.bind(this)
}
static propTypes = {
accessibilityLabel: View.propTypes.accessibilityLabel,
accessibilityRole: View.propTypes.accessibilityRole,
accessible: View.propTypes.accessible,
activeOpacity: PropTypes.number,
activeUnderlayColor: PropTypes.string,
children: PropTypes.element,
delayLongPress: PropTypes.number,
delayPressIn: PropTypes.number,
delayPressOut: PropTypes.number,
onLongPress: PropTypes.func,
onPress: PropTypes.func,
onPressIn: PropTypes.func,
onPressOut: PropTypes.func,
style: View.propTypes.style
};
static defaultProps = {
accessibilityRole: 'button',
activeOpacity: 0.8,
activeUnderlayColor: 'black',
delayLongPress: 500,
delayPressIn: 0,
delayPressOut: 100,
style: {}
};
_getChildren() {
const { activeOpacity, children } = this.props
return React.cloneElement(React.Children.only(children), {
style: [
children.props.style,
this.state.isActive && { opacity: activeOpacity }
]
})
}
_onKeyEnter(e, callback) {
var ENTER = 13
if (e.keyCode === ENTER) {
callback(e)
}
}
_onLongPress(e) {
if (this.props.onLongPress) this.props.onLongPress(e)
}
_onPress(e) {
if (this.props.onPress) this.props.onPress(e)
}
_onPressIn(e) {
this.setState({ isActive: true })
if (this.props.onPressIn) this.props.onPressIn(e)
}
_onPressOut(e) {
this.setState({ isActive: false })
if (this.props.onPressOut) this.props.onPressOut(e)
}
render() {
const {
accessibilityLabel,
accessibilityRole,
accessible,
activeUnderlayColor,
delayLongPress,
style
} = this.props
/**
* Creates a wrapping element that can receive keyboard focus. The
* highlight is applied as a background color on this wrapper. The opacity
* is set on the child element, allowing it to have its own background
* color.
*/
return (
<Tappable
accessibilityLabel={accessibilityLabel}
accessibilityRole={accessibilityRole}
accessible={accessible}
children={this._getChildren()}
component={View}
onKeyDown={(e) => { this._onKeyEnter(e, this._onPressIn) }}
onKeyPress={this._onPress}
onKeyUp={(e) => { this._onKeyEnter(e, this._onPressOut) }}
onMouseDown={this._onPressIn}
onMouseUp={this._onPressOut}
onPress={this._onLongPress}
onTap={this._onPress}
onTouchEnd={this._onPressOut}
onTouchStart={this._onPressIn}
pressDelay={delayLongPress}
pressMoveThreshold={5}
style={StyleSheet.flatten([
styles.initial,
style,
activeUnderlayColor && this.state.isActive && { backgroundColor: activeUnderlayColor }
])}
tabIndex='0'
/>
)
}
}
const styles = StyleSheet.create({
initial: {
cursor: 'pointer',
userSelect: undefined
}
})

View File

@@ -8,7 +8,7 @@ const { number, oneOf, string } = PropTypes
const autoOrHiddenOrVisible = oneOf([ 'auto', 'hidden', 'visible' ])
const hiddenOrVisible = oneOf([ 'hidden', 'visible' ])
export default {
module.exports = {
...BorderPropTypes,
...LayoutPropTypes,
...TransformPropTypes,

View File

@@ -1,4 +1,5 @@
import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import normalizeNativeEvent from '../../apis/PanResponder/normalizeNativeEvent'
import CoreComponent from '../CoreComponent'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
@@ -6,7 +7,7 @@ import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import ViewStylePropTypes from './ViewStylePropTypes'
@NativeMethodsDecorator
export default class View extends Component {
class View extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessibilityLiveRegion: CoreComponent.propTypes.accessibilityLiveRegion,
@@ -44,16 +45,7 @@ export default class View extends Component {
constructor(props, context) {
super(props, context)
this._handleClick = this._handleClick.bind(this)
this._handleClickCapture = this._handleClickCapture.bind(this)
this._handleTouchCancel = this._handleTouchCancel.bind(this)
this._handleTouchCancelCapture = this._handleTouchCancelCapture.bind(this)
this._handleTouchEnd = this._handleTouchEnd.bind(this)
this._handleTouchEndCapture = this._handleTouchEndCapture.bind(this)
this._handleTouchMove = this._handleTouchMove.bind(this)
this._handleTouchMoveCapture = this._handleTouchMoveCapture.bind(this)
this._handleTouchStart = this._handleTouchStart.bind(this)
this._handleTouchStartCapture = this._handleTouchStartCapture.bind(this)
this._normalizeEventForHandler = this._normalizeEventForHandler.bind(this)
}
render() {
@@ -69,15 +61,15 @@ export default class View extends Component {
<CoreComponent
{...other}
onClick={this._handleClick}
onClickCapture={this._handleClickCapture}
onTouchCancel={this._handleTouchCancel}
onTouchCancelCapture={this._handleTouchCancelCapture}
onTouchEnd={this._handleTouchEnd}
onTouchEndCapture={this._handleTouchEndCapture}
onTouchMove={this._handleTouchMove}
onTouchMoveCapture={this._handleTouchMoveCapture}
onTouchStart={this._handleTouchStart}
onTouchStartCapture={this._handleTouchStartCapture}
onClickCapture={this._normalizeEventForHandler(this.props.onClickCapture)}
onTouchCancel={this._normalizeEventForHandler(this.props.onTouchCancel)}
onTouchCancelCapture={this._normalizeEventForHandler(this.props.onTouchCancelCapture)}
onTouchEnd={this._normalizeEventForHandler(this.props.onTouchEnd)}
onTouchEndCapture={this._normalizeEventForHandler(this.props.onTouchEndCapture)}
onTouchMove={this._normalizeEventForHandler(this.props.onTouchMove)}
onTouchMoveCapture={this._normalizeEventForHandler(this.props.onTouchMoveCapture)}
onTouchStart={this._normalizeEventForHandler(this.props.onTouchStart)}
onTouchStartCapture={this._normalizeEventForHandler(this.props.onTouchStartCapture)}
style={[
styles.initial,
style,
@@ -91,73 +83,13 @@ export default class View extends Component {
* React Native expects `pageX` and `pageY` to be on the `nativeEvent`, but
* React doesn't include them for touch events.
*/
_normalizeTouchEvent(event) {
const { pageX, changedTouches } = event.nativeEvent
if (pageX === undefined) {
const { pageX, pageY } = changedTouches[0]
event.nativeEvent.pageX = pageX
event.nativeEvent.pageY = pageY
}
return event
}
_handleClick(e) {
if (this.props.onClick) {
this.props.onClick(this._normalizeTouchEvent(e))
}
}
_handleClickCapture(e) {
if (this.props.onClickCapture) {
this.props.onClickCapture(this._normalizeTouchEvent(e))
}
}
_handleTouchCancel(e) {
if (this.props.onTouchCancel) {
this.props.onTouchCancel(this._normalizeTouchEvent(e))
}
}
_handleTouchCancelCapture(e) {
if (this.props.onTouchCancelCapture) {
this.props.onTouchCancelCapture(this._normalizeTouchEvent(e))
}
}
_handleTouchEnd(e) {
if (this.props.onTouchEnd) {
this.props.onTouchEnd(this._normalizeTouchEvent(e))
}
}
_handleTouchEndCapture(e) {
if (this.props.onTouchEndCapture) {
this.props.onTouchEndCapture(this._normalizeTouchEvent(e))
}
}
_handleTouchMove(e) {
if (this.props.onTouchMove) {
this.props.onTouchMove(this._normalizeTouchEvent(e))
}
}
_handleTouchMoveCapture(e) {
if (this.props.onTouchMoveCapture) {
this.props.onTouchMoveCapture(this._normalizeTouchEvent(e))
}
}
_handleTouchStart(e) {
if (this.props.onTouchStart) {
this.props.onTouchStart(this._normalizeTouchEvent(e))
}
}
_handleTouchStartCapture(e) {
if (this.props.onTouchStartCapture) {
this.props.onTouchStartCapture(this._normalizeTouchEvent(e))
_normalizeEventForHandler(handler) {
return (e) => {
const { pageX } = e.nativeEvent
if (pageX === undefined) {
e.nativeEvent = normalizeNativeEvent(e.nativeEvent)
}
handler && handler(e)
}
}
}
@@ -173,15 +105,22 @@ const styles = StyleSheet.create({
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
listStyle: 'none',
margin: 0,
padding: 0,
position: 'relative',
textDecoration: 'none',
// button reset
// button and anchor reset
backgroundColor: 'transparent',
color: 'inherit',
font: 'inherit',
textAlign: 'inherit'
textAlign: 'inherit',
textDecoration: 'none',
// list reset
listStyle: 'none',
// fix flexbox bugs
maxWidth: '100%',
minHeight: 0,
minWidth: 0
}
})
module.exports = View

View File

@@ -17,6 +17,7 @@ import PanResponder from './apis/PanResponder'
import PixelRatio from './apis/PixelRatio'
import Platform from './apis/Platform'
import StyleSheet from './apis/StyleSheet'
import UIManager from './apis/UIManager'
// components
import ActivityIndicator from './components/ActivityIndicator'
@@ -26,12 +27,22 @@ import Portal from './components/Portal'
import ScrollView from './components/ScrollView'
import Text from './components/Text'
import TextInput from './components/TextInput'
import Touchable from './components/Touchable'
import Touchable from './components/Touchable/Touchable'
import TouchableBounce from './components/Touchable/TouchableBounce'
import TouchableHighlight from './components/Touchable/TouchableHighlight'
import TouchableOpacity from './components/Touchable/TouchableOpacity'
import TouchableWithoutFeedback from './components/Touchable/TouchableWithoutFeedback'
import View from './components/View'
// modules
import NativeModules from './modules/NativeModules'
// propTypes
import ColorPropType from './apis/StyleSheet/ColorPropType'
import EdgeInsetsPropType from './apis/StyleSheet/EdgeInsetsPropType'
import PointPropType from './apis/StyleSheet/PointPropType'
const ReactNative = {
// apis
Animated,
@@ -46,6 +57,7 @@ const ReactNative = {
PixelRatio,
Platform,
StyleSheet,
UIManager,
// components
ActivityIndicator,
@@ -55,15 +67,21 @@ const ReactNative = {
ScrollView,
Text,
TextInput,
TouchableBounce: Touchable,
TouchableHighlight: Touchable,
TouchableOpacity: Touchable,
TouchableWithoutFeedback: Touchable,
Touchable,
TouchableBounce,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
View,
// modules
NativeModules,
// propTypes
ColorPropType,
EdgeInsetsPropType,
PointPropType,
// React
...React,
...ReactDOM,

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2015-present, Nicolas Gallagher.
* All rights reserved.
*
* @flow
*/
import NativeMethodsMixin from '../NativeMethodsMixin'
const NativeMethodsDecorator = (Component) => {
Object.keys(NativeMethodsMixin).forEach((method) => {
if (!Component.prototype[method]) {
Component.prototype[method] = NativeMethodsMixin[method]
}
})
return Component
}
module.exports = NativeMethodsDecorator

View File

@@ -10,13 +10,11 @@ import { Component } from 'react'
import ReactDOM from 'react-dom'
import UIManager from '../../apis/UIManager'
type MeasureOnSuccessCallback = (
type MeasureInWindowOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number
) => void
type MeasureLayoutOnSuccessCallback = (
@@ -26,12 +24,21 @@ type MeasureLayoutOnSuccessCallback = (
height: number
) => void
type MeasureOnSuccessCallback = (
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number
) => void
const NativeMethodsMixin = {
/**
* Removes focus from an input or view. This is the opposite of `focus()`.
*/
blur() {
ReactDOM.findDOMNode(this).blur()
UIManager.blur(ReactDOM.findDOMNode(this))
},
/**
@@ -39,7 +46,7 @@ const NativeMethodsMixin = {
* The exact behavior triggered will depend the type of view.
*/
focus() {
ReactDOM.findDOMNode(this).focus()
UIManager.focus(ReactDOM.findDOMNode(this))
},
/**
@@ -52,11 +59,33 @@ const NativeMethodsMixin = {
)
},
/**
* Determines the location of the given view in the window and returns the
* values via an async callback. If the React root view is embedded in
* another native view, this will give you the absolute coordinates. If
* successful, the callback will be called be called with the following
* arguments:
*
* - x
* - y
* - width
* - height
*
* Note that these measurements are not available until after the rendering
* has been completed in native.
*/
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
UIManager.measureInWindow(
ReactDOM.findDOMNode(this),
mountSafeCallback(this, callback)
)
},
/**
* Measures the view relative to another view (usually an ancestor)
*/
measureLayout(
relativeToNativeNode: number,
relativeToNativeNode: Object,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail: () => void /* currently unused */
) {
@@ -90,11 +119,4 @@ const mountSafeCallback = (context: Component, callback: ?Function) => () => {
return callback.apply(context, arguments)
}
export const NativeMethodsDecorator = (Component) => {
Object.keys(NativeMethodsMixin).forEach((method) => {
Component.prototype[method] = NativeMethodsMixin[method]
})
return Component
}
export default NativeMethodsMixin
module.exports = NativeMethodsMixin

View File

@@ -1,3 +1,2 @@
// NativeModules shim
const NativeModules = {}
export default NativeModules
module.exports = {}

View File

@@ -0,0 +1,538 @@
/* eslint-disable */
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ScrollResponder
* @flow
*/
'use strict';
var Dimensions = require('../../apis/Dimensions');
var Platform = require('../../apis/Platform');
var React = require('react');
var ReactDOM = require('react-dom');
// var Subscribable = require('../Subscribable');
var TextInputState = require('../../components/TextInput/TextInputState');
var UIManager = require('../../apis/UIManager');
// var { ScrollViewManager } = require('../../modules/NativeModules');
var invariant = require('fbjs/lib/invariant');
var warning = require('fbjs/lib/warning');
// type Component = React.Component
/**
* Mixin that can be integrated in order to handle scrolling that plays well
* with `ResponderEventPlugin`. Integrate with your platform specific scroll
* views, or even your custom built (every-frame animating) scroll views so that
* all of these systems play well with the `ResponderEventPlugin`.
*
* iOS scroll event timing nuances:
* ===============================
*
*
* Scrolling without bouncing, if you touch down:
* -------------------------------
*
* 1. `onMomentumScrollBegin` (when animation begins after letting up)
* ... physical touch starts ...
* 2. `onTouchStartCapture` (when you press down to stop the scroll)
* 3. `onTouchStart` (same, but bubble phase)
* 4. `onResponderRelease` (when lifting up - you could pause forever before * lifting)
* 5. `onMomentumScrollEnd`
*
*
* Scrolling with bouncing, if you touch down:
* -------------------------------
*
* 1. `onMomentumScrollBegin` (when animation begins after letting up)
* ... bounce begins ...
* ... some time elapses ...
* ... physical touch during bounce ...
* 2. `onMomentumScrollEnd` (Makes no sense why this occurs first during bounce)
* 3. `onTouchStartCapture` (immediately after `onMomentumScrollEnd`)
* 4. `onTouchStart` (same, but bubble phase)
* 5. `onTouchEnd` (You could hold the touch start for a long time)
* 6. `onMomentumScrollBegin` (When releasing the view starts bouncing back)
*
* So when we receive an `onTouchStart`, how can we tell if we are touching
* *during* an animation (which then causes the animation to stop)? The only way
* to tell is if the `touchStart` occurred immediately after the
* `onMomentumScrollEnd`.
*
* This is abstracted out for you, so you can just call this.scrollResponderIsAnimating() if
* necessary
*
* `ScrollResponder` also includes logic for blurring a currently focused input
* if one is focused while scrolling. The `ScrollResponder` is a natural place
* to put this logic since it can support not dismissing the keyboard while
* scrolling, unless a recognized "tap"-like gesture has occurred.
*
* The public lifecycle API includes events for keyboard interaction, responder
* interaction, and scrolling (among others). The keyboard callbacks
* `onKeyboardWill/Did/*` are *global* events, but are invoked on scroll
* responder's props so that you can guarantee that the scroll responder's
* internal state has been updated accordingly (and deterministically) by
* the time the props callbacks are invoke. Otherwise, you would always wonder
* if the scroll responder is currently in a state where it recognizes new
* keyboard positions etc. If coordinating scrolling with keyboard movement,
* *always* use these hooks instead of listening to your own global keyboard
* events.
*
* Public keyboard lifecycle API: (props callbacks)
*
* Standard Keyboard Appearance Sequence:
*
* this.props.onKeyboardWillShow
* this.props.onKeyboardDidShow
*
* `onScrollResponderKeyboardDismissed` will be invoked if an appropriate
* tap inside the scroll responder's scrollable region was responsible
* for the dismissal of the keyboard. There are other reasons why the
* keyboard could be dismissed.
*
* this.props.onScrollResponderKeyboardDismissed
*
* Standard Keyboard Hide Sequence:
*
* this.props.onKeyboardWillHide
* this.props.onKeyboardDidHide
*/
var IS_ANIMATING_TOUCH_START_THRESHOLD_MS = 16;
type State = {
isTouching: boolean;
lastMomentumScrollBeginTime: number;
lastMomentumScrollEndTime: number;
observedScrollSinceBecomingResponder: boolean;
becameResponderWhileAnimating: boolean;
};
type Event = Object;
var ScrollResponderMixin = {
// mixins: [Subscribable.Mixin],
scrollResponderMixinGetInitialState: function(): State {
return {
isTouching: false,
lastMomentumScrollBeginTime: 0,
lastMomentumScrollEndTime: 0,
// Reset to false every time becomes responder. This is used to:
// - Determine if the scroll view has been scrolled and therefore should
// refuse to give up its responder lock.
// - Determine if releasing should dismiss the keyboard when we are in
// tap-to-dismiss mode (!this.props.keyboardShouldPersistTaps).
observedScrollSinceBecomingResponder: false,
becameResponderWhileAnimating: false,
};
},
/**
* Invoke this from an `onScroll` event.
*/
scrollResponderHandleScrollShouldSetResponder: function(): boolean {
return this.state.isTouching;
},
/**
* Merely touch starting is not sufficient for a scroll view to become the
* responder. Being the "responder" means that the very next touch move/end
* event will result in an action/movement.
*
* Invoke this from an `onStartShouldSetResponder` event.
*
* `onStartShouldSetResponder` is used when the next move/end will trigger
* some UI movement/action, but when you want to yield priority to views
* nested inside of the view.
*
* There may be some cases where scroll views actually should return `true`
* from `onStartShouldSetResponder`: Any time we are detecting a standard tap
* that gives priority to nested views.
*
* - If a single tap on the scroll view triggers an action such as
* recentering a map style view yet wants to give priority to interaction
* views inside (such as dropped pins or labels), then we would return true
* from this method when there is a single touch.
*
* - Similar to the previous case, if a two finger "tap" should trigger a
* zoom, we would check the `touches` count, and if `>= 2`, we would return
* true.
*
*/
scrollResponderHandleStartShouldSetResponder: function(): boolean {
return false;
},
/**
* There are times when the scroll view wants to become the responder
* (meaning respond to the next immediate `touchStart/touchEnd`), in a way
* that *doesn't* give priority to nested views (hence the capture phase):
*
* - Currently animating.
* - Tapping anywhere that is not the focused input, while the keyboard is
* up (which should dismiss the keyboard).
*
* Invoke this from an `onStartShouldSetResponderCapture` event.
*/
scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean {
// First see if we want to eat taps while the keyboard is up
// var currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
// if (!this.props.keyboardShouldPersistTaps &&
// currentlyFocusedTextInput != null &&
// e.target !== currentlyFocusedTextInput) {
// return true;
// }
return this.scrollResponderIsAnimating();
},
/**
* Invoke this from an `onResponderReject` event.
*
* Some other element is not yielding its role as responder. Normally, we'd
* just disable the `UIScrollView`, but a touch has already began on it, the
* `UIScrollView` will not accept being disabled after that. The easiest
* solution for now is to accept the limitation of disallowing this
* altogether. To improve this, find a way to disable the `UIScrollView` after
* a touch has already started.
*/
scrollResponderHandleResponderReject: function() {
warning(false, "ScrollView doesn't take rejection well - scrolls anyway");
},
/**
* We will allow the scroll view to give up its lock iff it acquired the lock
* during an animation. This is a very useful default that happens to satisfy
* many common user experiences.
*
* - Stop a scroll on the left edge, then turn that into an outer view's
* backswipe.
* - Stop a scroll mid-bounce at the top, continue pulling to have the outer
* view dismiss.
* - However, without catching the scroll view mid-bounce (while it is
* motionless), if you drag far enough for the scroll view to become
* responder (and therefore drag the scroll view a bit), any backswipe
* navigation of a swipe gesture higher in the view hierarchy, should be
* rejected.
*/
scrollResponderHandleTerminationRequest: function(): boolean {
return !this.state.observedScrollSinceBecomingResponder;
},
/**
* Invoke this from an `onTouchEnd` event.
*
* @param {SyntheticEvent} e Event.
*/
scrollResponderHandleTouchEnd: function(e: Event) {
var nativeEvent = e.nativeEvent;
this.state.isTouching = nativeEvent.touches.length !== 0;
this.props.onTouchEnd && this.props.onTouchEnd(e);
},
/**
* Invoke this from an `onResponderRelease` event.
*/
scrollResponderHandleResponderRelease: function(e: Event) {
this.props.onResponderRelease && this.props.onResponderRelease(e);
// By default scroll views will unfocus a textField
// if another touch occurs outside of it
var currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
if (!this.props.keyboardShouldPersistTaps &&
currentlyFocusedTextInput != null &&
e.target !== currentlyFocusedTextInput &&
!this.state.observedScrollSinceBecomingResponder &&
!this.state.becameResponderWhileAnimating) {
this.props.onScrollResponderKeyboardDismissed &&
this.props.onScrollResponderKeyboardDismissed(e);
TextInputState.blurTextInput(currentlyFocusedTextInput);
}
},
scrollResponderHandleScroll: function(e: Event) {
this.state.observedScrollSinceBecomingResponder = true;
this.props.onScroll && this.props.onScroll(e);
},
/**
* Invoke this from an `onResponderGrant` event.
*/
scrollResponderHandleResponderGrant: function(e: Event) {
this.state.observedScrollSinceBecomingResponder = false;
this.props.onResponderGrant && this.props.onResponderGrant(e);
this.state.becameResponderWhileAnimating = this.scrollResponderIsAnimating();
},
/**
* Unfortunately, `onScrollBeginDrag` also fires when *stopping* the scroll
* animation, and there's not an easy way to distinguish a drag vs. stopping
* momentum.
*
* Invoke this from an `onScrollBeginDrag` event.
*/
scrollResponderHandleScrollBeginDrag: function(e: Event) {
this.props.onScrollBeginDrag && this.props.onScrollBeginDrag(e);
},
/**
* Invoke this from an `onScrollEndDrag` event.
*/
scrollResponderHandleScrollEndDrag: function(e: Event) {
this.props.onScrollEndDrag && this.props.onScrollEndDrag(e);
},
/**
* Invoke this from an `onMomentumScrollBegin` event.
*/
scrollResponderHandleMomentumScrollBegin: function(e: Event) {
this.state.lastMomentumScrollBeginTime = Date.now();
this.props.onMomentumScrollBegin && this.props.onMomentumScrollBegin(e);
},
/**
* Invoke this from an `onMomentumScrollEnd` event.
*/
scrollResponderHandleMomentumScrollEnd: function(e: Event) {
this.state.lastMomentumScrollEndTime = Date.now();
this.props.onMomentumScrollEnd && this.props.onMomentumScrollEnd(e);
},
/**
* Invoke this from an `onTouchStart` event.
*
* Since we know that the `SimpleEventPlugin` occurs later in the plugin
* order, after `ResponderEventPlugin`, we can detect that we were *not*
* permitted to be the responder (presumably because a contained view became
* responder). The `onResponderReject` won't fire in that case - it only
* fires when a *current* responder rejects our request.
*
* @param {SyntheticEvent} e Touch Start event.
*/
scrollResponderHandleTouchStart: function(e: Event) {
this.state.isTouching = true;
this.props.onTouchStart && this.props.onTouchStart(e);
},
/**
* Invoke this from an `onTouchMove` event.
*
* Since we know that the `SimpleEventPlugin` occurs later in the plugin
* order, after `ResponderEventPlugin`, we can detect that we were *not*
* permitted to be the responder (presumably because a contained view became
* responder). The `onResponderReject` won't fire in that case - it only
* fires when a *current* responder rejects our request.
*
* @param {SyntheticEvent} e Touch Start event.
*/
scrollResponderHandleTouchMove: function(e: Event) {
this.props.onTouchMove && this.props.onTouchMove(e);
},
/**
* A helper function for this class that lets us quickly determine if the
* view is currently animating. This is particularly useful to know when
* a touch has just started or ended.
*/
scrollResponderIsAnimating: function(): boolean {
var now = Date.now();
var timeSinceLastMomentumScrollEnd = now - this.state.lastMomentumScrollEndTime;
var isAnimating = timeSinceLastMomentumScrollEnd < IS_ANIMATING_TOUCH_START_THRESHOLD_MS ||
this.state.lastMomentumScrollEndTime < this.state.lastMomentumScrollBeginTime;
return isAnimating;
},
/**
* Returns the node that represents native view that can be scrolled.
* Components can pass what node to use by defining a `getScrollableNode`
* function otherwise `this` is used.
*/
scrollResponderGetScrollableNode: function(): any {
return this.getScrollableNode ?
this.getScrollableNode() :
ReactDOM.findDOMNode(this);
},
/**
* A helper function to scroll to a specific point in the scrollview.
* This is currently used to help focus on child textviews, but can also
* be used to quickly scroll to any element we want to focus. Syntax:
*
* scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})
*
* Note: The weird argument signature is due to the fact that, for historical reasons,
* the function also accepts separate arguments as as alternative to the options object.
* This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
*/
scrollResponderScrollTo: function(
x?: number | { x?: number; y?: number; animated?: boolean },
y?: number,
animated?: boolean
) {
if (typeof x === 'number') {
console.warn('`scrollResponderScrollTo(x, y, animated)` is deprecated. Use `scrollResponderScrollTo({x: 5, y: 5, animated: true})` instead.');
} else {
({x, y, animated} = x || {});
}
const node = this.scrollResponderGetScrollableNode()
node.scrollLeft = x || 0
node.scrollTop = y || 0
},
/**
* Deprecated, do not use.
*/
scrollResponderScrollWithoutAnimationTo: function(offsetX: number, offsetY: number) {
console.warn('`scrollResponderScrollWithoutAnimationTo` is deprecated. Use `scrollResponderScrollTo` instead');
this.scrollResponderScrollTo({x: offsetX, y: offsetY, animated: false});
},
/**
* A helper function to zoom to a specific rect in the scrollview. The argument has the shape
* {x: number; y: number; width: number; height: number; animated: boolean = true}
*
* @platform ios
*/
scrollResponderZoomTo: function(
rect: { x: number; y: number; width: number; height: number; animated?: boolean },
animated?: boolean // deprecated, put this inside the rect argument instead
) {
if (Platform.OS !== 'ios') {
invariant('zoomToRect is not implemented');
}
},
/**
* This method should be used as the callback to onFocus in a TextInputs'
* parent view. Note that any module using this mixin needs to return
* the parent view's ref in getScrollViewRef() in order to use this method.
* @param {any} nodeHandle The TextInput node handle
* @param {number} additionalOffset The scroll view's top "contentInset".
* Default is 0.
* @param {bool} preventNegativeScrolling Whether to allow pulling the content
* down to make it meet the keyboard's top. Default is false.
*/
scrollResponderScrollNativeHandleToKeyboard: function(nodeHandle: any, additionalOffset?: number, preventNegativeScrollOffset?: bool) {
this.additionalScrollOffset = additionalOffset || 0;
this.preventNegativeScrollOffset = !!preventNegativeScrollOffset;
UIManager.measureLayout(
nodeHandle,
ReactDOM.findDOMNode(this.getInnerViewNode()),
this.scrollResponderTextInputFocusError,
this.scrollResponderInputMeasureAndScrollToKeyboard
);
},
/**
* The calculations performed here assume the scroll view takes up the entire
* screen - even if has some content inset. We then measure the offsets of the
* keyboard, and compensate both for the scroll view's "contentInset".
*
* @param {number} left Position of input w.r.t. table view.
* @param {number} top Position of input w.r.t. table view.
* @param {number} width Width of the text input.
* @param {number} height Height of the text input.
*/
scrollResponderInputMeasureAndScrollToKeyboard: function(left: number, top: number, width: number, height: number) {
var keyboardScreenY = Dimensions.get('window').height;
if (this.keyboardWillOpenTo) {
keyboardScreenY = this.keyboardWillOpenTo.endCoordinates.screenY;
}
var scrollOffsetY = top - keyboardScreenY + height + this.additionalScrollOffset;
// By default, this can scroll with negative offset, pulling the content
// down so that the target component's bottom meets the keyboard's top.
// If requested otherwise, cap the offset at 0 minimum to avoid content
// shifting down.
if (this.preventNegativeScrollOffset) {
scrollOffsetY = Math.max(0, scrollOffsetY);
}
this.scrollResponderScrollTo({x: 0, y: scrollOffsetY, animated: true});
this.additionalOffset = 0;
this.preventNegativeScrollOffset = false;
},
scrollResponderTextInputFocusError: function(e: Event) {
console.error('Error measuring text field: ', e);
},
/**
* `componentWillMount` is the closest thing to a standard "constructor" for
* React components.
*
* The `keyboardWillShow` is called before input focus.
*/
componentWillMount: function() {
this.keyboardWillOpenTo = null;
this.additionalScrollOffset = 0;
// this.addListenerOn(RCTDeviceEventEmitter, 'keyboardWillShow', this.scrollResponderKeyboardWillShow);
// this.addListenerOn(RCTDeviceEventEmitter, 'keyboardWillHide', this.scrollResponderKeyboardWillHide);
// this.addListenerOn(RCTDeviceEventEmitter, 'keyboardDidShow', this.scrollResponderKeyboardDidShow);
// this.addListenerOn(RCTDeviceEventEmitter, 'keyboardDidHide', this.scrollResponderKeyboardDidHide);
},
/**
* Warning, this may be called several times for a single keyboard opening.
* It's best to store the information in this method and then take any action
* at a later point (either in `keyboardDidShow` or other).
*
* Here's the order that events occur in:
* - focus
* - willShow {startCoordinates, endCoordinates} several times
* - didShow several times
* - blur
* - willHide {startCoordinates, endCoordinates} several times
* - didHide several times
*
* The `ScrollResponder` providesModule callbacks for each of these events.
* Even though any user could have easily listened to keyboard events
* themselves, using these `props` callbacks ensures that ordering of events
* is consistent - and not dependent on the order that the keyboard events are
* subscribed to. This matters when telling the scroll view to scroll to where
* the keyboard is headed - the scroll responder better have been notified of
* the keyboard destination before being instructed to scroll to where the
* keyboard will be. Stick to the `ScrollResponder` callbacks, and everything
* will work.
*
* WARNING: These callbacks will fire even if a keyboard is displayed in a
* different navigation pane. Filter out the events to determine if they are
* relevant to you. (For example, only if you receive these callbacks after
* you had explicitly focused a node etc).
*/
scrollResponderKeyboardWillShow: function(e: Event) {
this.keyboardWillOpenTo = e;
this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e);
},
scrollResponderKeyboardWillHide: function(e: Event) {
this.keyboardWillOpenTo = null;
this.props.onKeyboardWillHide && this.props.onKeyboardWillHide(e);
},
scrollResponderKeyboardDidShow: function(e: Event) {
// TODO(7693961): The event for DidShow is not available on iOS yet.
// Use the one from WillShow and do not assign.
if (e) {
this.keyboardWillOpenTo = e;
}
this.props.onKeyboardDidShow && this.props.onKeyboardDidShow(e);
},
scrollResponderKeyboardDidHide: function(e: Event) {
this.keyboardWillOpenTo = null;
this.props.onKeyboardDidHide && this.props.onKeyboardDidHide(e);
}
};
var ScrollResponder = {
Mixin: ScrollResponderMixin,
};
module.exports = ScrollResponder;

View File

@@ -0,0 +1,7 @@
import TextInputState from '../../components/TextInput/TextInputState'
const dismissKeyboard = () => {
TextInputState.blurTextInput(TextInputState.currentlyFocusedField())
}
module.exports = dismissKeyboard

222
src/modules/merge/index.js Normal file
View File

@@ -0,0 +1,222 @@
/* eslint-disable */
/**
* @generated SignedSource<<b68d78236d45828b3f7f7fcc740782a9>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* Copyright 2013-2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule mergeHelpers
*
* requiresPolyfills: Array.isArray
*/
"use strict";
var invariant = require('fbjs/lib/invariant');
var keyMirror = require('fbjs/lib/keyMirror');
/**
* Maximum number of levels to traverse. Will catch circular structures.
* @const
*/
var MAX_MERGE_DEPTH = 36;
/**
* We won't worry about edge cases like new String('x') or new Boolean(true).
* Functions are considered terminals, and arrays are not.
* @param {*} o The item/object/value to test.
* @return {boolean} true iff the argument is a terminal.
*/
var isTerminal = function(o) {
return typeof o !== 'object' || o === null;
};
var mergeHelpers = {
MAX_MERGE_DEPTH: MAX_MERGE_DEPTH,
isTerminal: isTerminal,
/**
* Converts null/undefined values into empty object.
*
* @param {?Object=} arg Argument to be normalized (nullable optional)
* @return {!Object}
*/
normalizeMergeArg: function(arg) {
return arg === undefined || arg === null ? {} : arg;
},
/**
* If merging Arrays, a merge strategy *must* be supplied. If not, it is
* likely the caller's fault. If this function is ever called with anything
* but `one` and `two` being `Array`s, it is the fault of the merge utilities.
*
* @param {*} one Array to merge into.
* @param {*} two Array to merge from.
*/
checkMergeArrayArgs: function(one, two) {
invariant(
Array.isArray(one) && Array.isArray(two),
'Tried to merge arrays, instead got %s and %s.',
one,
two
);
},
/**
* @param {*} one Object to merge into.
* @param {*} two Object to merge from.
*/
checkMergeObjectArgs: function(one, two) {
mergeHelpers.checkMergeObjectArg(one);
mergeHelpers.checkMergeObjectArg(two);
},
/**
* @param {*} arg
*/
checkMergeObjectArg: function(arg) {
invariant(
!isTerminal(arg) && !Array.isArray(arg),
'Tried to merge an object, instead got %s.',
arg
);
},
/**
* @param {*} arg
*/
checkMergeIntoObjectArg: function(arg) {
invariant(
(!isTerminal(arg) || typeof arg === 'function') && !Array.isArray(arg),
'Tried to merge into an object, instead got %s.',
arg
);
},
/**
* Checks that a merge was not given a circular object or an object that had
* too great of depth.
*
* @param {number} Level of recursion to validate against maximum.
*/
checkMergeLevel: function(level) {
invariant(
level < MAX_MERGE_DEPTH,
'Maximum deep merge depth exceeded. You may be attempting to merge ' +
'circular structures in an unsupported way.'
);
},
/**
* Checks that the supplied merge strategy is valid.
*
* @param {string} Array merge strategy.
*/
checkArrayStrategy: function(strategy) {
invariant(
strategy === undefined || strategy in mergeHelpers.ArrayStrategies,
'You must provide an array strategy to deep merge functions to ' +
'instruct the deep merge how to resolve merging two arrays.'
);
},
/**
* Set of possible behaviors of merge algorithms when encountering two Arrays
* that must be merged together.
* - `clobber`: The left `Array` is ignored.
* - `indexByIndex`: The result is achieved by recursively deep merging at
* each index. (not yet supported.)
*/
ArrayStrategies: keyMirror({
Clobber: true,
IndexByIndex: true
})
};
/**
* @generated SignedSource<<d3caa35be27b17ea4dd4c76bef72d1ab>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in of a static_upstream project! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Use `fjs use-upstream` to temporarily replace this with !!
* !! the latest version from upstream. !!
* !! 2) Make your changes, test them, etc. !!
* !! 3) Use `fjs push-upstream` to copy your changes back to !!
* !! static_upstream. !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* Copyright 2013-2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule mergeInto
* @typechecks static-only
*/
var checkMergeObjectArg = mergeHelpers.checkMergeObjectArg;
var checkMergeIntoObjectArg = mergeHelpers.checkMergeIntoObjectArg;
/**
* Shallow merges two structures by mutating the first parameter.
*
* @param {object|function} one Object to be merged into.
* @param {?object} two Optional object with properties to merge from.
*/
function mergeInto(one, two) {
checkMergeIntoObjectArg(one);
if (two != null) {
checkMergeObjectArg(two);
for (var key in two) {
if (!two.hasOwnProperty(key)) {
continue;
}
one[key] = two[key];
}
}
}
var merge = function(one, two) {
var result = {};
mergeInto(result, one);
mergeInto(result, two);
return result;
};
module.exports = merge;