diff --git a/.babelrc b/.babelrc index 73803b050c..2ff815b425 100644 --- a/.babelrc +++ b/.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", diff --git a/.travis.yml b/.travis.yml index bfbdfeaa20..634cf98e55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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" \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c337f6ed..524d48696a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 15.0.2 (April 29, 2016) + +### React +- Removed extraneous files from npm package. ([@gaearon](https://github.com/gaearon) in [#6388](https://github.com/facebook/react/pull/6388)) +- Ensure `componentWillUnmount` is only called once. ([@jimfb](https://github.com/jimfb) in [#6613](https://github.com/facebook/react/pull/6613)) + +### ReactDOM +- Fixed bug resulting in disabled buttons responding to mouse events in IE. ([@nhunzaker](https://github.com/nhunzaker) in [#6215](https://github.com/facebook/react/pull/6215)) +- Ensure ``. ([@trevorsmith](https://github.com/trevorsmith) in [#6442](https://github.com/facebook/react/pull/6442)) +- Restore support for rendering into a shadow root. ([@Wildhoney](https://github.com/Wildhoney) in [#6462](https://github.com/facebook/react/pull/6462)) +- Ensure nested `` elements are caught when warning for invalid markup. ([@keyanzhang](https://github.com/keyanzhang) in [#6469](https://github.com/facebook/react/pull/6469)) +- Improve warning when encountering multiple elements with the same key. ([@hkal](https://github.com/hkal) in [#6500](https://github.com/facebook/react/pull/6500)) + +### React TestUtils Add-on +- Ensure that functional components do not have an owner. ([@gaearon](https://github.com/gaearon) in [#6362](https://github.com/facebook/react/pull/6362)) +- Handle invalid arguments to `scryRenderedDOMComponentsWithClass` better. ([@ipeters90](https://github.com/ipeters90) in [#6529](https://github.com/facebook/react/pull/6529)) + +### React Perf Add-on +- Ignore DOM operations that occur outside the batch operation. ([@gaearon](https://github.com/gaearon) in [#6516](https://github.com/facebook/react/pull/6516)) + +### React Native Renderer +- These files are now shipped inside the React npm package. They have no impact on React core or ReactDOM. + + ## 15.0.1 (April 8, 2016) ### React diff --git a/Gruntfile.js b/Gruntfile.js index c3ee8c20d6..f1f3c43567 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -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', diff --git a/grunt/tasks/npm-react-native.js b/grunt/tasks/npm-react-native.js new file mode 100644 index 0000000000..7157e08f6b --- /dev/null +++ b/grunt/tasks/npm-react-native.js @@ -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, +}; diff --git a/grunt/tasks/release.js b/grunt/tasks/release.js index 857c2dbdf3..6181d79202 100644 --- a/grunt/tasks/release.js +++ b/grunt/tasks/release.js @@ -16,7 +16,7 @@ var BOWER_FILES = [ ]; var EXAMPLES_PATH = 'examples/'; -var EXAMPLES_GLOB = [EXAMPLES_PATH + '**/*.*']; +var EXAMPLES_GLOB = [EXAMPLES_PATH + '**/*.*', EXAMPLES_PATH + '**/.babelrc']; var STARTER_PATH = 'starter/'; var STARTER_GLOB = [STARTER_PATH + '/**/*.*']; diff --git a/grunt/tasks/version-check.js b/grunt/tasks/version-check.js index ff6da818c0..99f55feac0 100644 --- a/grunt/tasks/version-check.js +++ b/grunt/tasks/version-check.js @@ -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), diff --git a/gulpfile.js b/gulpfile.js index f885ea349a..2c2a7c9823 100644 --- a/gulpfile.js +++ b/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 }], ], }; diff --git a/package.json b/package.json index c1fc007c06..803e5ac2bf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-build", "private": true, - "version": "15.0.1", + "version": "15.0.2-alpha.4", "devDependencies": { "async": "^1.5.0", "babel-cli": "^6.6.5", @@ -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", diff --git a/packages/react-addons/package.json b/packages/react-addons/package.json index bcb1d4d18e..9094d3e2e5 100644 --- a/packages/react-addons/package.json +++ b/packages/react-addons/package.json @@ -1,6 +1,6 @@ { "name": "react-addons-template", - "version": "15.0.1", + "version": "15.0.2-alpha.4", "main": "index.js", "repository": "facebook/react", "keywords": [ @@ -10,6 +10,6 @@ "license": "BSD-3-Clause", "dependencies": {}, "peerDependencies": { - "react": "^15.0.1" + "react": "^15.0.2-alpha.4" } } diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index 24ed72a2e9..a5a9df7749 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -1,6 +1,6 @@ { "name": "react-dom", - "version": "15.0.1", + "version": "15.0.2-alpha.4", "description": "React package for working with the DOM.", "main": "index.js", "repository": "facebook/react", @@ -14,6 +14,6 @@ "homepage": "https://facebook.github.io/react/", "dependencies": {}, "peerDependencies": { - "react": "^15.0.1" + "react": "^15.0.2-alpha.4" } } diff --git a/packages/react-native-renderer/README.md b/packages/react-native-renderer/README.md new file mode 100644 index 0000000000..db3e7d3a59 --- /dev/null +++ b/packages/react-native-renderer/README.md @@ -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. diff --git a/packages/react-native-renderer/index.js b/packages/react-native-renderer/index.js new file mode 100644 index 0000000000..6f35853e7c --- /dev/null +++ b/packages/react-native-renderer/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('react/lib/ReactNative'); diff --git a/packages/react-native-renderer/package.json b/packages/react-native-renderer/package.json new file mode 100644 index 0000000000..c099e042ad --- /dev/null +++ b/packages/react-native-renderer/package.json @@ -0,0 +1,19 @@ +{ + "name": "react-native-renderer", + "version": "15.0.2-alpha.4", + "description": "React package for use inside react-native.", + "main": "index.js", + "repository": "facebook/react", + "keywords": [ + "react", + "react-native" + ], + "license": "BSD-3-Clause", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "homepage": "https://facebook.github.io/react-native/", + "dependencies": { + "react": "^15.0.2-alpha.4" + } +} diff --git a/packages/react/lib/ReactDOM.native.js b/packages/react/lib/ReactDOM.native.js deleted file mode 100644 index bdd9044a8b..0000000000 --- a/packages/react/lib/ReactDOM.native.js +++ /dev/null @@ -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, -}; diff --git a/packages/react/package.json b/packages/react/package.json index 249db88cb2..3b9988d88f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "name": "react", "description": "React is a JavaScript library for building user interfaces.", - "version": "15.0.1", + "version": "15.0.2-alpha.4", "keywords": [ "react" ], diff --git a/src/ReactVersion.js b/src/ReactVersion.js index 9c9a092153..a6fb07f103 100644 --- a/src/ReactVersion.js +++ b/src/ReactVersion.js @@ -11,4 +11,4 @@ 'use strict'; -module.exports = '15.0.1'; +module.exports = '15.0.2-alpha.4'; diff --git a/src/isomorphic/children/ReactChildren.js b/src/isomorphic/children/ReactChildren.js index da8848d229..417f78aa28 100644 --- a/src/isomorphic/children/ReactChildren.js +++ b/src/isomorphic/children/ReactChildren.js @@ -146,7 +146,7 @@ function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { /** * Maps children that are typically specified as `props.children`. * - * The provided mapFunction(child, key, index) will be called for each + * The provided mapFunction(child, index) will be called for each * leaf child. * * @param {?*} children Children tree container. diff --git a/src/isomorphic/children/onlyChild.js b/src/isomorphic/children/onlyChild.js index 0a33ee1801..e1d3706c7e 100644 --- a/src/isomorphic/children/onlyChild.js +++ b/src/isomorphic/children/onlyChild.js @@ -22,7 +22,7 @@ var invariant = require('invariant'); * of children. * * @param {?object} children Child collection structure. - * @return {ReactComponent} The first and only `ReactComponent` contained in the + * @return {ReactElement} The first and only `ReactElement` contained in the * structure. */ function onlyChild(children) { diff --git a/src/isomorphic/deprecated/OrderedMap.js b/src/isomorphic/deprecated/OrderedMap.js deleted file mode 100644 index 4336cdc384..0000000000 --- a/src/isomorphic/deprecated/OrderedMap.js +++ /dev/null @@ -1,505 +0,0 @@ -/** - * 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 OrderedMap - */ - -'use strict'; - -var invariant = require('invariant'); - -var PREFIX = 'key:'; - -/** - * Utility to extract a backing object from an initialization `Array`, allowing - * the caller to assist in resolving the unique ID for each entry via the - * `keyExtractor` callback. The `keyExtractor` must extract non-empty strings or - * numbers. - * @param {Array} arr Array of items. - * @param {function} keyExtractor Extracts a unique key from each item. - * @return {Object} Map from unique key to originating value that the key was - * extracted from. - * @throws Exception if the initialization array has duplicate extracted keys. - */ -function extractObjectFromArray(arr, keyExtractor) { - var normalizedObj = {}; - for (var i = 0; i < arr.length; i++) { - var item = arr[i]; - var key = keyExtractor(item); - assertValidPublicKey(key); - var normalizedKey = PREFIX + key; - invariant( - !(normalizedKey in normalizedObj), - 'OrderedMap: IDs returned by the key extraction function must be unique.' - ); - normalizedObj[normalizedKey] = item; - } - return normalizedObj; -} - -/** - * Utility class for mappings with ordering. This class is to be used in an - * immutable manner. A `OrderedMap` is very much like the native JavaScript - * object, where keys map to values via the `get()` function. Also, like the - * native JavaScript object, there is an ordering associated with the mapping. - * This class is helpful because it eliminates many of the pitfalls that come - * with the native JavaScript ordered mappings. Specifically, there are - * inconsistencies with numeric keys in some JavaScript implementations - * (enumeration ordering). This class protects against those pitfalls and - * provides functional utilities for dealing with these `OrderedMap`s. - * - * - TODO: - * - orderedMergeExclusive: Merges mutually exclusive `OrderedMap`s. - * - mapReverse(). - * - * @class {OrderedMap} - * @constructor {OrderedMap} - * @param {Object} normalizedObj Object that is known to be a defensive copy of - * caller supplied data. We require a defensive copy to guard against callers - * mutating. It is also assumed that the keys of `normalizedObj` have been - * normalized and do not contain any numeric-appearing strings. - * @param {number} computedLength The precomputed length of `_normalizedObj` - * keys. - * @private - */ -function OrderedMapImpl(normalizedObj, computedLength) { - this._normalizedObj = normalizedObj; - this._computedPositions = null; - this.length = computedLength; -} - -/** - * Validates a "public" key - that is, one that the public facing API supplies. - * The key is then normalized for internal storage. In order to be considered - * valid, all keys must be non-empty, defined, non-null strings or numbers. - * - * @param {string?} key Validates that the key is suitable for use in a - * `OrderedMap`. - * @throws Error if key is not appropriate for use in `OrderedMap`. - */ -function assertValidPublicKey(key) { - invariant( - key !== '' && (typeof key === 'string' || typeof key === 'number'), - 'OrderedMap: Key must be non-empty, non-null string or number.' - ); -} - -/** - * Validates that arguments to range operations are within the correct limits. - * - * @param {number} start Start of range. - * @param {number} length Length of range. - * @param {number} actualLen Actual length of range that should not be - * exceeded. - * @throws Error if range arguments are out of bounds. - */ -function assertValidRangeIndices(start, length, actualLen) { - invariant( - typeof start === 'number' && - typeof length === 'number' && - length >= 0 && - start >= 0 && - start + length <= actualLen, - 'OrderedMap: `mapRange` and `forEachRange` expect non-negative start and ' + - 'length arguments within the bounds of the instance.' - ); -} - -/** - * Merges two "normalized" objects (objects who's key have been normalized) into - * a `OrderedMap`. - * - * @param {Object} a Object of key value pairs. - * @param {Object} b Object of key value pairs. - * @return {OrderedMap} new `OrderedMap` that results in merging `a` and `b`. - */ -function _fromNormalizedObjects(a, b) { - // Second optional, both must be plain JavaScript objects. - invariant( - a && a.constructor === Object && (!b || b.constructor === Object), - 'OrderedMap: Corrupted instance of OrderedMap detected.' - ); - - var newSet = {}; - var length = 0; - var key; - for (key in a) { - if (a.hasOwnProperty(key)) { - newSet[key] = a[key]; - length++; - } - } - - for (key in b) { - if (b.hasOwnProperty(key)) { - // Increment length if not already added via first object (a) - if (!(key in newSet)) { - length++; - } - newSet[key] = b[key]; - } - } - return new OrderedMapImpl(newSet, length); -} - -/** - * Methods for `OrderedMap` instances. - * - * @lends OrderedMap.prototype - * TODO: Make this data structure lazy, unify with LazyArray. - * TODO: Unify this with ImmutableObject - it is to be used immutably. - * TODO: If so, consider providing `fromObject` API. - * TODO: Create faster implementation of merging/mapping from original Array, - * without having to first create an object - simply for the sake of merging. - */ -var OrderedMapMethods = { - - /** - * Returns whether or not a given key is present in the map. - * - * @param {string} key Valid string key to lookup membership for. - * @return {boolean} Whether or not `key` is a member of the map. - * @throws Error if provided known invalid key. - */ - has: function(key) { - assertValidPublicKey(key); - var normalizedKey = PREFIX + key; - return normalizedKey in this._normalizedObj; - }, - - /** - * Returns the object for a given key, or `undefined` if not present. To - * distinguish an undefined entry vs not being in the set, use `has()`. - * - * @param {string} key String key to lookup the value for. - * @return {Object?} Object at key `key`, or undefined if not in map. - * @throws Error if provided known invalid key. - */ - get: function(key) { - assertValidPublicKey(key); - var normalizedKey = PREFIX + key; - return this.has(key) ? this._normalizedObj[normalizedKey] : undefined; - }, - - /** - * Merges, appending new keys to the end of the ordering. Keys in `orderedMap` - * that are redundant with `this`, maintain the same ordering index that they - * had in `this`. This is how standard JavaScript object merging would work. - * If you wish to prepend a `OrderedMap` to the beginning of another - * `OrderedMap` then simply reverse the order of operation. This is the analog - * to `merge(x, y)`. - * - * @param {OrderedMap} orderedMap OrderedMap to merge onto the end. - * @return {OrderedMap} New OrderedMap that represents the result of the - * merge. - */ - merge: function(orderedMap) { - invariant( - orderedMap instanceof OrderedMapImpl, - 'OrderedMap.merge(...): Expected an OrderedMap instance.' - ); - return _fromNormalizedObjects( - this._normalizedObj, - orderedMap._normalizedObj - ); - }, - - /** - * Functional map API. Returns a new `OrderedMap`. - * - * @param {Function} cb Callback to invoke for each item. - * @param {Object?=} context Context to invoke callback from. - * @return {OrderedMap} OrderedMap that results from mapping. - */ - map: function(cb, context) { - return this.mapRange(cb, 0, this.length, context); - }, - - /** - * The callback `cb` is invoked with the arguments (item, key, - * indexInOriginal). - * - * @param {Function} cb Determines result for each item. - * @param {number} start Start index of map range. - * @param {end} length End index of map range. - * @param {*!?} context Context of callback invocation. - * @return {OrderedMap} OrderedMap resulting from mapping the range. - */ - mapRange: function(cb, start, length, context) { - var thisSet = this._normalizedObj; - var newSet = {}; - var i = 0; - assertValidRangeIndices(start, length, this.length); - var end = start + length - 1; - for (var key in thisSet) { - if (thisSet.hasOwnProperty(key)) { - if (i >= start) { - if (i > end) { - break; - } - var item = thisSet[key]; - newSet[key] = cb.call(context, item, key.substr(PREFIX.length), i); - } - i++; - } - } - return new OrderedMapImpl(newSet, length); - }, - - /** - * Function filter API. Returns new `OrderedMap`. - * - * @param {Function} cb Callback to invoke for each item. - * @param {Object?=} context Context to invoke callback from. - * @return {OrderedMap} OrderedMap that results from filtering. - */ - filter: function(cb, context) { - return this.filterRange(cb, 0, this.length, context); - }, - - /** - * The callback `cb` is invoked with the arguments (item, key, - * indexInOriginal). - * - * @param {Function} cb Returns true if item should be in result. - * @param {number} start Start index of filter range. - * @param {number} length End index of map range. - * @param {*!?} context Context of callback invocation. - * @return {OrderedMap} OrderedMap resulting from filtering the range. - */ - filterRange: function(cb, start, length, context) { - var newSet = {}; - var newSetLength = 0; - this.forEachRange(function(item, key, originalIndex) { - if (cb.call(context, item, key, originalIndex)) { - var normalizedKey = PREFIX + key; - newSet[normalizedKey] = item; - newSetLength++; - } - }, start, length); - return new OrderedMapImpl(newSet, newSetLength); - }, - - forEach: function(cb, context) { - this.forEachRange(cb, 0, this.length, context); - }, - - forEachRange: function(cb, start, length, context) { - assertValidRangeIndices(start, length, this.length); - var thisSet = this._normalizedObj; - var i = 0; - var end = start + length - 1; - for (var key in thisSet) { - if (thisSet.hasOwnProperty(key)) { - if (i >= start) { - if (i > end) { - break; - } - var item = thisSet[key]; - cb.call(context, item, key.substr(PREFIX.length), i); - } - i++; - } - } - }, - - /** - * Even though `mapRange`/`forEachKeyRange` allow zero length mappings, we'll - * impose an additional restriction here that the length of mapping be greater - * than zero - the only reason is that there are many ways to express length - * zero in terms of two keys and that is confusing. - */ - mapKeyRange: function(cb, startKey, endKey, context) { - var startIndex = this.indexOfKey(startKey); - var endIndex = this.indexOfKey(endKey); - invariant( - startIndex !== undefined && endIndex !== undefined, - 'mapKeyRange must be given keys that are present.' - ); - invariant( - endIndex >= startIndex, - 'OrderedMap.mapKeyRange(...): `endKey` must not come before `startIndex`.' - ); - return this.mapRange(cb, startIndex, (endIndex - startIndex) + 1, context); - }, - - forEachKeyRange: function(cb, startKey, endKey, context) { - var startIndex = this.indexOfKey(startKey); - var endIndex = this.indexOfKey(endKey); - invariant( - startIndex !== undefined && endIndex !== undefined, - 'forEachKeyRange must be given keys that are present.' - ); - invariant( - endIndex >= startIndex, - 'OrderedMap.forEachKeyRange(...): `endKey` must not come before ' + - '`startIndex`.' - ); - this.forEachRange(cb, startIndex, (endIndex - startIndex) + 1, context); - }, - - /** - * @param {number} pos Index to search for key at. - * @return {string|undefined} Either the key at index `pos` or undefined if - * not in map. - */ - keyAtIndex: function(pos) { - var computedPositions = this._getOrComputePositions(); - var keyAtPos = computedPositions.keyByIndex[pos]; - return keyAtPos ? keyAtPos.substr(PREFIX.length) : undefined; - }, - - /** - * @param {string} key String key from which to find the next key. - * @return {string|undefined} Either the next key, or undefined if there is no - * next key. - * @throws Error if `key` is not in this `OrderedMap`. - */ - keyAfter: function(key) { - return this.nthKeyAfter(key, 1); - }, - - /** - * @param {string} key String key from which to find the preceding key. - * @return {string|undefined} Either the preceding key, or undefined if there - * is no preceding.key. - * @throws Error if `key` is not in this `OrderedMap`. - */ - keyBefore: function(key) { - return this.nthKeyBefore(key, 1); - }, - - /** - * @param {string} key String key from which to find a following key. - * @param {number} n Distance to scan forward after `key`. - * @return {string|undefined} Either the nth key after `key`, or undefined if - * there is no next key. - * @throws Error if `key` is not in this `OrderedMap`. - */ - nthKeyAfter: function(key, n) { - var curIndex = this.indexOfKey(key); - invariant( - curIndex !== undefined, - 'OrderedMap.nthKeyAfter: The key `%s` does not exist in this instance.', - key - ); - return this.keyAtIndex(curIndex + n); - }, - - /** - * @param {string} key String key from which to find a preceding key. - * @param {number} n Distance to scan backwards before `key`. - * @return {string|undefined} Either the nth key before `key`, or undefined if - * there is no previous key. - * @throws Error if `key` is not in this `OrderedMap`. - */ - nthKeyBefore: function(key, n) { - return this.nthKeyAfter(key, -n); - }, - - /** - * @param {string} key Key to find the index of. - * @return {number|undefined} Index of the provided key, or `undefined` if the - * key is not found. - */ - indexOfKey: function(key) { - assertValidPublicKey(key); - var normalizedKey = PREFIX + key; - var computedPositions = this._getOrComputePositions(); - var computedPosition = computedPositions.indexByKey[normalizedKey]; - // Just writing it this way to make it clear this is intentional. - return computedPosition === undefined ? undefined : computedPosition; - }, - - /** - * @return {Array} An ordered array of this object's values. - */ - toArray: function() { - var result = []; - var thisSet = this._normalizedObj; - for (var key in thisSet) { - if (thisSet.hasOwnProperty(key)) { - result.push(thisSet[key]); - } - } - return result; - }, - - /** - * Finds the key at a given position, or indicates via `undefined` that that - * position does not exist in the `OrderedMap`. It is appropriate to return - * undefined, indicating that the key doesn't exist in the `OrderedMap` - * because `undefined` is not ever a valid `OrderedMap` key. - * - * @private - * @return {string?} Name of the item at position `pos`, or `undefined` if - * there is no item at that position. - */ - _getOrComputePositions: function() { - // TODO: Entertain computing this at construction time in some less - // performance critical paths. - var computedPositions = this._computedPositions; - if (!computedPositions) { - this._computePositions(); - } - return this._computedPositions; - }, - - /** - * Precomputes the index/key mapping for future lookup. Since `OrderedMap`s - * are immutable, there is only ever a need to perform this once. - * @private - */ - _computePositions: function() { - this._computedPositions = { - keyByIndex: {}, - indexByKey: {}, - }; - var keyByIndex = this._computedPositions.keyByIndex; - var indexByKey = this._computedPositions.indexByKey; - var index = 0; - var thisSet = this._normalizedObj; - for (var key in thisSet) { - if (thisSet.hasOwnProperty(key)) { - keyByIndex[index] = key; - indexByKey[key] = index; - index++; - } - } - }, -}; - -Object.assign(OrderedMapImpl.prototype, OrderedMapMethods); - -var OrderedMap = { - from: function(orderedMap) { - invariant( - orderedMap instanceof OrderedMapImpl, - 'OrderedMap.from(...): Expected an OrderedMap instance.' - ); - return _fromNormalizedObjects(orderedMap._normalizedObj, null); - }, - - fromArray: function(arr, keyExtractor) { - invariant( - Array.isArray(arr), - 'OrderedMap.fromArray(...): First argument must be an array.' - ); - invariant( - typeof keyExtractor === 'function', - 'OrderedMap.fromArray(...): Second argument must be a function used ' + - 'to determine the unique key for each entry.' - ); - return new OrderedMapImpl( - extractObjectFromArray(arr, keyExtractor), - arr.length - ); - }, -}; - -module.exports = OrderedMap; diff --git a/src/isomorphic/deprecated/ReactPropTransferer.js b/src/isomorphic/deprecated/ReactPropTransferer.js deleted file mode 100644 index da009cafe0..0000000000 --- a/src/isomorphic/deprecated/ReactPropTransferer.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * 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 ReactPropTransferer - */ - -'use strict'; - -var emptyFunction = require('emptyFunction'); -var joinClasses = require('joinClasses'); - -/** - * Creates a transfer strategy that will merge prop values using the supplied - * `mergeStrategy`. If a prop was previously unset, this just sets it. - * - * @param {function} mergeStrategy - * @return {function} - */ -function createTransferStrategy(mergeStrategy) { - return function(props, key, value) { - if (!props.hasOwnProperty(key)) { - props[key] = value; - } else { - props[key] = mergeStrategy(props[key], value); - } - }; -} - -var transferStrategyMerge = createTransferStrategy(function(a, b) { - // `merge` overrides the first object's (`props[key]` above) keys using the - // second object's (`value`) keys. An object's style's existing `propA` would - // get overridden. Flip the order here. - return Object.assign({}, b, a); -}); - -/** - * Transfer strategies dictate how props are transferred by `transferPropsTo`. - * NOTE: if you add any more exceptions to this list you should be sure to - * update `cloneWithProps()` accordingly. - */ -var TransferStrategies = { - /** - * Never transfer `children`. - */ - children: emptyFunction, - /** - * Transfer the `className` prop by merging them. - */ - className: createTransferStrategy(joinClasses), - /** - * Transfer the `style` prop (which is an object) by merging them. - */ - style: transferStrategyMerge, -}; - -/** - * Mutates the first argument by transferring the properties from the second - * argument. - * - * @param {object} props - * @param {object} newProps - * @return {object} - */ -function transferInto(props, newProps) { - for (var thisKey in newProps) { - if (!newProps.hasOwnProperty(thisKey)) { - continue; - } - - var transferStrategy = TransferStrategies[thisKey]; - - if (transferStrategy && TransferStrategies.hasOwnProperty(thisKey)) { - transferStrategy(props, thisKey, newProps[thisKey]); - } else if (!props.hasOwnProperty(thisKey)) { - props[thisKey] = newProps[thisKey]; - } - } - return props; -} - -/** - * ReactPropTransferer are capable of transferring props to another component - * using a `transferPropsTo` method. - * - * @class ReactPropTransferer - */ -var ReactPropTransferer = { - - /** - * Merge two props objects using TransferStrategies. - * - * @param {object} oldProps original props (they take precedence) - * @param {object} newProps new props to merge in - * @return {object} a new object containing both sets of props merged. - */ - mergeProps: function(oldProps, newProps) { - return transferInto(Object.assign({}, oldProps), newProps); - }, - -}; - -module.exports = ReactPropTransferer; diff --git a/src/renderers/dom/client/__tests__/validateDOMNesting-test.js b/src/renderers/dom/client/__tests__/validateDOMNesting-test.js index e0bdfe959f..dec17ae4ba 100644 --- a/src/renderers/dom/client/__tests__/validateDOMNesting-test.js +++ b/src/renderers/dom/client/__tests__/validateDOMNesting-test.js @@ -83,5 +83,7 @@ describe('ReactContextValidator', function() { expect(isTagStackValid(['table', 'tr'])).toBe(false); expect(isTagStackValid(['div', 'ul', 'li', 'div', 'li'])).toBe(false); expect(isTagStackValid(['div', 'html'])).toBe(false); + expect(isTagStackValid(['body', 'body'])).toBe(false); + expect(isTagStackValid(['svg', 'foreignObject', 'body', 'p'])).toBe(false); }); }); diff --git a/src/renderers/dom/client/validateDOMNesting.js b/src/renderers/dom/client/validateDOMNesting.js index 431d80fd2d..9422a079e1 100644 --- a/src/renderers/dom/client/validateDOMNesting.js +++ b/src/renderers/dom/client/validateDOMNesting.js @@ -208,6 +208,7 @@ if (__DEV__) { case 'rt': return impliedEndTags.indexOf(parentTag) === -1; + case 'body': case 'caption': case 'col': case 'colgroup': diff --git a/src/renderers/dom/client/wrappers/DisabledInputUtils.js b/src/renderers/dom/client/wrappers/DisabledInputUtils.js new file mode 100644 index 0000000000..37f8028351 --- /dev/null +++ b/src/renderers/dom/client/wrappers/DisabledInputUtils.js @@ -0,0 +1,50 @@ +/** + * 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 DisabledInputUtils + */ + +'use strict'; + +var disableableMouseListenerNames = { + onClick: true, + onDoubleClick: true, + onMouseDown: true, + onMouseMove: true, + onMouseUp: true, + + onClickCapture: true, + onDoubleClickCapture: true, + onMouseDownCapture: true, + onMouseMoveCapture: true, + onMouseUpCapture: true, +}; + +/** + * Implements a native component that does not receive mouse events + * when `disabled` is set. + */ +var DisabledInputUtils = { + getNativeProps: function(inst, props) { + if (!props.disabled) { + return props; + } + + // Copy the props, except the mouse listeners + var nativeProps = {}; + for (var key in props) { + if (!disableableMouseListenerNames[key] && props.hasOwnProperty(key)) { + nativeProps[key] = props[key]; + } + } + + return nativeProps; + }, +}; + +module.exports = DisabledInputUtils; diff --git a/src/renderers/dom/client/wrappers/ReactDOMButton.js b/src/renderers/dom/client/wrappers/ReactDOMButton.js index bf32c45600..961a1ebebc 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMButton.js +++ b/src/renderers/dom/client/wrappers/ReactDOMButton.js @@ -11,40 +11,14 @@ 'use strict'; -var mouseListenerNames = { - onClick: true, - onDoubleClick: true, - onMouseDown: true, - onMouseMove: true, - onMouseUp: true, - - onClickCapture: true, - onDoubleClickCapture: true, - onMouseDownCapture: true, - onMouseMoveCapture: true, - onMouseUpCapture: true, -}; +var DisabledInputUtils = require('DisabledInputUtils'); /** * Implements a