2015-02-20 04:10:52 +00:00
|
|
|
/**
|
2015-03-23 20:35:08 +00:00
|
|
|
* 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.
|
2015-02-20 04:10:52 +00:00
|
|
|
*
|
2015-10-06 02:19:16 +00:00
|
|
|
* @providesModule ReactNativeAttributePayload
|
2015-03-26 00:49:46 +00:00
|
|
|
* @flow
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
2015-10-06 02:19:16 +00:00
|
|
|
var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
|
|
|
|
2015-07-15 00:06:03 +00:00
|
|
|
var deepDiffer = require('deepDiffer');
|
2015-10-06 02:19:16 +00:00
|
|
|
var styleDiffer = require('styleDiffer');
|
|
|
|
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
|
|
|
var flattenStyle = require('flattenStyle');
|
|
|
|
var precomputeStyle = require('precomputeStyle');
|
2015-07-15 00:06:03 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* diffRawProperties takes two sets of props and a set of valid attributes
|
|
|
|
* and write to updatePayload the values that changed or were deleted
|
|
|
|
*
|
|
|
|
* @param {?object} updatePayload Overriden with the props that changed.
|
|
|
|
* @param {!object} prevProps Previous properties to diff against current
|
|
|
|
* properties. These properties are as supplied to component construction.
|
|
|
|
* @param {!object} prevProps Next "current" properties to diff against
|
|
|
|
* previous. These properties are as supplied to component construction.
|
|
|
|
* @return {?object}
|
|
|
|
*/
|
2015-03-26 00:49:46 +00:00
|
|
|
function diffRawProperties(
|
|
|
|
updatePayload: ?Object,
|
|
|
|
prevProps: ?Object,
|
|
|
|
nextProps: ?Object,
|
|
|
|
validAttributes: Object
|
|
|
|
): ?Object {
|
2015-02-20 04:10:52 +00:00
|
|
|
var validAttributeConfig;
|
|
|
|
var nextProp;
|
|
|
|
var prevProp;
|
|
|
|
var isScalar;
|
|
|
|
var shouldUpdate;
|
2015-07-15 00:06:03 +00:00
|
|
|
var differ;
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-03-26 00:49:46 +00:00
|
|
|
if (nextProps) {
|
|
|
|
for (var propKey in nextProps) {
|
|
|
|
validAttributeConfig = validAttributes[propKey];
|
|
|
|
if (!validAttributeConfig) {
|
|
|
|
continue; // not a valid native prop
|
|
|
|
}
|
|
|
|
prevProp = prevProps && prevProps[propKey];
|
|
|
|
nextProp = nextProps[propKey];
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
|
|
|
|
// functions are converted to booleans as markers that the associated
|
|
|
|
// events should be sent from native.
|
|
|
|
if (typeof prevProp === 'function') {
|
|
|
|
prevProp = true;
|
|
|
|
}
|
|
|
|
if (typeof nextProp === 'function') {
|
|
|
|
nextProp = true;
|
|
|
|
}
|
|
|
|
|
2015-03-26 00:49:46 +00:00
|
|
|
if (prevProp !== nextProp) {
|
2015-07-15 00:06:03 +00:00
|
|
|
// Scalars and new props are always updated. Objects use deepDiffer by
|
|
|
|
// default, but can be optimized with custom differs.
|
|
|
|
differ = validAttributeConfig.diff || deepDiffer;
|
2015-03-26 00:49:46 +00:00
|
|
|
isScalar = typeof nextProp !== 'object' || nextProp === null;
|
2015-07-15 00:06:03 +00:00
|
|
|
shouldUpdate = isScalar || !prevProp || differ(prevProp, nextProp);
|
2015-03-26 00:49:46 +00:00
|
|
|
if (shouldUpdate) {
|
|
|
|
updatePayload = updatePayload || {};
|
|
|
|
updatePayload[propKey] = nextProp;
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2015-03-26 00:49:46 +00:00
|
|
|
if (prevProps) {
|
|
|
|
for (var propKey in prevProps) {
|
|
|
|
validAttributeConfig = validAttributes[propKey];
|
|
|
|
if (!validAttributeConfig) {
|
|
|
|
continue; // not a valid native prop
|
|
|
|
}
|
|
|
|
if (updatePayload && updatePayload[propKey] !== undefined) {
|
|
|
|
continue; // Prop already specified
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-03-26 00:49:46 +00:00
|
|
|
prevProp = prevProps[propKey];
|
|
|
|
nextProp = nextProps && nextProps[propKey];
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
|
|
|
|
// functions are converted to booleans as markers that the associated
|
|
|
|
// events should be sent from native.
|
|
|
|
if (typeof prevProp === 'function') {
|
|
|
|
prevProp = true;
|
|
|
|
}
|
|
|
|
if (typeof nextProp === 'function') {
|
|
|
|
nextProp = true;
|
|
|
|
}
|
|
|
|
|
2015-03-26 00:49:46 +00:00
|
|
|
if (prevProp !== nextProp) {
|
|
|
|
if (nextProp === undefined) {
|
|
|
|
nextProp = null; // null is a sentinel we explicitly send to native
|
|
|
|
}
|
2015-07-15 00:06:03 +00:00
|
|
|
// Scalars and new props are always updated. Objects use deepDiffer by
|
|
|
|
// default, but can be optimized with custom differs.
|
|
|
|
differ = validAttributeConfig.diff || deepDiffer;
|
2015-03-26 00:49:46 +00:00
|
|
|
isScalar = typeof nextProp !== 'object' || nextProp === null;
|
2015-07-15 00:06:03 +00:00
|
|
|
shouldUpdate =
|
|
|
|
isScalar &&
|
|
|
|
prevProp !== nextProp ||
|
|
|
|
differ(prevProp, nextProp);
|
2015-03-26 00:49:46 +00:00
|
|
|
if (shouldUpdate) {
|
|
|
|
updatePayload = updatePayload || {};
|
|
|
|
updatePayload[propKey] = nextProp;
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return updatePayload;
|
|
|
|
}
|
|
|
|
|
2015-10-06 02:19:16 +00:00
|
|
|
var ReactNativeAttributePayload = {
|
|
|
|
|
|
|
|
create: function(
|
|
|
|
props : Object,
|
|
|
|
validAttributes : Object
|
|
|
|
) : ?Object {
|
|
|
|
return ReactNativeAttributePayload.diff({}, props, validAttributes);
|
|
|
|
},
|
|
|
|
|
|
|
|
diff: function(
|
|
|
|
prevProps : Object,
|
|
|
|
nextProps : Object,
|
|
|
|
validAttributes : Object
|
|
|
|
) : ?Object {
|
|
|
|
|
|
|
|
if (__DEV__) {
|
|
|
|
for (var key in nextProps) {
|
|
|
|
if (nextProps.hasOwnProperty(key) &&
|
|
|
|
nextProps[key] &&
|
|
|
|
validAttributes[key]) {
|
|
|
|
deepFreezeAndThrowOnMutationInDev(nextProps[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var updatePayload = diffRawProperties(
|
|
|
|
null, // updatePayload
|
|
|
|
prevProps,
|
|
|
|
nextProps,
|
|
|
|
validAttributes
|
|
|
|
);
|
|
|
|
|
|
|
|
for (var key in updatePayload) {
|
|
|
|
var process = validAttributes[key] && validAttributes[key].process;
|
|
|
|
if (process) {
|
|
|
|
updatePayload[key] = process(updatePayload[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The style property is a deeply nested element which includes numbers
|
|
|
|
// to represent static objects. Most of the time, it doesn't change across
|
|
|
|
// renders, so it's faster to spend the time checking if it is different
|
|
|
|
// before actually doing the expensive flattening operation in order to
|
|
|
|
// compute the diff.
|
|
|
|
if (styleDiffer(nextProps.style, prevProps.style)) {
|
|
|
|
// TODO: Use a cached copy of previousFlattenedStyle, or walk both
|
|
|
|
// props in parallel.
|
|
|
|
var previousFlattenedStyle = precomputeStyle(
|
|
|
|
flattenStyle(prevProps.style),
|
|
|
|
validAttributes
|
|
|
|
);
|
|
|
|
var nextFlattenedStyle = precomputeStyle(
|
|
|
|
flattenStyle(nextProps.style),
|
|
|
|
validAttributes
|
|
|
|
);
|
|
|
|
updatePayload = diffRawProperties(
|
|
|
|
updatePayload,
|
|
|
|
previousFlattenedStyle,
|
|
|
|
nextFlattenedStyle,
|
|
|
|
ReactNativeStyleAttributes
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return updatePayload;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = ReactNativeAttributePayload;
|