[add] initial 'onLayout' support

Add initial support for 'onLayout' when components mount and update.

Ref #60
This commit is contained in:
Nicolas Gallagher
2016-07-10 22:15:51 -07:00
parent 5e1e0ec8e5
commit 597fcc65e8
17 changed files with 112 additions and 37 deletions
+1 -1
View File
@@ -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]
+5
View File
@@ -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,
@@ -14,6 +14,15 @@ suite('components/Text', () => {
test('prop "numberOfLines"')
test('prop "onLayout"', (done) => {
mount(<Text onLayout={onLayout} />)
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 onPress={onPress} />)
text.simulate('click')
+11 -8
View File
@@ -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: {
+3 -3
View File
@@ -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 {
<View
accessibilityLabel={accessibilityLabel}
onClick={this._handleClick}
onLayout={onLayout}
style={[ styles.initial, rootStyles ]}
testID={testID}
>
+10 -1
View File
@@ -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(<View onLayout={onLayout} />)
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(<View pointerEvents='box-only' />)
assert.ok(includes(view.prop('className'), '__style_pebo') === true)
+4 -1
View File
@@ -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
+4 -4
View File
@@ -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
+43
View File
@@ -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