mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-06 11:53:18 +00:00
[add] initial 'onLayout' support
Add initial support for 'onLayout' when components mount and update. Ref #60
This commit is contained in:
@@ -31,7 +31,8 @@ Invoked on load error with `{nativeEvent: {error}}`.
|
|||||||
|
|
||||||
**onLayout**: function
|
**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
|
**onLoad**: function
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ could be an http address or a base64 encoded image.
|
|||||||
|
|
||||||
**style**: style
|
**style**: style
|
||||||
|
|
||||||
+ ...[View#style](View.md)
|
+ ...[View#style](./View.md)
|
||||||
+ `resizeMode`
|
+ `resizeMode`
|
||||||
|
|
||||||
**testID**: string
|
**testID**: string
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ TODO
|
|||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
|
[...ScrollView props](./ScrollView.md)
|
||||||
|
|
||||||
**children**: any
|
**children**: any
|
||||||
|
|
||||||
Content to display over the image.
|
Content to display over the image.
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ Determines whether the keyboard gets dismissed in response to a scroll drag.
|
|||||||
|
|
||||||
**onContentSizeChange**: function
|
**onContentSizeChange**: function
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Called when scrollable content view of the `ScrollView` changes. It's
|
Called when scrollable content view of the `ScrollView` changes. It's
|
||||||
implemented using the `onLayout` handler attached to the content container
|
implemented using the `onLayout` handler attached to the content container
|
||||||
which this `ScrollView` renders.
|
which this `ScrollView` renders.
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ Child content.
|
|||||||
|
|
||||||
Truncates the text with an ellipsis after this many lines. Currently only supports `1`.
|
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
|
**onPress**: function
|
||||||
|
|
||||||
This function is called on press.
|
This function is called on press.
|
||||||
|
|||||||
@@ -14,16 +14,11 @@ Unsupported React Native props:
|
|||||||
`enablesReturnKeyAutomatically` (ios),
|
`enablesReturnKeyAutomatically` (ios),
|
||||||
`returnKeyType` (ios),
|
`returnKeyType` (ios),
|
||||||
`selectionState` (ios),
|
`selectionState` (ios),
|
||||||
`textAlign` (android),
|
|
||||||
`textAlignVertical` (android),
|
|
||||||
`underlineColorAndroid` (android)
|
`underlineColorAndroid` (android)
|
||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
(web) **accessibilityLabel**: string
|
[...View props](./View.md)
|
||||||
|
|
||||||
Defines the text label available to assistive technologies upon interaction
|
|
||||||
with the element. (This is implemented using `aria-label`.)
|
|
||||||
|
|
||||||
(web) **autoComplete**: bool = false
|
(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.
|
Callback that is called when the text input is focused.
|
||||||
|
|
||||||
**onLayout**: function
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
(web) **onSelectionChange**: function
|
(web) **onSelectionChange**: function
|
||||||
|
|
||||||
Callback that is called when the text input's selection changes. The following
|
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
|
**style**: style
|
||||||
|
|
||||||
+ ...[Text#style](Text.md)
|
+ ...[Text#style](./Text.md)
|
||||||
+ `outline`
|
+ `outline`
|
||||||
|
|
||||||
**testID**: string
|
**testID**: string
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ several child components, wrap them in a View.
|
|||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
|
[...View props](./View.md)
|
||||||
|
|
||||||
**accessibilityLabel**: string
|
**accessibilityLabel**: string
|
||||||
|
|
||||||
Overrides the text that's read by the screen reader when the user interacts
|
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.
|
When `false`, the view is hidden from screenreaders.
|
||||||
|
|
||||||
|
**children**: View
|
||||||
|
|
||||||
**delayLongPress**: number
|
**delayLongPress**: number
|
||||||
|
|
||||||
Delay in ms, from `onPressIn`, before `onLongPress` is called.
|
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
|
**onLayout**: function
|
||||||
|
|
||||||
Invoked on mount and layout changes with.
|
Invoked on mount and layout changes with `{ nativeEvent: { layout: { x, y, width,
|
||||||
|
height } } }`, where `x` and `y` are the offsets from the parent node.
|
||||||
`{nativeEvent: {layout: {x, y, width, height}}}`
|
|
||||||
|
|
||||||
**onLongPress**: function
|
**onLongPress**: function
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ implemented using `aria-hidden`.)
|
|||||||
|
|
||||||
**onLayout**: function
|
**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
|
**onMoveShouldSetResponder**: function
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export default class App extends React.Component {
|
|||||||
|
|
||||||
<Heading size='large'>Image</Heading>
|
<Heading size='large'>Image</Heading>
|
||||||
<Image
|
<Image
|
||||||
|
onLayout={(e) => { console.log(e.nativeEvent.layout) }}
|
||||||
accessibilityLabel='accessible image'
|
accessibilityLabel='accessible image'
|
||||||
children={<Text>Inner content</Text>}
|
children={<Text>Inner content</Text>}
|
||||||
defaultSource={{
|
defaultSource={{
|
||||||
@@ -57,6 +58,7 @@ export default class App extends React.Component {
|
|||||||
<Heading size='large'>Text</Heading>
|
<Heading size='large'>Text</Heading>
|
||||||
<Text
|
<Text
|
||||||
onPress={(e) => { console.log('Text.onPress', e) }}
|
onPress={(e) => { console.log('Text.onPress', e) }}
|
||||||
|
onLayout={(e) => { console.log(e.nativeEvent.layout) }}
|
||||||
testID={'Example.text'}
|
testID={'Example.text'}
|
||||||
>
|
>
|
||||||
PRESS ME.
|
PRESS ME.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const UIManager = {
|
|||||||
_measureLayout(node, relativeTo, onSuccess)
|
_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) {
|
for (const prop in props) {
|
||||||
const value = props[prop]
|
const value = props[prop]
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,15 @@ const ImageSourcePropType = PropTypes.oneOfType([
|
|||||||
])
|
])
|
||||||
|
|
||||||
class Image extends Component {
|
class Image extends Component {
|
||||||
|
static displayName = 'Image'
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||||
accessible: createReactDOMComponent.propTypes.accessible,
|
accessible: createReactDOMComponent.propTypes.accessible,
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
defaultSource: ImageSourcePropType,
|
defaultSource: ImageSourcePropType,
|
||||||
onError: PropTypes.func,
|
onError: PropTypes.func,
|
||||||
|
onLayout: PropTypes.func,
|
||||||
onLoad: PropTypes.func,
|
onLoad: PropTypes.func,
|
||||||
onLoadEnd: PropTypes.func,
|
onLoadEnd: PropTypes.func,
|
||||||
onLoadStart: PropTypes.func,
|
onLoadStart: PropTypes.func,
|
||||||
@@ -82,6 +85,7 @@ class Image extends Component {
|
|||||||
accessible,
|
accessible,
|
||||||
children,
|
children,
|
||||||
defaultSource,
|
defaultSource,
|
||||||
|
onLayout,
|
||||||
source,
|
source,
|
||||||
testID
|
testID
|
||||||
} = this.props
|
} = this.props
|
||||||
@@ -107,6 +111,7 @@ class Image extends Component {
|
|||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
accessibilityRole='img'
|
accessibilityRole='img'
|
||||||
accessible={accessible}
|
accessible={accessible}
|
||||||
|
onLayout={onLayout}
|
||||||
ref='root'
|
ref='root'
|
||||||
style={[
|
style={[
|
||||||
styles.initial,
|
styles.initial,
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ suite('components/Text', () => {
|
|||||||
|
|
||||||
test('prop "numberOfLines"')
|
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) => {
|
test('prop "onPress"', (done) => {
|
||||||
const text = mount(<Text onPress={onPress} />)
|
const text = mount(<Text onPress={onPress} />)
|
||||||
text.simulate('click')
|
text.simulate('click')
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import applyLayout from '../../modules/applyLayout'
|
||||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||||
import { Component, PropTypes } from 'react'
|
import { Component, PropTypes } from 'react'
|
||||||
@@ -6,12 +7,15 @@ import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
|
|||||||
import TextStylePropTypes from './TextStylePropTypes'
|
import TextStylePropTypes from './TextStylePropTypes'
|
||||||
|
|
||||||
class Text extends Component {
|
class Text extends Component {
|
||||||
|
static displayName = 'Text'
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||||
accessibilityRole: createReactDOMComponent.propTypes.accessibilityRole,
|
accessibilityRole: createReactDOMComponent.propTypes.accessibilityRole,
|
||||||
accessible: createReactDOMComponent.propTypes.accessible,
|
accessible: createReactDOMComponent.propTypes.accessible,
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
numberOfLines: PropTypes.number,
|
numberOfLines: PropTypes.number,
|
||||||
|
onLayout: PropTypes.func,
|
||||||
onPress: PropTypes.func,
|
onPress: PropTypes.func,
|
||||||
style: StyleSheetPropType(TextStylePropTypes),
|
style: StyleSheetPropType(TextStylePropTypes),
|
||||||
testID: createReactDOMComponent.propTypes.testID
|
testID: createReactDOMComponent.propTypes.testID
|
||||||
@@ -21,16 +25,11 @@ class Text extends Component {
|
|||||||
accessible: true
|
accessible: true
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPress = (e) => {
|
|
||||||
if (this.props.onPress) this.props.onPress(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
numberOfLines,
|
numberOfLines,
|
||||||
/* eslint-disable no-unused-vars */
|
onLayout, // eslint-disable-line
|
||||||
onPress,
|
onPress, // eslint-disable-line
|
||||||
/* eslint-enable no-unused-vars */
|
|
||||||
style,
|
style,
|
||||||
...other
|
...other
|
||||||
} = this.props
|
} = 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({
|
const styles = StyleSheet.create({
|
||||||
initial: {
|
initial: {
|
||||||
|
|||||||
@@ -73,9 +73,7 @@ class TextInput extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
/* eslint-disable react/prop-types */
|
accessibilityLabel, // eslint-disable-line
|
||||||
accessibilityLabel,
|
|
||||||
/* eslint-enable react/prop-types */
|
|
||||||
autoComplete,
|
autoComplete,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
@@ -85,6 +83,7 @@ class TextInput extends Component {
|
|||||||
maxNumberOfLines,
|
maxNumberOfLines,
|
||||||
multiline,
|
multiline,
|
||||||
numberOfLines,
|
numberOfLines,
|
||||||
|
onLayout,
|
||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
placeholder,
|
placeholder,
|
||||||
placeholderTextColor,
|
placeholderTextColor,
|
||||||
@@ -171,6 +170,7 @@ class TextInput extends Component {
|
|||||||
<View
|
<View
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
onClick={this._handleClick}
|
onClick={this._handleClick}
|
||||||
|
onLayout={onLayout}
|
||||||
style={[ styles.initial, rootStyles ]}
|
style={[ styles.initial, rootStyles ]}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import includes from 'lodash/includes'
|
import includes from 'lodash/includes'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { shallow } from 'enzyme'
|
|
||||||
import View from '../'
|
import View from '../'
|
||||||
|
import { mount, shallow } from 'enzyme'
|
||||||
|
|
||||||
suite('components/View', () => {
|
suite('components/View', () => {
|
||||||
test('prop "children"', () => {
|
test('prop "children"', () => {
|
||||||
@@ -13,6 +13,15 @@ suite('components/View', () => {
|
|||||||
assert.equal(view.prop('children'), children)
|
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"', () => {
|
test('prop "pointerEvents"', () => {
|
||||||
const view = shallow(<View pointerEvents='box-only' />)
|
const view = shallow(<View pointerEvents='box-only' />)
|
||||||
assert.ok(includes(view.prop('className'), '__style_pebo') === true)
|
assert.ok(includes(view.prop('className'), '__style_pebo') === true)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import applyLayout from '../../modules/applyLayout'
|
||||||
import applyNativeMethods from '../../modules/applyNativeMethods'
|
import applyNativeMethods from '../../modules/applyNativeMethods'
|
||||||
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
import createReactDOMComponent from '../../modules/createReactDOMComponent'
|
||||||
import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType'
|
import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType'
|
||||||
@@ -8,6 +9,8 @@ import StyleSheetPropType from '../../propTypes/StyleSheetPropType'
|
|||||||
import ViewStylePropTypes from './ViewStylePropTypes'
|
import ViewStylePropTypes from './ViewStylePropTypes'
|
||||||
|
|
||||||
class View extends Component {
|
class View extends Component {
|
||||||
|
static displayName = 'View'
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
accessibilityLabel: createReactDOMComponent.propTypes.accessibilityLabel,
|
||||||
accessibilityLiveRegion: createReactDOMComponent.propTypes.accessibilityLiveRegion,
|
accessibilityLiveRegion: createReactDOMComponent.propTypes.accessibilityLiveRegion,
|
||||||
@@ -105,7 +108,7 @@ class View extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyNativeMethods(View)
|
applyLayout(applyNativeMethods(View))
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
// https://github.com/facebook/css-layout#default-values
|
// https://github.com/facebook/css-layout#default-values
|
||||||
|
|||||||
@@ -113,11 +113,11 @@ const NativeMethodsMixin = {
|
|||||||
* In the future, we should cleanup callbacks by cancelling them instead of
|
* In the future, we should cleanup callbacks by cancelling them instead of
|
||||||
* using this.
|
* using this.
|
||||||
*/
|
*/
|
||||||
const mountSafeCallback = (context: Component, callback: ?Function) => () => {
|
const mountSafeCallback = (context: Component, callback: ?Function) => (...args) => {
|
||||||
if (!callback || (context.isMounted && !context.isMounted())) {
|
if (!callback) {
|
||||||
return
|
return undefined
|
||||||
}
|
}
|
||||||
return callback.apply(context, arguments)
|
return callback.apply(context, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NativeMethodsMixin
|
module.exports = NativeMethodsMixin
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user