diff --git a/docs/components/Image.md b/docs/components/Image.md
index c04c8495..6dc9f1be 100644
--- a/docs/components/Image.md
+++ b/docs/components/Image.md
@@ -31,7 +31,8 @@ Invoked on load error with `{nativeEvent: {error}}`.
**onLayout**: function
-TODO
+Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
+height } } }`, where `x` and `y` are the offsets from the parent node.
**onLoad**: function
@@ -57,7 +58,7 @@ could be an http address or a base64 encoded image.
**style**: style
-+ ...[View#style](View.md)
++ ...[View#style](./View.md)
+ `resizeMode`
**testID**: string
diff --git a/docs/components/ListView.md b/docs/components/ListView.md
index cc8308c5..126ce84a 100644
--- a/docs/components/ListView.md
+++ b/docs/components/ListView.md
@@ -4,6 +4,8 @@ TODO
## Props
+[...ScrollView props](./ScrollView.md)
+
**children**: any
Content to display over the image.
diff --git a/docs/components/ScrollView.md b/docs/components/ScrollView.md
index 9d2a18e3..9a94c45c 100644
--- a/docs/components/ScrollView.md
+++ b/docs/components/ScrollView.md
@@ -29,8 +29,6 @@ Determines whether the keyboard gets dismissed in response to a scroll drag.
**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.
diff --git a/docs/components/Text.md b/docs/components/Text.md
index acc4c23a..cdeea937 100644
--- a/docs/components/Text.md
+++ b/docs/components/Text.md
@@ -45,6 +45,11 @@ Child content.
Truncates the text with an ellipsis after this many lines. Currently only supports `1`.
+**onLayout**: function
+
+Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
+height } } }`, where `x` and `y` are the offsets from the parent node.
+
**onPress**: function
This function is called on press.
diff --git a/docs/components/TextInput.md b/docs/components/TextInput.md
index c15464e0..f1ffdd76 100644
--- a/docs/components/TextInput.md
+++ b/docs/components/TextInput.md
@@ -14,16 +14,11 @@ Unsupported React Native props:
`enablesReturnKeyAutomatically` (ios),
`returnKeyType` (ios),
`selectionState` (ios),
-`textAlign` (android),
-`textAlignVertical` (android),
`underlineColorAndroid` (android)
## Props
-(web) **accessibilityLabel**: string
-
-Defines the text label available to assistive technologies upon interaction
-with the element. (This is implemented using `aria-label`.)
+[...View props](./View.md)
(web) **autoComplete**: bool = false
@@ -92,10 +87,6 @@ as an argument to the callback handler.
Callback that is called when the text input is focused.
-**onLayout**: function
-
-TODO
-
(web) **onSelectionChange**: function
Callback that is called when the text input's selection changes. The following
@@ -132,7 +123,7 @@ If `true`, all text will automatically be selected on focus.
**style**: style
-+ ...[Text#style](Text.md)
++ ...[Text#style](./Text.md)
+ `outline`
**testID**: string
diff --git a/docs/components/TouchableWithoutFeedback.md b/docs/components/TouchableWithoutFeedback.md
index 10f399a3..ec8bfdd9 100644
--- a/docs/components/TouchableWithoutFeedback.md
+++ b/docs/components/TouchableWithoutFeedback.md
@@ -9,6 +9,8 @@ several child components, wrap them in a View.
## Props
+[...View props](./View.md)
+
**accessibilityLabel**: string
Overrides the text that's read by the screen reader when the user interacts
@@ -22,6 +24,8 @@ Allows assistive technologies to present and support interaction with the view
When `false`, the view is hidden from screenreaders.
+**children**: View
+
**delayLongPress**: number
Delay in ms, from `onPressIn`, before `onLongPress` is called.
@@ -47,9 +51,8 @@ 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}}}`
+Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
+height } } }`, where `x` and `y` are the offsets from the parent node.
**onLongPress**: function
diff --git a/docs/components/View.md b/docs/components/View.md
index 61990bc3..c5120fa2 100644
--- a/docs/components/View.md
+++ b/docs/components/View.md
@@ -48,7 +48,8 @@ implemented using `aria-hidden`.)
**onLayout**: function
-(TODO)
+Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
+height } } }`, where `x` and `y` are the offsets from the parent node.
**onMoveShouldSetResponder**: function
diff --git a/examples/components/App.js b/examples/components/App.js
index 5b070778..07a512ae 100644
--- a/examples/components/App.js
+++ b/examples/components/App.js
@@ -33,6 +33,7 @@ export default class App extends React.Component {
Image
{ console.log(e.nativeEvent.layout) }}
accessibilityLabel='accessible image'
children={Inner content}
defaultSource={{
@@ -57,6 +58,7 @@ export default class App extends React.Component {
Text
{ console.log('Text.onPress', e) }}
+ onLayout={(e) => { console.log(e.nativeEvent.layout) }}
testID={'Example.text'}
>
PRESS ME.
diff --git a/src/apis/UIManager/index.js b/src/apis/UIManager/index.js
index 5f07e4bf..a35bc766 100644
--- a/src/apis/UIManager/index.js
+++ b/src/apis/UIManager/index.js
@@ -33,7 +33,7 @@ const UIManager = {
_measureLayout(node, relativeTo, onSuccess)
},
- updateView(node, props, component /* only needed to surpress React errors in __DEV__ */) {
+ updateView(node, props, component /* only needed to surpress React errors in development */) {
for (const prop in props) {
const value = props[prop]
diff --git a/src/components/Image/index.js b/src/components/Image/index.js
index 7c801c8d..4600bf67 100644
--- a/src/components/Image/index.js
+++ b/src/components/Image/index.js
@@ -23,12 +23,15 @@ const ImageSourcePropType = PropTypes.oneOfType([
])
class Image extends Component {
+ static displayName = 'Image'
+
static propTypes = {
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
accessible: createReactDOMComponent.propTypes.accessible,
children: PropTypes.any,
defaultSource: ImageSourcePropType,
onError: PropTypes.func,
+ onLayout: PropTypes.func,
onLoad: PropTypes.func,
onLoadEnd: PropTypes.func,
onLoadStart: PropTypes.func,
@@ -82,6 +85,7 @@ class Image extends Component {
accessible,
children,
defaultSource,
+ onLayout,
source,
testID
} = this.props
@@ -107,6 +111,7 @@ class Image extends Component {
accessibilityLabel={accessibilityLabel}
accessibilityRole='img'
accessible={accessible}
+ onLayout={onLayout}
ref='root'
style={[
styles.initial,
diff --git a/src/components/Text/__tests__/index-test.js b/src/components/Text/__tests__/index-test.js
index b0247467..0eb1170d 100644
--- a/src/components/Text/__tests__/index-test.js
+++ b/src/components/Text/__tests__/index-test.js
@@ -14,6 +14,15 @@ suite('components/Text', () => {
test('prop "numberOfLines"')
+ test('prop "onLayout"', (done) => {
+ mount()
+ function onLayout(e) {
+ const { layout } = e.nativeEvent
+ assert.deepEqual(layout, { x: 0, y: 0, width: 0, height: 0 })
+ done()
+ }
+ })
+
test('prop "onPress"', (done) => {
const text = mount()
text.simulate('click')
diff --git a/src/components/Text/index.js b/src/components/Text/index.js
index b9862a33..8359edb1 100644
--- a/src/components/Text/index.js
+++ b/src/components/Text/index.js
@@ -1,3 +1,4 @@
+import applyLayout from '../../modules/applyLayout'
import applyNativeMethods from '../../modules/applyNativeMethods'
import createReactDOMComponent from '../../modules/createReactDOMComponent'
import { Component, PropTypes } from 'react'
@@ -6,12 +7,15 @@ import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
import TextStylePropTypes from './TextStylePropTypes'
class Text extends Component {
+ static displayName = 'Text'
+
static propTypes = {
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
accessibilityRole: createReactDOMComponent.propTypes.accessibilityRole,
accessible: createReactDOMComponent.propTypes.accessible,
children: PropTypes.any,
numberOfLines: PropTypes.number,
+ onLayout: PropTypes.func,
onPress: PropTypes.func,
style: StyleSheetPropType(TextStylePropTypes),
testID: createReactDOMComponent.propTypes.testID
@@ -21,16 +25,11 @@ class Text extends Component {
accessible: true
};
- _onPress = (e) => {
- if (this.props.onPress) this.props.onPress(e)
- }
-
render() {
const {
numberOfLines,
- /* eslint-disable no-unused-vars */
- onPress,
- /* eslint-enable no-unused-vars */
+ onLayout, // eslint-disable-line
+ onPress, // eslint-disable-line
style,
...other
} = this.props
@@ -46,9 +45,13 @@ class Text extends Component {
]
})
}
+
+ _onPress = (e) => {
+ if (this.props.onPress) this.props.onPress(e)
+ }
}
-applyNativeMethods(Text)
+applyLayout(applyNativeMethods(Text))
const styles = StyleSheet.create({
initial: {
diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js
index 10fcefbe..cae95bda 100644
--- a/src/components/TextInput/index.js
+++ b/src/components/TextInput/index.js
@@ -73,9 +73,7 @@ class TextInput extends Component {
render() {
const {
- /* eslint-disable react/prop-types */
- accessibilityLabel,
- /* eslint-enable react/prop-types */
+ accessibilityLabel, // eslint-disable-line
autoComplete,
autoFocus,
defaultValue,
@@ -85,6 +83,7 @@ class TextInput extends Component {
maxNumberOfLines,
multiline,
numberOfLines,
+ onLayout,
onSelectionChange,
placeholder,
placeholderTextColor,
@@ -171,6 +170,7 @@ class TextInput extends Component {
diff --git a/src/components/View/__tests__/index-test.js b/src/components/View/__tests__/index-test.js
index 37a44465..cc3dd18e 100644
--- a/src/components/View/__tests__/index-test.js
+++ b/src/components/View/__tests__/index-test.js
@@ -3,8 +3,8 @@
import assert from 'assert'
import includes from 'lodash/includes'
import React from 'react'
-import { shallow } from 'enzyme'
import View from '../'
+import { mount, shallow } from 'enzyme'
suite('components/View', () => {
test('prop "children"', () => {
@@ -13,6 +13,15 @@ suite('components/View', () => {
assert.equal(view.prop('children'), children)
})
+ test('prop "onLayout"', (done) => {
+ mount()
+ function onLayout(e) {
+ const { layout } = e.nativeEvent
+ assert.deepEqual(layout, { x: 0, y: 0, width: 0, height: 0 })
+ done()
+ }
+ })
+
test('prop "pointerEvents"', () => {
const view = shallow()
assert.ok(includes(view.prop('className'), '__style_pebo') === true)
diff --git a/src/components/View/index.js b/src/components/View/index.js
index 4d36c7da..38adb1ea 100644
--- a/src/components/View/index.js
+++ b/src/components/View/index.js
@@ -1,3 +1,4 @@
+import applyLayout from '../../modules/applyLayout'
import applyNativeMethods from '../../modules/applyNativeMethods'
import createReactDOMComponent from '../../modules/createReactDOMComponent'
import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType'
@@ -8,6 +9,8 @@ import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
import ViewStylePropTypes from './ViewStylePropTypes'
class View extends Component {
+ static displayName = 'View'
+
static propTypes = {
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
accessibilityLiveRegion: createReactDOMComponent.propTypes.accessibilityLiveRegion,
@@ -105,7 +108,7 @@ class View extends Component {
}
}
-applyNativeMethods(View)
+applyLayout(applyNativeMethods(View))
const styles = StyleSheet.create({
// https://github.com/facebook/css-layout#default-values
diff --git a/src/modules/NativeMethodsMixin/index.js b/src/modules/NativeMethodsMixin/index.js
index 790bf818..d1a573aa 100644
--- a/src/modules/NativeMethodsMixin/index.js
+++ b/src/modules/NativeMethodsMixin/index.js
@@ -113,11 +113,11 @@ const NativeMethodsMixin = {
* In the future, we should cleanup callbacks by cancelling them instead of
* using this.
*/
-const mountSafeCallback = (context: Component, callback: ?Function) => () => {
- if (!callback || (context.isMounted && !context.isMounted())) {
- return
+const mountSafeCallback = (context: Component, callback: ?Function) => (...args) => {
+ if (!callback) {
+ return undefined
}
- return callback.apply(context, arguments)
+ return callback.apply(context, args)
}
module.exports = NativeMethodsMixin
diff --git a/src/modules/applyLayout/index.js b/src/modules/applyLayout/index.js
new file mode 100644
index 00000000..7fa3c7d9
--- /dev/null
+++ b/src/modules/applyLayout/index.js
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2016-present, Nicolas Gallagher.
+ * All rights reserved.
+ *
+ * @flow
+ */
+
+import emptyFunction from 'fbjs/lib/emptyFunction'
+
+const applyLayout = (Component) => {
+ const componentDidMount = Component.prototype.componentDidMount || emptyFunction
+ const componentDidUpdate = Component.prototype.componentDidUpdate || emptyFunction
+
+ Component.prototype.componentDidMount = function () {
+ componentDidMount()
+ this._layoutState = {}
+ this._handleLayout()
+ }
+
+ Component.prototype.componentDidUpdate = function () {
+ componentDidUpdate()
+ this._handleLayout()
+ }
+
+ Component.prototype._handleLayout = function () {
+ const layout = this._layoutState
+ const { onLayout } = this.props
+
+ if (onLayout) {
+ this.measure((x, y, width, height) => {
+ if (layout.x !== x || layout.y !== y || layout.width !== width || layout.height !== height) {
+ const nextLayout = { x, y, width, height }
+ const nativeEvent = { layout: nextLayout }
+ onLayout({ nativeEvent })
+ this._layoutState = nextLayout
+ }
+ })
+ }
+ }
+ return Component
+}
+
+module.exports = applyLayout