Merge pull request #6338 from sebmarkbage/reactnative2
Move React Core Integration and Injection to the Core Repo
This commit is contained in:
commit
c84ad52ddb
1
.babelrc
1
.babelrc
|
@ -4,6 +4,7 @@
|
|||
"plugins": [
|
||||
"fbjs-scripts/babel-6/dev-expression",
|
||||
"syntax-trailing-function-commas",
|
||||
"babel-plugin-transform-object-rest-spread",
|
||||
"transform-es2015-template-literals",
|
||||
"transform-es2015-literals",
|
||||
"transform-es2015-arrow-functions",
|
||||
|
|
|
@ -58,6 +58,7 @@ script:
|
|||
-F "react-dom-server.min=@build/react-dom-server.min.js" \
|
||||
-F "npm-react=@build/packages/react.tgz" \
|
||||
-F "npm-react-dom=@build/packages/react-dom.tgz" \
|
||||
-F "npm-react-native=@build/packages/react-native-renderer.tgz" \
|
||||
-F "commit=$TRAVIS_COMMIT" \
|
||||
-F "date=`git log --format='%ct' -1`" \
|
||||
-F "pull_request=$TRAVIS_PULL_REQUEST" \
|
||||
|
|
|
@ -74,6 +74,10 @@ module.exports = function(grunt) {
|
|||
grunt.registerTask('npm-react-dom:release', npmReactDOMTasks.buildRelease);
|
||||
grunt.registerTask('npm-react-dom:pack', npmReactDOMTasks.packRelease);
|
||||
|
||||
var npmReactNativeTasks = require('./grunt/tasks/npm-react-native');
|
||||
grunt.registerTask('npm-react-native:release', npmReactNativeTasks.buildRelease);
|
||||
grunt.registerTask('npm-react-native:pack', npmReactNativeTasks.packRelease);
|
||||
|
||||
var npmReactAddonsTasks = require('./grunt/tasks/npm-react-addons');
|
||||
grunt.registerTask('npm-react-addons:release', npmReactAddonsTasks.buildReleases);
|
||||
grunt.registerTask('npm-react-addons:pack', npmReactAddonsTasks.packReleases);
|
||||
|
@ -127,6 +131,8 @@ module.exports = function(grunt) {
|
|||
'npm-react:pack',
|
||||
'npm-react-dom:release',
|
||||
'npm-react-dom:pack',
|
||||
'npm-react-native:release',
|
||||
'npm-react-native:pack',
|
||||
'npm-react-addons:release',
|
||||
'npm-react-addons:pack',
|
||||
'compare_size',
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var grunt = require('grunt');
|
||||
|
||||
var src = 'packages/react-native-renderer/';
|
||||
var dest = 'build/packages/react-native-renderer/';
|
||||
|
||||
function buildRelease() {
|
||||
if (grunt.file.exists(dest)) {
|
||||
grunt.file.delete(dest);
|
||||
}
|
||||
|
||||
// Copy to build/packages/react-native-renderer
|
||||
var mappings = [].concat(
|
||||
grunt.file.expandMapping('**/*', dest, {cwd: src}),
|
||||
grunt.file.expandMapping('{LICENSE,PATENTS}', dest)
|
||||
);
|
||||
mappings.forEach(function(mapping) {
|
||||
var mappingSrc = mapping.src[0];
|
||||
var mappingDest = mapping.dest;
|
||||
if (grunt.file.isDir(mappingSrc)) {
|
||||
grunt.file.mkdir(mappingDest);
|
||||
} else {
|
||||
grunt.file.copy(mappingSrc, mappingDest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function packRelease() {
|
||||
var done = this.async();
|
||||
var spawnCmd = {
|
||||
cmd: 'npm',
|
||||
args: ['pack', 'packages/react-native-renderer'],
|
||||
};
|
||||
grunt.util.spawn(spawnCmd, function() {
|
||||
var buildSrc = 'react-native-renderer-' + grunt.config.data.pkg.version + '.tgz';
|
||||
var buildDest = 'build/packages/react-native-renderer.tgz';
|
||||
fs.rename(buildSrc, buildDest, done);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildRelease: buildRelease,
|
||||
packRelease: packRelease,
|
||||
};
|
|
@ -11,6 +11,8 @@ module.exports = function() {
|
|||
grunt.file.readJSON('./packages/react/package.json').version,
|
||||
'packages/react-dom/package.json':
|
||||
grunt.file.readJSON('./packages/react-dom/package.json').version,
|
||||
'packages/react-native-renderer/package.json':
|
||||
grunt.file.readJSON('./packages/react-native-renderer/package.json').version,
|
||||
'packages/react-addons/package.json (version)': addonsData.version,
|
||||
// Get the "version" without the range bit
|
||||
'packages/react-addons/package.json (react dependency)': addonsData.peerDependencies.react.slice(1),
|
||||
|
|
38
gulpfile.js
38
gulpfile.js
|
@ -20,6 +20,7 @@ var paths = {
|
|||
react: {
|
||||
src: [
|
||||
'src/**/*.js',
|
||||
'!src/**/__benchmarks__/**/*.js',
|
||||
'!src/**/__tests__/**/*.js',
|
||||
'!src/**/__mocks__/**/*.js',
|
||||
'!src/shared/vendor/**/*.js',
|
||||
|
@ -28,17 +29,36 @@ var paths = {
|
|||
},
|
||||
};
|
||||
|
||||
var fbjsModuleMap = require('fbjs/module-map');
|
||||
var moduleMap = {};
|
||||
for (var key in fbjsModuleMap) {
|
||||
moduleMap[key] = fbjsModuleMap[key];
|
||||
}
|
||||
var whiteListNames = [
|
||||
'deepDiffer',
|
||||
'deepFreezeAndThrowOnMutationInDev',
|
||||
'flattenStyle',
|
||||
'InitializeJavaScriptAppEngine',
|
||||
'InteractionManager',
|
||||
'JSTimersExecution',
|
||||
'merge',
|
||||
'Platform',
|
||||
'RCTEventEmitter',
|
||||
'RCTLog',
|
||||
'TextInputState',
|
||||
'UIManager',
|
||||
'View',
|
||||
];
|
||||
|
||||
whiteListNames.forEach(function(name) {
|
||||
moduleMap[name] = name;
|
||||
});
|
||||
|
||||
moduleMap['object-assign'] = 'object-assign';
|
||||
|
||||
var babelOpts = {
|
||||
plugins: [
|
||||
[babelPluginModules, {
|
||||
map: Object.assign(
|
||||
{},
|
||||
require('fbjs/module-map'),
|
||||
{
|
||||
'object-assign': 'object-assign',
|
||||
}
|
||||
),
|
||||
}],
|
||||
[babelPluginModules, { map: moduleMap }],
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"babel-plugin-transform-es2015-template-literals": "^6.5.2",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.5.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.5.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.6.5",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"browserify": "^13.0.0",
|
||||
"bundle-collapser": "^1.1.1",
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# `react-native-renderer`
|
||||
|
||||
This package is the renderer that is used by the react-native package.
|
||||
It is intended to be used inside the react-native environment. It is not
|
||||
intended to be used stand alone.
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = require('react/lib/ReactNative');
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "react-native-renderer",
|
||||
"version": "16.0.0-alpha",
|
||||
"description": "React package for use inside react-native.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/facebook/react.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"bugs": {
|
||||
"url": "https://github.com/facebook/react/issues"
|
||||
},
|
||||
"homepage": "https://facebook.github.io/react-native/",
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.0",
|
||||
"react": "^16.0.0-alpha"
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var ReactUpdates = require('./ReactUpdates');
|
||||
|
||||
// TODO: In React Native, ReactTestUtils depends on ./ReactDOM (for
|
||||
// renderIntoDocument, which should never be called) and Relay depends on
|
||||
// react-dom (for batching). Once those are fixed, nothing in RN should import
|
||||
// this module and this file can go away.
|
||||
|
||||
module.exports = {
|
||||
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule IOSDefaultEventPluginOrder
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var IOSDefaultEventPluginOrder = [
|
||||
'ResponderEventPlugin',
|
||||
'IOSNativeBridgeEventPlugin',
|
||||
];
|
||||
|
||||
module.exports = IOSDefaultEventPluginOrder;
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule IOSNativeBridgeEventPlugin
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EventPropagators = require('EventPropagators');
|
||||
var SyntheticEvent = require('SyntheticEvent');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var merge = require('merge');
|
||||
var warning = require('warning');
|
||||
|
||||
var customBubblingEventTypes = UIManager.customBubblingEventTypes;
|
||||
var customDirectEventTypes = UIManager.customDirectEventTypes;
|
||||
|
||||
var allTypesByEventName = {};
|
||||
|
||||
for (var bubblingTypeName in customBubblingEventTypes) {
|
||||
allTypesByEventName[bubblingTypeName] = customBubblingEventTypes[bubblingTypeName];
|
||||
}
|
||||
|
||||
for (var directTypeName in customDirectEventTypes) {
|
||||
warning(
|
||||
!customBubblingEventTypes[directTypeName],
|
||||
'Event cannot be both direct and bubbling: %s',
|
||||
directTypeName
|
||||
);
|
||||
allTypesByEventName[directTypeName] = customDirectEventTypes[directTypeName];
|
||||
}
|
||||
|
||||
var IOSNativeBridgeEventPlugin = {
|
||||
|
||||
eventTypes: merge(customBubblingEventTypes, customDirectEventTypes),
|
||||
|
||||
/**
|
||||
* @see {EventPluginHub.extractEvents}
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
): ?Object {
|
||||
var bubbleDispatchConfig = customBubblingEventTypes[topLevelType];
|
||||
var directDispatchConfig = customDirectEventTypes[topLevelType];
|
||||
var event = SyntheticEvent.getPooled(
|
||||
bubbleDispatchConfig || directDispatchConfig,
|
||||
targetInst,
|
||||
nativeEvent,
|
||||
nativeEventTarget
|
||||
);
|
||||
if (bubbleDispatchConfig) {
|
||||
EventPropagators.accumulateTwoPhaseDispatches(event);
|
||||
} else if (directDispatchConfig) {
|
||||
EventPropagators.accumulateDirectDispatches(event);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = IOSNativeBridgeEventPlugin;
|
|
@ -0,0 +1,219 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule NativeMethodsMixin
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
var TextInputState = require('TextInputState');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var findNodeHandle = require('findNodeHandle');
|
||||
var invariant = require('invariant');
|
||||
|
||||
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
|
||||
|
||||
function warnForStyleProps(props, validAttributes) {
|
||||
for (var key in validAttributes.style) {
|
||||
if (!(validAttributes[key] || props[key] === undefined)) {
|
||||
console.error(
|
||||
'You are setting the style `{ ' + key + ': ... }` as a prop. You ' +
|
||||
'should nest it in a style object. ' +
|
||||
'E.g. `{ style: { ' + key + ': ... } }`'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `NativeMethodsMixin` provides methods to access the underlying native
|
||||
* component directly. This can be useful in cases when you want to focus
|
||||
* a view or measure its on-screen dimensions, for example.
|
||||
*
|
||||
* The methods described here are available on most of the default components
|
||||
* provided by React Native. Note, however, that they are *not* available on
|
||||
* composite components that aren't directly backed by a native view. This will
|
||||
* generally include most components that you define in your own app. For more
|
||||
* information, see [Direct
|
||||
* Manipulation](docs/direct-manipulation.html).
|
||||
*/
|
||||
var NativeMethodsMixin = {
|
||||
/**
|
||||
* Determines the location on screen, width, and height of the given view and
|
||||
* returns the values via an async callback. If successful, the callback will
|
||||
* be called with the following arguments:
|
||||
*
|
||||
* - x
|
||||
* - y
|
||||
* - width
|
||||
* - height
|
||||
* - pageX
|
||||
* - pageY
|
||||
*
|
||||
* Note that these measurements are not available until after the rendering
|
||||
* has been completed in native. If you need the measurements as soon as
|
||||
* possible, consider using the [`onLayout`
|
||||
* prop](docs/view.html#onlayout) instead.
|
||||
*/
|
||||
measure: function(callback: MeasureOnSuccessCallback) {
|
||||
UIManager.measure(
|
||||
findNodeHandle(this),
|
||||
mountSafeCallback(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 with the following
|
||||
* arguments:
|
||||
*
|
||||
* - x
|
||||
* - y
|
||||
* - width
|
||||
* - height
|
||||
*
|
||||
* Note that these measurements are not available until after the rendering
|
||||
* has been completed in native.
|
||||
*/
|
||||
measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) {
|
||||
UIManager.measureInWindow(
|
||||
findNodeHandle(this),
|
||||
mountSafeCallback(this, callback)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Like [`measure()`](#measure), but measures the view relative an ancestor,
|
||||
* specified as `relativeToNativeNode`. This means that the returned x, y
|
||||
* are relative to the origin x, y of the ancestor view.
|
||||
*
|
||||
* As always, to obtain a native node handle for a component, you can use
|
||||
* `React.findNodeHandle(component)`.
|
||||
*/
|
||||
measureLayout: function(
|
||||
relativeToNativeNode: number,
|
||||
onSuccess: MeasureLayoutOnSuccessCallback,
|
||||
onFail: () => void /* currently unused */
|
||||
) {
|
||||
UIManager.measureLayout(
|
||||
findNodeHandle(this),
|
||||
relativeToNativeNode,
|
||||
mountSafeCallback(this, onFail),
|
||||
mountSafeCallback(this, onSuccess)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* This function sends props straight to native. They will not participate in
|
||||
* future diff process - this means that if you do not include them in the
|
||||
* next render, they will remain active (see [Direct
|
||||
* Manipulation](docs/direct-manipulation.html)).
|
||||
*/
|
||||
setNativeProps: function(nativeProps: Object) {
|
||||
if (__DEV__) {
|
||||
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
|
||||
}
|
||||
|
||||
var updatePayload = ReactNativeAttributePayload.create(
|
||||
nativeProps,
|
||||
this.viewConfig.validAttributes
|
||||
);
|
||||
|
||||
UIManager.updateView(
|
||||
findNodeHandle(this),
|
||||
this.viewConfig.uiViewClassName,
|
||||
updatePayload
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Requests focus for the given input or view. The exact behavior triggered
|
||||
* will depend on the platform and type of view.
|
||||
*/
|
||||
focus: function() {
|
||||
TextInputState.focusTextInput(findNodeHandle(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes focus from an input or view. This is the opposite of `focus()`.
|
||||
*/
|
||||
blur: function() {
|
||||
TextInputState.blurTextInput(findNodeHandle(this));
|
||||
},
|
||||
};
|
||||
|
||||
function throwOnStylesProp(component, props) {
|
||||
if (props.styles !== undefined) {
|
||||
var owner = component._owner || null;
|
||||
var name = component.constructor.displayName;
|
||||
var msg = '`styles` is not a supported property of `' + name + '`, did ' +
|
||||
'you mean `style` (singular)?';
|
||||
if (owner && owner.constructor && owner.constructor.displayName) {
|
||||
msg += '\n\nCheck the `' + owner.constructor.displayName + '` parent ' +
|
||||
' component.';
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
// hide this from Flow since we can't define these properties outside of
|
||||
// __DEV__ without actually implementing them (setting them to undefined
|
||||
// isn't allowed by ReactClass)
|
||||
var NativeMethodsMixin_DEV = (NativeMethodsMixin: any);
|
||||
invariant(
|
||||
!NativeMethodsMixin_DEV.componentWillMount &&
|
||||
!NativeMethodsMixin_DEV.componentWillReceiveProps,
|
||||
'Do not override existing functions.'
|
||||
);
|
||||
NativeMethodsMixin_DEV.componentWillMount = function() {
|
||||
throwOnStylesProp(this, this.props);
|
||||
};
|
||||
NativeMethodsMixin_DEV.componentWillReceiveProps = function(newProps) {
|
||||
throwOnStylesProp(this, newProps);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* In the future, we should cleanup callbacks by cancelling them instead of
|
||||
* using this.
|
||||
*/
|
||||
var mountSafeCallback = function(context: ReactComponent, callback: ?Function): any {
|
||||
return function() {
|
||||
if (!callback || (context.isMounted && !context.isMounted())) {
|
||||
return undefined;
|
||||
}
|
||||
return callback.apply(context, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = NativeMethodsMixin;
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNative
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// Require ReactNativeDefaultInjection first for its side effects of setting up
|
||||
// the JS environment
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeDefaultInjection = require('ReactNativeDefaultInjection');
|
||||
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactNativeMount = require('ReactNativeMount');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var findNodeHandle = require('findNodeHandle');
|
||||
|
||||
ReactNativeDefaultInjection.inject();
|
||||
|
||||
var render = function(
|
||||
element: ReactElement,
|
||||
mountInto: number,
|
||||
callback?: ?(() => void)
|
||||
): ?ReactComponent {
|
||||
return ReactNativeMount.renderComponent(element, mountInto, callback);
|
||||
};
|
||||
|
||||
var ReactNative = {
|
||||
hasReactNativeInitialized: false,
|
||||
findNodeHandle: findNodeHandle,
|
||||
render: render,
|
||||
unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode,
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer,
|
||||
};
|
||||
|
||||
// Inject the runtime into a devtools global hook regardless of browser.
|
||||
// Allows for debugging when the hook is injected on the page.
|
||||
/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__ */
|
||||
if (
|
||||
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
|
||||
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') {
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
|
||||
ComponentTree: {
|
||||
getClosestInstanceFromNode: function(node) {
|
||||
return ReactNativeComponentTree.getClosestInstanceFromNode(node);
|
||||
},
|
||||
getNodeFromInstance: function(inst) {
|
||||
// inst is an internal instance (but could be a composite)
|
||||
while (inst._renderedComponent) {
|
||||
inst = inst._renderedComponent;
|
||||
}
|
||||
if (inst) {
|
||||
return ReactNativeComponentTree.getNodeFromInstance(inst);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
Mount: ReactNativeMount,
|
||||
Reconciler: require('ReactReconciler'),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = ReactNative;
|
|
@ -0,0 +1,546 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeAttributePayload
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Platform = require('Platform');
|
||||
var ReactNativePropRegistry = require('ReactNativePropRegistry');
|
||||
|
||||
var deepDiffer = require('deepDiffer');
|
||||
var flattenStyle = require('flattenStyle');
|
||||
|
||||
var emptyObject = {};
|
||||
|
||||
/**
|
||||
* Create a payload that contains all the updates between two sets of props.
|
||||
*
|
||||
* These helpers are all encapsulated into a single module, because they use
|
||||
* mutation as a performance optimization which leads to subtle shared
|
||||
* dependencies between the code paths. To avoid this mutable state leaking
|
||||
* across modules, I've kept them isolated to this module.
|
||||
*/
|
||||
|
||||
type AttributeDiffer = (prevProp: mixed, nextProp: mixed) => boolean;
|
||||
type AttributePreprocessor = (nextProp: mixed) => mixed;
|
||||
|
||||
type CustomAttributeConfiguration =
|
||||
{ diff: AttributeDiffer, process: AttributePreprocessor } |
|
||||
{ diff: AttributeDiffer } |
|
||||
{ process: AttributePreprocessor };
|
||||
|
||||
type AttributeConfiguration =
|
||||
{ [key: string]: (
|
||||
CustomAttributeConfiguration | AttributeConfiguration /*| boolean*/
|
||||
) };
|
||||
|
||||
type NestedNode = Array<NestedNode> | Object | number;
|
||||
|
||||
// Tracks removed keys
|
||||
var removedKeys = null;
|
||||
var removedKeyCount = 0;
|
||||
|
||||
function translateKey(propKey: string) : string {
|
||||
if (propKey === 'transform') {
|
||||
// We currently special case the key for `transform`. iOS uses the
|
||||
// transformMatrix name and Android uses the decomposedMatrix name.
|
||||
// TODO: We could unify these names and just use the name `transform`
|
||||
// all the time. Just need to update the native side.
|
||||
if (Platform.OS === 'android') {
|
||||
return 'decomposedMatrix';
|
||||
} else {
|
||||
return 'transformMatrix';
|
||||
}
|
||||
}
|
||||
return propKey;
|
||||
}
|
||||
|
||||
function defaultDiffer(prevProp: mixed, nextProp: mixed) : boolean {
|
||||
if (typeof nextProp !== 'object' || nextProp === null) {
|
||||
// Scalars have already been checked for equality
|
||||
return true;
|
||||
} else {
|
||||
// For objects and arrays, the default diffing algorithm is a deep compare
|
||||
return deepDiffer(prevProp, nextProp);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveObject(idOrObject: number | Object) : Object {
|
||||
if (typeof idOrObject === 'number') {
|
||||
return ReactNativePropRegistry.getByID(idOrObject);
|
||||
}
|
||||
return idOrObject;
|
||||
}
|
||||
|
||||
function restoreDeletedValuesInNestedArray(
|
||||
updatePayload: Object,
|
||||
node: NestedNode,
|
||||
validAttributes: AttributeConfiguration
|
||||
) {
|
||||
if (Array.isArray(node)) {
|
||||
var i = node.length;
|
||||
while (i-- && removedKeyCount > 0) {
|
||||
restoreDeletedValuesInNestedArray(
|
||||
updatePayload,
|
||||
node[i],
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
} else if (node && removedKeyCount > 0) {
|
||||
var obj = resolveObject(node);
|
||||
for (var propKey in removedKeys) {
|
||||
if (!removedKeys[propKey]) {
|
||||
continue;
|
||||
}
|
||||
var nextProp = obj[propKey];
|
||||
if (nextProp === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var attributeConfig = validAttributes[propKey];
|
||||
if (!attributeConfig) {
|
||||
continue; // not a valid native prop
|
||||
}
|
||||
|
||||
if (typeof nextProp === 'function') {
|
||||
nextProp = true;
|
||||
}
|
||||
if (typeof nextProp === 'undefined') {
|
||||
nextProp = null;
|
||||
}
|
||||
|
||||
if (typeof attributeConfig !== 'object') {
|
||||
// case: !Object is the default case
|
||||
updatePayload[propKey] = nextProp;
|
||||
} else if (typeof attributeConfig.diff === 'function' ||
|
||||
typeof attributeConfig.process === 'function') {
|
||||
// case: CustomAttributeConfiguration
|
||||
var nextValue = typeof attributeConfig.process === 'function' ?
|
||||
attributeConfig.process(nextProp) :
|
||||
nextProp;
|
||||
updatePayload[propKey] = nextValue;
|
||||
}
|
||||
removedKeys[propKey] = false;
|
||||
removedKeyCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function diffNestedArrayProperty(
|
||||
updatePayload:? Object,
|
||||
prevArray: Array<NestedNode>,
|
||||
nextArray: Array<NestedNode>,
|
||||
validAttributes: AttributeConfiguration
|
||||
) : ?Object {
|
||||
var minLength = prevArray.length < nextArray.length ?
|
||||
prevArray.length :
|
||||
nextArray.length;
|
||||
var i;
|
||||
for (i = 0; i < minLength; i++) {
|
||||
// Diff any items in the array in the forward direction. Repeated keys
|
||||
// will be overwritten by later values.
|
||||
updatePayload = diffNestedProperty(
|
||||
updatePayload,
|
||||
prevArray[i],
|
||||
nextArray[i],
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
for (; i < prevArray.length; i++) {
|
||||
// Clear out all remaining properties.
|
||||
updatePayload = clearNestedProperty(
|
||||
updatePayload,
|
||||
prevArray[i],
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
for (; i < nextArray.length; i++) {
|
||||
// Add all remaining properties.
|
||||
updatePayload = addNestedProperty(
|
||||
updatePayload,
|
||||
nextArray[i],
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
function diffNestedProperty(
|
||||
updatePayload:? Object,
|
||||
prevProp: NestedNode,
|
||||
nextProp: NestedNode,
|
||||
validAttributes: AttributeConfiguration
|
||||
) : ?Object {
|
||||
|
||||
if (!updatePayload && prevProp === nextProp) {
|
||||
// If no properties have been added, then we can bail out quickly on object
|
||||
// equality.
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!prevProp || !nextProp) {
|
||||
if (nextProp) {
|
||||
return addNestedProperty(
|
||||
updatePayload,
|
||||
nextProp,
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
if (prevProp) {
|
||||
return clearNestedProperty(
|
||||
updatePayload,
|
||||
prevProp,
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!Array.isArray(prevProp) && !Array.isArray(nextProp)) {
|
||||
// Both are leaves, we can diff the leaves.
|
||||
return diffProperties(
|
||||
updatePayload,
|
||||
resolveObject(prevProp),
|
||||
resolveObject(nextProp),
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(prevProp) && Array.isArray(nextProp)) {
|
||||
// Both are arrays, we can diff the arrays.
|
||||
return diffNestedArrayProperty(
|
||||
updatePayload,
|
||||
prevProp,
|
||||
nextProp,
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(prevProp)) {
|
||||
return diffProperties(
|
||||
updatePayload,
|
||||
// $FlowFixMe - We know that this is always an object when the input is.
|
||||
flattenStyle(prevProp),
|
||||
// $FlowFixMe - We know that this isn't an array because of above flow.
|
||||
resolveObject(nextProp),
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
|
||||
return diffProperties(
|
||||
updatePayload,
|
||||
resolveObject(prevProp),
|
||||
// $FlowFixMe - We know that this is always an object when the input is.
|
||||
flattenStyle(nextProp),
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* addNestedProperty takes a single set of props and valid attribute
|
||||
* attribute configurations. It processes each prop and adds it to the
|
||||
* updatePayload.
|
||||
*/
|
||||
function addNestedProperty(
|
||||
updatePayload:? Object,
|
||||
nextProp: NestedNode,
|
||||
validAttributes: AttributeConfiguration
|
||||
) {
|
||||
if (!nextProp) {
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!Array.isArray(nextProp)) {
|
||||
// Add each property of the leaf.
|
||||
return addProperties(
|
||||
updatePayload,
|
||||
resolveObject(nextProp),
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0; i < nextProp.length; i++) {
|
||||
// Add all the properties of the array.
|
||||
updatePayload = addNestedProperty(
|
||||
updatePayload,
|
||||
nextProp[i],
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* clearNestedProperty takes a single set of props and valid attributes. It
|
||||
* adds a null sentinel to the updatePayload, for each prop key.
|
||||
*/
|
||||
function clearNestedProperty(
|
||||
updatePayload:? Object,
|
||||
prevProp: NestedNode,
|
||||
validAttributes: AttributeConfiguration
|
||||
) : ?Object {
|
||||
if (!prevProp) {
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!Array.isArray(prevProp)) {
|
||||
// Add each property of the leaf.
|
||||
return clearProperties(
|
||||
updatePayload,
|
||||
resolveObject(prevProp),
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0; i < prevProp.length; i++) {
|
||||
// Add all the properties of the array.
|
||||
updatePayload = clearNestedProperty(
|
||||
updatePayload,
|
||||
prevProp[i],
|
||||
validAttributes
|
||||
);
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* diffProperties takes two sets of props and a set of valid attributes
|
||||
* and write to updatePayload the values that changed or were deleted.
|
||||
* If no updatePayload is provided, a new one is created and returned if
|
||||
* anything changed.
|
||||
*/
|
||||
function diffProperties(
|
||||
updatePayload: ?Object,
|
||||
prevProps: Object,
|
||||
nextProps: Object,
|
||||
validAttributes: AttributeConfiguration
|
||||
): ?Object {
|
||||
var attributeConfig : ?(CustomAttributeConfiguration | AttributeConfiguration);
|
||||
var nextProp;
|
||||
var prevProp;
|
||||
var altKey;
|
||||
|
||||
for (var propKey in nextProps) {
|
||||
attributeConfig = validAttributes[propKey];
|
||||
if (!attributeConfig) {
|
||||
continue; // not a valid native prop
|
||||
}
|
||||
|
||||
altKey = translateKey(propKey);
|
||||
if (!validAttributes[altKey]) {
|
||||
// If there is no config for the alternative, bail out. Helps ART.
|
||||
altKey = propKey;
|
||||
}
|
||||
|
||||
prevProp = prevProps[propKey];
|
||||
nextProp = nextProps[propKey];
|
||||
|
||||
// functions are converted to booleans as markers that the associated
|
||||
// events should be sent from native.
|
||||
if (typeof nextProp === 'function') {
|
||||
nextProp = (true : any);
|
||||
// If nextProp is not a function, then don't bother changing prevProp
|
||||
// since nextProp will win and go into the updatePayload regardless.
|
||||
if (typeof prevProp === 'function') {
|
||||
prevProp = (true : any);
|
||||
}
|
||||
}
|
||||
|
||||
// An explicit value of undefined is treated as a null because it overrides
|
||||
// any other preceeding value.
|
||||
if (typeof nextProp === 'undefined') {
|
||||
nextProp = (null : any);
|
||||
if (typeof prevProp === 'undefined') {
|
||||
prevProp = (null : any);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedKeys) {
|
||||
removedKeys[propKey] = false;
|
||||
}
|
||||
|
||||
if (updatePayload && updatePayload[altKey] !== undefined) {
|
||||
// Something else already triggered an update to this key because another
|
||||
// value diffed. Since we're now later in the nested arrays our value is
|
||||
// more important so we need to calculate it and override the existing
|
||||
// value. It doesn't matter if nothing changed, we'll set it anyway.
|
||||
|
||||
// Pattern match on: attributeConfig
|
||||
if (typeof attributeConfig !== 'object') {
|
||||
// case: !Object is the default case
|
||||
updatePayload[altKey] = nextProp;
|
||||
} else if (typeof attributeConfig.diff === 'function' ||
|
||||
typeof attributeConfig.process === 'function') {
|
||||
// case: CustomAttributeConfiguration
|
||||
var nextValue = typeof attributeConfig.process === 'function' ?
|
||||
attributeConfig.process(nextProp) :
|
||||
nextProp;
|
||||
updatePayload[altKey] = nextValue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prevProp === nextProp) {
|
||||
continue; // nothing changed
|
||||
}
|
||||
|
||||
// Pattern match on: attributeConfig
|
||||
if (typeof attributeConfig !== 'object') {
|
||||
// case: !Object is the default case
|
||||
if (defaultDiffer(prevProp, nextProp)) {
|
||||
// a normal leaf has changed
|
||||
(updatePayload || (updatePayload = {}))[altKey] = nextProp;
|
||||
}
|
||||
} else if (typeof attributeConfig.diff === 'function' ||
|
||||
typeof attributeConfig.process === 'function') {
|
||||
// case: CustomAttributeConfiguration
|
||||
var shouldUpdate = prevProp === undefined || (
|
||||
typeof attributeConfig.diff === 'function' ?
|
||||
attributeConfig.diff(prevProp, nextProp) :
|
||||
defaultDiffer(prevProp, nextProp)
|
||||
);
|
||||
if (shouldUpdate) {
|
||||
nextValue = typeof attributeConfig.process === 'function' ?
|
||||
attributeConfig.process(nextProp) :
|
||||
nextProp;
|
||||
(updatePayload || (updatePayload = {}))[altKey] = nextValue;
|
||||
}
|
||||
} else {
|
||||
// default: fallthrough case when nested properties are defined
|
||||
removedKeys = null;
|
||||
removedKeyCount = 0;
|
||||
updatePayload = diffNestedProperty(
|
||||
updatePayload,
|
||||
prevProp,
|
||||
nextProp,
|
||||
attributeConfig
|
||||
);
|
||||
if (removedKeyCount > 0 && updatePayload) {
|
||||
restoreDeletedValuesInNestedArray(
|
||||
updatePayload,
|
||||
nextProp,
|
||||
attributeConfig
|
||||
);
|
||||
removedKeys = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also iterate through all the previous props to catch any that have been
|
||||
// removed and make sure native gets the signal so it can reset them to the
|
||||
// default.
|
||||
for (propKey in prevProps) {
|
||||
if (nextProps[propKey] !== undefined) {
|
||||
continue; // we've already covered this key in the previous pass
|
||||
}
|
||||
attributeConfig = validAttributes[propKey];
|
||||
if (!attributeConfig) {
|
||||
continue; // not a valid native prop
|
||||
}
|
||||
|
||||
altKey = translateKey(propKey);
|
||||
if (!attributeConfig[altKey]) {
|
||||
// If there is no config for the alternative, bail out. Helps ART.
|
||||
altKey = propKey;
|
||||
}
|
||||
|
||||
if (updatePayload && updatePayload[altKey] !== undefined) {
|
||||
// This was already updated to a diff result earlier.
|
||||
continue;
|
||||
}
|
||||
|
||||
prevProp = prevProps[propKey];
|
||||
if (prevProp === undefined) {
|
||||
continue; // was already empty anyway
|
||||
}
|
||||
// Pattern match on: attributeConfig
|
||||
if (typeof attributeConfig !== 'object' ||
|
||||
typeof attributeConfig.diff === 'function' ||
|
||||
typeof attributeConfig.process === 'function') {
|
||||
|
||||
// case: CustomAttributeConfiguration | !Object
|
||||
// Flag the leaf property for removal by sending a sentinel.
|
||||
(updatePayload || (updatePayload = {}))[altKey] = null;
|
||||
if (!removedKeys) {
|
||||
removedKeys = {};
|
||||
}
|
||||
if (!removedKeys[propKey]) {
|
||||
removedKeys[propKey] = true;
|
||||
removedKeyCount++;
|
||||
}
|
||||
} else {
|
||||
// default:
|
||||
// This is a nested attribute configuration where all the properties
|
||||
// were removed so we need to go through and clear out all of them.
|
||||
updatePayload = clearNestedProperty(
|
||||
updatePayload,
|
||||
prevProp,
|
||||
attributeConfig
|
||||
);
|
||||
}
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* addProperties adds all the valid props to the payload after being processed.
|
||||
*/
|
||||
function addProperties(
|
||||
updatePayload: ?Object,
|
||||
props: Object,
|
||||
validAttributes: AttributeConfiguration
|
||||
) : ?Object {
|
||||
// TODO: Fast path
|
||||
return diffProperties(updatePayload, emptyObject, props, validAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* clearProperties clears all the previous props by adding a null sentinel
|
||||
* to the payload for each valid key.
|
||||
*/
|
||||
function clearProperties(
|
||||
updatePayload: ?Object,
|
||||
prevProps: Object,
|
||||
validAttributes: AttributeConfiguration
|
||||
) :? Object {
|
||||
// TODO: Fast path
|
||||
return diffProperties(updatePayload, prevProps, emptyObject, validAttributes);
|
||||
}
|
||||
|
||||
var ReactNativeAttributePayload = {
|
||||
|
||||
create: function(
|
||||
props: Object,
|
||||
validAttributes: AttributeConfiguration
|
||||
) : ?Object {
|
||||
return addProperties(
|
||||
null, // updatePayload
|
||||
props,
|
||||
validAttributes
|
||||
);
|
||||
},
|
||||
|
||||
diff: function(
|
||||
prevProps: Object,
|
||||
nextProps: Object,
|
||||
validAttributes: AttributeConfiguration
|
||||
) : ?Object {
|
||||
return diffProperties(
|
||||
null, // updatePayload
|
||||
prevProps,
|
||||
nextProps,
|
||||
validAttributes
|
||||
);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactNativeAttributePayload;
|
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeBaseComponent
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
|
||||
var registrationNames = ReactNativeEventEmitter.registrationNames;
|
||||
var putListener = ReactNativeEventEmitter.putListener;
|
||||
var deleteListener = ReactNativeEventEmitter.deleteListener;
|
||||
var deleteAllListeners = ReactNativeEventEmitter.deleteAllListeners;
|
||||
|
||||
type ReactNativeBaseComponentViewConfig = {
|
||||
validAttributes: Object;
|
||||
uiViewClassName: string;
|
||||
}
|
||||
|
||||
// require('UIManagerStatTracker').install(); // uncomment to enable
|
||||
|
||||
/**
|
||||
* @constructor ReactNativeBaseComponent
|
||||
* @extends ReactComponent
|
||||
* @extends ReactMultiChild
|
||||
* @param {!object} UIKit View Configuration.
|
||||
*/
|
||||
var ReactNativeBaseComponent = function(
|
||||
viewConfig: ReactNativeBaseComponentViewConfig
|
||||
) {
|
||||
this.viewConfig = viewConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mixin for containers that contain UIViews. NOTE: markup is rendered markup
|
||||
* which is a `viewID` ... see the return value for `mountComponent` !
|
||||
*/
|
||||
ReactNativeBaseComponent.Mixin = {
|
||||
getPublicInstance: function() {
|
||||
// TODO: This should probably use a composite wrapper
|
||||
return this;
|
||||
},
|
||||
|
||||
unmountComponent: function() {
|
||||
ReactNativeComponentTree.uncacheNode(this);
|
||||
deleteAllListeners(this);
|
||||
this.unmountChildren();
|
||||
this._rootNodeID = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Every native component is responsible for allocating its own `tag`, and
|
||||
* issuing the native `createView` command. But it is not responsible for
|
||||
* recording the fact that its own `rootNodeID` is associated with a
|
||||
* `nodeHandle`. Only the code that actually adds its `nodeHandle` (`tag`) as
|
||||
* a child of a container can confidently record that in
|
||||
* `ReactNativeTagHandles`.
|
||||
*/
|
||||
initializeChildren: function(children, containerTag, transaction, context) {
|
||||
var mountImages = this.mountChildren(children, transaction, context);
|
||||
// In a well balanced tree, half of the nodes are in the bottom row and have
|
||||
// no children - let's avoid calling out to the native bridge for a large
|
||||
// portion of the children.
|
||||
if (mountImages.length) {
|
||||
|
||||
// TODO: Pool these per platform view class. Reusing the `mountImages`
|
||||
// array would likely be a jit deopt.
|
||||
var createdTags = [];
|
||||
for (var i = 0, l = mountImages.length; i < l; i++) {
|
||||
var mountImage = mountImages[i];
|
||||
var childTag = mountImage;
|
||||
createdTags[i] = childTag;
|
||||
}
|
||||
UIManager.setChildren(containerTag, createdTags);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the component's currently mounted representation.
|
||||
*
|
||||
* @param {object} nextElement
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @param {object} context
|
||||
* @internal
|
||||
*/
|
||||
receiveComponent: function(nextElement, transaction, context) {
|
||||
var prevElement = this._currentElement;
|
||||
this._currentElement = nextElement;
|
||||
|
||||
if (__DEV__) {
|
||||
for (var key in this.viewConfig.validAttributes) {
|
||||
if (nextElement.props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(nextElement.props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updatePayload = ReactNativeAttributePayload.diff(
|
||||
prevElement.props,
|
||||
nextElement.props,
|
||||
this.viewConfig.validAttributes
|
||||
);
|
||||
|
||||
if (updatePayload) {
|
||||
UIManager.updateView(
|
||||
this._rootNodeID,
|
||||
this.viewConfig.uiViewClassName,
|
||||
updatePayload
|
||||
);
|
||||
}
|
||||
|
||||
this._reconcileListenersUponUpdate(
|
||||
prevElement.props,
|
||||
nextElement.props
|
||||
);
|
||||
this.updateChildren(nextElement.props.children, transaction, context);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} initialProps Native component props.
|
||||
*/
|
||||
_registerListenersUponCreation: function(initialProps) {
|
||||
for (var key in initialProps) {
|
||||
// NOTE: The check for `!props[key]`, is only possible because this method
|
||||
// registers listeners the *first* time a component is created.
|
||||
if (registrationNames[key] && initialProps[key]) {
|
||||
var listener = initialProps[key];
|
||||
putListener(this, key, listener);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reconciles event listeners, adding or removing if necessary.
|
||||
* @param {object} prevProps Native component props including events.
|
||||
* @param {object} nextProps Next native component props including events.
|
||||
*/
|
||||
_reconcileListenersUponUpdate: function(prevProps, nextProps) {
|
||||
for (var key in nextProps) {
|
||||
if (registrationNames[key] && (nextProps[key] !== prevProps[key])) {
|
||||
if (nextProps[key]) {
|
||||
putListener(this, key, nextProps[key]);
|
||||
} else {
|
||||
deleteListener(this, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Currently this still uses IDs for reconciliation so this can return null.
|
||||
*
|
||||
* @return {null} Null.
|
||||
*/
|
||||
getNativeNode: function() {
|
||||
return this._rootNodeID;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} rootID Root ID of this subtree.
|
||||
* @param {Transaction} transaction For creating/updating.
|
||||
* @return {string} Unique iOS view tag.
|
||||
*/
|
||||
mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) {
|
||||
var tag = ReactNativeTagHandles.allocateTag();
|
||||
|
||||
this._rootNodeID = tag;
|
||||
this._nativeParent = nativeParent;
|
||||
this._nativeContainerInfo = nativeContainerInfo;
|
||||
|
||||
if (__DEV__) {
|
||||
for (var key in this.viewConfig.validAttributes) {
|
||||
if (this._currentElement.props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updatePayload = ReactNativeAttributePayload.create(
|
||||
this._currentElement.props,
|
||||
this.viewConfig.validAttributes
|
||||
);
|
||||
|
||||
var nativeTopRootTag = nativeContainerInfo._tag;
|
||||
UIManager.createView(
|
||||
tag,
|
||||
this.viewConfig.uiViewClassName,
|
||||
nativeTopRootTag,
|
||||
updatePayload
|
||||
);
|
||||
|
||||
ReactNativeComponentTree.precacheNode(this, tag);
|
||||
|
||||
this._registerListenersUponCreation(this._currentElement.props);
|
||||
this.initializeChildren(
|
||||
this._currentElement.props.children,
|
||||
tag,
|
||||
transaction,
|
||||
context
|
||||
);
|
||||
return tag;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Order of mixins is important. ReactNativeBaseComponent overrides methods in
|
||||
* ReactMultiChild.
|
||||
*/
|
||||
Object.assign(
|
||||
ReactNativeBaseComponent.prototype,
|
||||
ReactMultiChild.Mixin,
|
||||
ReactNativeBaseComponent.Mixin,
|
||||
NativeMethodsMixin
|
||||
);
|
||||
|
||||
module.exports = ReactNativeBaseComponent;
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeComponentEnvironment
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeDOMIDOperations = require('ReactNativeDOMIDOperations');
|
||||
var ReactNativeReconcileTransaction = require('ReactNativeReconcileTransaction');
|
||||
|
||||
var ReactNativeComponentEnvironment = {
|
||||
|
||||
processChildrenUpdates: ReactNativeDOMIDOperations.dangerouslyProcessChildrenUpdates,
|
||||
|
||||
replaceNodeWithMarkup: ReactNativeDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID,
|
||||
|
||||
/**
|
||||
* Nothing to do for UIKit bridge.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
unmountIDFromEnvironment: function(/*rootNodeID*/) {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {DOMElement} Element to clear.
|
||||
*/
|
||||
clearNode: function(/*containerView*/) {
|
||||
|
||||
},
|
||||
|
||||
ReactReconcileTransaction: ReactNativeReconcileTransaction,
|
||||
};
|
||||
|
||||
module.exports = ReactNativeComponentEnvironment;
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeComponentTree
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var instanceCache = {};
|
||||
|
||||
/**
|
||||
* Drill down (through composites and empty components) until we get a native or
|
||||
* native text component.
|
||||
*
|
||||
* This is pretty polymorphic but unavoidable with the current structure we have
|
||||
* for `_renderedChildren`.
|
||||
*/
|
||||
function getRenderedNativeOrTextFromComponent(component) {
|
||||
var rendered;
|
||||
while ((rendered = component._renderedComponent)) {
|
||||
component = rendered;
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate `_nativeNode` on the rendered native/text component with the given
|
||||
* DOM node. The passed `inst` can be a composite.
|
||||
*/
|
||||
function precacheNode(inst, tag) {
|
||||
var nativeInst = getRenderedNativeOrTextFromComponent(inst);
|
||||
instanceCache[tag] = nativeInst;
|
||||
}
|
||||
|
||||
function uncacheNode(inst) {
|
||||
var tag = inst._rootNodeID;
|
||||
if (tag) {
|
||||
delete instanceCache[tag];
|
||||
}
|
||||
}
|
||||
|
||||
function getInstanceFromTag(tag) {
|
||||
return instanceCache[tag] || null;
|
||||
}
|
||||
|
||||
function getTagFromInstance(inst) {
|
||||
invariant(inst._rootNodeID, 'All native instances should have a tag.');
|
||||
return inst._rootNodeID;
|
||||
}
|
||||
|
||||
var ReactNativeComponentTree = {
|
||||
getClosestInstanceFromNode: getInstanceFromTag,
|
||||
getInstanceFromNode: getInstanceFromTag,
|
||||
getNodeFromInstance: getTagFromInstance,
|
||||
precacheNode: precacheNode,
|
||||
uncacheNode: uncacheNode,
|
||||
};
|
||||
|
||||
module.exports = ReactNativeComponentTree;
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeContainerInfo
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function ReactNativeContainerInfo(tag) {
|
||||
var info = {
|
||||
_tag: tag,
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
module.exports = ReactNativeContainerInfo;
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeDOMIDOperations
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
/**
|
||||
* Updates a component's children by processing a series of updates.
|
||||
* For each of the update/create commands, the `fromIndex` refers to the index
|
||||
* that the item existed at *before* any of the updates are applied, and the
|
||||
* `toIndex` refers to the index after *all* of the updates are applied
|
||||
* (including deletes/moves). TODO: refactor so this can be shared with
|
||||
* DOMChildrenOperations.
|
||||
*
|
||||
* @param {ReactNativeBaseComponent} updates List of update configurations.
|
||||
* @param {array<string>} markup List of markup strings - in the case of React
|
||||
* IOS, the ids of new components assumed to be already created.
|
||||
*/
|
||||
var dangerouslyProcessChildrenUpdates = function(inst, childrenUpdates) {
|
||||
if (!childrenUpdates.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var containerTag = ReactNativeComponentTree.getNodeFromInstance(inst);
|
||||
|
||||
var moveFromIndices;
|
||||
var moveToIndices;
|
||||
var addChildTags;
|
||||
var addAtIndices;
|
||||
var removeAtIndices;
|
||||
|
||||
for (var i = 0; i < childrenUpdates.length; i++) {
|
||||
var update = childrenUpdates[i];
|
||||
if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING) {
|
||||
(moveFromIndices || (moveFromIndices = [])).push(update.fromIndex);
|
||||
(moveToIndices || (moveToIndices = [])).push(update.toIndex);
|
||||
} else if (update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) {
|
||||
(removeAtIndices || (removeAtIndices = [])).push(update.fromIndex);
|
||||
} else if (update.type === ReactMultiChildUpdateTypes.INSERT_MARKUP) {
|
||||
var mountImage = update.content;
|
||||
var tag = mountImage;
|
||||
(addAtIndices || (addAtIndices = [])).push(update.toIndex);
|
||||
(addChildTags || (addChildTags = [])).push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
UIManager.manageChildren(
|
||||
containerTag,
|
||||
moveFromIndices,
|
||||
moveToIndices,
|
||||
addChildTags,
|
||||
addAtIndices,
|
||||
removeAtIndices
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Operations used to process updates to DOM nodes. This is made injectable via
|
||||
* `ReactComponent.DOMIDOperations`.
|
||||
*/
|
||||
var ReactNativeDOMIDOperations = {
|
||||
dangerouslyProcessChildrenUpdates: ReactPerf.measure(
|
||||
// FIXME(frantic): #4441289 Hack to avoid modifying react-tools
|
||||
'ReactDOMIDOperations',
|
||||
'dangerouslyProcessChildrenUpdates',
|
||||
dangerouslyProcessChildrenUpdates
|
||||
),
|
||||
|
||||
/**
|
||||
* Replaces a view that exists in the document with markup.
|
||||
*
|
||||
* @param {string} id ID of child to be replaced.
|
||||
* @param {string} markup Mount image to replace child with id.
|
||||
*/
|
||||
dangerouslyReplaceNodeWithMarkupByID: ReactPerf.measure(
|
||||
'ReactDOMIDOperations',
|
||||
'dangerouslyReplaceNodeWithMarkupByID',
|
||||
function(id, mountImage) {
|
||||
var oldTag = id;
|
||||
UIManager.replaceExistingNonRootView(oldTag, mountImage);
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
module.exports = ReactNativeDOMIDOperations;
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeDefaultInjection
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Make sure essential globals are available and are patched correctly. Please don't remove this
|
||||
* line. Bundles created by react-packager `require` it before executing any application code. This
|
||||
* ensures it exists in the dependency graph and can be `require`d.
|
||||
*/
|
||||
require('InitializeJavaScriptAppEngine');
|
||||
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var EventPluginUtils = require('EventPluginUtils');
|
||||
var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder');
|
||||
var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactComponentEnvironment = require('ReactComponentEnvironment');
|
||||
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment');
|
||||
var ReactNativeGlobalInteractionHandler = require('ReactNativeGlobalInteractionHandler');
|
||||
var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler');
|
||||
var ReactNativeTextComponent = require('ReactNativeTextComponent');
|
||||
var ReactNativeTreeTraversal = require('ReactNativeTreeTraversal');
|
||||
var ReactNativeComponent = require('ReactNativeComponent');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
var ResponderEventPlugin = require('ResponderEventPlugin');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
// Just to ensure this gets packaged, since its only caller is from Native.
|
||||
require('RCTEventEmitter');
|
||||
require('RCTLog');
|
||||
require('JSTimersExecution');
|
||||
|
||||
function inject() {
|
||||
/**
|
||||
* Inject module for resolving DOM hierarchy and plugin ordering.
|
||||
*/
|
||||
EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder);
|
||||
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
|
||||
EventPluginUtils.injection.injectTreeTraversal(ReactNativeTreeTraversal);
|
||||
|
||||
ResponderEventPlugin.injection.injectGlobalResponderHandler(
|
||||
ReactNativeGlobalResponderHandler
|
||||
);
|
||||
|
||||
ResponderEventPlugin.injection.injectGlobalInteractionHandler(
|
||||
ReactNativeGlobalInteractionHandler
|
||||
);
|
||||
|
||||
/**
|
||||
* Some important event plugins included by default (without having to require
|
||||
* them).
|
||||
*/
|
||||
EventPluginHub.injection.injectEventPluginsByName({
|
||||
'ResponderEventPlugin': ResponderEventPlugin,
|
||||
'IOSNativeBridgeEventPlugin': IOSNativeBridgeEventPlugin,
|
||||
});
|
||||
|
||||
ReactUpdates.injection.injectReconcileTransaction(
|
||||
ReactNativeComponentEnvironment.ReactReconcileTransaction
|
||||
);
|
||||
|
||||
ReactUpdates.injection.injectBatchingStrategy(
|
||||
ReactDefaultBatchingStrategy
|
||||
);
|
||||
|
||||
ReactComponentEnvironment.injection.injectEnvironment(
|
||||
ReactNativeComponentEnvironment
|
||||
);
|
||||
|
||||
var EmptyComponent = (instantiate) => {
|
||||
// Can't import View at the top because it depends on React to make its composite
|
||||
var View = require('View');
|
||||
return new ReactSimpleEmptyComponent(
|
||||
ReactElement.createElement(View, {
|
||||
collapsable: true,
|
||||
style: { position: 'absolute' },
|
||||
}),
|
||||
instantiate
|
||||
);
|
||||
};
|
||||
|
||||
ReactEmptyComponent.injection.injectEmptyComponentFactory(EmptyComponent);
|
||||
|
||||
ReactNativeComponent.injection.injectTextComponentClass(
|
||||
ReactNativeTextComponent
|
||||
);
|
||||
ReactNativeComponent.injection.injectGenericComponentClass(function(tag) {
|
||||
// Show a nicer error message for non-function tags
|
||||
var info = '';
|
||||
if (typeof tag === 'string' && /^[a-z]/.test(tag)) {
|
||||
info += ' Each component name should start with an uppercase letter.';
|
||||
}
|
||||
invariant(false, 'Expected a component class, got %s.%s', tag, info);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
inject: inject,
|
||||
};
|
|
@ -0,0 +1,215 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeEventEmitter
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var EventPluginRegistry = require('EventPluginRegistry');
|
||||
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var EventConstants = require('EventConstants');
|
||||
|
||||
var merge = require('merge');
|
||||
var warning = require('warning');
|
||||
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
|
||||
/**
|
||||
* Version of `ReactBrowserEventEmitter` that works on the receiving side of a
|
||||
* serialized worker boundary.
|
||||
*/
|
||||
|
||||
// Shared default empty native event - conserve memory.
|
||||
var EMPTY_NATIVE_EVENT = {};
|
||||
|
||||
/**
|
||||
* Selects a subsequence of `Touch`es, without destroying `touches`.
|
||||
*
|
||||
* @param {Array<Touch>} touches Deserialized touch objects.
|
||||
* @param {Array<number>} indices Indices by which to pull subsequence.
|
||||
* @return {Array<Touch>} Subsequence of touch objects.
|
||||
*/
|
||||
var touchSubsequence = function(touches, indices) {
|
||||
var ret = [];
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
ret.push(touches[indices[i]]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Pool all of this.
|
||||
*
|
||||
* Destroys `touches` by removing touch objects at indices `indices`. This is
|
||||
* to maintain compatibility with W3C touch "end" events, where the active
|
||||
* touches don't include the set that has just been "ended".
|
||||
*
|
||||
* @param {Array<Touch>} touches Deserialized touch objects.
|
||||
* @param {Array<number>} indices Indices to remove from `touches`.
|
||||
* @return {Array<Touch>} Subsequence of removed touch objects.
|
||||
*/
|
||||
var removeTouchesAtIndices = function(
|
||||
touches: Array<Object>,
|
||||
indices: Array<number>
|
||||
): Array<Object> {
|
||||
var rippedOut = [];
|
||||
// use an unsafe downcast to alias to nullable elements,
|
||||
// so we can delete and then compact.
|
||||
var temp: Array<?Object> = (touches: Array<any>);
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
var index = indices[i];
|
||||
rippedOut.push(touches[index]);
|
||||
temp[index] = null;
|
||||
}
|
||||
var fillAt = 0;
|
||||
for (var j = 0; j < temp.length; j++) {
|
||||
var cur = temp[j];
|
||||
if (cur !== null) {
|
||||
temp[fillAt++] = cur;
|
||||
}
|
||||
}
|
||||
temp.length = fillAt;
|
||||
return rippedOut;
|
||||
};
|
||||
|
||||
/**
|
||||
* `ReactNativeEventEmitter` is used to attach top-level event listeners. For example:
|
||||
*
|
||||
* ReactNativeEventEmitter.putListener('myID', 'onClick', myFunction);
|
||||
*
|
||||
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
|
||||
|
||||
registrationNames: EventPluginRegistry.registrationNameModules,
|
||||
|
||||
putListener: EventPluginHub.putListener,
|
||||
|
||||
getListener: EventPluginHub.getListener,
|
||||
|
||||
deleteListener: EventPluginHub.deleteListener,
|
||||
|
||||
deleteAllListeners: EventPluginHub.deleteAllListeners,
|
||||
|
||||
/**
|
||||
* Internal version of `receiveEvent` in terms of normalized (non-tag)
|
||||
* `rootNodeID`.
|
||||
*
|
||||
* @see receiveEvent.
|
||||
*
|
||||
* @param {rootNodeID} rootNodeID React root node ID that event occurred on.
|
||||
* @param {TopLevelType} topLevelType Top level type of event.
|
||||
* @param {object} nativeEventParam Object passed from native.
|
||||
*/
|
||||
_receiveRootNodeIDEvent: function(
|
||||
rootNodeID: number,
|
||||
topLevelType: string,
|
||||
nativeEventParam: Object
|
||||
) {
|
||||
var nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
|
||||
var inst = ReactNativeComponentTree.getInstanceFromNode(rootNodeID);
|
||||
ReactNativeEventEmitter.handleTopLevel(
|
||||
topLevelType,
|
||||
inst,
|
||||
nativeEvent,
|
||||
nativeEvent.target
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Publicly exposed method on module for native objc to invoke when a top
|
||||
* level event is extracted.
|
||||
* @param {rootNodeID} rootNodeID React root node ID that event occurred on.
|
||||
* @param {TopLevelType} topLevelType Top level type of event.
|
||||
* @param {object} nativeEventParam Object passed from native.
|
||||
*/
|
||||
receiveEvent: function(
|
||||
tag: number,
|
||||
topLevelType: string,
|
||||
nativeEventParam: Object
|
||||
) {
|
||||
var rootNodeID = tag;
|
||||
ReactNativeEventEmitter._receiveRootNodeIDEvent(
|
||||
rootNodeID,
|
||||
topLevelType,
|
||||
nativeEventParam
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Simple multi-wrapper around `receiveEvent` that is intended to receive an
|
||||
* efficient representation of `Touch` objects, and other information that
|
||||
* can be used to construct W3C compliant `Event` and `Touch` lists.
|
||||
*
|
||||
* This may create dispatch behavior that differs than web touch handling. We
|
||||
* loop through each of the changed touches and receive it as a single event.
|
||||
* So two `touchStart`/`touchMove`s that occur simultaneously are received as
|
||||
* two separate touch event dispatches - when they arguably should be one.
|
||||
*
|
||||
* This implementation reuses the `Touch` objects themselves as the `Event`s
|
||||
* since we dispatch an event for each touch (though that might not be spec
|
||||
* compliant). The main purpose of reusing them is to save allocations.
|
||||
*
|
||||
* TODO: Dispatch multiple changed touches in one event. The bubble path
|
||||
* could be the first common ancestor of all the `changedTouches`.
|
||||
*
|
||||
* One difference between this behavior and W3C spec: cancelled touches will
|
||||
* not appear in `.touches`, or in any future `.touches`, though they may
|
||||
* still be "actively touching the surface".
|
||||
*
|
||||
* Web desktop polyfills only need to construct a fake touch event with
|
||||
* identifier 0, also abandoning traditional click handlers.
|
||||
*/
|
||||
receiveTouches: function(
|
||||
eventTopLevelType: string,
|
||||
touches: Array<Object>,
|
||||
changedIndices: Array<number>
|
||||
) {
|
||||
var changedTouches =
|
||||
eventTopLevelType === topLevelTypes.topTouchEnd ||
|
||||
eventTopLevelType === topLevelTypes.topTouchCancel ?
|
||||
removeTouchesAtIndices(touches, changedIndices) :
|
||||
touchSubsequence(touches, changedIndices);
|
||||
|
||||
for (var jj = 0; jj < changedTouches.length; jj++) {
|
||||
var touch = changedTouches[jj];
|
||||
// Touch objects can fulfill the role of `DOM` `Event` objects if we set
|
||||
// the `changedTouches`/`touches`. This saves allocations.
|
||||
touch.changedTouches = changedTouches;
|
||||
touch.touches = touches;
|
||||
var nativeEvent = touch;
|
||||
var rootNodeID = null;
|
||||
var target = nativeEvent.target;
|
||||
if (target !== null && target !== undefined) {
|
||||
if (target < ReactNativeTagHandles.tagsStartAt) {
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
false,
|
||||
'A view is reporting that a touch occured on tag zero.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
rootNodeID = target;
|
||||
}
|
||||
}
|
||||
ReactNativeEventEmitter._receiveRootNodeIDEvent(
|
||||
rootNodeID,
|
||||
eventTopLevelType,
|
||||
nativeEvent
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = ReactNativeEventEmitter;
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeGlobalInteractionHandler
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var InteractionManager = require('InteractionManager');
|
||||
|
||||
// Interaction handle is created/cleared when responder is granted or
|
||||
// released/terminated.
|
||||
var interactionHandle = null;
|
||||
|
||||
var ReactNativeGlobalInteractionHandler = {
|
||||
onChange: function(numberActiveTouches: number) {
|
||||
if (numberActiveTouches === 0) {
|
||||
if (interactionHandle) {
|
||||
InteractionManager.clearInteractionHandle(interactionHandle);
|
||||
interactionHandle = null;
|
||||
}
|
||||
} else if (!interactionHandle) {
|
||||
interactionHandle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeGlobalInteractionHandler;
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeGlobalResponderHandler
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var ReactNativeGlobalResponderHandler = {
|
||||
onChange: function(from, to, blockNativeResponder) {
|
||||
if (to !== null) {
|
||||
UIManager.setJSResponder(
|
||||
to._rootNodeID,
|
||||
blockNativeResponder
|
||||
);
|
||||
} else {
|
||||
UIManager.clearJSResponder();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeGlobalResponderHandler;
|
|
@ -0,0 +1,238 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeMount
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactNativeContainerInfo = require('ReactNativeContainerInfo');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactReconciler = require('ReactReconciler');
|
||||
var ReactUpdateQueue = require('ReactUpdateQueue');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var emptyObject = require('emptyObject');
|
||||
var instantiateReactComponent = require('instantiateReactComponent');
|
||||
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
||||
|
||||
/**
|
||||
* Temporary (?) hack so that we can store all top-level pending updates on
|
||||
* composites instead of having to worry about different types of components
|
||||
* here.
|
||||
*/
|
||||
var TopLevelWrapper = function() {};
|
||||
TopLevelWrapper.prototype.isReactComponent = {};
|
||||
if (__DEV__) {
|
||||
TopLevelWrapper.displayName = 'TopLevelWrapper';
|
||||
}
|
||||
TopLevelWrapper.prototype.render = function() {
|
||||
// this.props is actually a ReactElement
|
||||
return this.props;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mounts this component and inserts it into the DOM.
|
||||
*
|
||||
* @param {ReactComponent} componentInstance The instance to mount.
|
||||
* @param {number} rootID ID of the root node.
|
||||
* @param {number} containerTag container element to mount into.
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
*/
|
||||
function mountComponentIntoNode(
|
||||
componentInstance,
|
||||
containerTag,
|
||||
transaction) {
|
||||
var markup = ReactReconciler.mountComponent(
|
||||
componentInstance,
|
||||
transaction,
|
||||
null,
|
||||
ReactNativeContainerInfo(containerTag),
|
||||
emptyObject
|
||||
);
|
||||
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
|
||||
ReactNativeMount._mountImageIntoNode(markup, containerTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batched mount.
|
||||
*
|
||||
* @param {ReactComponent} componentInstance The instance to mount.
|
||||
* @param {number} rootID ID of the root node.
|
||||
* @param {number} containerTag container element to mount into.
|
||||
*/
|
||||
function batchedMountComponentIntoNode(
|
||||
componentInstance,
|
||||
containerTag) {
|
||||
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
|
||||
transaction.perform(
|
||||
mountComponentIntoNode,
|
||||
null,
|
||||
componentInstance,
|
||||
containerTag,
|
||||
transaction
|
||||
);
|
||||
ReactUpdates.ReactReconcileTransaction.release(transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* As soon as `ReactMount` is refactored to not rely on the DOM, we can share
|
||||
* code between the two. For now, we'll hard code the ID logic.
|
||||
*/
|
||||
var ReactNativeMount = {
|
||||
_instancesByContainerID: {},
|
||||
|
||||
// these two functions are needed by React Devtools
|
||||
findNodeHandle: require('findNodeHandle'),
|
||||
|
||||
/**
|
||||
* @param {ReactComponent} instance Instance to render.
|
||||
* @param {containerTag} containerView Handle to native view tag
|
||||
*/
|
||||
renderComponent: function(
|
||||
nextElement: ReactElement,
|
||||
containerTag: number,
|
||||
callback?: ?(() => void)
|
||||
): ?ReactComponent {
|
||||
var nextWrappedElement = new ReactElement(
|
||||
TopLevelWrapper,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
nextElement
|
||||
);
|
||||
|
||||
var topRootNodeID = containerTag;
|
||||
var prevComponent = ReactNativeMount._instancesByContainerID[topRootNodeID];
|
||||
if (prevComponent) {
|
||||
var prevWrappedElement = prevComponent._currentElement;
|
||||
var prevElement = prevWrappedElement.props;
|
||||
if (shouldUpdateReactComponent(prevElement, nextElement)) {
|
||||
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement);
|
||||
if (callback) {
|
||||
ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
|
||||
}
|
||||
return prevComponent;
|
||||
} else {
|
||||
ReactNativeMount.unmountComponentAtNode(containerTag);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) {
|
||||
console.error('You cannot render into anything but a top root');
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactNativeTagHandles.assertRootTag(containerTag);
|
||||
|
||||
var instance = instantiateReactComponent(nextWrappedElement);
|
||||
ReactNativeMount._instancesByContainerID[containerTag] = instance;
|
||||
|
||||
// The initial render is synchronous but any updates that happen during
|
||||
// rendering, in componentWillMount or componentDidMount, will be batched
|
||||
// according to the current batching strategy.
|
||||
|
||||
ReactUpdates.batchedUpdates(
|
||||
batchedMountComponentIntoNode,
|
||||
instance,
|
||||
containerTag
|
||||
);
|
||||
var component = instance.getPublicInstance();
|
||||
if (callback) {
|
||||
callback.call(component);
|
||||
}
|
||||
return component;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {View} view View tree image.
|
||||
* @param {number} containerViewID View to insert sub-view into.
|
||||
*/
|
||||
_mountImageIntoNode: ReactPerf.measure(
|
||||
// FIXME(frantic): #4441289 Hack to avoid modifying react-tools
|
||||
'ReactComponentBrowserEnvironment',
|
||||
'mountImageIntoNode',
|
||||
function(mountImage, containerID) {
|
||||
// Since we now know that the `mountImage` has been mounted, we can
|
||||
// mark it as such.
|
||||
var childTag = mountImage;
|
||||
UIManager.setChildren(
|
||||
containerID,
|
||||
[childTag]
|
||||
);
|
||||
}
|
||||
),
|
||||
|
||||
/**
|
||||
* Standard unmounting of the component that is rendered into `containerID`,
|
||||
* but will also execute a command to remove the actual container view
|
||||
* itself. This is useful when a client is cleaning up a React tree, and also
|
||||
* knows that the container will no longer be needed. When executing
|
||||
* asynchronously, it's easier to just have this method be the one that calls
|
||||
* for removal of the view.
|
||||
*/
|
||||
unmountComponentAtNodeAndRemoveContainer: function(
|
||||
containerTag: number
|
||||
) {
|
||||
ReactNativeMount.unmountComponentAtNode(containerTag);
|
||||
// call back into native to remove all of the subviews from this container
|
||||
UIManager.removeRootView(containerTag);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmount component at container ID by iterating through each child component
|
||||
* that has been rendered and unmounting it. There should just be one child
|
||||
* component at this time.
|
||||
*/
|
||||
unmountComponentAtNode: function(containerTag: number): boolean {
|
||||
if (!ReactNativeTagHandles.reactTagIsNativeTopRootID(containerTag)) {
|
||||
console.error('You cannot render into anything but a top root');
|
||||
return false;
|
||||
}
|
||||
|
||||
var instance = ReactNativeMount._instancesByContainerID[containerTag];
|
||||
if (!instance) {
|
||||
return false;
|
||||
}
|
||||
ReactNativeMount.unmountComponentFromNode(instance, containerTag);
|
||||
delete ReactNativeMount._instancesByContainerID[containerTag];
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmounts a component and sends messages back to iOS to remove its subviews.
|
||||
*
|
||||
* @param {ReactComponent} instance React component instance.
|
||||
* @param {string} containerID ID of container we're removing from.
|
||||
* @final
|
||||
* @internal
|
||||
* @see {ReactNativeMount.unmountComponentAtNode}
|
||||
*/
|
||||
unmountComponentFromNode: function(
|
||||
instance: ReactComponent,
|
||||
containerID: string
|
||||
) {
|
||||
// Call back into native to remove all of the subviews from this container
|
||||
ReactReconciler.unmountComponent(instance);
|
||||
UIManager.removeSubviewsFromContainerWithID(containerID);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
ReactNativeMount.renderComponent = ReactPerf.measure(
|
||||
'ReactMount',
|
||||
'_renderNewRootComponent',
|
||||
ReactNativeMount.renderComponent
|
||||
);
|
||||
|
||||
module.exports = ReactNativeMount;
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativePropRegistry
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var objects = {};
|
||||
var uniqueID = 1;
|
||||
var emptyObject = {};
|
||||
|
||||
class ReactNativePropRegistry {
|
||||
static register(object: Object): number {
|
||||
var id = ++uniqueID;
|
||||
if (__DEV__) {
|
||||
Object.freeze(object);
|
||||
}
|
||||
objects[id] = object;
|
||||
return id;
|
||||
}
|
||||
|
||||
static getByID(id: number): Object {
|
||||
if (!id) {
|
||||
// Used in the style={[condition && id]} pattern,
|
||||
// we want it to be a no-op when the value is false or null
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
var object = objects[id];
|
||||
if (!object) {
|
||||
console.warn('Invalid style with id `' + id + '`. Skipping ...');
|
||||
return emptyObject;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReactNativePropRegistry;
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeReconcileTransaction
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var CallbackQueue = require('CallbackQueue');
|
||||
var PooledClass = require('PooledClass');
|
||||
var Transaction = require('Transaction');
|
||||
|
||||
/**
|
||||
* Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during
|
||||
* the performing of the transaction.
|
||||
*/
|
||||
var ON_DOM_READY_QUEUEING = {
|
||||
/**
|
||||
* Initializes the internal `onDOMReady` queue.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.reactMountReady.reset();
|
||||
},
|
||||
|
||||
/**
|
||||
* After DOM is flushed, invoke all registered `onDOMReady` callbacks.
|
||||
*/
|
||||
close: function() {
|
||||
this.reactMountReady.notifyAll();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Executed within the scope of the `Transaction` instance. Consider these as
|
||||
* being member methods, but with an implied ordering while being isolated from
|
||||
* each other.
|
||||
*/
|
||||
var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING];
|
||||
|
||||
/**
|
||||
* Currently:
|
||||
* - The order that these are listed in the transaction is critical:
|
||||
* - Suppresses events.
|
||||
* - Restores selection range.
|
||||
*
|
||||
* Future:
|
||||
* - Restore document/overflow scroll positions that were unintentionally
|
||||
* modified via DOM insertions above the top viewport boundary.
|
||||
* - Implement/integrate with customized constraint based layout system and keep
|
||||
* track of which dimensions must be remeasured.
|
||||
*
|
||||
* @class ReactNativeReconcileTransaction
|
||||
*/
|
||||
function ReactNativeReconcileTransaction() {
|
||||
this.reinitializeTransaction();
|
||||
this.reactMountReady = CallbackQueue.getPooled(null);
|
||||
}
|
||||
|
||||
var Mixin = {
|
||||
/**
|
||||
* @see Transaction
|
||||
* @abstract
|
||||
* @final
|
||||
* @return {array<object>} List of operation wrap procedures.
|
||||
* TODO: convert to array<TransactionWrapper>
|
||||
*/
|
||||
getTransactionWrappers: function() {
|
||||
return TRANSACTION_WRAPPERS;
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {object} The queue to collect `onDOMReady` callbacks with.
|
||||
* TODO: convert to ReactMountReady
|
||||
*/
|
||||
getReactMountReady: function() {
|
||||
return this.reactMountReady;
|
||||
},
|
||||
|
||||
/**
|
||||
* `PooledClass` looks for this, and will invoke this before allowing this
|
||||
* instance to be reused.
|
||||
*/
|
||||
destructor: function() {
|
||||
CallbackQueue.release(this.reactMountReady);
|
||||
this.reactMountReady = null;
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(
|
||||
ReactNativeReconcileTransaction.prototype,
|
||||
Transaction.Mixin,
|
||||
ReactNativeReconcileTransaction,
|
||||
Mixin
|
||||
);
|
||||
|
||||
PooledClass.addPoolingTo(ReactNativeReconcileTransaction);
|
||||
|
||||
module.exports = ReactNativeReconcileTransaction;
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeTagHandles
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Keeps track of allocating and associating native "tags" which are numeric,
|
||||
* unique view IDs. All the native tags are negative numbers, to avoid
|
||||
* collisions, but in the JS we keep track of them as positive integers to store
|
||||
* them effectively in Arrays. So we must refer to them as "inverses" of the
|
||||
* native tags (that are * normally negative).
|
||||
*
|
||||
* It *must* be the case that every `rootNodeID` always maps to the exact same
|
||||
* `tag` forever. The easiest way to accomplish this is to never delete
|
||||
* anything from this table.
|
||||
* Why: Because `dangerouslyReplaceNodeWithMarkupByID` relies on being able to
|
||||
* unmount a component with a `rootNodeID`, then mount a new one in its place,
|
||||
*/
|
||||
var INITIAL_TAG_COUNT = 1;
|
||||
var ReactNativeTagHandles = {
|
||||
tagsStartAt: INITIAL_TAG_COUNT,
|
||||
tagCount: INITIAL_TAG_COUNT,
|
||||
|
||||
allocateTag: function(): number {
|
||||
// Skip over root IDs as those are reserved for native
|
||||
while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) {
|
||||
ReactNativeTagHandles.tagCount++;
|
||||
}
|
||||
var tag = ReactNativeTagHandles.tagCount;
|
||||
ReactNativeTagHandles.tagCount++;
|
||||
return tag;
|
||||
},
|
||||
|
||||
assertRootTag: function(tag: number): void {
|
||||
invariant(
|
||||
this.reactTagIsNativeTopRootID(tag),
|
||||
'Expect a native root tag, instead got %s', tag
|
||||
);
|
||||
},
|
||||
|
||||
reactTagIsNativeTopRootID: function(reactTag: number): bool {
|
||||
// We reserve all tags that are 1 mod 10 for native root views
|
||||
return reactTag % 10 === 1;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeTagHandles;
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeTextComponent
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
var UIManager = require('UIManager');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var ReactNativeTextComponent = function(text) {
|
||||
// This is really a ReactText (ReactNode), not a ReactElement
|
||||
this._currentElement = text;
|
||||
this._stringText = '' + text;
|
||||
this._nativeParent = null;
|
||||
this._rootNodeID = null;
|
||||
};
|
||||
|
||||
Object.assign(ReactNativeTextComponent.prototype, {
|
||||
|
||||
mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) {
|
||||
// TODO: nativeParent should have this context already. Stop abusing context.
|
||||
invariant(
|
||||
context.isInAParentText,
|
||||
'RawText "%s" must be wrapped in an explicit <Text> component.',
|
||||
this._stringText
|
||||
);
|
||||
this._nativeParent = nativeParent;
|
||||
var tag = ReactNativeTagHandles.allocateTag();
|
||||
this._rootNodeID = tag;
|
||||
var nativeTopRootTag = nativeContainerInfo._tag;
|
||||
UIManager.createView(
|
||||
tag,
|
||||
'RCTRawText',
|
||||
nativeTopRootTag,
|
||||
{text: this._stringText}
|
||||
);
|
||||
|
||||
ReactNativeComponentTree.precacheNode(this, tag);
|
||||
|
||||
return tag;
|
||||
},
|
||||
|
||||
getNativeNode: function() {
|
||||
return this._rootNodeID;
|
||||
},
|
||||
|
||||
receiveComponent: function(nextText, transaction, context) {
|
||||
if (nextText !== this._currentElement) {
|
||||
this._currentElement = nextText;
|
||||
var nextStringText = '' + nextText;
|
||||
if (nextStringText !== this._stringText) {
|
||||
this._stringText = nextStringText;
|
||||
UIManager.updateView(
|
||||
this._rootNodeID,
|
||||
'RCTRawText',
|
||||
{text: this._stringText}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unmountComponent: function() {
|
||||
ReactNativeComponentTree.uncacheNode(this);
|
||||
this._currentElement = null;
|
||||
this._stringText = null;
|
||||
this._rootNodeID = null;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.exports = ReactNativeTextComponent;
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* Copyright 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeTreeTraversal
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Same as ReactDOMTreeTraversal without the invariants.
|
||||
|
||||
/**
|
||||
* Return the lowest common ancestor of A and B, or null if they are in
|
||||
* different trees.
|
||||
*/
|
||||
function getLowestCommonAncestor(instA, instB) {
|
||||
var depthA = 0;
|
||||
for (var tempA = instA; tempA; tempA = tempA._nativeParent) {
|
||||
depthA++;
|
||||
}
|
||||
var depthB = 0;
|
||||
for (var tempB = instB; tempB; tempB = tempB._nativeParent) {
|
||||
depthB++;
|
||||
}
|
||||
|
||||
// If A is deeper, crawl up.
|
||||
while (depthA - depthB > 0) {
|
||||
instA = instA._nativeParent;
|
||||
depthA--;
|
||||
}
|
||||
|
||||
// If B is deeper, crawl up.
|
||||
while (depthB - depthA > 0) {
|
||||
instB = instB._nativeParent;
|
||||
depthB--;
|
||||
}
|
||||
|
||||
// Walk in lockstep until we find a match.
|
||||
var depth = depthA;
|
||||
while (depth--) {
|
||||
if (instA === instB) {
|
||||
return instA;
|
||||
}
|
||||
instA = instA._nativeParent;
|
||||
instB = instB._nativeParent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if A is an ancestor of B.
|
||||
*/
|
||||
function isAncestor(instA, instB) {
|
||||
while (instB) {
|
||||
if (instB === instA) {
|
||||
return true;
|
||||
}
|
||||
instB = instB._nativeParent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent instance of the passed-in instance.
|
||||
*/
|
||||
function getParentInstance(inst) {
|
||||
return inst._nativeParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
|
||||
*/
|
||||
function traverseTwoPhase(inst, fn, arg) {
|
||||
var path = [];
|
||||
while (inst) {
|
||||
path.push(inst);
|
||||
inst = inst._nativeParent;
|
||||
}
|
||||
var i;
|
||||
for (i = path.length; i-- > 0;) {
|
||||
fn(path[i], false, arg);
|
||||
}
|
||||
for (i = 0; i < path.length; i++) {
|
||||
fn(path[i], true, arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
|
||||
* should would receive a `mouseEnter` or `mouseLeave` event.
|
||||
*
|
||||
* Does not invoke the callback on the nearest common ancestor because nothing
|
||||
* "entered" or "left" that element.
|
||||
*/
|
||||
function traverseEnterLeave(from, to, fn, argFrom, argTo) {
|
||||
var common = from && to ? getLowestCommonAncestor(from, to) : null;
|
||||
var pathFrom = [];
|
||||
while (from && from !== common) {
|
||||
pathFrom.push(from);
|
||||
from = from._nativeParent;
|
||||
}
|
||||
var pathTo = [];
|
||||
while (to && to !== common) {
|
||||
pathTo.push(to);
|
||||
to = to._nativeParent;
|
||||
}
|
||||
var i;
|
||||
for (i = 0; i < pathFrom.length; i++) {
|
||||
fn(pathFrom[i], true, argFrom);
|
||||
}
|
||||
for (i = pathTo.length; i-- > 0;) {
|
||||
fn(pathTo[i], false, argTo);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAncestor: isAncestor,
|
||||
getLowestCommonAncestor: getLowestCommonAncestor,
|
||||
getParentInstance: getParentInstance,
|
||||
traverseTwoPhase: traverseTwoPhase,
|
||||
traverseEnterLeave: traverseEnterLeave,
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Noop
|
||||
|
||||
// TODO: Move all initialization callers back into react-native
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO: Figure out a way to drop this dependency
|
||||
|
||||
var InteractionManager = {};
|
||||
|
||||
module.exports = InteractionManager;
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Noop
|
||||
|
||||
// TODO: Move all initialization callers back into react-native
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Mock of the Native Hooks
|
||||
|
||||
var Platform = {};
|
||||
|
||||
module.exports = Platform;
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Noop
|
||||
|
||||
// TODO: Move all initialization callers back into react-native
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Noop
|
||||
|
||||
// TODO: Move all initialization callers back into react-native
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Mock of the Native Hooks
|
||||
// TODO: Should this move into the components themselves? E.g. focusable
|
||||
|
||||
var TextInputState = {};
|
||||
|
||||
module.exports = TextInputState;
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Mock of the Native Hooks
|
||||
|
||||
var RCTUIManager = {
|
||||
createView: jest.genMockFunction(),
|
||||
setChildren: jest.genMockFunction(),
|
||||
manageChildren: jest.genMockFunction(),
|
||||
updateView: jest.genMockFunction(),
|
||||
};
|
||||
|
||||
module.exports = RCTUIManager;
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO: Move deepDiffer into react
|
||||
|
||||
var deepDiffer = function(one: any, two: any): bool {
|
||||
if (one === two) {
|
||||
// Short circuit on identical object references instead of traversing them.
|
||||
return false;
|
||||
}
|
||||
if ((typeof one === 'function') && (typeof two === 'function')) {
|
||||
// We consider all functions equal
|
||||
return false;
|
||||
}
|
||||
if ((typeof one !== 'object') || (one === null)) {
|
||||
// Primitives can be directly compared
|
||||
return one !== two;
|
||||
}
|
||||
if ((typeof two !== 'object') || (two === null)) {
|
||||
// We know they are different because the previous case would have triggered
|
||||
// otherwise.
|
||||
return true;
|
||||
}
|
||||
if (one.constructor !== two.constructor) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(one)) {
|
||||
// We know two is also an array because the constructors are equal
|
||||
var len = one.length;
|
||||
if (two.length !== len) {
|
||||
return true;
|
||||
}
|
||||
for (var ii = 0; ii < len; ii++) {
|
||||
if (deepDiffer(one[ii], two[ii])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var key in one) {
|
||||
if (deepDiffer(one[key], two[key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (var twoKey in two) {
|
||||
// The only case we haven't checked yet is keys that are in two but aren't
|
||||
// in one, which means they are different.
|
||||
if (one[twoKey] === undefined && two[twoKey] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = deepDiffer;
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO: move into react or fbjs
|
||||
|
||||
var deepFreezeAndThrowOnMutationInDev = function() { };
|
||||
|
||||
module.exports = deepFreezeAndThrowOnMutationInDev;
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO: Move flattenStyle into react
|
||||
|
||||
var flattenStyle = function() { };
|
||||
|
||||
module.exports = flattenStyle;
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO: Replace all callers with spread
|
||||
|
||||
var merge = function(a, b) {
|
||||
return {...a, ...b};
|
||||
};
|
||||
|
||||
module.exports = merge;
|
|
@ -0,0 +1,246 @@
|
|||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.dontMock('ReactNativeAttributePayload');
|
||||
jest.dontMock('ReactNativePropRegistry');
|
||||
// jest.dontMock('deepDiffer');
|
||||
// jest.dontMock('flattenStyle');
|
||||
|
||||
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
var ReactNativePropRegistry = require('ReactNativePropRegistry');
|
||||
|
||||
var diff = ReactNativeAttributePayload.diff;
|
||||
|
||||
describe('ReactNativeAttributePayload', function() {
|
||||
|
||||
it('should work with simple example', () => {
|
||||
expect(diff(
|
||||
{a: 1, c: 3},
|
||||
{b: 2, c: 3},
|
||||
{a: true, b: true}
|
||||
)).toEqual({a: null, b: 2});
|
||||
});
|
||||
|
||||
it('should skip fields that are equal', () => {
|
||||
expect(diff(
|
||||
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
|
||||
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
|
||||
{a: true, b: true, c: true, d: true, e: true, f: true}
|
||||
)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should remove fields', () => {
|
||||
expect(diff(
|
||||
{a: 1},
|
||||
{},
|
||||
{a: true}
|
||||
)).toEqual({a: null});
|
||||
});
|
||||
|
||||
it('should remove fields that are set to undefined', () => {
|
||||
expect(diff(
|
||||
{a: 1},
|
||||
{a: undefined},
|
||||
{a: true}
|
||||
)).toEqual({a: null});
|
||||
});
|
||||
|
||||
it('should ignore invalid fields', () => {
|
||||
expect(diff(
|
||||
{a: 1},
|
||||
{b: 2},
|
||||
{}
|
||||
)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should use the diff attribute', () => {
|
||||
var diffA = jest.genMockFunction().mockImpl((a, b) => true);
|
||||
var diffB = jest.genMockFunction().mockImpl((a, b) => false);
|
||||
expect(diff(
|
||||
{a: [1], b: [3]},
|
||||
{a: [2], b: [4]},
|
||||
{a: {diff: diffA}, b: {diff: diffB}}
|
||||
)).toEqual({a: [2]});
|
||||
expect(diffA).toBeCalledWith([1], [2]);
|
||||
expect(diffB).toBeCalledWith([3], [4]);
|
||||
});
|
||||
|
||||
it('should not use the diff attribute on addition/removal', () => {
|
||||
var diffA = jest.genMockFunction();
|
||||
var diffB = jest.genMockFunction();
|
||||
expect(diff(
|
||||
{a: [1]},
|
||||
{b: [2]},
|
||||
{a: {diff: diffA}, b: {diff: diffB}}
|
||||
)).toEqual({a: null, b: [2]});
|
||||
expect(diffA).not.toBeCalled();
|
||||
expect(diffB).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should do deep diffs of Objects by default', () => {
|
||||
expect(diff(
|
||||
{a: [1], b: {k: [3, 4]}, c: {k: [4, 4]} },
|
||||
{a: [2], b: {k: [3, 4]}, c: {k: [4, 5]} },
|
||||
{a: true, b: true, c: true}
|
||||
)).toEqual({a: [2], c: {k: [4, 5]}});
|
||||
});
|
||||
|
||||
it('should work with undefined styles', () => {
|
||||
expect(diff(
|
||||
{ style: { a: '#ffffff', b: 1 } },
|
||||
{ style: undefined },
|
||||
{ style: { b: true } }
|
||||
)).toEqual({ b: null });
|
||||
expect(diff(
|
||||
{ style: undefined },
|
||||
{ style: { a: '#ffffff', b: 1 } },
|
||||
{ style: { b: true } }
|
||||
)).toEqual({ b: 1 });
|
||||
expect(diff(
|
||||
{ style: undefined },
|
||||
{ style: undefined },
|
||||
{ style: { b: true } }
|
||||
)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should work with empty styles', () => {
|
||||
expect(diff(
|
||||
{a: 1, c: 3},
|
||||
{},
|
||||
{a: true, b: true}
|
||||
)).toEqual({a: null});
|
||||
expect(diff(
|
||||
{},
|
||||
{a: 1, c: 3},
|
||||
{a: true, b: true}
|
||||
)).toEqual({a: 1});
|
||||
expect(diff(
|
||||
{},
|
||||
{},
|
||||
{a: true, b: true}
|
||||
)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should flatten nested styles and predefined styles', () => {
|
||||
var validStyleAttribute = { someStyle: { foo: true, bar: true } };
|
||||
|
||||
expect(diff(
|
||||
{},
|
||||
{ someStyle: [{ foo: 1 }, { bar: 2 }]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: 1, bar: 2 });
|
||||
|
||||
expect(diff(
|
||||
{ someStyle: [{ foo: 1 }, { bar: 2 }]},
|
||||
{},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: null, bar: null });
|
||||
|
||||
var barStyle = ReactNativePropRegistry.register({
|
||||
bar: 3,
|
||||
});
|
||||
|
||||
expect(diff(
|
||||
{},
|
||||
{ someStyle: [[{ foo: 1 }, { foo: 2 }], barStyle]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: 2, bar: 3 });
|
||||
});
|
||||
|
||||
it('should reset a value to a previous if it is removed', () => {
|
||||
var validStyleAttribute = { someStyle: { foo: true, bar: true } };
|
||||
|
||||
expect(diff(
|
||||
{ someStyle: [{ foo: 1 }, { foo: 3 }]},
|
||||
{ someStyle: [{ foo: 1 }, { bar: 2 }]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: 1, bar: 2 });
|
||||
});
|
||||
|
||||
it('should not clear removed props if they are still in another slot', () => {
|
||||
var validStyleAttribute = { someStyle: { foo: true, bar: true } };
|
||||
|
||||
expect(diff(
|
||||
{ someStyle: [{}, { foo: 3, bar: 2 }]},
|
||||
{ someStyle: [{ foo: 3 }, { bar: 2 }]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: 3 }); // this should ideally be null. heuristic tradeoff.
|
||||
|
||||
expect(diff(
|
||||
{ someStyle: [{}, { foo: 3, bar: 2 }]},
|
||||
{ someStyle: [{ foo: 1, bar: 1 }, { bar: 2 }]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ bar: 2, foo: 1 });
|
||||
});
|
||||
|
||||
it('should clear a prop if a later style is explicit null/undefined', () => {
|
||||
var validStyleAttribute = { someStyle: { foo: true, bar: true } };
|
||||
expect(diff(
|
||||
{ someStyle: [{}, { foo: 3, bar: 2 }]},
|
||||
{ someStyle: [{ foo: 1 }, { bar: 2, foo: null }]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: null });
|
||||
|
||||
expect(diff(
|
||||
{ someStyle: [{ foo: 3 }, { foo: null, bar: 2 }]},
|
||||
{ someStyle: [{ foo: null }, { bar: 2 }]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: null });
|
||||
|
||||
expect(diff(
|
||||
{ someStyle: [{ foo: 1 }, { foo: null }]},
|
||||
{ someStyle: [{ foo: 2 }, { foo: null }]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: null }); // this should ideally be null. heuristic.
|
||||
|
||||
// Test the same case with object equality because an early bailout doesn't
|
||||
// work in this case.
|
||||
var fooObj = { foo: 3 };
|
||||
expect(diff(
|
||||
{ someStyle: [{ foo: 1 }, fooObj]},
|
||||
{ someStyle: [{ foo: 2 }, fooObj]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: 3 }); // this should ideally be null. heuristic.
|
||||
|
||||
expect(diff(
|
||||
{ someStyle: [{ foo: 1 }, { foo: 3 }]},
|
||||
{ someStyle: [{ foo: 2 }, { foo: undefined }]},
|
||||
validStyleAttribute
|
||||
)).toEqual({ foo: null }); // this should ideally be null. heuristic.
|
||||
});
|
||||
|
||||
// Function properties are just markers to native that events should be sent.
|
||||
it('should convert functions to booleans', () => {
|
||||
// Note that if the property changes from one function to another, we don't
|
||||
// need to send an update.
|
||||
expect(diff(
|
||||
{
|
||||
a: function() {
|
||||
return 1;
|
||||
},
|
||||
b: function() {
|
||||
return 2;
|
||||
},
|
||||
c: 3,
|
||||
},
|
||||
{
|
||||
b: function() {
|
||||
return 9;
|
||||
},
|
||||
c: function() {
|
||||
return 3;
|
||||
},
|
||||
},
|
||||
{a: true, b: true, c: true}
|
||||
)).toEqual({a: null, c: true});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNative;
|
||||
var createReactNativeComponentClass;
|
||||
var UIManager;
|
||||
|
||||
describe('ReactNative', function() {
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactNative = require('ReactNative');
|
||||
UIManager = require('UIManager');
|
||||
createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
});
|
||||
|
||||
it('should be able to create and render a native component', function() {
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: { foo: true },
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
ReactNative.render(<View foo="test" />, 1);
|
||||
expect(UIManager.createView).toBeCalled();
|
||||
expect(UIManager.setChildren).toBeCalled();
|
||||
expect(UIManager.manageChildren).not.toBeCalled();
|
||||
expect(UIManager.updateView).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should be able to create and update a native component', function() {
|
||||
var View = createReactNativeComponentClass({
|
||||
validAttributes: { foo: true },
|
||||
uiViewClassName: 'View',
|
||||
});
|
||||
|
||||
ReactNative.render(<View foo="foo" />, 11);
|
||||
|
||||
expect(UIManager.createView.mock.calls.length).toBe(2);
|
||||
expect(UIManager.setChildren.mock.calls.length).toBe(2);
|
||||
expect(UIManager.manageChildren).not.toBeCalled();
|
||||
expect(UIManager.updateView).not.toBeCalled();
|
||||
|
||||
ReactNative.render(<View foo="bar" />, 11);
|
||||
|
||||
expect(UIManager.createView.mock.calls.length).toBe(2);
|
||||
expect(UIManager.setChildren.mock.calls.length).toBe(2);
|
||||
expect(UIManager.manageChildren).not.toBeCalled();
|
||||
expect(UIManager.updateView).toBeCalledWith(3, 'View', { foo: 'bar' });
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule createReactNativeComponentClass
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactNativeBaseComponent = require('ReactNativeBaseComponent');
|
||||
|
||||
// See also ReactNativeBaseComponent
|
||||
type ReactNativeBaseComponentViewConfig = {
|
||||
validAttributes: Object;
|
||||
uiViewClassName: string;
|
||||
propTypes?: Object,
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} config iOS View configuration.
|
||||
* @private
|
||||
*/
|
||||
var createReactNativeComponentClass = function(
|
||||
viewConfig: ReactNativeBaseComponentViewConfig
|
||||
): ReactClass<any> {
|
||||
var Constructor = function(element) {
|
||||
this._currentElement = element;
|
||||
this._topLevelWrapper = null;
|
||||
this._nativeParent = null;
|
||||
this._nativeContainerInfo = null;
|
||||
this._rootNodeID = null;
|
||||
this._renderedChildren = null;
|
||||
};
|
||||
Constructor.displayName = viewConfig.uiViewClassName;
|
||||
Constructor.viewConfig = viewConfig;
|
||||
Constructor.propTypes = viewConfig.propTypes;
|
||||
Constructor.prototype = new ReactNativeBaseComponent(viewConfig);
|
||||
Constructor.prototype.constructor = Constructor;
|
||||
|
||||
return ((Constructor: any): ReactClass);
|
||||
};
|
||||
|
||||
module.exports = createReactNativeComponentClass;
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule findNodeHandle
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var warning = require('warning');
|
||||
|
||||
/**
|
||||
* ReactNative vs ReactWeb
|
||||
* -----------------------
|
||||
* React treats some pieces of data opaquely. This means that the information
|
||||
* is first class (it can be passed around), but cannot be inspected. This
|
||||
* allows us to build infrastructure that reasons about resources, without
|
||||
* making assumptions about the nature of those resources, and this allows that
|
||||
* infra to be shared across multiple platforms, where the resources are very
|
||||
* different. General infra (such as `ReactMultiChild`) reasons opaquely about
|
||||
* the data, but platform specific code (such as `ReactNativeBaseComponent`) can
|
||||
* make assumptions about the data.
|
||||
*
|
||||
*
|
||||
* `rootNodeID`, uniquely identifies a position in the generated native view
|
||||
* tree. Many layers of composite components (created with `React.createClass`)
|
||||
* can all share the same `rootNodeID`.
|
||||
*
|
||||
* `nodeHandle`: A sufficiently unambiguous way to refer to a lower level
|
||||
* resource (dom node, native view etc). The `rootNodeID` is sufficient for web
|
||||
* `nodeHandle`s, because the position in a tree is always enough to uniquely
|
||||
* identify a DOM node (we never have nodes in some bank outside of the
|
||||
* document). The same would be true for `ReactNative`, but we must maintain a
|
||||
* mapping that we can send efficiently serializable
|
||||
* strings across native boundaries.
|
||||
*
|
||||
* Opaque name TodaysWebReact FutureWebWorkerReact ReactNative
|
||||
* ----------------------------------------------------------------------------
|
||||
* nodeHandle N/A rootNodeID tag
|
||||
*/
|
||||
|
||||
function findNodeHandle(componentOrHandle: any): ?number {
|
||||
if (__DEV__) {
|
||||
var owner = ReactCurrentOwner.current;
|
||||
if (owner !== null) {
|
||||
warning(
|
||||
owner._warnedAboutRefsInRender,
|
||||
'%s is accessing findNodeHandle inside its render(). ' +
|
||||
'render() should be a pure function of props and state. It should ' +
|
||||
'never access something that requires stale data from the previous ' +
|
||||
'render, such as refs. Move this logic to componentDidMount and ' +
|
||||
'componentDidUpdate instead.',
|
||||
owner.getName() || 'A component'
|
||||
);
|
||||
owner._warnedAboutRefsInRender = true;
|
||||
}
|
||||
}
|
||||
if (componentOrHandle == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof componentOrHandle === 'number') {
|
||||
// Already a node handle
|
||||
return componentOrHandle;
|
||||
}
|
||||
|
||||
var component = componentOrHandle;
|
||||
|
||||
// TODO (balpert): Wrap iOS native components in a composite wrapper, then
|
||||
// ReactInstanceMap.get here will always succeed for mounted components
|
||||
var internalInstance = ReactInstanceMap.get(component);
|
||||
if (internalInstance) {
|
||||
return internalInstance.getNativeNode();
|
||||
} else {
|
||||
var rootNodeID = component._rootNodeID;
|
||||
if (rootNodeID) {
|
||||
return rootNodeID;
|
||||
} else {
|
||||
invariant(
|
||||
(
|
||||
// Native
|
||||
typeof component === 'object' &&
|
||||
'_rootNodeID' in component
|
||||
) || (
|
||||
// Composite
|
||||
component.render != null &&
|
||||
typeof component.render === 'function'
|
||||
),
|
||||
'findNodeHandle(...): Argument is not a component ' +
|
||||
'(type: %s, keys: %s)',
|
||||
typeof component,
|
||||
Object.keys(component)
|
||||
);
|
||||
invariant(
|
||||
false,
|
||||
'findNodeHandle(...): Unable to find node handle for unmounted ' +
|
||||
'component.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = findNodeHandle;
|
|
@ -204,7 +204,7 @@ function executeDirectDispatch(event) {
|
|||
!Array.isArray(dispatchListener),
|
||||
'executeDirectDispatch(...): Invalid `event`.'
|
||||
);
|
||||
event.currentTarget = EventPluginUtils.getNodeFromInstance(dispatchInstance);
|
||||
event.currentTarget = dispatchListener ? EventPluginUtils.getNodeFromInstance(dispatchInstance) : null;
|
||||
var res = dispatchListener ? dispatchListener(event) : null;
|
||||
event.currentTarget = null;
|
||||
event._dispatchListeners = null;
|
||||
|
|
|
@ -0,0 +1,379 @@
|
|||
/**
|
||||
* @providesModule PanResponder
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var TouchHistoryMath = require('TouchHistoryMath');
|
||||
|
||||
var currentCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
|
||||
var currentCentroidYOfTouchesChangedAfter =
|
||||
TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
|
||||
var previousCentroidXOfTouchesChangedAfter =
|
||||
TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
|
||||
var previousCentroidYOfTouchesChangedAfter =
|
||||
TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
|
||||
var currentCentroidX = TouchHistoryMath.currentCentroidX;
|
||||
var currentCentroidY = TouchHistoryMath.currentCentroidY;
|
||||
|
||||
/**
|
||||
* `PanResponder` reconciles several touches into a single gesture. It makes
|
||||
* single-touch gestures resilient to extra touches, and can be used to
|
||||
* recognize simple multi-touch gestures.
|
||||
*
|
||||
* It provides a predictable wrapper of the responder handlers provided by the
|
||||
* [gesture responder system](docs/gesture-responder-system.html).
|
||||
* For each handler, it provides a new `gestureState` object alongside the
|
||||
* native event object:
|
||||
*
|
||||
* ```
|
||||
* onPanResponderMove: (event, gestureState) => {}
|
||||
* ```
|
||||
*
|
||||
* A native event is a synthetic touch event with the following form:
|
||||
*
|
||||
* - `nativeEvent`
|
||||
* + `changedTouches` - Array of all touch events that have changed since the last event
|
||||
* + `identifier` - The ID of the touch
|
||||
* + `locationX` - The X position of the touch, relative to the element
|
||||
* + `locationY` - The Y position of the touch, relative to the element
|
||||
* + `pageX` - The X position of the touch, relative to the root element
|
||||
* + `pageY` - The Y position of the touch, relative to the root element
|
||||
* + `target` - The node id of the element receiving the touch event
|
||||
* + `timestamp` - A time identifier for the touch, useful for velocity calculation
|
||||
* + `touches` - Array of all current touches on the screen
|
||||
*
|
||||
* A `gestureState` object has the following:
|
||||
*
|
||||
* - `stateID` - ID of the gestureState- persisted as long as there at least
|
||||
* one touch on screen
|
||||
* - `moveX` - the latest screen coordinates of the recently-moved touch
|
||||
* - `moveY` - the latest screen coordinates of the recently-moved touch
|
||||
* - `x0` - the screen coordinates of the responder grant
|
||||
* - `y0` - the screen coordinates of the responder grant
|
||||
* - `dx` - accumulated distance of the gesture since the touch started
|
||||
* - `dy` - accumulated distance of the gesture since the touch started
|
||||
* - `vx` - current velocity of the gesture
|
||||
* - `vy` - current velocity of the gesture
|
||||
* - `numberActiveTouches` - Number of touches currently on screen
|
||||
*
|
||||
* ### Basic Usage
|
||||
*
|
||||
* ```
|
||||
* componentWillMount: function() {
|
||||
* this._panResponder = PanResponder.create({
|
||||
* // Ask to be the responder:
|
||||
* onStartShouldSetPanResponder: (evt, gestureState) => true,
|
||||
* onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
|
||||
* onMoveShouldSetPanResponder: (evt, gestureState) => true,
|
||||
* onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
|
||||
*
|
||||
* onPanResponderGrant: (evt, gestureState) => {
|
||||
* // The guesture has started. Show visual feedback so the user knows
|
||||
* // what is happening!
|
||||
*
|
||||
* // gestureState.{x,y}0 will be set to zero now
|
||||
* },
|
||||
* onPanResponderMove: (evt, gestureState) => {
|
||||
* // The most recent move distance is gestureState.move{X,Y}
|
||||
*
|
||||
* // The accumulated gesture distance since becoming responder is
|
||||
* // gestureState.d{x,y}
|
||||
* },
|
||||
* onPanResponderTerminationRequest: (evt, gestureState) => true,
|
||||
* onPanResponderRelease: (evt, gestureState) => {
|
||||
* // The user has released all touches while this view is the
|
||||
* // responder. This typically means a gesture has succeeded
|
||||
* },
|
||||
* onPanResponderTerminate: (evt, gestureState) => {
|
||||
* // Another component has become the responder, so this gesture
|
||||
* // should be cancelled
|
||||
* },
|
||||
* onShouldBlockNativeResponder: (evt, gestureState) => {
|
||||
* // Returns whether this component should block native components from becoming the JS
|
||||
* // responder. Returns true by default. Is currently only supported on android.
|
||||
* return true;
|
||||
* },
|
||||
* });
|
||||
* },
|
||||
*
|
||||
* render: function() {
|
||||
* return (
|
||||
* <View {...this._panResponder.panHandlers} />
|
||||
* );
|
||||
* },
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ### Working Example
|
||||
*
|
||||
* To see it in action, try the
|
||||
* [PanResponder example in UIExplorer](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/PanResponderExample.js)
|
||||
*/
|
||||
|
||||
var PanResponder = {
|
||||
|
||||
/**
|
||||
*
|
||||
* A graphical explanation of the touch data flow:
|
||||
*
|
||||
* +----------------------------+ +--------------------------------+
|
||||
* | ResponderTouchHistoryStore | |TouchHistoryMath |
|
||||
* +----------------------------+ +----------+---------------------+
|
||||
* |Global store of touchHistory| |Allocation-less math util |
|
||||
* |including activeness, start | |on touch history (centroids |
|
||||
* |position, prev/cur position.| |and multitouch movement etc) |
|
||||
* | | | |
|
||||
* +----^-----------------------+ +----^---------------------------+
|
||||
* | |
|
||||
* | (records relevant history |
|
||||
* | of touches relevant for |
|
||||
* | implementing higher level |
|
||||
* | gestures) |
|
||||
* | |
|
||||
* +----+-----------------------+ +----|---------------------------+
|
||||
* | ResponderEventPlugin | | | Your App/Component |
|
||||
* +----------------------------+ +----|---------------------------+
|
||||
* |Negotiates which view gets | Low level | | High level |
|
||||
* |onResponderMove events. | events w/ | +-+-------+ events w/ |
|
||||
* |Also records history into | touchHistory| | Pan | multitouch + |
|
||||
* |ResponderTouchHistoryStore. +---------------->Responder+-----> accumulative|
|
||||
* +----------------------------+ attached to | | | distance and |
|
||||
* each event | +---------+ velocity. |
|
||||
* | |
|
||||
* | |
|
||||
* +--------------------------------+
|
||||
*
|
||||
*
|
||||
*
|
||||
* Gesture that calculates cumulative movement over time in a way that just
|
||||
* "does the right thing" for multiple touches. The "right thing" is very
|
||||
* nuanced. When moving two touches in opposite directions, the cumulative
|
||||
* distance is zero in each dimension. When two touches move in parallel five
|
||||
* pixels in the same direction, the cumulative distance is five, not ten. If
|
||||
* two touches start, one moves five in a direction, then stops and the other
|
||||
* touch moves fives in the same direction, the cumulative distance is ten.
|
||||
*
|
||||
* This logic requires a kind of processing of time "clusters" of touch events
|
||||
* so that two touch moves that essentially occur in parallel but move every
|
||||
* other frame respectively, are considered part of the same movement.
|
||||
*
|
||||
* Explanation of some of the non-obvious fields:
|
||||
*
|
||||
* - moveX/moveY: If no move event has been observed, then `(moveX, moveY)` is
|
||||
* invalid. If a move event has been observed, `(moveX, moveY)` is the
|
||||
* centroid of the most recently moved "cluster" of active touches.
|
||||
* (Currently all move have the same timeStamp, but later we should add some
|
||||
* threshold for what is considered to be "moving"). If a palm is
|
||||
* accidentally counted as a touch, but a finger is moving greatly, the palm
|
||||
* will move slightly, but we only want to count the single moving touch.
|
||||
* - x0/y0: Centroid location (non-cumulative) at the time of becoming
|
||||
* responder.
|
||||
* - dx/dy: Cumulative touch distance - not the same thing as sum of each touch
|
||||
* distance. Accounts for touch moves that are clustered together in time,
|
||||
* moving the same direction. Only valid when currently responder (otherwise,
|
||||
* it only represents the drag distance below the threshold).
|
||||
* - vx/vy: Velocity.
|
||||
*/
|
||||
|
||||
_initializeGestureState: function(gestureState) {
|
||||
gestureState.moveX = 0;
|
||||
gestureState.moveY = 0;
|
||||
gestureState.x0 = 0;
|
||||
gestureState.y0 = 0;
|
||||
gestureState.dx = 0;
|
||||
gestureState.dy = 0;
|
||||
gestureState.vx = 0;
|
||||
gestureState.vy = 0;
|
||||
gestureState.numberActiveTouches = 0;
|
||||
// All `gestureState` accounts for timeStamps up until:
|
||||
gestureState._accountsForMovesUpTo = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is nuanced and is necessary. It is incorrect to continuously take all
|
||||
* active *and* recently moved touches, find the centroid, and track how that
|
||||
* result changes over time. Instead, we must take all recently moved
|
||||
* touches, and calculate how the centroid has changed just for those
|
||||
* recently moved touches, and append that change to an accumulator. This is
|
||||
* to (at least) handle the case where the user is moving three fingers, and
|
||||
* then one of the fingers stops but the other two continue.
|
||||
*
|
||||
* This is very different than taking all of the recently moved touches and
|
||||
* storing their centroid as `dx/dy`. For correctness, we must *accumulate
|
||||
* changes* in the centroid of recently moved touches.
|
||||
*
|
||||
* There is also some nuance with how we handle multiple moved touches in a
|
||||
* single event. With the way `ReactNativeEventEmitter` dispatches touches as
|
||||
* individual events, multiple touches generate two 'move' events, each of
|
||||
* them triggering `onResponderMove`. But with the way `PanResponder` works,
|
||||
* all of the gesture inference is performed on the first dispatch, since it
|
||||
* looks at all of the touches (even the ones for which there hasn't been a
|
||||
* native dispatch yet). Therefore, `PanResponder` does not call
|
||||
* `onResponderMove` passed the first dispatch. This diverges from the
|
||||
* typical responder callback pattern (without using `PanResponder`), but
|
||||
* avoids more dispatches than necessary.
|
||||
*/
|
||||
_updateGestureStateOnMove: function(gestureState, touchHistory) {
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
gestureState.moveX = currentCentroidXOfTouchesChangedAfter(
|
||||
touchHistory,
|
||||
gestureState._accountsForMovesUpTo
|
||||
);
|
||||
gestureState.moveY = currentCentroidYOfTouchesChangedAfter(
|
||||
touchHistory,
|
||||
gestureState._accountsForMovesUpTo
|
||||
);
|
||||
var movedAfter = gestureState._accountsForMovesUpTo;
|
||||
var prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
|
||||
var nextDX = gestureState.dx + (x - prevX);
|
||||
var nextDY = gestureState.dy + (y - prevY);
|
||||
|
||||
// TODO: This must be filtered intelligently.
|
||||
var dt =
|
||||
(touchHistory.mostRecentTimeStamp - gestureState._accountsForMovesUpTo);
|
||||
gestureState.vx = (nextDX - gestureState.dx) / dt;
|
||||
gestureState.vy = (nextDY - gestureState.dy) / dt;
|
||||
|
||||
gestureState.dx = nextDX;
|
||||
gestureState.dy = nextDY;
|
||||
gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} config Enhanced versions of all of the responder callbacks
|
||||
* that provide not only the typical `ResponderSyntheticEvent`, but also the
|
||||
* `PanResponder` gesture state. Simply replace the word `Responder` with
|
||||
* `PanResponder` in each of the typical `onResponder*` callbacks. For
|
||||
* example, the `config` object would look like:
|
||||
*
|
||||
* - `onMoveShouldSetPanResponder: (e, gestureState) => {...}`
|
||||
* - `onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}`
|
||||
* - `onStartShouldSetPanResponder: (e, gestureState) => {...}`
|
||||
* - `onStartShouldSetPanResponderCapture: (e, gestureState) => {...}`
|
||||
* - `onPanResponderReject: (e, gestureState) => {...}`
|
||||
* - `onPanResponderGrant: (e, gestureState) => {...}`
|
||||
* - `onPanResponderStart: (e, gestureState) => {...}`
|
||||
* - `onPanResponderEnd: (e, gestureState) => {...}`
|
||||
* - `onPanResponderRelease: (e, gestureState) => {...}`
|
||||
* - `onPanResponderMove: (e, gestureState) => {...}`
|
||||
* - `onPanResponderTerminate: (e, gestureState) => {...}`
|
||||
* - `onPanResponderTerminationRequest: (e, gestureState) => {...}`
|
||||
* - `onShouldBlockNativeResponder: (e, gestureState) => {...}`
|
||||
*
|
||||
* In general, for events that have capture equivalents, we update the
|
||||
* gestureState once in the capture phase and can use it in the bubble phase
|
||||
* as well.
|
||||
*
|
||||
* Be careful with onStartShould* callbacks. They only reflect updated
|
||||
* `gestureState` for start/end events that bubble/capture to the Node.
|
||||
* Once the node is the responder, you can rely on every start/end event
|
||||
* being processed by the gesture and `gestureState` being updated
|
||||
* accordingly. (numberActiveTouches) may not be totally accurate unless you
|
||||
* are the responder.
|
||||
*/
|
||||
create: function(config) {
|
||||
var gestureState = {
|
||||
// Useful for debugging
|
||||
stateID: Math.random(),
|
||||
};
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
var panHandlers = {
|
||||
onStartShouldSetResponder: function(e) {
|
||||
return config.onStartShouldSetPanResponder === undefined ? false :
|
||||
config.onStartShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onMoveShouldSetResponder: function(e) {
|
||||
return config.onMoveShouldSetPanResponder === undefined ? false :
|
||||
config.onMoveShouldSetPanResponder(e, gestureState);
|
||||
},
|
||||
onStartShouldSetResponderCapture: function(e) {
|
||||
// TODO: Actually, we should reinitialize the state any time
|
||||
// touches.length increases from 0 active to > 0 active.
|
||||
if (e.nativeEvent.touches.length === 1) {
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
}
|
||||
gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
|
||||
return config.onStartShouldSetPanResponderCapture !== undefined ?
|
||||
config.onStartShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onMoveShouldSetResponderCapture: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
// Responder system incorrectly dispatches should* to current responder
|
||||
// Filter out any touch moves past the first one - we would have
|
||||
// already processed multi-touch geometry during the first event.
|
||||
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
||||
return false;
|
||||
}
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
return config.onMoveShouldSetPanResponderCapture ?
|
||||
config.onMoveShouldSetPanResponderCapture(e, gestureState) : false;
|
||||
},
|
||||
|
||||
onResponderGrant: function(e) {
|
||||
gestureState.x0 = currentCentroidX(e.touchHistory);
|
||||
gestureState.y0 = currentCentroidY(e.touchHistory);
|
||||
gestureState.dx = 0;
|
||||
gestureState.dy = 0;
|
||||
if (config.onPanResponderGrant) config.onPanResponderGrant(e, gestureState);
|
||||
// TODO: t7467124 investigate if this can be removed
|
||||
return config.onShouldBlockNativeResponder === undefined ? true :
|
||||
config.onShouldBlockNativeResponder();
|
||||
},
|
||||
|
||||
onResponderReject: function(e) {
|
||||
if (config.onPanResponderReject) config.onPanResponderReject(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderRelease: function(e) {
|
||||
if (config.onPanResponderRelease) config.onPanResponderRelease(e, gestureState);
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
},
|
||||
|
||||
onResponderStart: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
if (config.onPanResponderStart) config.onPanResponderStart(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderMove: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
// Guard against the dispatch of two touch moves when there are two
|
||||
// simultaneously changed touches.
|
||||
if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
|
||||
return;
|
||||
}
|
||||
// Filter out any touch moves past the first one - we would have
|
||||
// already processed multi-touch geometry during the first event.
|
||||
PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
|
||||
if (config.onPanResponderMove) config.onPanResponderMove(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderEnd: function(e) {
|
||||
var touchHistory = e.touchHistory;
|
||||
gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
|
||||
if (config.onPanResponderEnd) config.onPanResponderEnd(e, gestureState);
|
||||
},
|
||||
|
||||
onResponderTerminate: function(e) {
|
||||
if (config.onPanResponderTerminate) {
|
||||
config.onPanResponderTerminate(e, gestureState);
|
||||
}
|
||||
PanResponder._initializeGestureState(gestureState);
|
||||
},
|
||||
|
||||
onResponderTerminationRequest: function(e) {
|
||||
return config.onPanResponderTerminationRequest === undefined ? true :
|
||||
config.onPanResponderTerminationRequest(e, gestureState);
|
||||
},
|
||||
};
|
||||
return {panHandlers: panHandlers};
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = PanResponder;
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* @providesModule TouchHistoryMath
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var TouchHistoryMath = {
|
||||
/**
|
||||
* This code is optimized and not intended to look beautiful. This allows
|
||||
* computing of touch centroids that have moved after `touchesChangedAfter`
|
||||
* timeStamp. You can compute the current centroid involving all touches
|
||||
* moves after `touchesChangedAfter`, or you can compute the previous
|
||||
* centroid of all touches that were moved after `touchesChangedAfter`.
|
||||
*
|
||||
* @param {TouchHistoryMath} touchHistory Standard Responder touch track
|
||||
* data.
|
||||
* @param {number} touchesChangedAfter timeStamp after which moved touches
|
||||
* are considered "actively moving" - not just "active".
|
||||
* @param {boolean} isXAxis Consider `x` dimension vs. `y` dimension.
|
||||
* @param {boolean} ofCurrent Compute current centroid for actively moving
|
||||
* touches vs. previous centroid of now actively moving touches.
|
||||
* @return {number} value of centroid in specified dimension.
|
||||
*/
|
||||
centroidDimension: function(touchHistory, touchesChangedAfter, isXAxis, ofCurrent) {
|
||||
var touchBank = touchHistory.touchBank;
|
||||
var total = 0;
|
||||
var count = 0;
|
||||
|
||||
var oneTouchData = touchHistory.numberActiveTouches === 1 ?
|
||||
touchHistory.touchBank[touchHistory.indexOfSingleActiveTouch] : null;
|
||||
|
||||
if (oneTouchData !== null) {
|
||||
if (oneTouchData.touchActive && oneTouchData.currentTimeStamp > touchesChangedAfter) {
|
||||
total += ofCurrent && isXAxis ? oneTouchData.currentPageX :
|
||||
ofCurrent && !isXAxis ? oneTouchData.currentPageY :
|
||||
!ofCurrent && isXAxis ? oneTouchData.previousPageX :
|
||||
oneTouchData.previousPageY;
|
||||
count = 1;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < touchBank.length; i++) {
|
||||
var touchTrack = touchBank[i];
|
||||
if (touchTrack !== null &&
|
||||
touchTrack !== undefined &&
|
||||
touchTrack.touchActive &&
|
||||
touchTrack.currentTimeStamp >= touchesChangedAfter) {
|
||||
var toAdd; // Yuck, program temporarily in invalid state.
|
||||
if (ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.currentPageX;
|
||||
} else if (ofCurrent && !isXAxis) {
|
||||
toAdd = touchTrack.currentPageY;
|
||||
} else if (!ofCurrent && isXAxis) {
|
||||
toAdd = touchTrack.previousPageX;
|
||||
} else {
|
||||
toAdd = touchTrack.previousPageY;
|
||||
}
|
||||
total += toAdd;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 0 ? total / count : TouchHistoryMath.noCentroid;
|
||||
},
|
||||
|
||||
currentCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidXOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
true, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
previousCentroidYOfTouchesChangedAfter: function(touchHistory, touchesChangedAfter) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
touchesChangedAfter,
|
||||
false, // isXAxis
|
||||
false // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidX: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
true, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
currentCentroidY: function(touchHistory) {
|
||||
return TouchHistoryMath.centroidDimension(
|
||||
touchHistory,
|
||||
0, // touchesChangedAfter
|
||||
false, // isXAxis
|
||||
true // ofCurrent
|
||||
);
|
||||
},
|
||||
|
||||
noCentroid: -1,
|
||||
};
|
||||
|
||||
module.exports = TouchHistoryMath;
|
Loading…
Reference in New Issue