mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-24 15:18:19 +00:00
[change] modernize View
Rewrite View to use function components and hooks. The 'usePlatformMethods' hook also fixes a bug in the class-based implementation of 'setNativeProps' which was unable to correctly merge its styles with those provided via the component API. In the future, 'setNativeProps' will be removed from React Native anyway. See (3) in #1136 for more context.
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
const TextAncestorContext = React.createContext(false);
|
||||
export default (TextAncestorContext: React.Context<boolean>);
|
||||
import type { Context } from 'react';
|
||||
|
||||
import { createContext } from 'react';
|
||||
|
||||
const TextAncestorContext = createContext(false);
|
||||
export default (TextAncestorContext: Context<boolean>);
|
||||
|
||||
@@ -44,12 +44,12 @@ describe('components/View', () => {
|
||||
});
|
||||
|
||||
describe('prop "hitSlop"', () => {
|
||||
it('renders a span with negative position offsets', () => {
|
||||
test('renders a span with negative position offsets', () => {
|
||||
const { container } = render(<View hitSlop={{ top: 10, bottom: 10, right: 5, left: 5 }} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles partial offsets', () => {
|
||||
test('handles partial offsets', () => {
|
||||
const { container } = render(<View hitSlop={{ top: 10 }} />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
+53
-53
@@ -10,73 +10,73 @@
|
||||
|
||||
import type { ViewProps } from './types';
|
||||
|
||||
import applyLayout from '../../modules/applyLayout';
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import createElement from '../createElement';
|
||||
import css from '../StyleSheet/css';
|
||||
import filterSupportedProps from './filterSupportedProps';
|
||||
import setAndForwardRef from '../../modules/setAndForwardRef';
|
||||
import useElementLayout from '../../hooks/useElementLayout';
|
||||
import usePlatformMethods from '../../hooks/usePlatformMethods';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import TextAncestorContext from '../Text/TextAncestorContext';
|
||||
import React from 'react';
|
||||
import React, { forwardRef, useContext, useRef } from 'react';
|
||||
|
||||
export type { ViewProps };
|
||||
|
||||
const calculateHitSlopStyle = hitSlop => {
|
||||
const hitStyle = {};
|
||||
function createHitSlopElement(hitSlop) {
|
||||
const hitSlopStyle = {};
|
||||
for (const prop in hitSlop) {
|
||||
if (hitSlop.hasOwnProperty(prop)) {
|
||||
const value = hitSlop[prop];
|
||||
hitStyle[prop] = value > 0 ? -1 * value : 0;
|
||||
hitSlopStyle[prop] = value > 0 ? -1 * value : 0;
|
||||
}
|
||||
}
|
||||
return hitStyle;
|
||||
};
|
||||
|
||||
class View extends React.Component<ViewProps> {
|
||||
static displayName = 'View';
|
||||
|
||||
renderView(hasTextAncestor) {
|
||||
const hitSlop = this.props.hitSlop;
|
||||
const supportedProps = filterSupportedProps(this.props);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
React.Children.toArray(this.props.children).forEach(item => {
|
||||
if (typeof item === 'string') {
|
||||
console.error(
|
||||
`Unexpected text node: ${item}. A text node cannot be a child of a <View>.`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
supportedProps.classList = [classes.view];
|
||||
supportedProps.ref = this.props.forwardedRef;
|
||||
supportedProps.style = StyleSheet.compose(
|
||||
hasTextAncestor && styles.inline,
|
||||
this.props.style
|
||||
);
|
||||
|
||||
if (hitSlop) {
|
||||
const hitSlopStyle = calculateHitSlopStyle(hitSlop);
|
||||
const hitSlopChild = createElement('span', {
|
||||
classList: [classes.hitSlop],
|
||||
style: hitSlopStyle
|
||||
});
|
||||
supportedProps.children = React.Children.toArray([hitSlopChild, supportedProps.children]);
|
||||
}
|
||||
|
||||
return createElement('div', supportedProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TextAncestorContext.Consumer>
|
||||
{hasTextAncestor => this.renderView(hasTextAncestor)}
|
||||
</TextAncestorContext.Consumer>
|
||||
);
|
||||
}
|
||||
return createElement('span', {
|
||||
classList: [classes.hitSlop],
|
||||
style: hitSlopStyle
|
||||
});
|
||||
}
|
||||
|
||||
const View = forwardRef<ViewProps, *>((props, ref) => {
|
||||
const { forwardedRef, hitSlop, onLayout, style, ...rest } = props;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
React.Children.toArray(props.children).forEach(item => {
|
||||
if (typeof item === 'string') {
|
||||
console.error(`Unexpected text node: ${item}. A text node cannot be a child of a <View>.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const classList = [classes.view];
|
||||
const hasTextAncestor = useContext(TextAncestorContext);
|
||||
const hostRef = useRef(null);
|
||||
|
||||
const setRef = setAndForwardRef({
|
||||
getForwardedRef: () => forwardedRef,
|
||||
setLocalRef: c => {
|
||||
hostRef.current = c;
|
||||
}
|
||||
});
|
||||
|
||||
useElementLayout(hostRef, onLayout);
|
||||
usePlatformMethods(hostRef, ref, classList, style);
|
||||
|
||||
const supportedProps = filterSupportedProps(rest);
|
||||
supportedProps.children = hitSlop
|
||||
? React.Children.toArray([createHitSlopElement(hitSlop), props.children])
|
||||
: props.children;
|
||||
supportedProps.classList = classList;
|
||||
supportedProps.ref = setRef;
|
||||
supportedProps.style = StyleSheet.compose(
|
||||
hasTextAncestor && styles.inline,
|
||||
style
|
||||
);
|
||||
|
||||
return createElement('div', supportedProps);
|
||||
});
|
||||
|
||||
View.displayName = 'View';
|
||||
|
||||
const classes = css.create({
|
||||
view: {
|
||||
alignItems: 'stretch',
|
||||
@@ -111,4 +111,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
});
|
||||
|
||||
export default applyLayout(applyNativeMethods(View));
|
||||
export default View;
|
||||
|
||||
Reference in New Issue
Block a user