Reorganize internal modules

This commit is contained in:
Nicolas Gallagher
2020-06-26 14:51:45 -07:00
parent 603adc46ab
commit a7da67cd09
15 changed files with 10 additions and 552 deletions
+2 -2
View File
@@ -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
}));
@@ -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';
@@ -7,7 +7,7 @@
* @flow
*/
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
import ReactNativePropRegistry from './ReactNativePropRegistry';
import flattenStyle from './flattenStyle';
const absoluteFillObject = {
@@ -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",
},
}
`;
@@ -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();
});
});
});
@@ -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 };
};
@@ -8,7 +8,7 @@
* @flow
*/
import ReactNativePropRegistry from '../../modules/ReactNativePropRegistry';
import ReactNativePropRegistry from './ReactNativePropRegistry';
import invariant from 'fbjs/lib/invariant';
function getStyle(style) {
+3 -2
View File
@@ -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;
@@ -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;
@@ -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() {
@@ -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;
@@ -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;
@@ -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;
-11
View File
@@ -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;