diff --git a/packages/react-native-web/jest/setup.js b/packages/react-native-web/jest/setup.js index 445afa86..9fa7afe0 100644 --- a/packages/react-native-web/jest/setup.js +++ b/packages/react-native-web/jest/setup.js @@ -7,12 +7,12 @@ global.__DEV__ = true; const mockEmptyObject = {}; // Make sure snapshots contain the original style objects -jest.mock('../dist/cjs/modules/ReactNativePropRegistry', () => ({ +jest.mock('../dist/cjs/modules/StyleSheet/ReactNativePropRegistry', () => ({ register: id => id, getByID: () => mockEmptyObject })); -jest.mock('../dist/modules/ReactNativePropRegistry', () => ({ +jest.mock('../dist/modules/StyleSheet/ReactNativePropRegistry', () => ({ register: id => id, getByID: () => mockEmptyObject })); diff --git a/packages/react-native-web/src/exports/AppRegistry/renderApplication.js b/packages/react-native-web/src/exports/AppRegistry/renderApplication.js index 2d0466a7..9f0c90cc 100644 --- a/packages/react-native-web/src/exports/AppRegistry/renderApplication.js +++ b/packages/react-native-web/src/exports/AppRegistry/renderApplication.js @@ -12,8 +12,7 @@ import type { ComponentType } from 'react'; import AppContainer from './AppContainer'; import invariant from 'fbjs/lib/invariant'; -import hydrate from '../../modules/hydrate'; -import render from '../render'; +import render, { hydrate } from '../render'; import styleResolver from '../StyleSheet/styleResolver'; import React from 'react'; diff --git a/packages/react-native-web/src/modules/ReactNativePropRegistry/index.js b/packages/react-native-web/src/exports/StyleSheet/ReactNativePropRegistry.js similarity index 100% rename from packages/react-native-web/src/modules/ReactNativePropRegistry/index.js rename to packages/react-native-web/src/exports/StyleSheet/ReactNativePropRegistry.js diff --git a/packages/react-native-web/src/exports/StyleSheet/StyleSheet.js b/packages/react-native-web/src/exports/StyleSheet/StyleSheet.js index 7e9c293c..6ca8dab7 100644 --- a/packages/react-native-web/src/exports/StyleSheet/StyleSheet.js +++ b/packages/react-native-web/src/exports/StyleSheet/StyleSheet.js @@ -7,7 +7,7 @@ * @flow */ -import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'; +import ReactNativePropRegistry from './ReactNativePropRegistry'; import flattenStyle from './flattenStyle'; const absoluteFillObject = { diff --git a/packages/react-native-web/src/exports/StyleSheet/__tests__/__snapshots__/createStyleResolver-test.js.snap b/packages/react-native-web/src/exports/StyleSheet/__tests__/__snapshots__/createStyleResolver-test.js.snap index 5275e45b..957cd3a4 100644 --- a/packages/react-native-web/src/exports/StyleSheet/__tests__/__snapshots__/createStyleResolver-test.js.snap +++ b/packages/react-native-web/src/exports/StyleSheet/__tests__/__snapshots__/createStyleResolver-test.js.snap @@ -201,72 +201,3 @@ Object { }, } `; - -exports[`StyleSheet/createStyleResolver resolveWithNode next class names have priority over current inline styles 1`] = ` -Object { - "className": "r-opacity-6dt33c", - "style": Object { - "opacity": "", - }, -} -`; - -exports[`StyleSheet/createStyleResolver resolveWithNode next inline styles have priority over current inline styles 1`] = ` -Object { - "className": "", - "style": Object { - "WebkitTransform": "scale(2)", - "opacity": 1, - "transform": "scale(2)", - }, -} -`; - -exports[`StyleSheet/createStyleResolver resolveWithNode preserves unrelated class names 1`] = ` -Object { - "className": "unknown-class-1 unknown-class-2", - "style": Object {}, -} -`; - -exports[`StyleSheet/createStyleResolver resolveWithNode preserves unrelated inline styles 1`] = ` -Object { - "className": "", - "style": Object { - "fontSize": "20px", - "opacity": 1, - }, -} -`; - -exports[`StyleSheet/createStyleResolver resolveWithNode when isRTL=true & doLeftAndRightSwapInRTL=false, resolves to non-flipped inline styles 1`] = ` -Object { - "className": "", - "style": Object { - "marginRight": "10px", - "paddingLeft": "10px", - "right": "10px", - }, -} -`; - -exports[`StyleSheet/createStyleResolver resolveWithNode when isRTL=true, resolves to flipped classNames 1`] = ` -Object { - "className": "r-left-1u10d71 r-marginRight-zso239", - "style": Object { - "marginRight": "", - "right": "5px", - }, -} -`; - -exports[`StyleSheet/createStyleResolver resolveWithNode when isRTL=true, resolves to flipped inline styles 1`] = ` -Object { - "className": "", - "style": Object { - "left": "10px", - "marginRight": "10px", - "right": "5px", - }, -} -`; diff --git a/packages/react-native-web/src/exports/StyleSheet/__tests__/createStyleResolver-test.js b/packages/react-native-web/src/exports/StyleSheet/__tests__/createStyleResolver-test.js index 834899f0..e468f404 100644 --- a/packages/react-native-web/src/exports/StyleSheet/__tests__/createStyleResolver-test.js +++ b/packages/react-native-web/src/exports/StyleSheet/__tests__/createStyleResolver-test.js @@ -1,7 +1,7 @@ /* eslint-env jasmine, jest */ import I18nManager from '../../I18nManager'; -import ReactNativePropRegistry from '../../../modules/ReactNativePropRegistry'; +import ReactNativePropRegistry from '../ReactNativePropRegistry'; import createStyleResolver from '../createStyleResolver'; let styleResolver; @@ -75,73 +75,4 @@ describe('StyleSheet/createStyleResolver', () => { expect(styleResolver.resolve({ pointerEvents: 'box-none' })).toMatchSnapshot(); }); }); - - describe('resolveWithNode', () => { - let node; - - beforeEach(() => { - node = document.createElement('div'); - }); - - test('preserves unrelated class names', () => { - node.classList.add('unknown-class-1', 'unknown-class-2'); - const resolved = styleResolver.resolveWithNode({}, node); - expect(resolved).toMatchSnapshot(); - }); - - test('preserves unrelated inline styles', () => { - node.style.cssText = 'font-size: 20px;'; - const resolved = styleResolver.resolveWithNode({ opacity: 1 }, node); - expect(resolved).toMatchSnapshot(); - }); - - test('next class names have priority over current inline styles', () => { - node.style.cssText = 'opacity: 0.5;'; - const nextStyle = ReactNativePropRegistry.register({ opacity: 1 }); - const resolved = styleResolver.resolveWithNode(nextStyle, node); - expect(resolved).toMatchSnapshot(); - }); - - test('next inline styles have priority over current inline styles', () => { - // note: this also checks for correctly uppercasing the first letter of DOM vendor prefixes - node.style.cssText = 'opacity: 0.5; transform: scale(1);'; - const style = { opacity: 1, transform: [{ scale: 2 }] }; - const resolved = styleResolver.resolveWithNode(style, node); - expect(resolved).toMatchSnapshot(); - }); - - test('when isRTL=true, resolves to flipped inline styles', () => { - // note: DOM state resolved from { marginLeft: 5, left: 5 } in RTL mode - node.style.cssText = 'margin-right: 5px; right: 5px;'; - I18nManager.forceRTL(true); - const resolved = styleResolver.resolveWithNode({ marginLeft: 10, right: 10 }, node); - I18nManager.forceRTL(false); - expect(resolved).toMatchSnapshot(); - }); - - test('when isRTL=true, resolves to flipped classNames', () => { - // note: DOM state resolved from { marginLeft: 5, left: 5 } - node.style.cssText = 'margin-right: 5px; right: 5px;'; - const nextStyle = ReactNativePropRegistry.register({ marginLeft: 10, right: 1 }); - - I18nManager.forceRTL(true); - const resolved = styleResolver.resolveWithNode(nextStyle, node); - I18nManager.forceRTL(false); - expect(resolved).toMatchSnapshot(); - }); - - test('when isRTL=true & doLeftAndRightSwapInRTL=false, resolves to non-flipped inline styles', () => { - // note: DOM state resolved from { marginRight 5, right: 5, paddingEnd: 5 } - node.style.cssText = 'margin-right: 5px; right: 5px; padding-left: 5px'; - I18nManager.forceRTL(true); - I18nManager.swapLeftAndRightInRTL(false); - const resolved = styleResolver.resolveWithNode( - { marginRight: 10, right: 10, paddingEnd: 10 }, - node - ); - I18nManager.forceRTL(false); - I18nManager.swapLeftAndRightInRTL(true); - expect(resolved).toMatchSnapshot(); - }); - }); }); diff --git a/packages/react-native-web/src/exports/StyleSheet/createStyleResolver.js b/packages/react-native-web/src/exports/StyleSheet/createStyleResolver.js index a1a26966..17b063a0 100644 --- a/packages/react-native-web/src/exports/StyleSheet/createStyleResolver.js +++ b/packages/react-native-web/src/exports/StyleSheet/createStyleResolver.js @@ -25,8 +25,6 @@ import initialRules from './initialRules'; import modality from './modality'; import { STYLE_ELEMENT_ID, STYLE_GROUPS } from './constants'; -const emptyObject = {}; - export default function createStyleResolver() { let inserted, sheet, lookup; const resolved = { css: {}, ltr: {}, rtl: {}, rtlNoSwap: {} }; @@ -148,59 +146,6 @@ export default function createStyleResolver() { return finalProps; } - /** - * Resolves a React Native style object to DOM attributes, accounting for - * the existing styles applied to the DOM node. - * - * To determine the next style, some of the existing DOM state must be - * converted back into React Native styles. - */ - function resolveWithNode(rnStyleNext, node) { - function getDeclaration(className) { - return lookup.byClassName[className] || emptyObject; - } - - const { classList: rdomClassList, style: rdomStyle } = getDOMStyleInfo(node); - // Convert the DOM classList back into a React Native form - // Preserves unrecognized class names. - const { classList: rnClassList, style: rnStyle } = rdomClassList.reduce( - (styleProps, className) => { - const { prop, value } = getDeclaration(className); - if (prop) { - styleProps.style[prop] = value; - } else { - styleProps.classList.push(className); - } - return styleProps; - }, - { classList: [], style: {} } - ); - - // Create next DOM style props from current and next RN styles - const { classList: rdomClassListNext, style: rdomStyleNext } = resolve([ - i18nStyle(rnStyle), - rnStyleNext - ]); - - // Final className - // Add the current class names not managed by React Native - const className = classListToString(rdomClassListNext.concat(rnClassList)); - - // Final style - // Next class names take priority over current inline styles - const style = { ...rdomStyle }; - rdomClassListNext.forEach(className => { - const { prop } = getDeclaration(className); - if (style[prop]) { - style[prop] = ''; - } - }); - // Next inline styles take priority over current inline styles - Object.assign(style, rdomStyleNext); - - return { className, style }; - } - /** * Resolves a React Native style object */ @@ -297,8 +242,7 @@ export default function createStyleResolver() { return result; }, resolve, - sheet, - resolveWithNode + sheet }; } @@ -311,26 +255,3 @@ const createCacheKey = id => { }; const classListToString = list => list.join(' ').trim(); - -/** - * Copies classList and style data from a DOM node - */ -const hyphenPattern = /-([a-z])/g; -const toCamelCase = str => str.replace(hyphenPattern, m => m[1].toUpperCase()); - -const getDOMStyleInfo = node => { - const nodeStyle = node.style; - const classList = Array.prototype.slice.call(node.classList); - const style = {}; - // DOM style is a CSSStyleDeclaration - // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration - for (let i = 0; i < nodeStyle.length; i += 1) { - const property = nodeStyle.item(i); - if (property) { - // DOM style uses hyphenated prop names and may include vendor prefixes - // Transform back into React DOM style. - style[toCamelCase(property)] = nodeStyle.getPropertyValue(property); - } - } - return { classList, style }; -}; diff --git a/packages/react-native-web/src/exports/StyleSheet/flattenStyle.js b/packages/react-native-web/src/exports/StyleSheet/flattenStyle.js index 10d42961..f4cfbd9c 100644 --- a/packages/react-native-web/src/exports/StyleSheet/flattenStyle.js +++ b/packages/react-native-web/src/exports/StyleSheet/flattenStyle.js @@ -8,7 +8,7 @@ * @flow */ -import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry'; +import ReactNativePropRegistry from './ReactNativePropRegistry'; import invariant from 'fbjs/lib/invariant'; function getStyle(style) { diff --git a/packages/react-native-web/src/exports/render/index.js b/packages/react-native-web/src/exports/render/index.js index f4a64a3e..2c458c35 100644 --- a/packages/react-native-web/src/exports/render/index.js +++ b/packages/react-native-web/src/exports/render/index.js @@ -4,8 +4,9 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @noflow + * @flow */ -import { render } from 'react-dom'; +import { hydrate, render } from 'react-dom'; +export { hydrate }; export default render; diff --git a/packages/react-native-web/src/modules/NativeMethodsMixin/index.js b/packages/react-native-web/src/modules/NativeMethodsMixin/index.js deleted file mode 100644 index 34f933cd..00000000 --- a/packages/react-native-web/src/modules/NativeMethodsMixin/index.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) Nicolas Gallagher. - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import createDOMProps from '../createDOMProps'; -import findNodeHandle from '../../exports/findNodeHandle'; -import styleResolver from '../../exports/StyleSheet/styleResolver'; -import UIManager from '../../exports/UIManager'; - -type MeasureOnSuccessCallback = ( - x: number, - y: number, - width: number, - height: number, - pageX: number, - pageY: number -) => void; - -type MeasureInWindowOnSuccessCallback = ( - x: number, - y: number, - width: number, - height: number -) => void; - -type MeasureLayoutOnSuccessCallback = ( - left: number, - top: number, - width: number, - height: number -) => void; - -const NativeMethodsMixin = { - /** - * Removes focus from an input or view. This is the opposite of `focus()`. - */ - blur() { - UIManager.blur(findNodeHandle(this)); - }, - - /** - * Requests focus for the given input or view. - * The exact behavior triggered will depend the type of view. - */ - focus() { - UIManager.focus(findNodeHandle(this)); - }, - - /** - * Determines the position and dimensions of the view - */ - measure(callback: MeasureOnSuccessCallback) { - UIManager.measure(findNodeHandle(this), callback); - }, - - /** - * Determines the location of the given view in the window and returns the - * values via an async callback. If the React root view is embedded in - * another native view, this will give you the absolute coordinates. If - * successful, the callback will be called be called with the following - * arguments: - * - * - x - * - y - * - width - * - height - * - * Note that these measurements are not available until after the rendering - * has been completed. - */ - measureInWindow(callback: MeasureInWindowOnSuccessCallback) { - UIManager.measureInWindow(findNodeHandle(this), callback); - }, - - /** - * Measures the view relative to another view (usually an ancestor) - */ - measureLayout( - relativeToNativeNode: Object, - onSuccess: MeasureLayoutOnSuccessCallback, - onFail: () => void - ) { - UIManager.measureLayout(findNodeHandle(this), relativeToNativeNode, onFail, onSuccess); - }, - - /** - * This function sends props straight to the underlying DOM node. - * This works as if all styles were set as inline styles. Since a DOM node - * may aleady be styled with class names and inline styles, we need to get - * the initial styles from the DOM node and merge them with incoming props. - */ - setNativeProps(nativeProps: Object) { - if (!nativeProps) { - return; - } - const node = findNodeHandle(this); - if (node) { - // Next state is determined by comparison to existing state (in the DOM). - // Existing state has already gone through i18n transform - const domProps = createDOMProps(null, nativeProps, style => - styleResolver.resolveWithNode(style, node) - ); - UIManager.updateView(node, domProps, this); - } - } -}; - -export default NativeMethodsMixin; diff --git a/packages/react-native-web/src/modules/UnimplementedView/index.js b/packages/react-native-web/src/modules/UnimplementedView/index.js index bc765586..17558315 100644 --- a/packages/react-native-web/src/modules/UnimplementedView/index.js +++ b/packages/react-native-web/src/modules/UnimplementedView/index.js @@ -16,8 +16,6 @@ import React from 'react'; class UnimplementedView extends React.Component<*, *> { setNativeProps() { // Do nothing. - // This method is required in order to use this view as a Touchable* child. - // See ensureComponentIsNative.js for more info } render() { diff --git a/packages/react-native-web/src/modules/applyLayout/index.js b/packages/react-native-web/src/modules/applyLayout/index.js deleted file mode 100644 index 558e7559..00000000 --- a/packages/react-native-web/src/modules/applyLayout/index.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (c) Nicolas Gallagher. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @noflow - */ - -import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; -import debounce from 'debounce'; -import findNodeHandle from '../../exports/findNodeHandle'; - -const emptyObject = {}; -const registry = {}; - -let id = 1; -const guid = () => `r-${id++}`; - -let resizeObserver; -if (canUseDOM) { - if (typeof window.ResizeObserver !== 'undefined') { - resizeObserver = new window.ResizeObserver(entries => { - entries.forEach(({ target }) => { - const instance = registry[target._layoutId]; - instance && instance._handleLayout(); - }); - }); - } else { - if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { - console.warn( - 'onLayout relies on ResizeObserver which is not supported by your browser. ' + - 'Please include a polyfill, e.g., https://github.com/que-etc/resize-observer-polyfill. ' + - 'Falling back to window.onresize.' - ); - } - - const triggerAll = () => { - Object.keys(registry).forEach(key => { - const instance = registry[key]; - instance._handleLayout(); - }); - }; - - window.addEventListener('resize', debounce(triggerAll, 16), false); - } -} - -const observe = instance => { - const id = guid(); - registry[id] = instance; - - if (resizeObserver) { - const node = findNodeHandle(instance); - if (node) { - node._layoutId = id; - resizeObserver.observe(node); - } - } else { - instance._layoutId = id; - instance._handleLayout(); - } -}; - -const unobserve = instance => { - if (resizeObserver) { - const node = findNodeHandle(instance); - if (node) { - delete registry[node._layoutId]; - delete node._layoutId; - resizeObserver.unobserve(node); - } - } else { - delete registry[instance._layoutId]; - delete instance._layoutId; - } -}; - -const safeOverride = (original, next) => { - if (original) { - return function prototypeOverride() { - /* eslint-disable prefer-rest-params */ - original.call(this, arguments); - next.call(this, arguments); - /* eslint-enable prefer-rest-params */ - }; - } - return next; -}; - -const applyLayout = Component => { - const componentDidMount = Component.prototype.componentDidMount; - const componentDidUpdate = Component.prototype.componentDidUpdate; - const componentWillUnmount = Component.prototype.componentWillUnmount; - - Component.prototype.componentDidMount = safeOverride( - componentDidMount, - function componentDidMount() { - this._layoutState = emptyObject; - this._isMounted = true; - if (this.props.onLayout) { - observe(this); - } - } - ); - - Component.prototype.componentDidUpdate = safeOverride( - componentDidUpdate, - function componentDidUpdate(prevProps) { - if (this.props.onLayout && !prevProps.onLayout) { - observe(this); - } else if (!this.props.onLayout && prevProps.onLayout) { - unobserve(this); - } - } - ); - - Component.prototype.componentWillUnmount = safeOverride( - componentWillUnmount, - function componentWillUnmount() { - this._isMounted = false; - if (this.props.onLayout) { - unobserve(this); - } - } - ); - - Component.prototype._handleLayout = function() { - const layout = this._layoutState; - const { onLayout } = this.props; - - if (onLayout) { - this.measure((x, y, width, height) => { - if (this._isMounted) { - if ( - layout.x !== x || - layout.y !== y || - layout.width !== width || - layout.height !== height - ) { - this._layoutState = { x, y, width, height }; - const nativeEvent = { - layout: this._layoutState - }; - Object.defineProperty(nativeEvent, 'target', { - enumerable: true, - get: () => findNodeHandle(this) - }); - onLayout({ nativeEvent, timeStamp: Date.now() }); - } - } - }); - } - }; - return Component; -}; - -export default applyLayout; diff --git a/packages/react-native-web/src/modules/applyNativeMethods/index.js b/packages/react-native-web/src/modules/applyNativeMethods/index.js deleted file mode 100644 index 369db257..00000000 --- a/packages/react-native-web/src/modules/applyNativeMethods/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) Nicolas Gallagher. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @noflow - */ - -import NativeMethodsMixin from '../NativeMethodsMixin'; - -const applyNativeMethods = Component => { - Object.keys(NativeMethodsMixin).forEach(method => { - if (!Component.prototype[method]) { - Component.prototype[method] = NativeMethodsMixin[method]; - } - }); - return Component; -}; - -export default applyNativeMethods; diff --git a/packages/react-native-web/src/modules/ensureComponentIsNative/index.js b/packages/react-native-web/src/modules/ensureComponentIsNative/index.js deleted file mode 100644 index d82dcb5c..00000000 --- a/packages/react-native-web/src/modules/ensureComponentIsNative/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) Nicolas Gallagher. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import invariant from 'fbjs/lib/invariant'; - -const ensureComponentIsNative = (component: any) => { - invariant( - component && typeof component.setNativeProps === 'function', - 'Touchable child must either be native or forward setNativeProps to a native component' - ); -}; - -export default ensureComponentIsNative; diff --git a/packages/react-native-web/src/modules/hydrate/index.js b/packages/react-native-web/src/modules/hydrate/index.js deleted file mode 100644 index 5d246b91..00000000 --- a/packages/react-native-web/src/modules/hydrate/index.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) Nicolas Gallagher. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @noflow - */ - -import { hydrate } from 'react-dom'; -export default hydrate;