diff --git a/packages/react-native-web/src/exports/Text/TextAncestorContext.js b/packages/react-native-web/src/exports/Text/TextAncestorContext.js index 020069de..eb691f4c 100644 --- a/packages/react-native-web/src/exports/Text/TextAncestorContext.js +++ b/packages/react-native-web/src/exports/Text/TextAncestorContext.js @@ -7,6 +7,9 @@ * @flow strict */ -import * as React from 'react'; -const TextAncestorContext = React.createContext(false); -export default (TextAncestorContext: React.Context); +import type { Context } from 'react'; + +import { createContext } from 'react'; + +const TextAncestorContext = createContext(false); +export default (TextAncestorContext: Context); diff --git a/packages/react-native-web/src/exports/View/__tests__/index-test.js b/packages/react-native-web/src/exports/View/__tests__/index-test.js index cc5bf530..e6579565 100644 --- a/packages/react-native-web/src/exports/View/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/View/__tests__/index-test.js @@ -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(); expect(container.firstChild).toMatchSnapshot(); }); - it('handles partial offsets', () => { + test('handles partial offsets', () => { const { container } = render(); expect(container.firstChild).toMatchSnapshot(); }); diff --git a/packages/react-native-web/src/exports/View/index.js b/packages/react-native-web/src/exports/View/index.js index 43902f65..d37a6732 100644 --- a/packages/react-native-web/src/exports/View/index.js +++ b/packages/react-native-web/src/exports/View/index.js @@ -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 { - 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 .` - ); - } - }); - } - - 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 ( - - {hasTextAncestor => this.renderView(hasTextAncestor)} - - ); - } + return createElement('span', { + classList: [classes.hitSlop], + style: hitSlopStyle + }); } +const View = forwardRef((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 .`); + } + }); + } + + 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;