Merge branch 'master' into async-state

This commit is contained in:
Ben Alpert
2013-07-01 16:08:57 -07:00
122 changed files with 3196 additions and 2159 deletions
+2
View File
@@ -6,11 +6,13 @@ static
.grunt
_SpecRunner.html
build/
.module-cache
*.gem
docs/code
docs/_site
docs/.sass-cache
docs/css/react.css
docs/js/.module-cache
docs/js/JSXTransformer.js
docs/js/react.min.js
docs/js/docs.js
+5 -11
View File
@@ -3,7 +3,7 @@
var visitors = require('../vendor/fbtransform/visitors').transformVisitors;
var transform = require('../vendor/fbtransform/lib/transform').transform;
var debranch = require("../vendor/woodchipper").debranch;
var propagate = require("../vendor/constants").propagate;
require("commoner").resolve(function(id) {
var context = this;
@@ -25,18 +25,12 @@ require("commoner").resolve(function(id) {
}).process(function(id, source) {
var context = this;
var constants = context.config.constants || {};
// This is where JSX, ES6, etc. desugaring happens.
source = transform(visitors.react, source).code;
return context.makePromise(function(callback) {
var constants = context.config.constants || {};
// Debranching means removing any obviously dead code after
// replacing constant conditional expressions with literal
// (boolean) values.
debranch(constants, source, function(source) {
callback(null, source);
});
});
// Constant propagation means removing any obviously dead code after
// replacing constant expressions with literal (boolean) values.
return propagate(constants, source);
});
+7
View File
@@ -21,4 +21,11 @@
<li><a href="/react/docs/api.html"{% if page.id == 'docs-api' %} class="active"{% endif %}>API</a></li>
</ul>
</div>
<div class="nav-docs-section">
<h3>Appendix</h3>
<ul>
<li><a href="/react/docs/jsx-is-not-html.html"{% if page.id == 'docs-jsx-is-not-html' %} class="active"{% endif %}>JSX is not HTML</a></li>
</ul>
</div>
</div>
+1 -1
View File
@@ -20,6 +20,6 @@ sectionid: docs
{% endif %}
</div>
<div class="fb-comments" data-width="650" data-num-posts="10" data-href="/react{{ page.url }}"></div>
<div class="fb-comments" data-width="650" data-num-posts="10" data-href="{{ site.url }}{{ site.baseurl }}{{ page.url }}"></div>
</div>
</section>
+1 -1
View File
@@ -16,6 +16,6 @@ sectionid: blog
</div>
<div class="fb-like" data-send="true" data-width="650" data-show-faces="false"></div>
<div class="fb-comments" data-width="650" data-num-posts="10" data-href="/react{{ page.url }}"></div>
<div class="fb-comments" data-width="650" data-num-posts="10" data-href="{{ site.url }}{{ site.baseurl }}{{ page.url }}"></div>
</div>
</section>
+3 -3
View File
@@ -8,14 +8,14 @@ React was open sourced two weeks ago and it's time for a little round-up of what
## Khan Academy Question Editor
It looks like [Ben Alpert](http://benalpert.com/) is the first person outside of Facebook and Instagram to push React code to production. We are very grateful for his contributions in form of pull requests, bug reports and presence on IRC ([#reactjs on Freenode](irc://chat.freenode.net/reactjs)). Ben wrote about his experience using React on Quora:
It looks like [Ben Alpert](http://benalpert.com/) is the first person outside of Facebook and Instagram to push React code to production. We are very grateful for his contributions in form of pull requests, bug reports and presence on IRC ([#reactjs on Freenode](irc://chat.freenode.net/reactjs)). Ben wrote about his experience using React:
> I just rewrote a 2000-line project in React and have now made a handful of pull requests to React. Everything about React I've seen so far seems really well thought-out and I'm proud to be the first non-FB/IG production user of React.
>
> The project that I rewrote in React (and am continuing to improve) is the Khan Academy question editor which content creators can use to enter questions and hints that will be presented to students:
> <figure>[![](/react/img/blog/khan-academy-editor.png)](http://www.quora.com/React-JS-Library/How-is-Facebooks-React-JavaScript-library/answer/Ben-Alpert)</figure>
> <figure>[![](/react/img/blog/khan-academy-editor.png)](http://benalpert.com/2013/06/09/using-react-to-speed-up-khan-academy.html)</figure>
>
> [Read the full post...](http://www.quora.com/React-JS-Library/How-is-Facebooks-React-JavaScript-library/answer/Ben-Alpert)
> [Read the full post...](http://benalpert.com/2013/06/09/using-react-to-speed-up-khan-academy.html)
## Pimp my Backbone.View (by replacing it with React)
@@ -0,0 +1,71 @@
---
title: "Community Round-up #2"
layout: post
author: Vjeux
---
Since the launch we have received a lot of feedback and are actively working on React 0.4. In the meantime, here are the highlights of this week.
## Some quick thoughts on React
[Andrew Greig](http://www.andrewgreig.com/) made a blog post that gives a high level description of what React is.
> I have been using Facebooks recently released Javascript framework called React.js for the last few days and have managed to obtain a rather high level understanding of how it works and formed a good perspective on how it fits in to the entire javascript framework ecosystem.
>
> Basically, React is not an MVC framework. It is not a replacement for Backbone or Knockout or Angular, instead it is designed to work with existing frameworks and help extend their functionality.
>
> It is designed for building big UIs. The type where you have lots of reusable components that are handling events and presenting and changing some backend data. In a traditional MVC app, React fulfils the role of the View. So you would still need to handle the Model and Controller on your own.
>
> I found the best way to utilise React was to pair it with Backbone, with React replacing the Backbone View, or to write your own Model/Data object and have React communicate with that.
>
> [Read the full post...](http://www.andrewgreig.com/637/)
## React and Socket.IO Chat Application
[Danial Khosravi](http://danialk.github.io/) made a real-time chat application that interacts with the back-end using Socket.IO.
> A week ago I was playing with AngularJS and [this little chat application](https://github.com/btford/angular-socket-io-im) which uses socket.io and nodejs for realtime communication. Yesterday I saw a post about ReactJS in [EchoJS](http://www.echojs.com/) and started playing with this UI library. After playing a bit with React, I decided to write and chat application using React and I used Bran Ford's Backend for server side of this little app.
> <figure>[![](/react/img/blog/chatapp.png)](http://danialk.github.io/blog/2013/06/16/reactjs-and-socket-dot-io-chat-application/)</figure>
>
> [Read the full post...](http://danialk.github.io/blog/2013/06/16/reactjs-and-socket-dot-io-chat-application/)
## React and Other Frameworks
[Pete Hunt](http://www.petehunt.net/blog/) wrote an answer on Quora comparing React and Angular directives. At the end, he explains how you can make an Angular directive that is in fact being rendered with React.
> To set the record straight: React components are far more powerful than Angular templates; they should be compared with Angular's directives instead. So I took the first Google hit for "AngularJS directive tutorial" (AngularJS Directives Tutorial - Fundoo Solutions), rewrote it in React and compared them. [...]
>
> We've designed React from the beginning to work well with other libraries. Angular is no exception. Let's take the original Angular example and use React to implement the fundoo-rating directive.
>
> [Read the full post...](http://www.quora.com/Pete-Hunt/Posts/Facebooks-React-vs-AngularJS-A-Closer-Look)
In the same vein, [Markov Twain](https://twitter.com/markov_twain/status/345702941845499906) re-implemented the examples on the front-page [with Ember](http://jsbin.com/azihiw/2/edit) and [Vlad Yazhbin](https://twitter.com/vla) re-implemented the tutorial [with Angular](http://jsfiddle.net/vla/Cdrse/).
## Web Components: React & x-tags
Mozilla and Google are actively working on Web Components. [Vjeux](http://blog.vjeux.com/) wrote a proof of concept that shows how to implement them using React.
> Using [x-tags](http://www.x-tags.org/) from Mozilla, we can write custom tags within the DOM. This is a great opportunity to be able to write reusable components without being tied to a particular library. I wrote [x-react](https://github.com/vjeux/react-xtags/) to have them being rendered in React.
> <figure>[![](/react/img/blog/xreact.png)](http://blog.vjeux.com/2013/javascript/custom-components-react-x-tags.html)</figure>
>
> [Read the full post...](http://blog.vjeux.com/2013/javascript/custom-components-react-x-tags.html)
## React TodoMVC Example
[TodoMVC.com](http://todomvc.com/) is a website that collects various implementations of the same basic Todo app. [Pete Hunt](http://www.petehunt.net/blog/) wrote an idiomatic React version.
> Developers these days are spoiled with choice when it comes to selecting an MV* framework for structuring and organizing their JavaScript web apps.
>
> To help solve this problem, we created TodoMVC - a project which offers the same Todo application implemented using MV* concepts in most of the popular JavaScript MV* frameworks of today.
> <figure>[![](/react/img/blog/todomvc.png)](http://todomvc.com/labs/architecture-examples/react/)</figure>
>
> [Read the source code...](https://github.com/tastejs/todomvc/tree/gh-pages/labs/architecture-examples/react)
## JSX is not HTML
Many of you pointed out differences between JSX and HTML. In order to clear up some confusion, we have added some documentation that covers the four main differences:
- [Whitespace removal](http://facebook.github.io/react/docs/jsx-is-not-html.html)
- [HTML Entities](http://facebook.github.io/react/docs/jsx-is-not-html.html)
- [Comments](http://facebook.github.io/react/docs/jsx-is-not-html.html)
- [Custom HTML Attributes](http://facebook.github.io/react/docs/jsx-is-not-html.html)
+24
View File
@@ -0,0 +1,24 @@
---
title: "React v0.3.3"
layout: post
author: Paul O'Shannessy
---
We have a ton of great stuff coming in v0.4, but in the meantime we're releasing v0.3.3. This release addresses some small issues people were having and simplifies our tools to make them easier to use.
## react-tools
* Upgrade Commoner so `require` statements are no longer relativized when passing through the transformer. This was a feature needed when building React, but doesn't translate well for other consumers of `bin/jsx`.
* Upgraded our dependencies on Commoner and Recast so they use a different directory for their cache.
* Freeze our esprima dependency.
## React
* Allow reusing the same DOM node to render different components. e.g. `React.renderComponent(<div/>, domNode); React.renderComponent(<span/>, domNode);` will work now.
## JSXTransformer
* Improved the in-browser transformer so that transformed scripts will execute in the expected scope. The allows components to be defined and used from separate files.
@@ -0,0 +1,91 @@
---
title: "Community Round-up #3"
layout: post
author: Vjeux
---
The highlight of this week is that an interaction-heavy app has been ported to React. React components are solving issues they had with nested views.
## Moving From Backbone To React
[Clay Allsopp](http://twitter.com/clayallsopp) successfuly ported [Propeller](http://usepropeller.com/blog/posts/from-backbone-to-react/), a fairly big, interaction-heavy JavaScript app, to React.
> [<img style="float: right; margin: 0 0 10px 10px;" src="/react/img/blog/propeller-logo.png" />](http://usepropeller.com/blog/posts/from-backbone-to-react/)Subviews involve a lot of easy-to-forget boilerplate that Backbone (by design) doesn't automate. Libraries like Backbone.Marionette offer more abstractions to make view nesting easier, but they're all limited by the fact that Backbone delegates how and went view-document attachment occurs to the application code.
>
> React, on the other hand, manages the DOM and only exposes real nodes at select points in its API. The "elements" you code in React are actually objects which wrap DOM nodes, not the actual objects which get inserted into the DOM. Internally, React converts those abstractions into actual DOMElements and fills out the document accordingly. [...]
>
> We moved about 20 different Backbone view classes to React over the past few weeks, including the live-preview pane that you see in our little iOS demo. Most importantly, it's allowed us to put energy into making each component work great on its own, instead of spending extra cycles to ensure they function in unison. For that reason, we think React is a more scalable way to build view-intensive apps than Backbone alone, and it doesn't require you to drop-everything-and-refactor like a move to Ember or Angular would demand.
>
> [Read the full post...](http://usepropeller.com/blog/posts/from-backbone-to-react/)
## Grunt Task for JSX
[Eric Clemmons](http://ericclemmons.github.io/) wrote a task for [Grunt](http://gruntjs.com/) that applies the JSX transformation to your Javascript files. It also works with [Browserify](http://browserify.org/) if you want all your files to be concatenated and minified together.
> Grunt task for compiling Facebook React's .jsx templates into .js
>
> ```javascript
grunt.initConfig({
react: {
app: {
options: { extension: 'js' },
files: { 'path/to/output/dir': 'path/to/jsx/templates/dir' }
```
>
> It also works great with `grunt-browserify`!
>
> ```javascript
browserify: {
options: {
transform: [ require('grunt-react').browserify ]
},
app: {
src: 'path/to/source/main.js',
dest: 'path/to/target/output.js'
```
>
> [Check out the project ...](https://github.com/ericclemmons/grunt-react)
## Backbone/Handlebars Nested Views
[Joel Burget](http://joelburget.com/) wrote a blog post talking about the way we would write React-like components in Backbone and Handlebars.
> The problem here is that we're trying to maniplate a tree, but there's a textual layer we have to go through. Our views are represented as a tree - the subviews are children of CommentCollectionView - and they end up as part of a tree in the DOM. But there's a Handlebars layer in the middle (which deals in flat strings), so the hierarchy must be destructed and rebuilt when we render.
>
> What does it take to render a collection view? In the Backbone/Handlebars view of the world you have to render the template (with stubs), render each subview which replaces a stub, and keep a reference to each subview (or anything within the view that could change in the future).
>
> So while our view is conceptually hierarchical, due to the fact that it has to go through a flat textual representation, we need to do a lot of extra work to reassemble that structure after rendering.
>
> [Read the full post...](http://joelburget.com/react/)
## JSRomandie Meetup
[Renault John Lecoultre](https://twitter.com/renajohn/) from [BugBuster](http://www.bugbuster.com) did a React introduction talk at a JS meetup called [JS Romandie](https://twitter.com/jsromandie) last week.
<script async class="speakerdeck-embed" data-id="888a9d50c01b01300df36658d0894ac1" data-ratio="1.33333333333333" src="//speakerdeck.com/assets/embed.js"></script>
## CoffeeScript integration
[Vjeux](http://blog.vjeux.com/) used the fact that JSX is just a syntactic sugar on-top of regular JS to rewrite the React front-page examples in CoffeeScript.
> Multiple people asked what's the story about JSX and CoffeeScript. There is no JSX pre-processor for CoffeeScript and I'm not aware of anyone working on it. Fortunately, CoffeeScript is pretty expressive and we can play around the syntax to come up with something that is usable.
>
> ```javascript
{div, h3, textarea} = React.DOM
(div {className: 'MarkdownEditor'}, [
(h3 {}, 'Input'),
(textarea {onKeyUp: @handleKeyUp, ref: 'textarea'},
@state.value
)
])
```
>
> [Read the full post...](http://blog.vjeux.com/2013/javascript/react-coffeescript.html)
## Tutorial in Plain Javascript
We've seen a lot of people comparing React with various frameworks. [Ricardo Tomasi](http://ricardo.cc/) decided to re-implement the tutorial without any framework, just plain Javascript.
> Facebook & Instagram launched the React framework and an accompanying tutorial. Developer Vlad Yazhbin decided to rewrite that using AngularJS. The end result is pretty neat, but if you're like me you will not actually appreciate the HTML speaking for itself and doing all the hard work. So let's see what that looks like in plain javascript.
>
> [Read the full post...](http://ricardo.cc/2013/06/07/react-tutorial-rewritten-in-plain-javascript.html)
+1
View File
@@ -3,6 +3,7 @@ id: docs-api
title: React API
layout: docs
prev: mixins.html
next: jsx-is-not-html.html
---
## React
+1 -1
View File
@@ -22,7 +22,7 @@ In this one simple line, we have accomplished the following:
- The component is **mounted** into `document.body`.
**Mounting** is the process of initializing a React component by creating its
DOM nodes and inserting the them into a supplied container node.
DOM nodes and inserting them into a supplied container node.
At this point, the entire page consists of a single `<div>` with "Hello,
world!".
+4
View File
@@ -88,6 +88,10 @@ React.renderComponent(
);
```
> Note:
>
> The comment parser is very strict right now, in order for it to pick up the `@jsx` modifier, two conditions are required. The `@jsx` comment block must be the first comment on the file. The comment must start with `/**` (`/*` and `//` will not work). If the parser can't find the `@jsx` comment, it will output the file without transforming it.
Update your HTML file as below:
```html{6,10}
+95
View File
@@ -0,0 +1,95 @@
---
id: docs-jsx-is-not-html
title: JSX is not HTML
description: Differences between JSX and HTML.
layout: docs
prev: api.html
---
JSX looks like HTML but there are some important differences you may run into.
## Whitespace removal
JSX doesn't follow the same whitespace elimination rules as HTML. JSX removes all whitespace between two curly braces expressions. If you want to have whitespace, simply add `{' '}`.
```javascript
<div>{this.props.name} {' '} {this.props.surname}</div>
```
Follow [Issue #65](https://github.com/facebook/react/issues/65) for discussion on this behavior.
## HTML Entities
You can insert HTML entities within literal text in JSX:
```javascript
<div>First &middot; Second</div>
```
If you want to display an HTML entity within dynamic content, you will run into double escaping issues as React escapes all the strings you are displaying in order to prevent a wide range of XSS attacks by default.
```javascript
// Bad: It displays "First &middot; Second"
<div>{'First &middot; Second'}</div>
```
There are various ways to work-around this issue. The easiest one is to write unicode character directly in Javascript. You need to make sure that the file is saved as UTF-8 and that the proper UTF-8 directives are set so the browser will display it correctly.
```javascript
<div>{'First · Second'}</div>
```
A safer alternative is to find the [unicode number corresponding to the entity](http://www.fileformat.info/info/unicode/char/b7/index.htm) and use it inside of a JavaScript string.
```javascript
<div>{'First \u00b7 Second'}</div>
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>
```
You can use mixed arrays with strings and JSX elements.
```javascript
<div>{['First ', <span>&middot;</span>, ' Second']}</div>
```
As a last resort, you always have the ability to insert raw HTML.
```javascript
<div dangerouslySetInnerHTML={{'{{'}}__html: 'First &middot; Second'}} />
```
## Comments
JSX supports both single-line and multi-line JavaScript comments within a tag declaration:
```javascript
<div // This is a single-line comment:
/*
And a multi-line
comment
*/
/>
```
As of React 0.3, there is no good way to insert comments within the children section. [Issue #82](https://github.com/facebook/react/issues/82) is tracking progress to enable the following:
```javascript
// Note: This is not implemented yet!
<div>
{/* This is a comment */}
</div>
```
## Custom HTML Attributes
If you pass properties to native HTML elements that do not exist in the HTML specification, React will not render them. If you want to use a custom attribute, you should prefix it with `data-`.
```javascript
<div data-custom-attribute="foo" />
```
[Web Accessibility](http://www.w3.org/WAI/intro/aria) attributes starting with `aria-` will be rendered properly.
```javascript
<div aria-hidden={true} />
```
+1 -1
View File
@@ -53,7 +53,7 @@ var app = Nav({color:'blue'}, Profile({}, 'click'));
```
Use the [JSX Compiler](/react/jsx-compiler.html) to try out JSX and see how it
desguars into native JavaScript.
desugars into native JavaScript.
If you want to use JSX, the [Getting Started](getting-started.html) guide shows
how to setup compilation.
+5 -5
View File
@@ -262,8 +262,8 @@ So far we've been inserting the comments directly in the source code. Instead, l
```javascript
// tutorial8.js
var data = [
{author: 'Pete Hunt', text: 'This is one comment'},
{author: 'Jordan Walke', text: 'This is *another* comment'}
{author: "Pete Hunt", text: "This is one comment"},
{author: "Jordan Walke", text: "This is *another* comment"}
];
```
@@ -357,8 +357,8 @@ When the component is first created, we want to GET some JSON from the server an
```javascript
// tutorial13.json
[
{'author': 'Pete Hunt', 'text': 'This is one comment'},
{'author': 'Jordan Walke', 'text': 'This is *another* comment'}
{"author": "Pete Hunt", "text": "This is one comment"},
{"author": "Jordan Walke", "text": "This is *another* comment"}
]
```
@@ -435,7 +435,7 @@ React.renderComponent(
```
All we have done here is move the AJAX call to a separate method and call it when the component is first loaded and every 60 seconds after that. Try running this in your browser and changing the `comments.json` file; within 5 seconds, the changes will show!
All we have done here is move the AJAX call to a separate method and call it when the component is first loaded and every 5 seconds after that. Try running this in your browser and changing the `comments.json` file; within 5 seconds, the changes will show!
## Adding new comments
+2
View File
@@ -49,6 +49,8 @@ $ npm install -g react-tools
## Release Notes
**0.3.3** Upgrade `react-tools` dependencies, improve in-browser JSX transformer, make `React.renderComponent` more versatile. [Details...](blog/2013/06/20/react-v0-3-3.html)
**0.3.2** Improve compatibility of JSX Transformer; make `react-tools` compatible with [browserify](https://github.com/substack/node-browserify)
**0.3.1** Fix `react-tools` module
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

+1 -1
View File
@@ -12,6 +12,6 @@ module.exports = {
return gzip.zip(contents, {}).length;
}
},
cache: "build/.sizecache.json"
cache: ".grunt/sizecache.json"
}
};
+18 -4
View File
@@ -1,16 +1,17 @@
'use strict';
var exec = require("child_process").exec;
var expand = require("grunt").file.expand;
var grunt = require("grunt");
var expand = grunt.file.expand;
var spawn = grunt.util.spawn;
module.exports = function() {
var done = this.async();
var config = this.data;
var args = [
"bin/jsx",
"--cache-dir", ".module-cache",
"--relativize",
"--follow-requires",
config.sourceDir,
config.outputDir
];
@@ -25,5 +26,18 @@ module.exports = function() {
args.push.apply(args, rootIDs);
args.push("--config", config.configFile);
exec(args.join(" "), done);
var child = spawn({
cmd: "bin/jsx",
args: args
}, function(error, result, code) {
if (error) {
grunt.log.error(error);
done(false);
} else {
done();
}
});
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
};
+2 -3
View File
@@ -2,13 +2,11 @@
var assert = require("assert");
var path = require("path");
var fs = require("fs");
var tmp = require("tmp");
var grunt = require("grunt");
var spawn = grunt.util.spawn;
module.exports = function() {
var config = this.data;
var done = this.async();
function run(cmd, args, opts, callback) {
@@ -25,7 +23,8 @@ module.exports = function() {
grunt.log.writeln("> " + cmd + " " + args.join(" "));
var proc = spawn({
// var proc =
spawn({
cmd: cmd,
args: args,
opts: opts
+1 -1
View File
@@ -38,7 +38,7 @@ function run(config, done) {
}
args.push("--tests");
var tests = grunt.file.expand({
grunt.file.expand({
nonull: true,
cwd: "src"
}, config.tests || []).forEach(function(file) {
+8 -5
View File
@@ -1,6 +1,6 @@
{
"name": "react-tools",
"version": "0.3.2",
"version": "0.4.0a",
"keywords": [
"react",
"jsx",
@@ -21,7 +21,7 @@
"bin/jsx",
"build/modules/",
"vendor/fbtransform/",
"vendor/woodchipper.js"
"vendor/constants.js"
],
"main": "main.js",
"bin": {
@@ -36,9 +36,9 @@
},
"dependencies": {
"base62": "~0.1.1",
"commoner": "~0.7.0",
"esprima": "git://github.com/facebook/esprima#fb-harmony",
"recast": "~0.4.5",
"commoner": "~0.8.0",
"esprima": "https://github.com/facebook/esprima/tarball/a3e0ea3979eb8d54d8bfade220c272903f928b1e",
"recast": "~0.4.8",
"source-map": "~0.1.22"
},
"devDependencies": {
@@ -58,5 +58,8 @@
"tmp": "~0.0.18",
"grunt-contrib-compress": "~0.5.1"
},
"engines": {
"node": ">=0.10.0"
},
"preferGlobal": true
}
+1 -1
View File
@@ -31,7 +31,7 @@ ReactDefaultInjection.inject();
var React = {
DOM: ReactDOM,
Props: ReactProps,
Props: ReactProps,
initializeTouchEvents: function(shouldUseTouch) {
ReactMount.useTouchEvents = shouldUseTouch;
},
+118 -98
View File
@@ -16,11 +16,13 @@
* @providesModule ReactComponent
*/
/*jslint evil: true */
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactID = require('ReactID');
var ReactMount = require('ReactMount');
var ReactOwner = require('ReactOwner');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
@@ -35,6 +37,12 @@ var merge = require('merge');
*/
var OWNER = '{owner}';
/**
* Props key that determines if a component's key was already validated.
* @private
*/
var IS_KEY_VALIDATED = '{is.key.validated}';
/**
* Every React component is in one of these life cycles.
*/
@@ -55,52 +63,70 @@ var ComponentLifeCycle = keyMirror({
* This allows us to keep track of children between updates.
*/
var CHILD_HAS_NO_IDENTITY =
'You are passing a dynamic array of children. You should set the ' +
'property "key" to a string that uniquely identifies each child.';
var HAS_WARNED = false;
var ownerHasWarned = {};
/**
* Helpers for flattening child arguments onto a new array or use an existing
* one.
* Warn if the component doesn't have an explicit key assigned to it.
* This component is in an array. The array could grow and shrink or be
* reordered. All children, that hasn't already been validated, are required to
* have a "key" property assigned to it.
*
* @internal
* @param {ReactComponent} component Component that requires a key.
*/
function validateExplicitKey(component) {
if (component[IS_KEY_VALIDATED] || component.props.key != null) {
return;
}
component[IS_KEY_VALIDATED] = true;
function isEmptyChild(child) {
return child == null || typeof child === 'boolean';
// We can't provide friendly warnings for top level components.
if (!ReactCurrentOwner.current) {
return;
}
// Name of the component whose render method tried to pass children.
var currentName = ReactCurrentOwner.current.constructor.displayName;
if (ownerHasWarned.hasOwnProperty(currentName)) {
return;
}
ownerHasWarned[currentName] = true;
var message = 'Each child in an array should have a unique "key" prop. ' +
'Check the render method of ' + currentName + '.';
if (!component.isOwnedBy(ReactCurrentOwner.current)) {
// Name of the component that originally created this child.
var childOwnerName =
component.props[OWNER] && component.props[OWNER].constructor.displayName;
// Usually the current owner is the offender, but if it accepts
// children as a property, it may be the creator of the child that's
// responsible for assigning it a key.
message += ' It was passed a child from ' + childOwnerName + '.';
}
global.console && console.warn && console.warn(message);
}
function assignKey(setKey, child, index) {
if (ReactComponent.isValidComponent(child)) {
var key = child.props.key;
if (__DEV__) {
if (!HAS_WARNED && !key) {
HAS_WARNED = true;
console && console.warn && console.warn(CHILD_HAS_NO_IDENTITY);
/**
* Ensure that every component either is passed in a static location or, if
* if it's passed in an array, has an explicit key property defined.
*
* @internal
* @param {*} component Statically passed child of any type.
* @return {boolean}
*/
function validateChildKeys(component) {
if (Array.isArray(component)) {
for (var i = 0; i < component.length; i++) {
var child = component[i];
if (ReactComponent.isValidComponent(child)) {
validateExplicitKey(child);
}
}
child._key = (setKey ? setKey + ':' : '') + (key || ('' + index));
}
}
function tryToReuseArray(children) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (isEmptyChild(child)) return false;
assignKey('0', child, i);
}
return true;
}
function appendNestedChildren(parentKey, sourceArray, targetArray) {
for (var i = 0; i < sourceArray.length; i++) {
var child = sourceArray[i];
if (isEmptyChild(child)) continue;
assignKey(parentKey, child, i);
// TODO: Invalid components like strings could possibly need
// keys assigned to them here. Usually they're not stateful but
// CSS transitions and special events could make them stateful.
targetArray.push(child);
} else if (ReactComponent.isValidComponent(component)) {
// This component was passed in a valid location.
component[IS_KEY_VALIDATED] = true;
}
}
@@ -144,6 +170,23 @@ var ReactComponent = {
);
},
/**
* Generate a key string that identifies a component within a set.
*
* @param {*} component A component that could contain a manual key.
* @param {number} index Index that is used if a manual key is not provided.
* @return {string}
* @internal
*/
getKey: function(component, index) {
if (component && component.props && component.props.key != null) {
// Explicit key
return '' + component.props.key;
}
// Implicit key determined by the index in the set
return '' + index;
},
/**
* @internal
*/
@@ -202,29 +245,16 @@ var ReactComponent = {
/**
* Returns the DOM node rendered by this component.
*
* @return {?DOMElement} The root node of this component.
* @return {DOMElement} The root node of this component.
* @final
* @protected
*/
getDOMNode: function() {
invariant(
ExecutionEnvironment.canUseDOM,
'getDOMNode(): The DOM is not supported in the current environment.'
);
invariant(
this.isMounted(),
'getDOMNode(): A component must be mounted to have a DOM node.'
);
var rootNode = this._rootNode;
if (!rootNode) {
rootNode = document.getElementById(this._rootNodeID);
if (!rootNode) {
// TODO: Log the frequency that we reach this path.
rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
}
this._rootNode = rootNode;
}
return rootNode;
return ReactID.getNode(this._rootNodeID);
},
/**
@@ -280,53 +310,23 @@ var ReactComponent = {
// All components start unmounted.
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
// Children can be either an array or more than one argument
if (arguments.length < 2) {
return;
}
if (arguments.length === 2) {
// A single string or number child is treated as content, not an array.
var type = typeof children;
if (children == null || type === 'string' || type === 'number') {
this.props.children = children;
return;
// Children can be more than one argument
var childrenLength = arguments.length - 1;
if (childrenLength === 1) {
if (__DEV__) {
validateChildKeys(children);
}
// A single array can be reused if it's already flat
if (Array.isArray(children) && tryToReuseArray(children)) {
this.props.children = children;
return;
}
}
// Subsequent arguments are rolled into one child array. Array arguments
// are flattened onto it. This is inlined to avoid extra heap allocation.
var targetArray = null;
for (var i = 1; i < arguments.length; i++) {
var child = arguments[i];
if (Array.isArray(child)) {
if (child.length === 0) continue;
if (targetArray === null) targetArray = [];
appendNestedChildren('' + (i - 1), child, targetArray);
} else if (!isEmptyChild(child)) {
if (ReactComponent.isValidComponent(child)) {
// This is a static node and therefore safe to key by index.
// No warning necessary.
child._key = child.props.key || ('' + (i - 1));
this.props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
if (__DEV__) {
validateChildKeys(arguments[i + 1]);
}
if (targetArray === null) targetArray = [];
targetArray.push(child);
childArray[i] = arguments[i + 1];
}
this.props.children = childArray;
}
this.props.children = targetArray;
},
/**
@@ -376,7 +376,7 @@ var ReactComponent = {
if (props.ref != null) {
ReactOwner.removeComponentAsRefFrom(this, props.ref, props[OWNER]);
}
this._rootNode = null;
ReactID.purgeID(this._rootNodeID);
this._rootNodeID = null;
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
},
@@ -447,6 +447,10 @@ var ReactComponent = {
container,
transaction,
shouldReuseMarkup) {
invariant(
container && container.nodeType === 1,
'mountComponentIntoNode(...): Target container is not a DOM element.'
);
var renderStart = Date.now();
var markup = this.mountComponent(rootID, transaction);
ReactMount.totalInstantiationTime += (Date.now() - renderStart);
@@ -500,6 +504,22 @@ var ReactComponent = {
*/
isOwnedBy: function(owner) {
return this.props[OWNER] === owner;
},
/**
* Gets another component, that shares the same owner as this one, by ref.
*
* @param {string} ref of a sibling Component.
* @return {?ReactComponent} the actual sibling Component.
* @final
* @internal
*/
getSiblingByRef: function(ref) {
var owner = this.props[OWNER];
if (!owner || !owner.refs) {
return null;
}
return owner.refs[ref];
}
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactComponentWithPureRender
* @providesModule ReactComponentWithPureRenderMixin
*/
"use strict";
@@ -27,9 +27,10 @@
*
* Example:
*
* var ReactComponentWithPureRender = require('ReactComponentWithPureRender');
* var ReactComponentWithPureRender =
* require('ReactComponentWithPureRenderMixin');
* React.createClass({
* mixins: [ReactComponentWithPureRender],
* mixins: [ReactComponentWithPureRenderMixin],
*
* render: function() {
* return <div className={this.props.className}>foo</div>;
@@ -41,7 +42,7 @@
* differences. Only mixin to components which have simple props and state, or
* use `forceUpdate()` when you know deep data structures have changed.
*/
var ReactComponentWithPureRender = {
var ReactComponentWithPureRenderMixin = {
shouldComponentUpdate: function(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
@@ -74,4 +75,4 @@ function shallowEqual(objA, objB) {
return true;
}
module.exports = ReactComponentWithPureRender;
module.exports = ReactComponentWithPureRenderMixin;
+147 -129
View File
@@ -28,6 +28,19 @@ var keyMirror = require('keyMirror');
var merge = require('merge');
var mixInto = require('mixInto');
function invokeWithWarning(func) {
return function() {
console && console.warn && console.warn(
'You have invoked a method that is automatically bound, before the ' +
'instance has been mounted. There is nothing conceptually wrong with ' +
'this, but since this method will be replaced with a new version once' +
' the component is mounted - you should be aware of the fact that ' +
'this method will soon be replaced.'
);
return func.apply(this, arguments);
};
}
/**
* Policies that describe methods in `ReactCompositeComponentInterface`.
*/
@@ -92,9 +105,8 @@ var ReactCompositeComponentInterface = {
// ==== Definition methods ====
/**
* Invoked when the component is mounted and whenever new props are received.
* Values in the returned mapping will be set on `this.props` if that prop is
* not specified (i.e. using an `in` check).
* Invoked when the component is mounted. Values in the mapping will be set on
* `this.props` if that prop is not specified (i.e. using an `in` check).
*
* This method is invoked before `getInitialState` and therefore cannot rely
* on `this.state` or use `this.setState`.
@@ -283,6 +295,48 @@ var RESERVED_SPEC_KEYS = {
}
};
function validateMethodOverride(proto, name) {
var specPolicy = ReactCompositeComponentInterface[name];
// Disallow overriding of base class methods unless explicitly allowed.
if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
invariant(
specPolicy === SpecPolicy.OVERRIDE_BASE,
'ReactCompositeComponentInterface: You are attempting to override ' +
'`%s` from your class specification. Ensure that your method names ' +
'do not overlap with React methods.',
name
);
}
// Disallow defining methods more than once unless explicitly allowed.
if (proto.hasOwnProperty(name)) {
invariant(
specPolicy === SpecPolicy.DEFINE_MANY,
'ReactCompositeComponentInterface: You are attempting to define ' +
'`%s` on your component more than once. This conflict may be due ' +
'to a mixin.',
name
);
}
}
function validateLifeCycleOnReplaceState(instance) {
var compositeLifeCycleState = instance._compositeLifeCycleState;
invariant(
instance.isMounted() ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'replaceState(...): Can only update a mounted or mounting component.'
);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
'replaceState(...): Cannot update while unmounting component or during ' +
'an existing state transition (such as within `render`).'
);
}
/**
* Custom version of `mixInto` which handles policy validation and reserved
* specification keys when building `ReactCompositeComponent` classses.
@@ -290,58 +344,47 @@ var RESERVED_SPEC_KEYS = {
function mixSpecIntoComponent(Constructor, spec) {
var proto = Constructor.prototype;
for (var name in spec) {
if (!spec.hasOwnProperty(name)) {
var property = spec[name];
if (!spec.hasOwnProperty(name) || !property) {
continue;
}
var property = spec[name];
var specPolicy = ReactCompositeComponentInterface[name];
// Disallow overriding of base class methods unless explicitly allowed.
if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
invariant(
specPolicy === SpecPolicy.OVERRIDE_BASE,
'ReactCompositeComponentInterface: You are attempting to override ' +
'`%s` from your class specification. Ensure that your method names ' +
'do not overlap with React methods.',
name
);
}
// Disallow using `React.autoBind` on internal methods.
if (specPolicy != null) {
invariant(
!property || !property.__reactAutoBind,
'ReactCompositeComponentInterface: You are attempting to use ' +
'`React.autoBind` on `%s`, a method that is internal to React.' +
'Internal methods are called with the component as the context.',
name
);
}
// Disallow defining methods more than once unless explicitly allowed.
if (proto.hasOwnProperty(name)) {
invariant(
specPolicy === SpecPolicy.DEFINE_MANY,
'ReactCompositeComponentInterface: You are attempting to define ' +
'`%s` on your component more than once. This conflict may be due ' +
'to a mixin.',
name
);
}
validateMethodOverride(proto, name);
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
RESERVED_SPEC_KEYS[name](Constructor, property);
} else if (property && property.__reactAutoBind) {
if (!proto.__reactAutoBindMap) {
proto.__reactAutoBindMap = {};
}
proto.__reactAutoBindMap[name] = property.__reactAutoBind;
} else if (proto.hasOwnProperty(name)) {
// For methods which are defined more than once, call the existing methods
// before calling the new property.
proto[name] = createChainedFunction(proto[name], property);
} else {
proto[name] = property;
// Setup methods on prototype:
// The following member methods should not be automatically bound:
// 1. Expected ReactCompositeComponent methods (in the "interface").
// 2. Overridden methods (that were mixed in).
var isCompositeComponentMethod = name in ReactCompositeComponentInterface;
var isInherited = name in proto;
var markedDontBind = property.__reactDontBind;
var isFunction = typeof property === 'function';
var shouldAutoBind =
isFunction &&
!isCompositeComponentMethod &&
!isInherited &&
!markedDontBind;
if (shouldAutoBind) {
if (!proto.__reactAutoBindMap) {
proto.__reactAutoBindMap = {};
}
proto.__reactAutoBindMap[name] = property;
proto[name] = property;
if (__DEV__) {
proto[name] = invokeWithWarning(property);
}
} else {
if (isInherited) {
// For methods which are defined more than once, call the existing
// methods before calling the new property.
proto[name] = createChainedFunction(proto[name], property);
} else {
proto[name] = property;
}
}
}
}
}
@@ -355,13 +398,9 @@ function mixSpecIntoComponent(Constructor, spec) {
* @private
*/
function createChainedFunction(one, two) {
return function chainedFunction(a, b, c, d, e, tooMany) {
invariant(
typeof tooMany === 'undefined',
'Chained function can only take a maximum of 5 arguments.'
);
one.call(this, a, b, c, d, e);
two.call(this, a, b, c, d, e);
return function chainedFunction() {
one.apply(this, arguments);
two.apply(this, arguments);
};
}
@@ -370,7 +409,26 @@ function createChainedFunction(one, two) {
* `this._compositeLifeCycleState` (which can be null).
*
* This is different from the life cycle state maintained by `ReactComponent` in
* `this._lifeCycleState`.
* `this._lifeCycleState`. The following diagram shows how the states overlap in
* time. There are times when the CompositeLifeCycle is null - at those times it
* is only meaningful to look at ComponentLifeCycle alone.
*
* Top Row: ReactComponent.ComponentLifeCycle
* Low Row: ReactComponent.CompositeLifeCycle
*
* +-------+------------------------------------------------------+--------+
* | UN | MOUNTED | UN |
* |MOUNTED| | MOUNTED|
* +-------+------------------------------------------------------+--------+
* | ^--------+ +------+ +------+ +------+ +--------^ |
* | | | | | | | | | | | |
* | 0--|MOUNTING|-0-|RECEIV|-0-|RECEIV|-0-|RECEIV|-0-| UN |--->0 |
* | | | |PROPS | | PROPS| | STATE| |MOUNTING| |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +--------+ +------+ +------+ +------+ +--------+ |
* | | | |
* +-------+------------------------------------------------------+--------+
*/
var CompositeLifeCycle = keyMirror({
/**
@@ -416,6 +474,17 @@ var ReactCompositeComponentMixin = {
this._compositeLifeCycleState = null;
},
/**
* Checks whether or not this composite component is mounted.
* @return {boolean} True if mounted, false otherwise.
* @protected
* @final
*/
isMounted: function() {
return ReactComponent.Mixin.isMounted.call(this) &&
this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING;
},
/**
* Initializes the component, renders markup, and registers event listeners.
*
@@ -427,11 +496,9 @@ var ReactCompositeComponentMixin = {
*/
mountComponent: function(rootID, transaction) {
ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
// Unset `this._lifeCycleState` until after this method is finished.
this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
this._processProps(this.props);
if (this.__reactAutoBindMap) {
@@ -455,15 +522,11 @@ var ReactCompositeComponentMixin = {
// Done with mounting, `setState` will now trigger UI changes.
this._compositeLifeCycleState = null;
this._lifeCycleState = ReactComponent.LifeCycle.MOUNTED;
var html = this._renderedComponent.mountComponent(rootID, transaction);
var markup = this._renderedComponent.mountComponent(rootID, transaction);
if (this.componentDidMount) {
transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
}
return html;
return markup;
},
/**
@@ -479,6 +542,8 @@ var ReactCompositeComponentMixin = {
}
this._compositeLifeCycleState = null;
this._defaultProps = null;
ReactComponent.Mixin.unmountComponent.call(this);
this._renderedComponent.unmountComponent();
this._renderedComponent = null;
@@ -554,18 +619,7 @@ var ReactCompositeComponentMixin = {
*/
replaceState: function(completeState, callback) {
var compositeLifeCycleState = this._compositeLifeCycleState;
invariant(
this.isMounted() ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'replaceState(...): Can only update a mounted (or mounting) component.'
);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
'replaceState(...): Cannot update while unmounting component or during ' +
'an existing state transition (such as within `render`).'
);
validateLifeCycleOnReplaceState.call(null, this);
this._pendingState = completeState;
// Do not trigger a state transition if we are in the middle of mounting or
@@ -586,7 +640,6 @@ var ReactCompositeComponentMixin = {
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
this._compositeLifeCycleState = null;
}
@@ -603,12 +656,10 @@ var ReactCompositeComponentMixin = {
*/
_processProps: function(props) {
var propName;
if (this.getDefaultProps) {
var defaultProps = this.getDefaultProps();
for (propName in defaultProps) {
if (!(propName in props)) {
props[propName] = defaultProps[propName];
}
var defaultProps = this._defaultProps;
for (propName in defaultProps) {
if (!(propName in props)) {
props[propName] = defaultProps[propName];
}
}
var propDeclarations = this.constructor.propDeclarations;
@@ -721,8 +772,10 @@ var ReactCompositeComponentMixin = {
forceUpdate: function(callback) {
var compositeLifeCycleState = this._compositeLifeCycleState;
invariant(
this.isMounted(),
'forceUpdate(...): Can only force an update on mounted components.'
this.isMounted() ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'forceUpdate(...): Can only force an update on mounted or mounting ' +
'components.'
);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
@@ -786,27 +839,9 @@ var ReactCompositeComponentMixin = {
*/
_bindAutoBindMethod: function(method) {
var component = this;
var hasWarned = false;
function autoBound(a, b, c, d, e, tooMany) {
invariant(
typeof tooMany === 'undefined',
'React.autoBind(...): Methods can only take a maximum of 5 arguments.'
);
if (component._lifeCycleState === ReactComponent.LifeCycle.MOUNTED) {
return method.call(component, a, b, c, d, e);
} else if (!hasWarned) {
hasWarned = true;
if (__DEV__) {
console.warn(
'React.autoBind(...): Attempted to invoke an auto-bound method ' +
'on an unmounted instance of `%s`. You either have a memory leak ' +
'or an event handler that is being run after unmounting.',
component.constructor.displayName || 'ReactCompositeComponent'
);
}
}
}
return autoBound;
return function() {
return method.apply(component, arguments);
};
}
};
@@ -859,33 +894,16 @@ var ReactCompositeComponent = {
},
/**
* Marks the provided method to be automatically bound to the component.
* This means the method's context will always be the component.
*
* React.createClass({
* handleClick: React.autoBind(function() {
* this.setState({jumping: true});
* }),
* render: function() {
* return <a onClick={this.handleClick}>Jump</a>;
* }
* });
* TODO: Delete this when all callers have been updated to rely on this
* behavior being the default.
*
* Backwards compatible stub for what is now the default behavior.
* @param {function} method Method to be bound.
* @public
*/
autoBind: function(method) {
function unbound() {
invariant(
false,
'React.autoBind(...): Attempted to invoke an auto-bound method that ' +
'was not correctly defined on the class specification.'
);
}
unbound.__reactAutoBind = method;
return unbound;
return method;
}
};
module.exports = ReactCompositeComponent;
+2
View File
@@ -23,6 +23,8 @@
*
* The current owner is the component who should own any components that are
* currently being constructed.
*
* The depth indicate how many composite components are above this render level.
*/
var ReactCurrentOwner = {
+6 -6
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule ReactDOM
* @typechecks
* @typechecks static-only
*/
"use strict";
@@ -41,15 +41,16 @@ var objMapKeyVal = require('objMapKeyVal');
*/
function createDOMComponentClass(tag, omitClose) {
var Constructor = function() {};
Constructor.prototype = new ReactNativeComponent(tag, omitClose);
Constructor.prototype.constructor = Constructor;
return function(props, children) {
var ConvenienceConstructor = function(props, children) {
var instance = new Constructor();
instance.construct.apply(instance, arguments);
return instance;
};
ConvenienceConstructor.componentConstructor = Constructor;
return ConvenienceConstructor;
}
/**
@@ -79,8 +80,7 @@ var ReactDOM = objMapKeyVal({
embed: true,
fieldset: false,
footer: false,
// Danger: this gets monkeypatched! See ReactDOMForm for more info.
form: false,
form: false, // NOTE: Injected, see `ReactDOMForm`.
h1: false,
h2: false,
h3: false,
@@ -115,7 +115,7 @@ var ReactDOM = objMapKeyVal({
table: false,
tbody: false,
td: false,
textarea: false,
textarea: false, // NOTE: Injected, see `ReactDOMTextarea`.
tfoot: false,
th: false,
thead: false,
+12 -13
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule ReactDOMIDOperations
* @typechecks
* @typechecks static-only
*/
/*jslint evil: true */
@@ -24,7 +24,7 @@
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMChildrenOperations = require('DOMChildrenOperations');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactDOMNodeCache = require('ReactDOMNodeCache');
var ReactID = require('ReactID');
var getTextContentAccessor = require('getTextContentAccessor');
var invariant = require('invariant');
@@ -36,7 +36,6 @@ var invariant = require('invariant');
* @private
*/
var INVALID_PROPERTY_ERRORS = {
content: '`content` must be set using `updateTextContentByID()`.',
dangerouslySetInnerHTML:
'`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.',
style: '`style` must be set using `updateStylesByID()`.'
@@ -66,7 +65,7 @@ var ReactDOMIDOperations = {
* @internal
*/
updatePropertyByID: function(id, name, value) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
invariant(
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
'updatePropertyByID(...): %s',
@@ -84,7 +83,7 @@ var ReactDOMIDOperations = {
* @internal
*/
deletePropertyByID: function(id, name, value) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
invariant(
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
'updatePropertyByID(...): %s',
@@ -121,7 +120,7 @@ var ReactDOMIDOperations = {
* @internal
*/
updateStylesByID: function(id, styles) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
CSSPropertyOperations.setValueForStyles(node, styles);
},
@@ -133,7 +132,7 @@ var ReactDOMIDOperations = {
* @internal
*/
updateInnerHTMLByID: function(id, html) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
// HACK: IE8- normalize whitespace in innerHTML, removing leading spaces.
// @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html
node.innerHTML = (html && html.__html || '').replace(/^ /g, '&nbsp;');
@@ -147,7 +146,7 @@ var ReactDOMIDOperations = {
* @internal
*/
updateTextContentByID: function(id, content) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
node[textContentAccessor] = content;
},
@@ -160,9 +159,9 @@ var ReactDOMIDOperations = {
* @see {Danger.dangerouslyReplaceNodeWithMarkup}
*/
dangerouslyReplaceNodeWithMarkupByID: function(id, markup) {
var node = ReactDOMNodeCache.getCachedNodeByID(id);
var node = ReactID.getNode(id);
DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup);
ReactDOMNodeCache.purgeEntireCache();
ReactID.purgeEntireCache();
},
/**
@@ -170,13 +169,13 @@ var ReactDOMIDOperations = {
* Detect if any elements were removed instead of blindly purging.
*/
manageChildrenByParentID: function(parentID, domOperations) {
var parent = ReactDOMNodeCache.getCachedNodeByID(parentID);
var parent = ReactID.getNode(parentID);
DOMChildrenOperations.manageChildren(parent, domOperations);
ReactDOMNodeCache.purgeEntireCache();
ReactID.purgeEntireCache();
},
setTextNodeValueAtIndexByParentID: function(parentID, index, value) {
var parent = ReactDOMNodeCache.getCachedNodeByID(parentID);
var parent = ReactID.getNode(parentID);
DOMChildrenOperations.setTextNodeValueAtIndex(parent, index, value);
}
-52
View File
@@ -1,52 +0,0 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactDOMNodeCache
*/
"use strict";
var ReactMount = require('ReactMount');
var nodeCache = {};
/**
* DOM node cache only intended for use by React. Placed into a shared module so
* that both read and write utilities may benefit from a shared cache.
*/
var ReactDOMNodeCache = {
/**
* Releases fast id lookups (node/style cache). This implementation is
* aggressive with purging because the bookkeeping associated with doing fine
* grained deleted from the cache may outweight the benefits of the cache. The
* heuristic that should be used to purge is 'any time anything is deleted'.
* Typically this means that a large amount of content is being replaced and
* several elements would need purging regardless. It's also a time when an
* application is likely not in the middle of a "smooth operation" (such as
* animating/scrolling).
*/
purgeEntireCache: function() {
nodeCache = {};
return nodeCache;
},
getCachedNodeByID: function(id) {
return nodeCache[id] ||
(nodeCache[id] =
document.getElementById(id) ||
ReactMount.findReactRenderedDOMNodeSlow(id));
}
};
module.exports = ReactDOMNodeCache;
+11 -8
View File
@@ -20,10 +20,11 @@
var ReactDOM = require('ReactDOM');
var ReactDOMForm = require('ReactDOMForm');
var ReactDOMTextarea = require('ReactDOMTextarea');
var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
var TextChangeEventPlugin = require('TextChangeEventPlugin');
var ChangeEventPlugin = require('ChangeEventPlugin');
var EventPluginHub = require('EventPluginHub');
var ReactInstanceHandles = require('ReactInstanceHandles');
var SimpleEventPlugin = require('SimpleEventPlugin');
@@ -42,17 +43,19 @@ function inject() {
EventPluginHub.injection.injectEventPluginsByName({
'SimpleEventPlugin': SimpleEventPlugin,
'EnterLeaveEventPlugin': EnterLeaveEventPlugin,
'TextChangeEventPlugin': TextChangeEventPlugin
'ChangeEventPlugin': ChangeEventPlugin
});
/*
* This is a bit of a hack. We need to override the <form> element
* to be a composite component because IE8 does not bubble or capture
* submit to the top level. In order to make this work with our
* dependency graph we need to inject it here.
/**
* This is a bit of a hack. We need to override the <form> element to be a
* composite component because IE8 does not bubble or capture submit to the
* top level. In order to make this work with our dependency graph we need to
* inject it here.
*/
ReactDOM.injection.injectComponentClasses({
form: ReactDOMForm
form: ReactDOMForm,
// TODO: Inject `ReactDOMInput`.
textarea: ReactDOMTextarea
});
}
+48
View File
@@ -0,0 +1,48 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactDoNotBindDeprecated
*/
var ReactDoNotBindDeprecated = {
/**
* Marks the method for not being automatically bound on component mounting. A
* couple of reasons you might want to use this:
*
* - Automatically supporting the previous behavior in components that were
* built with previous versions of React.
* - Tuning performance, by avoiding binding on initial render for methods
* that are always invoked while being preceded by `this.`. Such binds are
* unnecessary.
*
* React.createClass({
* handleClick: ReactDoNotBindDeprecated.doNotBind(function() {
* alert(this.setState); // undefined!
* }),
* render: function() {
* return <a onClick={this.handleClick}>Jump</a>;
* }
* });
*
* @param {function} method Method to avoid automatically binding.
* @public
*/
doNotBind: function(method) {
method.__reactDontBind = true; // Mutating
return method;
}
};
module.exports = ReactDoNotBindDeprecated;
+3 -3
View File
@@ -14,16 +14,16 @@
* limitations under the License.
*
* @providesModule ReactEventEmitter
* @typechecks
* @typechecks static-only
*/
"use strict";
var BrowserEnv = require('BrowserEnv');
var EventConstants = require('EventConstants');
var EventListener = require('EventListener');
var EventPluginHub = require('EventPluginHub');
var ExecutionEnvironment = require('ExecutionEnvironment');
var ViewportMetrics = require('ViewportMetrics');
var invariant = require('invariant');
var isEventSupported = require('isEventSupported');
@@ -126,7 +126,7 @@ function trapCapturedEvent(topLevelType, handlerBaseName, element) {
* @see http://www.quirksmode.org/dom/events/scroll.html
*/
function registerScrollValueMonitoring() {
var refresh = BrowserEnv.refreshAuthoritativeScrollValues;
var refresh = ViewportMetrics.refreshScrollValues;
EventListener.listen(window, 'scroll', refresh);
EventListener.listen(window, 'resize', refresh);
}
+3 -3
View File
@@ -14,16 +14,16 @@
* limitations under the License.
*
* @providesModule ReactEventTopLevelCallback
* @typechecks
* @typechecks static-only
*/
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactID = require('ReactID');
var ReactInstanceHandles = require('ReactInstanceHandles');
var getDOMNodeID = require('getDOMNodeID');
var getEventTarget = require('getEventTarget');
/**
@@ -76,7 +76,7 @@ var ReactEventTopLevelCallback = {
var topLevelTarget = ReactInstanceHandles.getFirstReactDOM(
getEventTarget(nativeEvent)
) || ExecutionEnvironment.global;
var topLevelTargetID = getDOMNodeID(topLevelTarget) || '';
var topLevelTargetID = ReactID.getID(topLevelTarget) || '';
ReactEventEmitter.handleTopLevel(
topLevelType,
topLevelTarget,
+173
View File
@@ -0,0 +1,173 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactID
* @typechecks static-only
*/
"use strict";
var invariant = require('invariant');
var ReactMount = require('ReactMount');
var ATTR_NAME = 'data-reactid';
var nodeCache = {};
/**
* Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form
* element can return its control whose name or ID equals ATTR_NAME. All
* DOM nodes support `getAttributeNode` but this can also get called on
* other objects so just return '' if we're given something other than a
* DOM node (such as window).
*
* @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.
* @return {string} ID of the supplied `domNode`.
*/
function getID(node) {
var id = internalGetID(node);
if (id) {
if (nodeCache.hasOwnProperty(id)) {
var cached = nodeCache[id];
if (cached !== node) {
invariant(
!isValid(cached, id),
'ReactID: Two valid but unequal nodes with the same `%s`: %s',
ATTR_NAME, id
);
nodeCache[id] = node;
}
} else {
nodeCache[id] = node;
}
}
return id;
}
function internalGetID(node) {
if (node && node.getAttributeNode) {
var attributeNode = node.getAttributeNode(ATTR_NAME);
if (attributeNode) {
return attributeNode.value || '';
}
}
return '';
}
/**
* Sets the React-specific ID of the given node.
*
* @param {DOMElement} node The DOM node whose ID will be set.
* @param {string} id The value of the ID attribute.
*/
function setID(node, id) {
var oldID = internalGetID(node);
if (oldID !== id) {
delete nodeCache[oldID];
}
node.setAttribute(ATTR_NAME, id);
nodeCache[id] = node;
}
/**
* Finds the node with the supplied React-generated DOM ID.
*
* @param {string} id A React-generated DOM ID.
* @return {DOMElement} DOM node with the suppled `id`.
* @internal
*/
function getNode(id) {
if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
nodeCache[id] = ReactMount.findReactNodeByID(id);
}
return nodeCache[id];
}
/**
* A node is "valid" if it is contained by a currently mounted container.
*
* This means that the node does not have to be contained by a document in
* order to be considered valid.
*
* @param {?DOMElement} node The candidate DOM node.
* @param {string} id The expected ID of the node.
* @return {boolean} Whether the node is contained by a mounted container.
*/
function isValid(node, id) {
if (node) {
invariant(
internalGetID(node) === id,
'ReactID: Unexpected modification of `%s`',
ATTR_NAME
);
var container = ReactMount.findReactContainerForID(id);
if (container && contains(container, node)) {
return true;
}
}
return false;
}
function contains(ancestor, descendant) {
if (ancestor.contains) {
// Supported natively in virtually all browsers, but not in jsdom.
return ancestor.contains(descendant);
}
if (descendant === ancestor) {
return true;
}
if (descendant.nodeType === 3) {
// If descendant is a text node, start from descendant.parentNode
// instead, so that we can assume all ancestors worth considering are
// element nodes with nodeType === 1.
descendant = descendant.parentNode;
}
while (descendant && descendant.nodeType === 1) {
if (descendant === ancestor) {
return true;
}
descendant = descendant.parentNode;
}
return false;
}
/**
* Causes the cache to forget about one React-specific ID.
*
* @param {string} id The ID to forget.
*/
function purgeID(id) {
delete nodeCache[id];
}
/**
* Clears the entire cache.
*/
function purgeEntireCache() {
nodeCache = {};
}
exports.ATTR_NAME = ATTR_NAME;
exports.getID = getID;
exports.setID = setID;
exports.getNode = getNode;
exports.purgeID = purgeID;
exports.purgeEntireCache = purgeEntireCache;
+5 -1
View File
@@ -27,6 +27,10 @@ function getActiveElement() {
}
}
function isInDocument(node) {
return document.documentElement.contains(node);
}
/**
* @ReactInputSelection: React input selection module. Based on Selection.js,
* but modified to be suitable for react and has a couple of bug fixes (doesn't
@@ -64,7 +68,7 @@ var ReactInputSelection = {
var priorFocusedElem = priorSelectionInformation.focusedElem;
var priorSelectionRange = priorSelectionInformation.selectionRange;
if (curFocusedElem !== priorFocusedElem &&
document.getElementById(priorFocusedElem.id)) {
isInDocument(priorFocusedElem)) {
if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
ReactInputSelection.setSelection(
priorFocusedElem,
+26 -11
View File
@@ -14,12 +14,13 @@
* limitations under the License.
*
* @providesModule ReactInstanceHandles
* @typechecks
* @typechecks static-only
*/
"use strict";
var getDOMNodeID = require('getDOMNodeID');
var ReactID = require('ReactID');
var invariant = require('invariant');
var SEPARATOR = '.';
@@ -47,7 +48,7 @@ var GLOBAL_MOUNT_POINT_MAX = 9999999;
* @internal
*/
function getReactRootIDString(index) {
return '.reactRoot[' + index + ']';
return SEPARATOR + 'r[' + index.toString(36) + ']';
}
/**
@@ -250,7 +251,7 @@ var ReactInstanceHandles = {
// Not a DOMElement, therefore not a React component
return false;
}
var id = getDOMNodeID(node);
var id = ReactID.getID(node);
return id ? id.charAt(0) === SEPARATOR : false;
},
@@ -279,20 +280,34 @@ var ReactInstanceHandles = {
*
* @param {DOMEventTarget} ancestorNode Search from this root.
* @pararm {string} id ID of the DOM representation of the component.
* @return {?DOMEventTarget} DOM node with the supplied `id`, if one exists.
* @return {DOMEventTarget} DOM node with the supplied `id`.
* @internal
*/
findComponentRoot: function(ancestorNode, id) {
var child = ancestorNode.firstChild;
while (child) {
if (id === child.id) {
return child;
} else if (isAncestorIDOf(child.id, id)) {
return ReactInstanceHandles.findComponentRoot(child, id);
var childID = ReactID.getID(child);
if (childID) {
if (id === childID) {
return child;
} else if (isAncestorIDOf(childID, id)) {
return ReactInstanceHandles.findComponentRoot(child, id);
}
}
child = child.nextSibling;
}
// Effectively: return null;
global.console && console.error && console.error(
'Error while invoking `findComponentRoot` with the following ' +
'ancestor node:',
ancestorNode
);
invariant(
false,
'findComponentRoot(..., %s): Unable to find element. This probably ' +
'means the DOM was unexpectedly mutated (e.g. by the browser).',
id,
ReactID.getID(ancestorNode)
);
},
/**
@@ -304,7 +319,7 @@ var ReactInstanceHandles = {
* @internal
*/
getReactRootIDFromNodeID: function(id) {
var regexResult = /\.reactRoot\[[^\]]+\]/.exec(id);
var regexResult = /\.r\[[^\]]+\]/.exec(id);
return regexResult && regexResult[0];
},
+9 -9
View File
@@ -21,6 +21,7 @@
var ReactEventEmitter = require('ReactEventEmitter');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
var ReactID = require('ReactID');
var $ = require('$');
@@ -35,7 +36,7 @@ var containersByReactRootID = {};
* @return {?*} DOM element that may have the reactRoot ID, or null.
*/
function getReactRootElementInContainer(container) {
return container.firstChild;
return container && container.firstChild;
}
/**
@@ -44,7 +45,7 @@ function getReactRootElementInContainer(container) {
*/
function getReactRootID(container) {
var rootElement = getReactRootElementInContainer(container);
return rootElement && rootElement.id;
return rootElement && ReactID.getID(rootElement);
}
/**
@@ -54,9 +55,9 @@ function getReactRootID(container) {
*
* ReactMount.renderComponent(component, $('container'));
*
* <div id="container"> <-- Supplied `container`.
* <div id=".reactRoot[3]"> <-- Rendered reactRoot of React component.
* // ...
* <div id="container"> <-- Supplied `container`.
* <div data-reactid=".r[3]"> <-- Rendered reactRoot of React
* // ... component.
* </div>
* </div>
*
@@ -268,13 +269,12 @@ var ReactMount = {
},
/**
* Given the ID of a DOM node rendered by a React component, finds the root
* DOM node of the React component.
* Finds an element rendered by React with the supplied ID.
*
* @param {string} id ID of a DOM node in the React component.
* @return {?DOMElement} Root DOM node of the React component.
* @return {DOMElement} Root DOM node of the React component.
*/
findReactRenderedDOMNodeSlow: function(id) {
findReactNodeByID: function(id) {
var reactRoot = ReactMount.findReactContainerForID(id);
return ReactInstanceHandles.findComponentRoot(reactRoot, id);
}
+12 -27
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule ReactNativeComponent
* @typechecks
* @typechecks static-only
*/
"use strict";
@@ -24,6 +24,7 @@ var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactComponent = require('ReactComponent');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactMultiChild = require('ReactMultiChild');
var ReactID = require('ReactID');
var escapeTextForBrowser = require('escapeTextForBrowser');
var flattenChildren = require('flattenChildren');
@@ -39,7 +40,6 @@ var registrationNames = ReactEventEmitter.registrationNames;
// For quickly matching children type, to test if can be treated as content.
var CONTENT_TYPES = {'string': true, 'number': true};
var CONTENT = keyOf({content: null});
var DANGEROUSLY_SET_INNER_HTML = keyOf({dangerouslySetInnerHTML: null});
var STYLE = keyOf({style: null});
@@ -50,14 +50,10 @@ function assertValidProps(props) {
if (!props) {
return;
}
// Note the use of `!=` which checks for null or undefined.
var hasChildren = props.children != null ? 1 : 0;
var hasContent = props.content != null ? 1 : 0;
var hasInnerHTML = props.dangerouslySetInnerHTML != null ? 1 : 0;
// Note the use of `==` which checks for null or undefined.
invariant(
hasChildren + hasContent + hasInnerHTML <= 1,
'Can only set one of `children`, `props.content`, or ' +
'`props.dangerouslySetInnerHTML`.'
props.children == null || props.dangerouslySetInnerHTML == null,
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.'
);
invariant(
props.style == null || typeof props.style === 'object',
@@ -138,7 +134,8 @@ ReactNativeComponent.Mixin = {
}
}
return ret + ' id="' + this._rootNodeID + '">';
var escapedID = escapeTextForBrowser(this._rootNodeID);
return ret + ' ' + ReactID.ATTR_NAME + '="' + escapedID + '">';
},
/**
@@ -156,7 +153,7 @@ ReactNativeComponent.Mixin = {
return innerHTML.__html;
}
} else {
var contentToUse = this.props.content != null ? this.props.content :
var contentToUse =
CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
var childrenToUse = contentToUse != null ? null : this.props.children;
if (contentToUse != null) {
@@ -219,8 +216,8 @@ ReactNativeComponent.Mixin = {
styleUpdates[styleName] = '';
}
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML ||
propKey === CONTENT) {
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
// http://jsperf.com/emptying-speed
ReactComponent.DOMIDOperations.updateTextContentByID(
this._rootNodeID,
''
@@ -274,11 +271,6 @@ ReactNativeComponent.Mixin = {
nextProp
);
}
} else if (propKey === CONTENT) {
ReactComponent.DOMIDOperations.updateTextContentByID(
this._rootNodeID,
'' + nextProp
);
} else if (registrationNames[propKey]) {
putListener(this._rootNodeID, propKey, nextProp);
} else {
@@ -305,17 +297,10 @@ ReactNativeComponent.Mixin = {
* @param {ReactReconcileTransaction} transaction
*/
_updateDOMChildren: function(nextProps, transaction) {
var thisPropsContentType = typeof this.props.content;
var thisPropsContentEmpty =
this.props.content == null || thisPropsContentType === 'boolean';
var nextPropsContentType = typeof nextProps.content;
var nextPropsContentEmpty =
nextProps.content == null || nextPropsContentType === 'boolean';
var lastUsedContent = !thisPropsContentEmpty ? this.props.content :
var lastUsedContent =
CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
var contentToUse = !nextPropsContentEmpty ? nextProps.content :
var contentToUse =
CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
// Note the use of `!=` which checks for null or undefined.
+6 -2
View File
@@ -44,13 +44,17 @@ function createTransferStrategy(mergeStrategy) {
*/
var TransferStrategies = {
/**
* Never transfer the `ref` prop.
* Never transfer `children`.
*/
ref: emptyFunction,
children: emptyFunction,
/**
* Transfer the `className` prop by merging them.
*/
className: createTransferStrategy(joinClasses),
/**
* Never transfer the `ref` prop.
*/
ref: emptyFunction,
/**
* Transfer the `style` prop (which is an object) by merging them.
*/
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule ReactReconcileTransaction
* @typechecks
* @typechecks static-only
*/
"use strict";
+3 -2
View File
@@ -14,12 +14,13 @@
* limitations under the License.
*
* @providesModule ReactTextComponent
* @typechecks
* @typechecks static-only
*/
"use strict";
var ReactComponent = require('ReactComponent');
var ReactID = require('ReactID');
var escapeTextForBrowser = require('escapeTextForBrowser');
var mixInto = require('mixInto');
@@ -57,7 +58,7 @@ mixInto(ReactTextComponent, {
mountComponent: function(rootID) {
ReactComponent.Mixin.mountComponent.call(this, rootID);
return (
'<span id="' + rootID + '">' +
'<span ' + ReactID.ATTR_NAME + '="' + rootID + '">' +
escapeTextForBrowser(this.props.text) +
'</span>'
);
+15 -8
View File
@@ -21,9 +21,11 @@
var mocks = require('mocks');
var React = require('React');
var ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
var ReactTestUtils = require('ReactTestUtils');
var reactComponentExpect = require('reactComponentExpect');
// TODO: Test render and all stock methods.
describe('React.autoBind', function() {
it('Holds reference to instance', function() {
@@ -31,16 +33,20 @@ describe('React.autoBind', function() {
var mouseDidEnter = mocks.getMockFunction();
var mouseDidLeave = mocks.getMockFunction();
var mouseDidClick = mocks.getMockFunction();
var didBadIdea = mocks.getMockFunction();
var TestBindComponent = React.createClass({
onMouseEnter: mouseDidEnter,
onMouseLeave: mouseDidLeave,
onClick: React.autoBind(mouseDidClick),
getInitialState: function() {
return {something: 'hi'};
},
onMouseEnter: ReactDoNotBindDeprecated.doNotBind(mouseDidEnter),
onMouseLeave: ReactDoNotBindDeprecated.doNotBind(mouseDidLeave),
onClick: mouseDidClick,
// autoBind needs to be on the top-level spec.
// auto binding only occurs on top level functions in class defs.
badIdeas: {
badBind: React.autoBind(didBadIdea)
badBind: function() {
this.state.something;
}
},
render: function() {
@@ -48,7 +54,8 @@ describe('React.autoBind', function() {
<div
onMouseEnter={this.onMouseEnter.bind(this)}
onMouseLeave={this.onMouseLeave}
onClick={this.onClick} />
onClick={this.onClick}
/>
);
}
});
@@ -103,7 +110,7 @@ describe('React.autoBind', function() {
var mouseDidClick = mocks.getMockFunction();
var TestMixin = {
onClick: React.autoBind(mouseDidClick)
onClick: mouseDidClick
};
var TestBindComponent = React.createClass({
+37
View File
@@ -31,6 +31,24 @@ describe('ReactComponent', function() {
reactComponentExpect = require('reactComponentExpect');
});
it('should throw on invalid render targets', function() {
var container = document.createElement('div');
// jQuery objects are basically arrays; people often pass them in by mistake
expect(function() {
React.renderComponent(<div></div>, [container]);
}).toThrow(
'Invariant Violation: mountComponentIntoNode(...): Target container is ' +
'not a DOM element.'
);
expect(function() {
React.renderComponent(<div></div>, null);
}).toThrow(
'Invariant Violation: mountComponentIntoNode(...): Target container is ' +
'not a DOM element.'
);
});
it('should throw when supplying a ref outside of render method', function() {
var instance = <div ref="badDiv" />;
expect(function() {
@@ -95,4 +113,23 @@ describe('ReactComponent', function() {
ReactTestUtils.renderIntoDocument(instance);
});
it('should correctly determine if a component is mounted', function() {
var Component = React.createClass({
componentWillMount: function() {
expect(this.isMounted()).toBeFalsy();
},
componentDidMount: function() {
expect(this.isMounted()).toBeTruthy();
},
render: function() {
return <div/>;
}
});
var instance = <Component />;
expect(instance.isMounted()).toBeFalsy();
ReactTestUtils.renderIntoDocument(instance);
expect(instance.isMounted()).toBeTruthy();
});
});
@@ -1,159 +0,0 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @jsx React.DOM
* @emails react-core
*/
"use strict";
var React;
var Fake;
var singleChild = function(children) {
return (<Fake>{children}</Fake>).props.children;
};
var multiChild = function(child1, child2, child3, child4) {
return (<Fake>{child1} {child2} {child3}{child4}</Fake>).props.children;
};
describe('ReactComponentFlatten', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
React = require('React');
var ReactComponent = require('ReactComponent');
var copyProperties = require('copyProperties');
var Constructor = function() {};
copyProperties(Constructor.prototype, ReactComponent.Mixin);
Fake = function() {
var instance = new Constructor();
instance.construct.apply(instance, arguments);
return instance;
};
});
it("should reuse a flat array", function() {
var children = [
<div key="foo" />,
<div key="bar" />
];
var flat = singleChild(children);
expect(flat).toBe(children);
expect(flat[0]._key).toEqual('0:foo');
expect(flat[1]._key).toEqual('0:bar');
});
it("should collapse a sparse array", function() {
var children = [
null,
<div key="foo" />,
<div key="bar" />
];
var flat = singleChild(children);
expect(flat).not.toBe(children);
expect(flat.length).toEqual(2);
expect(flat[0]._key).toEqual('0:foo');
expect(flat[1]._key).toEqual('0:bar');
});
it("should collapse a nested array into a flat array", function() {
var flat = multiChild(
null,
<div key="foo" />,
[<div key="bar" />, null]
);
expect(flat.length).toEqual(2);
expect(flat[0]._key).toEqual('foo');
expect(flat[1]._key).toEqual('2:bar');
});
it("should use retain key index despite static empty values", function() {
// TODO: Wrap in another single child (currently breaks)
var before = multiChild(
<div />,
<div />,
[<div key="foo" />],
[<div key="foo" />]
);
var after = multiChild(
null,
<div />,
null,
[<div key="foo" />]
);
expect(before.length).toEqual(4);
expect(after.length).toEqual(2);
expect(before[1]._key).toEqual(after[0]._key);
expect(before[3]._key).toEqual(after[1]._key);
});
it("should assign idempotent keys for extra flattening layers", function() {
var flat = multiChild(
null,
[null, <div key="FOO" />],
false
);
var preFlat = multiChild(
null,
singleChild([null, <div key="FOO" />]),
false
);
expect(flat.length).toBe(1);
expect(preFlat.length).toBe(1);
expect(preFlat[0]._key).toBe(flat[0]._key);
});
it("should assign idempotent strings through flattening", function() {
var children = [
'FOO',
'BAR'
];
var flat = singleChild(children);
var wrappedFlat = multiChild(null, flat);
expect(flat).toBe(children);
expect(wrappedFlat.length).toBe(2);
expect(wrappedFlat[0]).toBe(flat[0]);
expect(wrappedFlat[1]).toBe(flat[1]);
});
it("cannot keep keys unique when children are unboxed", function() {
// This is a case that difficult to solve and should not actually be solved.
var children1 = [<div key="foo" />];
var children2 = [<div key="foo" />];
var flat1 = singleChild(children1);
var flat2 = singleChild(children2);
// There's no way to tell flat1[0] and flat2[0] apart.
var mergedChildren = [flat1[0], flat2[0]];
var newChildren = multiChild(mergedChildren);
expect(newChildren.length).toBe(2);
expect(newChildren[0]._key).toBe(newChildren[1]._key);
});
});
@@ -286,6 +286,8 @@ describe('ReactComponentLifeCycle', function() {
if (isInitialRender) {
this._testJournal.stateInInitialRender = clone(this.state);
this._testJournal.lifeCycleInInitialRender = this._lifeCycleState;
this._testJournal.compositeLifeCycleInInitialRender =
this._compositeLifeCycleState;
} else {
this._testJournal.stateInLaterRender = clone(this.state);
this._testJournal.lifeCycleInLaterRender = this._lifeCycleState;
@@ -319,7 +321,7 @@ describe('ReactComponentLifeCycle', function() {
GET_INIT_STATE_RETURN_VAL
);
expect(instance._testJournal.lifeCycleAtStartOfGetInitialState)
.toBe(ComponentLifeCycle.UNMOUNTED);
.toBe(ComponentLifeCycle.MOUNTED);
expect(instance._testJournal.compositeLifeCycleAtStartOfGetInitialState)
.toBe(CompositeComponentLifeCycle.MOUNTING);
@@ -328,7 +330,7 @@ describe('ReactComponentLifeCycle', function() {
instance._testJournal.returnedFromGetInitialState
);
expect(instance._testJournal.lifeCycleAtStartOfWillMount)
.toBe(ComponentLifeCycle.UNMOUNTED);
.toBe(ComponentLifeCycle.MOUNTED);
expect(instance._testJournal.compositeLifeCycleAtStartOfWillMount)
.toBe(CompositeComponentLifeCycle.MOUNTING);
@@ -343,7 +345,10 @@ describe('ReactComponentLifeCycle', function() {
expect(instance._testJournal.stateInInitialRender)
.toEqual(INIT_RENDER_STATE);
expect(instance._testJournal.lifeCycleInInitialRender).toBe(
ComponentLifeCycle.UNMOUNTED
ComponentLifeCycle.MOUNTED
);
expect(instance._testJournal.compositeLifeCycleInInitialRender).toBe(
CompositeComponentLifeCycle.MOUNTING
);
expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
@@ -493,6 +498,5 @@ describe('ReactComponentLifeCycle', function() {
'inner componentWillUnmount'
]);
});
});
@@ -26,6 +26,8 @@ var React;
var ReactCurrentOwner;
var ReactProps;
var ReactTestUtils;
var ReactID;
var ReactDoNotBindDeprecated;
var cx;
var reactComponentExpect;
@@ -37,8 +39,10 @@ describe('ReactCompositeComponent', function() {
reactComponentExpect = require('reactComponentExpect');
React = require('React');
ReactCurrentOwner = require('ReactCurrentOwner');
ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
ReactProps = require('ReactProps');
ReactTestUtils = require('ReactTestUtils');
ReactID = require('ReactID');
MorphingComponent = React.createClass({
getInitialState: function() {
@@ -50,7 +54,7 @@ describe('ReactCompositeComponent', function() {
},
render: function() {
var toggleActivatedState = this._toggleActivatedState.bind(this);
var toggleActivatedState = this._toggleActivatedState;
return !this.state.activated ?
<a ref="x" onClick={toggleActivatedState} /> :
<b ref="x" onClick={toggleActivatedState} />;
@@ -62,9 +66,9 @@ describe('ReactCompositeComponent', function() {
return {activated: false};
},
_toggleActivatedState: React.autoBind(function() {
_toggleActivatedState:function() {
this.setState({activated: !this.state.activated});
}),
},
render: function() {
return !this.state.activated ?
@@ -160,20 +164,23 @@ describe('ReactCompositeComponent', function() {
// rerender
instance.setProps({renderAnchor: true, anchorClassOn: false});
var anchorID = instance.getAnchorID();
var actualDOMAnchorNode = document.getElementById(anchorID);
var actualDOMAnchorNode = ReactID.getNode(anchorID);
expect(actualDOMAnchorNode.className).toBe('');
});
it('should auto bind methods and values correctly', function() {
var RETURN_VALUE_AFTER_MOUNT = 'returnValue';
var ComponentClass = React.createClass({
getInitialState: function() {
return {
valueToReturn: RETURN_VALUE_AFTER_MOUNT
};
return {valueToReturn: 'hi'};
},
methodBoundOnMount: React.autoBind(function() {
return this.state.valueToReturn;
methodToBeExplicitlyBound: function() {
return this;
},
methodAutoBound: function() {
return this;
},
methodExplicitlyNotBound: ReactDoNotBindDeprecated.doNotBind(function() {
return this;
}),
render: function() {
return <div> </div>;
@@ -181,19 +188,34 @@ describe('ReactCompositeComponent', function() {
});
var instance = <ComponentClass />;
// Autobound methods will throw before mounting.
// These are controversial assertions for now, they just exist
// because existing code depends on these assumptions.
expect(function() {
instance.methodBoundOnMount();
}).toThrow();
instance.methodToBeExplicitlyBound.bind(instance)();
}).not.toThrow();
expect(function() {
instance.methodAutoBound();
}).not.toThrow();
expect(function() {
instance.methodExplicitlyNotBound();
}).not.toThrow();
// Next, prove that once mounted, the scope is bound correctly to the actual
// component.
ReactTestUtils.renderIntoDocument(instance);
var retValAfterMount = instance.methodBoundOnMount();
expect(retValAfterMount).toBe(RETURN_VALUE_AFTER_MOUNT);
var retValAfterMountWithCrazyScope =
instance.methodBoundOnMount.call({thisIsACrazyScope:null});
expect(retValAfterMountWithCrazyScope).toBe(RETURN_VALUE_AFTER_MOUNT);
var explicitlyBound = instance.methodToBeExplicitlyBound.bind(instance);
var autoBound = instance.methodAutoBound;
var explicitlyNotBound = instance.methodExplicitlyNotBound;
var context = {};
expect(explicitlyBound.call(context)).toBe(instance);
expect(autoBound.call(context)).toBe(instance);
expect(explicitlyNotBound.call(context)).toBe(context);
expect(explicitlyBound.call(instance)).toBe(instance);
expect(autoBound.call(instance)).toBe(instance);
expect(explicitlyNotBound.call(instance)).toBe(instance);
});
it('should normalize props with default values', function() {
@@ -287,7 +309,7 @@ describe('ReactCompositeComponent', function() {
instance.forceUpdate();
}).toThrow(
'Invariant Violation: forceUpdate(...): Can only force an update on ' +
'mounted components.'
'mounted or mounting components.'
);
React.renderComponent(instance, container);
@@ -300,7 +322,7 @@ describe('ReactCompositeComponent', function() {
instance.forceUpdate();
}).toThrow(
'Invariant Violation: forceUpdate(...): Can only force an update on ' +
'mounted components.'
'mounted or mounting components.'
);
});
@@ -51,8 +51,8 @@ describe('ReactCompositeComponent-transferProps', function() {
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild(instance)
.toBeDOMComponentWithTag('input')
.expectRenderedChild()
.toBeComponentOfType(React.DOM.input)
.scalarPropsEqual({
className: 'textinput',
style: {display: 'block'},
@@ -66,8 +66,8 @@ describe('ReactCompositeComponent-transferProps', function() {
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild(instance)
.toBeDOMComponentWithTag('input')
.expectRenderedChild()
.toBeComponentOfType(React.DOM.input)
.scalarPropsEqual({placeholder: 'Type here...'});
});
@@ -80,8 +80,8 @@ describe('ReactCompositeComponent-transferProps', function() {
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild(instance)
.toBeDOMComponentWithTag('input')
.expectRenderedChild()
.toBeComponentOfType(React.DOM.input)
.scalarPropsEqual({
className: 'textinput hidden_elem',
style: {
@@ -91,6 +91,25 @@ describe('ReactCompositeComponent-transferProps', function() {
});
});
it('should not transfer children', function() {
var ChildrenTestComponent = React.createClass({
render: function() {
return this.transferPropsTo(<div />);
}
});
var instance =
<ChildrenTestComponent>
<span>Hello!</span>
</ChildrenTestComponent>;
ReactTestUtils.renderIntoDocument(instance);
reactComponentExpect(instance)
.expectRenderedChild()
.toBeDOMComponentWithTag('div')
.toBeDOMComponentWithNoChildren();
});
it('should not transfer ref', function() {
var RefTestComponent = React.createClass({
render: function() {
+19 -13
View File
@@ -17,12 +17,15 @@
* @emails react-core
*/
/*jslint evil: true */
"use strict";
var React = require('React');
var ReactDOM = require('ReactDOM');
var ReactTestUtils = require('ReactTestUtils');
var React = require('React');
var ReactID = require('ReactID');
describe('ref swapping', function() {
// TODO: uncomment this test once we can run in phantom, which
@@ -58,7 +61,7 @@ describe('ref swapping', function() {
var argDiv = ReactTestUtils.renderIntoDocument(
ReactDOM.div(null, 'child')
);
var argNode = document.getElementById(argDiv._rootNodeID);
var argNode = ReactID.getNode(argDiv._rootNodeID);
expect(argNode.innerHTML).toBe('child');
});
@@ -66,7 +69,7 @@ describe('ref swapping', function() {
var conflictDiv = ReactTestUtils.renderIntoDocument(
ReactDOM.div({children: 'fakechild'}, 'child')
);
var conflictNode = document.getElementById(conflictDiv._rootNodeID);
var conflictNode = ReactID.getNode(conflictDiv._rootNodeID);
expect(conflictNode.innerHTML).toBe('child');
});
@@ -81,31 +84,34 @@ describe('ref swapping', function() {
theBird: <div class="bird" />
}}</div>
);
// Warm the cache with theDog
myDiv.setProps({
children: {
theDog: <div class="dogbeforedelete" />, // Warm the cache with theDog
theDog: <div class="dogbeforedelete" />,
theBird: <div class="bird" />
}
});
myDiv.setProps({
children: { // This better purge that cache
theBird: <div class="bird" />
}
});
// Now, put the dog back.
// Remove theDog - this should purge the cache
myDiv.setProps({
children: {
theDog: <div class="dog" />, // This is a different node than before
theBird: <div class="bird" />
}
});
// Now, put theDog back. It's now a different DOM node.
myDiv.setProps({
children: { // className changed to bigdog.
theDog: <div class="bigdog" />, // but will it use the proper element
children: {
theDog: <div class="dog" />,
theBird: <div class="bird" />
}
});
var root = document.getElementById(myDiv._rootNodeID);
// Change the className of theDog. It will use the same element
myDiv.setProps({
children: {
theDog: <div class="bigdog" />,
theBird: <div class="bird" />
}
});
var root = ReactID.getNode(myDiv._rootNodeID);
var dog = root.childNodes[0];
expect(dog.className).toBe('bigdog');
});
@@ -23,23 +23,23 @@
describe('ReactDOMIDOperations', function() {
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactDOMNodeCache = require('ReactDOMNodeCache');
var ReactID = require('ReactID');
var keyOf = require('keyOf');
it('should disallow updating special properties', function() {
spyOn(ReactDOMNodeCache, "getCachedNodeByID");
spyOn(ReactID, "getNode");
spyOn(DOMPropertyOperations, "setValueForProperty");
expect(function() {
ReactDOMIDOperations.updatePropertyByID(
'testID',
keyOf({content: null}),
'testContent'
keyOf({dangerouslySetInnerHTML: null}),
{__html: 'testContent'}
);
}).toThrow();
expect(
ReactDOMNodeCache.getCachedNodeByID.argsForCall[0][0]
ReactID.getNode.argsForCall[0][0]
).toBe('testID');
expect(
@@ -49,7 +49,7 @@ describe('ReactDOMIDOperations', function() {
it('should update innerHTML and special-case whitespace', function() {
var stubNode = document.createElement('div');
spyOn(ReactDOMNodeCache, "getCachedNodeByID").andReturn(stubNode);
spyOn(ReactID, "getNode").andReturn(stubNode);
ReactDOMIDOperations.updateInnerHTMLByID(
'testID',
@@ -57,7 +57,7 @@ describe('ReactDOMIDOperations', function() {
);
expect(
ReactDOMNodeCache.getCachedNodeByID.argsForCall[0][0]
ReactID.getNode.argsForCall[0][0]
).toBe('testID');
expect(stubNode.innerHTML).toBe('&nbsp;testContent');
@@ -22,6 +22,7 @@ require('mock-modules')
.dontMock('BrowserScroll')
.dontMock('CallbackRegistry')
.dontMock('EventPluginHub')
.dontMock('ReactID')
.dontMock('ReactEventEmitter')
.dontMock('ReactInstanceHandles')
.dontMock('EventPluginHub')
@@ -34,6 +35,9 @@ var keyOf = require('keyOf');
var mocks = require('mocks');
var EventPluginHub;
var ReactID = require('ReactID');
var getID = ReactID.getID;
var setID = ReactID.setID;
var ReactEventEmitter;
var ReactEventTopLevelCallback;
var ReactTestUtils;
@@ -68,15 +72,15 @@ var ON_TOUCH_TAP_KEY = keyOf({onTouchTap: null});
var CHILD = document.createElement('div');
var PARENT = document.createElement('div');
var GRANDPARENT = document.createElement('div');
CHILD.id = '.reactRoot.[0].[0].[0]';
PARENT.id = '.reactRoot.[0].[0]';
GRANDPARENT.id = '.reactRoot.[0]';
setID(CHILD, '.reactRoot.[0].[0].[0]');
setID(PARENT, '.reactRoot.[0].[0]');
setID(GRANDPARENT, '.reactRoot.[0]');
function registerSimpleTestHandler() {
ReactEventEmitter.putListener(CHILD.id, ON_CLICK_KEY, LISTENER);
var listener = ReactEventEmitter.getListener(CHILD.id, ON_CLICK_KEY);
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, LISTENER);
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
expect(listener).toEqual(LISTENER);
return ReactEventEmitter.getListener(CHILD.id, ON_CLICK_KEY);
return ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
}
@@ -85,6 +89,9 @@ describe('ReactEventEmitter', function() {
require('mock-modules').dumpCache();
EventPluginHub = require('EventPluginHub');
TapEventPlugin = require('TapEventPlugin');
ReactID = require('ReactID');
getID = ReactID.getID;
setID = ReactID.setID;
ReactEventEmitter = require('ReactEventEmitter');
ReactTestUtils = require('ReactTestUtils');
ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
@@ -98,20 +105,20 @@ describe('ReactEventEmitter', function() {
it('should store a listener correctly', function() {
registerSimpleTestHandler();
var listener = ReactEventEmitter.getListener(CHILD.id, ON_CLICK_KEY);
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
expect(listener).toBe(LISTENER);
});
it('should retrieve a listener correctly', function() {
registerSimpleTestHandler();
var listener = ReactEventEmitter.getListener(CHILD.id, ON_CLICK_KEY);
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
expect(listener).toEqual(LISTENER);
});
it('should clear all handlers when asked to', function() {
registerSimpleTestHandler();
ReactEventEmitter.deleteAllListeners(CHILD.id);
var listener = ReactEventEmitter.getListener(CHILD.id, ON_CLICK_KEY);
ReactEventEmitter.deleteAllListeners(getID(CHILD));
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
expect(listener).toBe(undefined);
});
@@ -133,89 +140,89 @@ describe('ReactEventEmitter', function() {
it('should bubble simply', function() {
ReactEventEmitter.putListener(
CHILD.id,
getID(CHILD),
ON_CLICK_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactEventEmitter.putListener(
PARENT.id,
getID(PARENT),
ON_CLICK_KEY,
recordID.bind(null, PARENT.id)
recordID.bind(null, getID(PARENT))
);
ReactEventEmitter.putListener(
GRANDPARENT.id,
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.click(CHILD);
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[1]).toBe(PARENT.id);
expect(idCallOrder[2]).toBe(GRANDPARENT.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
expect(idCallOrder[1]).toBe(getID(PARENT));
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
});
it('should support stopPropagation()', function() {
ReactEventEmitter.putListener(
CHILD.id,
getID(CHILD),
ON_CLICK_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactEventEmitter.putListener(
PARENT.id,
getID(PARENT),
ON_CLICK_KEY,
recordIDAndStopPropagation.bind(null, PARENT.id)
recordIDAndStopPropagation.bind(null, getID(PARENT))
);
ReactEventEmitter.putListener(
GRANDPARENT.id,
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.click(CHILD);
expect(idCallOrder.length).toBe(2);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[1]).toBe(PARENT.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
expect(idCallOrder[1]).toBe(getID(PARENT));
});
it('should stop after first dispatch if stopPropagation', function() {
ReactEventEmitter.putListener(
CHILD.id,
getID(CHILD),
ON_CLICK_KEY,
recordIDAndStopPropagation.bind(null, CHILD.id)
recordIDAndStopPropagation.bind(null, getID(CHILD))
);
ReactEventEmitter.putListener(
PARENT.id,
getID(PARENT),
ON_CLICK_KEY,
recordID.bind(null, PARENT.id)
recordID.bind(null, getID(PARENT))
);
ReactEventEmitter.putListener(
GRANDPARENT.id,
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.click(CHILD);
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
});
it('should stopPropagation if false is returned', function() {
ReactEventEmitter.putListener(
CHILD.id,
getID(CHILD),
ON_CLICK_KEY,
recordIDAndReturnFalse.bind(null, CHILD.id)
recordIDAndReturnFalse.bind(null, getID(CHILD))
);
ReactEventEmitter.putListener(
PARENT.id,
getID(PARENT),
ON_CLICK_KEY,
recordID.bind(null, PARENT.id)
recordID.bind(null, getID(PARENT))
);
ReactEventEmitter.putListener(
GRANDPARENT.id,
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.click(CHILD);
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
});
/**
@@ -229,30 +236,38 @@ describe('ReactEventEmitter', function() {
it('should invoke handlers that were removed while bubbling', function() {
var handleParentClick = mocks.getMockFunction();
var handleChildClick = function(abstractEvent) {
ReactEventEmitter.deleteAllListeners(PARENT.id);
var handleChildClick = function(event) {
ReactEventEmitter.deleteAllListeners(getID(PARENT));
};
ReactEventEmitter.putListener(CHILD.id, ON_CLICK_KEY, handleChildClick);
ReactEventEmitter.putListener(PARENT.id, ON_CLICK_KEY, handleParentClick);
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, handleChildClick);
ReactEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
handleParentClick
);
ReactTestUtils.Simulate.click(CHILD);
expect(handleParentClick.mock.calls.length).toBe(1);
});
it('should not invoke newly inserted handlers while bubbling', function() {
var handleParentClick = mocks.getMockFunction();
var handleChildClick = function(abstractEvent) {
ReactEventEmitter.putListener(PARENT.id, ON_CLICK_KEY, handleParentClick);
var handleChildClick = function(event) {
ReactEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
handleParentClick
);
};
ReactEventEmitter.putListener(CHILD.id, ON_CLICK_KEY, handleChildClick);
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, handleChildClick);
ReactTestUtils.Simulate.click(CHILD);
expect(handleParentClick.mock.calls.length).toBe(0);
});
it('should infer onTouchTap from a touchStart/End', function() {
ReactEventEmitter.putListener(
CHILD.id,
getID(CHILD),
ON_TOUCH_TAP_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactTestUtils.Simulate.touchStart(
CHILD,
@@ -263,14 +278,14 @@ describe('ReactEventEmitter', function() {
ReactTestUtils.nativeTouchData(0, 0)
);
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
});
it('should infer onTouchTap from when dragging below threshold', function() {
ReactEventEmitter.putListener(
CHILD.id,
getID(CHILD),
ON_TOUCH_TAP_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactTestUtils.Simulate.touchStart(
CHILD,
@@ -281,14 +296,14 @@ describe('ReactEventEmitter', function() {
ReactTestUtils.nativeTouchData(0, tapMoveThreshold - 1)
);
expect(idCallOrder.length).toBe(1);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
});
it('should not onTouchTap from when dragging beyond threshold', function() {
ReactEventEmitter.putListener(
CHILD.id,
getID(CHILD),
ON_TOUCH_TAP_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactTestUtils.Simulate.touchStart(
CHILD,
@@ -304,19 +319,19 @@ describe('ReactEventEmitter', function() {
it('should bubble onTouchTap', function() {
ReactEventEmitter.putListener(
CHILD.id,
getID(CHILD),
ON_TOUCH_TAP_KEY,
recordID.bind(null, CHILD.id)
recordID.bind(null, getID(CHILD))
);
ReactEventEmitter.putListener(
PARENT.id,
getID(PARENT),
ON_TOUCH_TAP_KEY,
recordID.bind(null, PARENT.id)
recordID.bind(null, getID(PARENT))
);
ReactEventEmitter.putListener(
GRANDPARENT.id,
getID(GRANDPARENT),
ON_TOUCH_TAP_KEY,
recordID.bind(null, GRANDPARENT.id)
recordID.bind(null, getID(GRANDPARENT))
);
ReactTestUtils.Simulate.touchStart(
CHILD,
@@ -327,9 +342,9 @@ describe('ReactEventEmitter', function() {
ReactTestUtils.nativeTouchData(0, 0)
);
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD.id);
expect(idCallOrder[1]).toBe(PARENT.id);
expect(idCallOrder[2]).toBe(GRANDPARENT.id);
expect(idCallOrder[0]).toBe(getID(CHILD));
expect(idCallOrder[1]).toBe(getID(PARENT));
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
});
});
+154 -34
View File
@@ -22,6 +22,7 @@
var React;
var ReactTestUtils;
var reactComponentExpect;
var ReactID;
describe('ReactIdentity', function() {
@@ -30,11 +31,12 @@ describe('ReactIdentity', function() {
React = require('React');
ReactTestUtils = require('ReactTestUtils');
reactComponentExpect = require('reactComponentExpect');
ReactID = require('ReactID');
});
var idExp = /^\.reactRoot\[\d+\](.*)$/;
var idExp = /^\.r\[.+?\](.*)$/;
function checkId(child, expectedId) {
var actual = idExp.exec(child.id);
var actual = idExp.exec(ReactID.getID(child));
var expected = idExp.exec(expectedId);
expect(actual).toBeTruthy();
expect(expected).toBeTruthy();
@@ -53,8 +55,8 @@ describe('ReactIdentity', function() {
React.renderComponent(instance, document.createElement('div'));
var node = instance.getDOMNode();
reactComponentExpect(instance).toBeDOMComponentWithChildCount(2);
checkId(node.childNodes[0], '.reactRoot[0].[0]{first}');
checkId(node.childNodes[1], '.reactRoot[0].[0]{second}');
checkId(node.childNodes[0], '.r[0].{first}');
checkId(node.childNodes[1], '.r[0].{second}');
});
it('should allow key property to express identity', function() {
@@ -62,13 +64,17 @@ describe('ReactIdentity', function() {
<div>
<div key="apple" />
<div key="banana" />
<div key={0} />
<div key={123} />
</div>;
React.renderComponent(instance, document.createElement('div'));
var node = instance.getDOMNode();
reactComponentExpect(instance).toBeDOMComponentWithChildCount(2);
checkId(node.childNodes[0], '.reactRoot[0].[apple]');
checkId(node.childNodes[1], '.reactRoot[0].[banana]');
reactComponentExpect(instance).toBeDOMComponentWithChildCount(4);
checkId(node.childNodes[0], '.r[0].[apple]');
checkId(node.childNodes[1], '.r[0].[banana]');
checkId(node.childNodes[2], '.r[0].[0]');
checkId(node.childNodes[3], '.r[0].[123]');
});
it('should use instance identity', function() {
@@ -89,37 +95,157 @@ describe('ReactIdentity', function() {
React.renderComponent(instance, document.createElement('div'));
var node = instance.getDOMNode();
reactComponentExpect(instance).toBeDOMComponentWithChildCount(3);
checkId(node.childNodes[0], '.reactRoot[0].[wrap1]');
checkId(
node.childNodes[0].firstChild,
'.reactRoot[0].[wrap1].[0:squirrel]'
);
checkId(node.childNodes[1], '.reactRoot[0].[wrap2]');
checkId(node.childNodes[1].firstChild, '.reactRoot[0].[wrap2].[0:bunny]');
checkId(node.childNodes[2], '.reactRoot[0].[2]');
checkId(node.childNodes[2].firstChild, '.reactRoot[0].[2].[0:chipmunk]');
checkId(node.childNodes[0], '.r[0].[wrap1]');
checkId(node.childNodes[0].firstChild, '.r[0].[wrap1].[squirrel]');
checkId(node.childNodes[1], '.r[0].[wrap2]');
checkId(node.childNodes[1].firstChild, '.r[0].[wrap2].[bunny]');
checkId(node.childNodes[2], '.r[0].[2]');
checkId(node.childNodes[2].firstChild, '.r[0].[2].[chipmunk]');
});
function renderAComponentWithKeyIntoContainer(key, container) {
var span1 = <span key={key} />;
var span2 = <span />;
var map = {};
map[key] = span2;
React.renderComponent(<div>{[span1, map]}</div>, container);
expect(span1.getDOMNode()).not.toBe(null);
expect(span2.getDOMNode()).not.toBe(null);
checkId(span1.getDOMNode(), '.r[0].[' + key + ']');
checkId(span2.getDOMNode(), '.r[0].[1]{' + key + '}');
}
it('should allow any character as a key, in a detached parent', function() {
var detachedContainer = document.createElement('div');
renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", detachedContainer);
});
it('should allow any character as a key, in an attached parent', function() {
// This test exists to protect against implementation details that
// incorrectly query escaped IDs using DOM tools like getElementById.
var attachedContainer = document.createElement('div');
document.body.appendChild(attachedContainer);
renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", attachedContainer);
document.body.removeChild(attachedContainer);
});
it('should not allow scripts in keys to execute', function() {
var h4x0rKey = '"><script>window.YOUVEBEENH4X0RED=true;</script><div id="';
var attachedContainer = document.createElement('div');
document.body.appendChild(attachedContainer);
renderAComponentWithKeyIntoContainer(h4x0rKey, attachedContainer);
document.body.removeChild(attachedContainer);
// If we get this far, make sure we haven't executed the code
expect(window.YOUVEBEENH4X0RED).toBe(undefined);
});
it('should let restructured components retain their uniqueness', function() {
var instance0 = <span />;
var instance1 = <span />;
var instance2 = <span />;
var wrapped = <div>{instance0} {instance1}</div>;
var unwrappedAndAdded =
<div>
{instance2}
{wrapped.props.children[0]}
{wrapped.props.children[1]}
</div>;
var TestComponent = React.createClass({
render: function() {
return (
<div>
{instance2}
{this.props.children[0]}
{this.props.children[1]}
</div>
);
}
});
var TestContainer = React.createClass({
render: function() {
return <TestComponent>{instance0} {instance1}</TestComponent>;
}
});
expect(function() {
React.renderComponent(unwrappedAndAdded, document.createElement('div'));
React.renderComponent(<TestContainer />, document.createElement('div'));
}).not.toThrow();
});
it('should retain keys during updates in composite components', function() {
it('should let nested restructures retain their uniqueness', function() {
var instance0 = <span />;
var instance1 = <span />;
var instance2 = <span />;
var TestComponent = React.createClass({
render: function() {
return (
<div>
{instance2}
{this.props.children[0]}
{this.props.children[1]}
</div>
);
}
});
var TestContainer = React.createClass({
render: function() {
return (
<div>
<TestComponent>{instance0} {instance1}</TestComponent>
</div>
);
}
});
expect(function() {
React.renderComponent(<TestContainer />, document.createElement('div'));
}).not.toThrow();
});
it('should let text nodes retain their uniqueness', function() {
var TestComponent = React.createClass({
render: function() {
return <div>{this.props.children}<span /></div>;
}
});
var TestContainer = React.createClass({
render: function() {
return (
<TestComponent>
<div />
{'second'}
</TestComponent>
);
}
});
expect(function() {
React.renderComponent(<TestContainer />, document.createElement('div'));
}).not.toThrow();
});
it('should retain key during updates in composite components', function() {
var TestComponent = React.createClass({
render: function() {
@@ -155,19 +281,13 @@ describe('ReactIdentity', function() {
React.renderComponent(wrapped, document.createElement('div'));
var beforeKey = wrapped
._renderedComponent
._renderedComponent
.props.children[0]._key;
var beforeID = ReactID.getID(wrapped.getDOMNode().firstChild);
wrapped.swap();
var afterKey = wrapped
._renderedComponent
._renderedComponent
.props.children[0]._key;
var afterID = ReactID.getID(wrapped.getDOMNode().firstChild);
expect(beforeKey).not.toEqual(afterKey);
expect(beforeID).not.toEqual(afterID);
});
@@ -21,8 +21,7 @@
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
var reactComponentExpect= require('reactComponentExpect');
var ReactID = require('ReactID');
/**
* Ensure that all callbacks are invoked, passing this unique argument.
@@ -92,20 +91,65 @@ describe('ReactInstanceHandles', function() {
parentNode.appendChild(childNodeA);
parentNode.appendChild(childNodeB);
parentNode.id = '.react[0]';
childNodeA.id = '.react[0].0';
childNodeB.id = '.react[0].0:1';
ReactID.setID(parentNode, '.react[0]');
ReactID.setID(childNodeA, '.react[0].0');
ReactID.setID(childNodeB, '.react[0].0:1');
expect(
ReactInstanceHandles.findComponentRoot(parentNode, childNodeB.id)
ReactInstanceHandles.findComponentRoot(
parentNode,
ReactID.getID(childNodeB)
)
).toBe(childNodeB);
});
it('should work around unidentified nodes', function() {
var parentNode = document.createElement('div');
var childNodeA = document.createElement('div');
var childNodeB = document.createElement('div');
parentNode.appendChild(childNodeA);
parentNode.appendChild(childNodeB);
ReactID.setID(parentNode, '.react[0]');
// No ID on `childNodeA`.
ReactID.setID(childNodeB, '.react[0].0:1');
expect(
ReactInstanceHandles.findComponentRoot(
parentNode,
ReactID.getID(childNodeB)
)
).toBe(childNodeB);
});
it('should throw if a rendered element cannot be found', function() {
var parentNode = document.createElement('table');
var childNodeA = document.createElement('tbody');
var childNodeB = document.createElement('tr');
parentNode.appendChild(childNodeA);
childNodeA.appendChild(childNodeB);
ReactID.setID(parentNode, '.react[0]');
// No ID on `childNodeA`, it was "rendered by the browser".
ReactID.setID(childNodeB, '.react[0].1:0');
expect(function() {
ReactInstanceHandles.findComponentRoot(
parentNode,
ReactID.getID(childNodeB)
);
}).toThrow(
'Invariant Violation: findComponentRoot(..., .react[0].1:0): Unable ' +
'to find element. This probably means the DOM was unexpectedly ' +
'mutated (e.g. by the browser).'
);
});
});
describe('getReactRootIDFromNodeID', function() {
it('should support strings', function() {
var test = '.reactRoot[s_0_1][0]..[1]';
var expected = '.reactRoot[s_0_1]';
var test = '.r[s_0_1][0]..[1]';
var expected = '.r[s_0_1]';
var actual = ReactInstanceHandles.getReactRootIDFromNodeID(test);
expect(actual).toEqual(expected);
});
@@ -19,12 +19,7 @@
"use strict";
var ReactMount = require('ReactMount');
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
var ReactMount = require('ReactMount');
var reactComponentExpect = require('reactComponentExpect');
describe('ReactMount', function() {
it("should destroy a react root upon request", function() {
@@ -23,6 +23,7 @@ require('mock-modules');
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
var ReactID = require('ReactID');
var objMapKeyVal = require('objMapKeyVal');
@@ -41,12 +42,11 @@ var stripEmptyValues = function(obj) {
};
/**
* These children are wrapped in an array and therefore their keys are prefixed.
* Their name is also wrapped in an prefix and suffix character. We strip those
* Child names is are wrapped in an prefix and suffix character. We strip those
* out. This relies on a tiny implementation detail of the rendering system.
*/
var getOriginalKey = function(childName) {
return childName.substr(4, childName.length - 5);
return childName.substr(1, childName.length - 2);
};
/**
@@ -191,7 +191,7 @@ function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
var i;
var orderedDomIds = [];
for (i=0; i < statusDisplayNodes.length; i++) {
orderedDomIds.push(statusDisplayNodes[i].id);
orderedDomIds.push(ReactID.getID(statusDisplayNodes[i]));
}
var orderedLogicalIds = [];
+11 -30
View File
@@ -17,13 +17,14 @@
* @emails react-core
*/
/*jslint evil: true */
"use strict";
require('mock-modules');
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
var ReactTextComponent = require('ReactTextComponent');
var reactComponentExpect = require('reactComponentExpect');
@@ -70,10 +71,6 @@ var assertSingleChild = function(instance, text) {
};
// Helpers
var renderSingleContentChild = function(text) {
var d = ReactTestUtils.renderIntoDocument(<div content={text} />);
return d;
};
var renderSingleTextChild = function(text) {
var d = ReactTestUtils.renderIntoDocument(<div>{text}</div>);
return d;
@@ -141,7 +138,7 @@ describe('ReactMultiChildText', function() {
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render zero string as text node then switch to spans', function() {
it('should render zero string as text node then switch to spans', function() {
var d = renderSingleTextChild('0');
// false should act exactly as a null child
assertNodeText(d, '0');
@@ -149,7 +146,7 @@ describe('ReactMultiChildText', function() {
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render zero number as text node then switch to spans', function() {
it('should render zero number as text node then switch to spans', function() {
var d = renderSingleTextChild('0');
// false should act exactly as a null child
assertNodeText(d, 0);
@@ -157,11 +154,6 @@ describe('ReactMultiChildText', function() {
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render content to single text node', function() {
var d = renderSingleContentChild('hello');
assertNodeText(d, 'hello');
});
it('should render a single text child to a single text node', function() {
var d = renderSingleTextChild('hello');
assertNodeText(d, 'hello');
@@ -233,12 +225,6 @@ describe('ReactMultiChildText', function() {
assertNodeText(d, '0');
});
it('should render content number zero as text node', function() {
var d = renderSingleContentChild(0);
// false should act exactly as a null child
assertNodeText(d, '0');
});
it('should render zero string as string child', function() {
var d = renderMultipleTextChildren('0', 234.2);
// false should act exactly as a null child
@@ -249,7 +235,7 @@ describe('ReactMultiChildText', function() {
var d = renderMultipleTextChildren('0', 234.2);
// false should act exactly as a null child
assertMultiChild(d, '0', '234.2');
d.replaceProps({content: '0'});
d.replaceProps({children: '0'});
assertNodeText(d, '0');
});
@@ -257,7 +243,7 @@ describe('ReactMultiChildText', function() {
var d = renderMultipleTextChildren(0, 234.2);
// false should act exactly as a null child
assertMultiChild(d, '0', '234.2');
d.replaceProps({content: 0});
d.replaceProps({children: 0});
// BELOW REVEALS A BUG IN JSDOM
// assertNodeText(d, '0'); // This works in the browser.
});
@@ -265,7 +251,7 @@ describe('ReactMultiChildText', function() {
it('should render multiple children then switch to inline', function() {
var d = renderMultipleTextChildren('hello', 'goodbye');
assertMultiChild(d, 'hello', 'goodbye');
d.replaceProps({content: 'hello'});
d.replaceProps({children: 'hello'});
assertNodeText(d, 'hello');
});
@@ -285,13 +271,6 @@ describe('ReactMultiChildText', function() {
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render content, then switch to text components ', function() {
var d = renderSingleContentChild('hello');
assertNodeText(d, 'hello');
d.replaceProps({children: ['hello', 'goodbye']});
assertMultiChild(d, 'hello', 'goodbye');
});
it('should render inline child, then switch to composite', function() {
var d = renderSingleTextChild('hello');
assertNodeText(d, 'hello');
@@ -301,9 +280,11 @@ describe('ReactMultiChildText', function() {
.toBeCompositeComponentWithType(TestCompositeComponent);
});
it('should throw if rendering both content and children', function() {
it('should throw if rendering both HTML and children', function() {
expect(function() {
ReactTestUtils.renderIntoDocument(<div content="asdf">ghjkl</div>);
ReactTestUtils.renderIntoDocument(
<div dangerouslySetInnerHTML={{_html: 'abcdef'}}>ghjkl</div>
);
}).toThrow();
});
});
@@ -292,25 +292,11 @@ describe('ReactNativeComponent', function() {
});
it("should validate against multiple children props", function() {
expect(function() {
mountComponent({ content: '', children: '' });
}).toThrow(
'Invariant Violation: Can only set one of `children`, ' +
'`props.content`, or `props.dangerouslySetInnerHTML`.'
);
expect(function() {
mountComponent({ content: '', dangerouslySetInnerHTML: '' });
}).toThrow(
'Invariant Violation: Can only set one of `children`, ' +
'`props.content`, or `props.dangerouslySetInnerHTML`.'
);
expect(function() {
mountComponent({ children: '', dangerouslySetInnerHTML: '' });
}).toThrow(
'Invariant Violation: Can only set one of `children`, ' +
'`props.content`, or `props.dangerouslySetInnerHTML`.'
'Invariant Violation: Can only set one of `children` or ' +
'`props.dangerouslySetInnerHTML`.'
);
});
@@ -328,6 +314,7 @@ describe('ReactNativeComponent', function() {
it("should clean up listeners", function() {
var React = require('React');
var ReactEventEmitter = require('ReactEventEmitter');
var ReactID = require('ReactID');
var container = document.createElement('div');
document.documentElement.appendChild(container);
@@ -337,7 +324,7 @@ describe('ReactNativeComponent', function() {
React.renderComponent(instance, container);
var rootNode = instance.getDOMNode();
var rootNodeID = rootNode.id;
var rootNodeID = ReactID.getID(rootNode);
expect(
ReactEventEmitter.getListener(rootNodeID, 'onClick')
).toBe(callback);
+1 -1
View File
@@ -26,7 +26,7 @@ function typeCheck(declaration, value) {
if (arguments.length > 1) {
props.testProp = value;
}
return declaration.bind(this, props, 'testProp', 'testComponent');
return declaration.bind(null, props, 'testProp', 'testComponent');
}
describe('Primitive Types', function() {
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule CSSPropertyOperations
* @typechecks
* @typechecks static-only
*/
"use strict";
+4 -1
View File
@@ -16,6 +16,9 @@
* @providesModule DOMChildrenOperations
*/
// Empty blocks improve readability so disable that warning
// jshint -W035
"use strict";
var Danger = require('Danger');
@@ -44,7 +47,7 @@ var REMOVE_AT = keyOf({removeAt: null});
*
* @param {Element} parent Parent DOM node.
* @param {Object} childOperations Description of child operations.
* @returns {Array?} Sparse array containing elements by their current index in
* @return {Array?} Sparse array containing elements by their current index in
* the DOM.
*/
var _getNodesByOriginalIndex = function(parent, childOperations) {
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule DOMProperty
* @typechecks
* @typechecks static-only
*/
/*jslint bitwise: true */
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule DOMPropertyOperations
* @typechecks
* @typechecks static-only
*/
"use strict";
+3 -1
View File
@@ -16,6 +16,8 @@
* @providesModule Danger
*/
/*jslint evil: true */
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
@@ -63,7 +65,7 @@ function getParentDummy(parent) {
* @param {Element} elem Parent element.
* @param {Element} insert Element to insert.
* @param {Element} after Element to insert after.
* @returns {Element} Element that was inserted.
* @return {Element} Element that was inserted.
*/
function insertNodeAfterNode(elem, insert, after) {
if (__DEV__) {
@@ -13,24 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule BrowserEnv
* @providesModule ViewportMetrics
*/
"use strict";
/**
* A place to share/cache browser/chrome level computations.
*/
var BrowserEnv = {
var ViewportMetrics = {
currentScrollLeft: 0,
currentScrollTop: 0,
browserInfo: null,
refreshAuthoritativeScrollValues: function() {
BrowserEnv.currentScrollLeft =
refreshScrollValues: function() {
ViewportMetrics.currentScrollLeft =
document.body.scrollLeft + document.documentElement.scrollLeft;
BrowserEnv.currentScrollTop =
ViewportMetrics.currentScrollTop =
document.body.scrollTop + document.documentElement.scrollTop;
}
};
module.exports = BrowserEnv;
module.exports = ViewportMetrics;
@@ -17,6 +17,8 @@
* @emails react-core
*/
/*jslint evil: true */
"use strict";
var React = require('React');
+112
View File
@@ -0,0 +1,112 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactDOMInput
*/
"use strict";
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDOM = require('ReactDOM');
var merge = require('merge');
// Store a reference to the <input> `ReactNativeComponent`.
var input = ReactDOM.input;
/**
* Implements an <input> native component that allows setting these optional
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
*
* If `checked` or `value` are not supplied (or null/undefined), user actions
* that affect the checked state or value will trigger updates to the element.
*
* If they are supplied (and not null/undefined), the rendered element will not
* trigger updates to the element. Instead, the props must change in order for
* the rendered element to be updated.
*
* The rendered element will be initialized as unchecked (or `defaultChecked`)
* with an empty value (or `defaultValue`).
*
* @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
*/
var ReactDOMInput = ReactCompositeComponent.createClass({
getInitialState: function() {
return {
checked: this.props.defaultChecked || false,
value: this.props.defaultValue || ''
};
},
shouldComponentUpdate: function() {
// Defer any updates to this component during the `onChange` handler.
return !this._isChanging;
},
getChecked: function() {
return this.props.checked != null ? this.props.checked : this.state.checked;
},
getValue: function() {
return this.props.value != null ? this.props.value : this.state.value;
},
render: function() {
// Clone `this.props` so we don't mutate the input.
var props = merge(this.props);
props.checked = this.getChecked();
props.value = this.getValue();
props.onChange = this.handleChange;
return input(props, this.props.children);
},
componentDidUpdate: function(prevProps, prevState, rootNode) {
if (this.props.checked != null) {
DOMPropertyOperations.setValueForProperty(
rootNode,
'checked',
this.props.checked || false
);
}
if (this.props.value != null) {
DOMPropertyOperations.setValueForProperty(
rootNode,
'value',
this.props.value || ''
);
}
},
handleChange: ReactCompositeComponent.autoBind(function(event) {
var returnValue;
if (this.props.onChange) {
this._isChanging = true;
returnValue = this.props.onChange(event);
this._isChanging = false;
}
this.setState({
checked: event.target.checked,
value: event.target.value
});
return returnValue;
})
});
module.exports = ReactDOMInput;
+136
View File
@@ -0,0 +1,136 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ReactDOMTextarea
*/
"use strict";
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDOM = require('ReactDOM');
var invariant = require('invariant');
var merge = require('merge');
// Store a reference to the <textarea> `ReactNativeComponent`.
var textarea = ReactDOM.textarea;
// For quickly matching children type, to test if can be treated as content.
var CONTENT_TYPES = {'string': true, 'number': true};
/**
* Implements a <textarea> native component that allows setting `value`, and
* `defaultValue`. This differs from the traditional DOM API because value is
* usually set as PCDATA children.
*
* If `value` is not supplied (or null/undefined), user actions that affect the
* value will trigger updates to the element.
*
* If `value` is supplied (and not null/undefined), the rendered element will
* not trigger updates to the element. Instead, the `value` prop must change in
* order for the rendered element to be updated.
*
* The rendered element will be initialized with an empty value, the prop
* `defaultValue` if specified, or the children content (deprecated).
*/
var ReactDOMTextarea = ReactCompositeComponent.createClass({
getInitialState: function() {
var defaultValue = this.props.defaultValue;
// TODO (yungsters): Remove support for children content in <textarea>.
var children = this.props.children;
if (children != null) {
global.console && console.warn && console.warn(
'Use the `defaultValue` or `value` props instead of setting children ' +
'on <textarea>.'
);
invariant(
defaultValue == null,
'If you supply `defaultValue` on a <textarea>, do not pass children.'
);
if (Array.isArray(children)) {
invariant(
children.length <= 1,
'<textarea> can only have at most one child.'
);
children = children[0];
}
invariant(
CONTENT_TYPES[typeof children],
'If you specify children to <textarea>, it must be a single string ' +
'or number., not an array or object.'
);
defaultValue = '' + children;
}
defaultValue = defaultValue || '';
return {
// We save the initial value so that `ReactNativeComponent` doesn't update
// `textContent` (unnecessary since we update value).
initialValue: this.props.value != null ? this.props.value : defaultValue,
value: defaultValue
};
},
shouldComponentUpdate: function() {
// Defer any updates to this component during the `onChange` handler.
return !this._isChanging;
},
getValue: function() {
return this.props.value != null ? this.props.value : this.state.value;
},
render: function() {
// Clone `this.props` so we don't mutate the input.
var props = merge(this.props);
invariant(
props.dangerouslySetInnerHTML == null,
'`dangerouslySetInnerHTML` does not make sense on <textarea>.'
);
props.value = this.getValue();
props.onChange = this.handleChange;
// Always set children to the same thing. In IE9, the selection range will
// get reset if `textContent` is mutated.
return textarea(props, this.state.initialValue);
},
componentDidUpdate: function(prevProps, prevState, rootNode) {
if (this.props.value != null) {
DOMPropertyOperations.setValueForProperty(
rootNode,
'value',
this.props.value || ''
);
}
},
handleChange: ReactCompositeComponent.autoBind(function(event) {
var returnValue;
if (this.props.onChange) {
this._isChanging = true;
returnValue = this.props.onChange(event);
this._isChanging = false;
}
this.setState({value: event.target.value});
return returnValue;
})
});
module.exports = ReactDOMTextarea;
@@ -0,0 +1,93 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @jsx React.DOM
* @emails react-core
*/
"use strict";
/*jshint evil:true */
describe('ReactDOMTextarea', function() {
var React;
var ReactTestUtils;
var renderTextarea;
beforeEach(function() {
React = require('React');
ReactTestUtils = require('ReactTestUtils');
renderTextarea = function(component) {
var stub = ReactTestUtils.renderIntoDocument(component);
var node = stub.getDOMNode();
// Polyfilling the browser's quirky behavior.
node.value = node.innerHTML;
return node;
};
});
it('should allow setting `defaultValue`', function() {
var stub = <textarea defaultValue="giraffe" />;
var node = renderTextarea(stub);
expect(node.value).toBe('giraffe');
// Changing `defaultValue` should do nothing.
stub.replaceProps({defaultValue: 'gorilla'});
expect(node.value).toEqual('giraffe');
});
it('should allow setting `value`', function() {
var stub = <textarea value="giraffe" />;
var node = renderTextarea(stub);
expect(node.value).toBe('giraffe');
stub.replaceProps({value: 'gorilla'});
expect(node.value).toEqual('gorilla');
});
it('should treat children like `defaultValue`', function() {
var stub = <textarea>giraffe</textarea>;
var node = renderTextarea(stub);
expect(node.value).toBe('giraffe');
// Changing children should do nothing, it functions like `defaultValue`.
stub.replaceProps({children: 'gorilla'});
expect(node.value).toEqual('giraffe');
});
it('should allow numbers as children', function() {
var node = renderTextarea(<textarea>{17}</textarea>);
expect(node.value).toBe('17');
});
it("should throw with multiple or invalid children", function() {
expect(function() {
ReactTestUtils.renderIntoDocument(
<textarea>{'hello'}{'there'}</textarea>
);
}).toThrow();
expect(function() {
ReactTestUtils.renderIntoDocument(
<textarea><strong /></textarea>
);
}).toThrow();
});
});
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule dangerousStyleValue
* @typechecks
* @typechecks static-only
*/
"use strict";
-40
View File
@@ -1,40 +0,0 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule getDOMNodeID
* @typechecks
*/
"use strict";
/**
* Accessing "id" or calling getAttribute('id') on a form element can return its
* control whose name or ID is "id". All DOM nodes support `getAttributeNode`
* but this can also get called on other objects so just return '' if we're
* given something other than a DOM node (such as window).
*
* @param {DOMElement|DOMWindow|DOMDocument} domNode DOM node.
* @returns {string} ID of the supplied `domNode`.
*/
function getDOMNodeID(domNode) {
if (domNode.getAttributeNode) {
var attributeNode = domNode.getAttributeNode('id');
return attributeNode && attributeNode.value || '';
} else {
return '';
}
}
module.exports = getDOMNodeID;
+3 -1
View File
@@ -14,9 +14,11 @@
* limitations under the License.
*
* @providesModule getEventTarget
* @typechecks
* @typechecks static-only
*/
"use strict";
var ExecutionEnvironment = require('ExecutionEnvironment');
/**
+1 -1
View File
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @typechecks
* @typechecks static-only
* @providesModule ReactServerRendering
*/
"use strict";
@@ -17,15 +17,19 @@
* @emails react-core
*/
/*jslint evil: true */
"use strict";
require('mock-modules')
.dontMock('ExecutionEnvironment')
.dontMock('React')
.dontMock('ReactID')
.dontMock('ReactServerRendering')
.dontMock('ReactTestUtils');
var React;
var ReactID;
var ReactTestUtils;
var ReactServerRendering;
var ExecutionEnvironment;
@@ -34,6 +38,7 @@ describe('ReactServerRendering', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
React = require('React');
ReactID = require('ReactID');
ReactTestUtils = require('ReactTestUtils');
ExecutionEnvironment = require('ExecutionEnvironment');
ExecutionEnvironment.canUseDOM = false;
@@ -49,7 +54,7 @@ describe('ReactServerRendering', function() {
}
);
expect(response).toMatch(
'<span id="[^"]+">hello world</span>'
'<span ' + ReactID.ATTR_NAME + '="[^"]+">hello world</span>'
);
});
@@ -72,8 +77,12 @@ describe('ReactServerRendering', function() {
}
);
expect(response).toMatch(
'<div id="[^"]+"><span id="[^"]+"><span id="[^"]+">My name is </span>' +
'<span id="[^"]+">child</span></span></div>'
'<div ' + ReactID.ATTR_NAME + '="[^"]+">' +
'<span ' + ReactID.ATTR_NAME + '="[^"]+">' +
'<span ' + ReactID.ATTR_NAME + '="[^"]+">My name is </span>' +
'<span ' + ReactID.ATTR_NAME + '="[^"]+">child</span>' +
'</span>' +
'</div>'
);
});
@@ -121,8 +130,10 @@ describe('ReactServerRendering', function() {
);
expect(response).toMatch(
'<span id="[^"]+"><span id="[^"]+">Component name: </span>' +
'<span id="[^"]+">TestComponent</span></span>'
'<span ' + ReactID.ATTR_NAME + '="[^"]+">' +
'<span ' + ReactID.ATTR_NAME + '="[^"]+">Component name: </span>' +
'<span ' + ReactID.ATTR_NAME + '="[^"]+">TestComponent</span>' +
'</span>'
);
expect(lifecycle).toEqual(
['getInitialState', 'componentWillMount', 'render']
@@ -147,7 +158,7 @@ describe('ReactServerRendering', function() {
return (
<span ref="span" onClick={this.click}>Name: {this.props.name}</span>
);
},
}
});
var element = document.createElement('div');
-253
View File
@@ -1,253 +0,0 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule AbstractEvent
*/
"use strict";
var BrowserEnv = require('BrowserEnv');
var PooledClass = require('PooledClass');
var TouchEventUtils = require('TouchEventUtils');
var emptyFunction = require('emptyFunction');
var MAX_POOL_SIZE = 20;
/**
* AbstractEvent copy constructor. @see `PooledClass`. Provides a single place
* to define all cross browser normalization of DOM events. Does not attempt to
* extend a native event, rather creates a completely new object that has a
* reference to the nativeEvent through .nativeEvent member. The property .data
* should hold all data that is extracted from the event in a cross browser
* manner. Application code should use the data field when possible, not the
* unreliable native event.
*/
function AbstractEvent(
reactEventType,
reactTargetID, // Allows the abstract target to differ from native.
nativeEvent,
data) {
this.reactEventType = reactEventType;
this.reactTargetID = reactTargetID || '';
this.nativeEvent = nativeEvent;
this.data = data;
// TODO: Deprecate storing target - doesn't always make sense for some types
this.target = nativeEvent && nativeEvent.target;
/**
* As a performance optimization, we tag the existing event with the listeners
* (or listener [singular] if only one). This avoids having to package up an
* abstract event along with the set of listeners into a wrapping "dispatch"
* object. No one should ever read this property except event system and
* plugin/dispatcher code. We also tag the abstract event with a parallel
* ID array. _dispatchListeners[i] is being dispatched to a DOM node at ID
* _dispatchIDs[i]. The lengths should never, ever, ever be different.
*/
this._dispatchListeners = null;
this._dispatchIDs = null;
this.isPropagationStopped = false;
this.isPersistent = emptyFunction.thatReturnsFalse;
}
/** `PooledClass` looks for this. */
AbstractEvent.poolSize = MAX_POOL_SIZE;
/**
* `PooledClass` looks for `destructor` on each instance it releases. We need to
* ensure that we remove all references to listeners which could trap large
* amounts of memory in their closures.
*/
AbstractEvent.prototype.destructor = function() {
this.target = null;
this._dispatchListeners = null;
this._dispatchIDs = null;
};
/**
* Enhance the `AbstractEvent` class to have pooling abilities. We instruct
* `PooledClass` that our copy constructor accepts five arguments (this is just
* a performance optimization). These objects are instantiated frequently.
*/
PooledClass.addPoolingTo(AbstractEvent, PooledClass.fiveArgumentPooler);
AbstractEvent.prototype.stopPropagation = function() {
this.isPropagationStopped = true;
if (this.nativeEvent.stopPropagation) {
this.nativeEvent.stopPropagation();
}
// IE8 only understands cancelBubble, not stopPropagation().
this.nativeEvent.cancelBubble = true;
};
AbstractEvent.prototype.preventDefault = function() {
AbstractEvent.preventDefaultOnNativeEvent(this.nativeEvent);
};
/**
* We clear out all dispatched `AbstractEvent`s after each event loop, adding
* them back into the pool. This allows a way to hold onto a reference that
* won't be added back into the pool.
*/
AbstractEvent.prototype.persist = function() {
this.isPersistent = emptyFunction.thatReturnsTrue;
};
/**
* Utility function for preventing default in cross browser manner.
*/
AbstractEvent.preventDefaultOnNativeEvent = function(nativeEvent) {
if (nativeEvent.preventDefault) {
nativeEvent.preventDefault();
} else {
nativeEvent.returnValue = false;
}
};
/**
* @param {Element} target The target element.
*/
AbstractEvent.normalizeScrollDataFromTarget = function(target) {
return {
scrollTop: target.scrollTop,
scrollLeft: target.scrollLeft,
clientWidth: target.clientWidth,
clientHeight: target.clientHeight,
scrollHeight: target.scrollHeight,
scrollWidth: target.scrollWidth
};
};
/*
* There are some normalizations that need to happen for various browsers. In
* addition to replacing the general event fixing with a framework such as
* jquery, we need to normalize mouse events here. Code below is mostly borrowed
* from: jScrollPane/script/jquery.mousewheel.js
*/
AbstractEvent.normalizeMouseWheelData = function(nativeEvent) {
var delta = 0;
var deltaX = 0;
var deltaY = 0;
/* traditional scroll wheel data */
if ( nativeEvent.wheelDelta ) { delta = nativeEvent.wheelDelta/120; }
if ( nativeEvent.detail ) { delta = -nativeEvent.detail/3; }
/* Multidimensional scroll (touchpads) with deltas */
deltaY = delta;
/* Gecko based browsers */
if (nativeEvent.axis !== undefined &&
nativeEvent.axis === nativeEvent.HORIZONTAL_AXIS ) {
deltaY = 0;
deltaX = -delta;
}
/* Webkit based browsers */
if (nativeEvent.wheelDeltaY !== undefined ) {
deltaY = nativeEvent.wheelDeltaY/120;
}
if (nativeEvent.wheelDeltaX !== undefined ) {
deltaX = -nativeEvent.wheelDeltaX/120;
}
return { delta: delta, deltaX: deltaX, deltaY: deltaY };
};
/**
* I <3 Quirksmode.org:
* http://www.quirksmode.org/js/events_properties.html
*/
AbstractEvent.isNativeClickEventRightClick = function(nativeEvent) {
return nativeEvent.which ? nativeEvent.which === 3 :
nativeEvent.button ? nativeEvent.button === 2 :
false;
};
AbstractEvent.normalizePointerData = function(nativeEvent) {
return {
globalX: AbstractEvent.eventPageX(nativeEvent),
globalY: AbstractEvent.eventPageY(nativeEvent),
rightMouseButton:
AbstractEvent.isNativeClickEventRightClick(nativeEvent)
};
};
AbstractEvent.normalizeDragEventData =
function(nativeEvent, globalX, globalY, startX, startY) {
return {
globalX: globalX,
globalY: globalY,
startX: startX,
startY: startY
};
};
/**
* Warning: It is possible to move your finger on a touch surface, yet not
* effect the `eventPageX/Y` because the touch had caused a scroll that
* compensated for your movement. To track movements across the page, prevent
* default to avoid scrolling, and control scrolling in javascript.
*/
/**
* Gets the exact position of a touch/mouse event on the page with respect to
* the document body. The only reason why this method is needed instead of using
* `TouchEventUtils.extractSingleTouch` is to support IE8-. Mouse events in all
* browsers except IE8- contain a pageY. IE8 and below require clientY
* computation:
*
* @param {Event} nativeEvent Native event, possibly touch or mouse.
* @return {number} Coordinate with respect to document body.
*/
AbstractEvent.eventPageY = function(nativeEvent) {
var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
if (singleTouch) {
return singleTouch.pageY;
} else if (typeof nativeEvent.pageY !== 'undefined') {
return nativeEvent.pageY;
} else {
return nativeEvent.clientY + BrowserEnv.currentPageScrollTop;
}
};
/**
* @see `AbstractEvent.eventPageY`.
*
* @param {Event} nativeEvent Native event, possibly touch or mouse.
* @return {number} Coordinate with respect to document body.
*/
AbstractEvent.eventPageX = function(nativeEvent) {
var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
if (singleTouch) {
return singleTouch.pageX;
} else if (typeof nativeEvent.pageX !== 'undefined') {
return nativeEvent.pageX;
} else {
return nativeEvent.clientX + BrowserEnv.currentPageScrollLeft;
}
};
/**
* @deprecated
*/
AbstractEvent.persistentCloneOf = function(abstractEvent) {
abstractEvent.persist();
return abstractEvent;
};
module.exports = AbstractEvent;
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*
* @providesModule CallbackRegistry
* @typechecks
* @typechecks static-only
*/
"use strict";
+1 -1
View File
@@ -65,7 +65,7 @@ var executeDispatchesAndRelease = function(event) {
* Required. When a top-level event is fired, this method is expected to
* extract synthetic events that will in turn be queued and dispatched.
*
* `abstractEventTypes` {object}
* `eventTypes` {object}
* Optional, plugins that fire events must publish a mapping of registration
* names that are used to register listeners. Values of this mapping must
* be objects that contain `registrationName` or `phasedRegistrationNames`.
+17 -3
View File
@@ -1,6 +1,20 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule EventPluginRegistry
* @typechecks
* @typechecks static-only
*/
"use strict";
@@ -46,7 +60,7 @@ function recomputePluginOrdering() {
pluginName
);
EventPluginRegistry.plugins[pluginIndex] = PluginModule;
var publishedEvents = PluginModule.abstractEventTypes;
var publishedEvents = PluginModule.eventTypes;
for (var eventName in publishedEvents) {
invariant(
publishEventForPlugin(publishedEvents[eventName], PluginModule),
@@ -184,7 +198,7 @@ var EventPluginRegistry = {
* @internal
*/
getPluginModuleForEvent: function(event) {
var dispatchConfig = event.reactEventType;
var dispatchConfig = event.dispatchConfig;
if (dispatchConfig.registrationName) {
return EventPluginRegistry.registrationNames[
dispatchConfig.registrationName
+46 -67
View File
@@ -19,7 +19,6 @@
"use strict";
var EventConstants = require('EventConstants');
var AbstractEvent = require('AbstractEvent');
var invariant = require('invariant');
@@ -40,27 +39,11 @@ function isStartish(topLevelType) {
topLevelType === topLevelTypes.topTouchStart;
}
function storePageCoordsIn(obj, nativeEvent) {
var pageX = AbstractEvent.eventPageX(nativeEvent);
var pageY = AbstractEvent.eventPageY(nativeEvent);
obj.pageX = pageX;
obj.pageY = pageY;
}
function eventDistance(coords, nativeEvent) {
var pageX = AbstractEvent.eventPageX(nativeEvent);
var pageY = AbstractEvent.eventPageY(nativeEvent);
return Math.pow(
Math.pow(pageX - coords.pageX, 2) + Math.pow(pageY - coords.pageY, 2),
0.5
);
}
var validateEventDispatches;
if (__DEV__) {
validateEventDispatches = function(abstractEvent) {
var dispatchListeners = abstractEvent._dispatchListeners;
var dispatchIDs = abstractEvent._dispatchIDs;
validateEventDispatches = function(event) {
var dispatchListeners = event._dispatchListeners;
var dispatchIDs = event._dispatchIDs;
var listenersIsArr = Array.isArray(dispatchListeners);
var idsIsArr = Array.isArray(dispatchIDs);
@@ -71,81 +54,79 @@ if (__DEV__) {
invariant(
idsIsArr === listenersIsArr && IDsLen === listenersLen,
'EventPluginUtils: Invalid `abstractEvent`.'
'EventPluginUtils: Invalid `event`.'
);
};
}
/**
* Invokes `cb(abstractEvent, listener, id)`. Avoids using call if no scope is
* Invokes `cb(event, listener, id)`. Avoids using call if no scope is
* provided. The `(listener,id)` pair effectively forms the "dispatch" but are
* kept separate to conserve memory.
*/
function forEachEventDispatch(abstractEvent, cb) {
var dispatchListeners = abstractEvent._dispatchListeners;
var dispatchIDs = abstractEvent._dispatchIDs;
function forEachEventDispatch(event, cb) {
var dispatchListeners = event._dispatchListeners;
var dispatchIDs = event._dispatchIDs;
if (__DEV__) {
validateEventDispatches(abstractEvent);
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
var i;
for (
i = 0;
i < dispatchListeners.length && !abstractEvent.isPropagationStopped;
i++) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and IDs are two parallel arrays that are always in sync.
cb(abstractEvent, dispatchListeners[i], dispatchIDs[i]);
cb(event, dispatchListeners[i], dispatchIDs[i]);
}
} else if (dispatchListeners) {
cb(abstractEvent, dispatchListeners, dispatchIDs);
cb(event, dispatchListeners, dispatchIDs);
}
}
/**
* Default implementation of PluginModule.executeDispatch().
* @param {AbstractEvent} AbstractEvent to handle
* @param {SyntheticEvent} SyntheticEvent to handle
* @param {function} Application-level callback
* @param {string} domID DOM id to pass to the callback.
*/
function executeDispatch(abstractEvent, listener, domID) {
listener(abstractEvent, domID);
function executeDispatch(event, listener, domID) {
listener(event, domID);
}
/**
* Standard/simple iteration through an event's collected dispatches.
*/
function executeDispatchesInOrder(abstractEvent, executeDispatch) {
forEachEventDispatch(abstractEvent, executeDispatch);
abstractEvent._dispatchListeners = null;
abstractEvent._dispatchIDs = null;
function executeDispatchesInOrder(event, executeDispatch) {
forEachEventDispatch(event, executeDispatch);
event._dispatchListeners = null;
event._dispatchIDs = null;
}
/**
* Standard/simple iteration through an event's collected dispatches, but stops
* at the first dispatch execution returning true, and returns that id.
*
* @returns id of the first dispatch execution who's listener returns true, or
* @return id of the first dispatch execution who's listener returns true, or
* null if no listener returned true.
*/
function executeDispatchesInOrderStopAtTrue(abstractEvent) {
var dispatchListeners = abstractEvent._dispatchListeners;
var dispatchIDs = abstractEvent._dispatchIDs;
function executeDispatchesInOrderStopAtTrue(event) {
var dispatchListeners = event._dispatchListeners;
var dispatchIDs = event._dispatchIDs;
if (__DEV__) {
validateEventDispatches(abstractEvent);
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
var i;
for (
i = 0;
i < dispatchListeners.length && !abstractEvent.isPropagationStopped;
i++) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and IDs are two parallel arrays that are always in sync.
if (dispatchListeners[i](abstractEvent, dispatchIDs[i])) {
if (dispatchListeners[i](event, dispatchIDs[i])) {
return dispatchIDs[i];
}
}
} else if (dispatchListeners) {
if (dispatchListeners(abstractEvent, dispatchIDs)) {
if (dispatchListeners(event, dispatchIDs)) {
return dispatchIDs;
}
}
@@ -159,32 +140,32 @@ function executeDispatchesInOrderStopAtTrue(abstractEvent) {
* return values at each dispatch execution, but it does tend to make sense when
* dealing with "direct" dispatches.
*
* @returns The return value of executing the single dispatch.
* @return The return value of executing the single dispatch.
*/
function executeDirectDispatch(abstractEvent) {
function executeDirectDispatch(event) {
if (__DEV__) {
validateEventDispatches(abstractEvent);
validateEventDispatches(event);
}
var dispatchListener = abstractEvent._dispatchListeners;
var dispatchID = abstractEvent._dispatchIDs;
var dispatchListener = event._dispatchListeners;
var dispatchID = event._dispatchIDs;
invariant(
!Array.isArray(dispatchListener),
'executeDirectDispatch(...): Invalid `abstractEvent`.'
'executeDirectDispatch(...): Invalid `event`.'
);
var res = dispatchListener ?
dispatchListener(abstractEvent, dispatchID) :
dispatchListener(event, dispatchID) :
null;
abstractEvent._dispatchListeners = null;
abstractEvent._dispatchIDs = null;
event._dispatchListeners = null;
event._dispatchIDs = null;
return res;
}
/**
* @param {AbstractEvent} abstractEvent
* @returns {bool} True iff number of dispatches accumulated is greater than 0.
* @param {SyntheticEvent} event
* @return {bool} True iff number of dispatches accumulated is greater than 0.
*/
function hasDispatches(abstractEvent) {
return !!abstractEvent._dispatchListeners;
function hasDispatches(event) {
return !!event._dispatchListeners;
}
/**
@@ -194,8 +175,6 @@ var EventPluginUtils = {
isEndish: isEndish,
isMoveish: isMoveish,
isStartish: isStartish,
storePageCoordsIn: storePageCoordsIn,
eventDistance: eventDistance,
executeDispatchesInOrder: executeDispatchesInOrder,
executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
executeDirectDispatch: executeDirectDispatch,
+27 -29
View File
@@ -56,19 +56,19 @@ var injection = {
* Some event types have a notion of different registration names for different
* "phases" of propagation. This finds listeners by a given phase.
*/
function listenerAtPhase(id, abstractEvent, propagationPhase) {
function listenerAtPhase(id, event, propagationPhase) {
var registrationName =
abstractEvent.reactEventType.phasedRegistrationNames[propagationPhase];
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(id, registrationName);
}
/**
* Tags an `AbstractEvent` with dispatched listeners. Creating this function
* Tags a `SyntheticEvent` with dispatched listeners. Creating this function
* here, allows us to not have to bind or create functions for each event.
* Mutating the event's members allows us to not have to create a wrapping
* "dispatch" object that pairs the event with the listener.
*/
function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
function accumulateDirectionalDispatches(domID, upwards, event) {
if (__DEV__) {
if (!domID) {
throw new Error('Dispatching id must not be null');
@@ -76,11 +76,10 @@ function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
injection.validate();
}
var phase = upwards ? PropagationPhases.bubbled : PropagationPhases.captured;
var listener = listenerAtPhase(domID, abstractEvent, phase);
var listener = listenerAtPhase(domID, event, phase);
if (listener) {
abstractEvent._dispatchListeners =
accumulate(abstractEvent._dispatchListeners, listener);
abstractEvent._dispatchIDs = accumulate(abstractEvent._dispatchIDs, domID);
event._dispatchListeners = accumulate(event._dispatchListeners, listener);
event._dispatchIDs = accumulate(event._dispatchIDs, domID);
}
}
@@ -91,12 +90,12 @@ function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
* single traversal for the entire collection of events because each event may
* have a different target.
*/
function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
if (abstractEvent && abstractEvent.reactEventType.phasedRegistrationNames) {
function accumulateTwoPhaseDispatchesSingle(event) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
injection.InstanceHandle.traverseTwoPhase(
abstractEvent.reactTargetID,
event.dispatchMarker,
accumulateDirectionalDispatches,
abstractEvent
event
);
}
}
@@ -105,36 +104,35 @@ function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
/**
* Accumulates without regard to direction, does not look for phased
* registration names. Same as `accumulateDirectDispatchesSingle` but without
* requiring that the `reactTargetID` be the same as the dispatched ID.
* requiring that the `dispatchMarker` be the same as the dispatched ID.
*/
function accumulateDispatches(id, ignoredDirection, abstractEvent) {
if (abstractEvent && abstractEvent.reactEventType.registrationName) {
var registrationName = abstractEvent.reactEventType.registrationName;
function accumulateDispatches(id, ignoredDirection, event) {
if (event && event.dispatchConfig.registrationName) {
var registrationName = event.dispatchConfig.registrationName;
var listener = getListener(id, registrationName);
if (listener) {
abstractEvent._dispatchListeners =
accumulate(abstractEvent._dispatchListeners, listener);
abstractEvent._dispatchIDs = accumulate(abstractEvent._dispatchIDs, id);
event._dispatchListeners = accumulate(event._dispatchListeners, listener);
event._dispatchIDs = accumulate(event._dispatchIDs, id);
}
}
}
/**
* Accumulates dispatches on an `AbstractEvent`, but only for the
* `reactTargetID`.
* @param {AbstractEvent} abstractEvent
* Accumulates dispatches on an `SyntheticEvent`, but only for the
* `dispatchMarker`.
* @param {SyntheticEvent} event
*/
function accumulateDirectDispatchesSingle(abstractEvent) {
if (abstractEvent && abstractEvent.reactEventType.registrationName) {
accumulateDispatches(abstractEvent.reactTargetID, null, abstractEvent);
function accumulateDirectDispatchesSingle(event) {
if (event && event.dispatchConfig.registrationName) {
accumulateDispatches(event.dispatchMarker, null, event);
}
}
function accumulateTwoPhaseDispatches(abstractEvents) {
function accumulateTwoPhaseDispatches(events) {
if (__DEV__) {
injection.validate();
}
forEachAccumulated(abstractEvents, accumulateTwoPhaseDispatchesSingle);
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}
function accumulateEnterLeaveDispatches(leave, enter, fromID, toID) {
@@ -151,11 +149,11 @@ function accumulateEnterLeaveDispatches(leave, enter, fromID, toID) {
}
function accumulateDirectDispatches(abstractEvents) {
function accumulateDirectDispatches(events) {
if (__DEV__) {
injection.validate();
}
forEachAccumulated(abstractEvents, accumulateDirectDispatchesSingle);
forEachAccumulated(events, accumulateDirectDispatchesSingle);
}
@@ -1,4 +1,18 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @emails react-core
*/
@@ -141,13 +155,13 @@ describe('EventPluginRegistry', function() {
it('should publish registration names of injected plugins', function() {
var OnePlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
click: {registrationName: 'onClick'},
focus: {registrationName: 'onFocus'}
}
});
var TwoPlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
magic: {
phasedRegistrationNames: {
bubbled: 'onMagicBubble',
@@ -175,12 +189,12 @@ describe('EventPluginRegistry', function() {
it('should throw if multiple registration names collide', function() {
var OnePlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
photoCapture: {registrationName: 'onPhotoCapture'}
}
});
var TwoPlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
photo: {
phasedRegistrationNames: {
bubbled: 'onPhotoBubble',
@@ -205,7 +219,7 @@ describe('EventPluginRegistry', function() {
it('should throw if an invalid event is published', function() {
var OnePlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
badEvent: {/* missing configuration */}
}
});
@@ -232,14 +246,14 @@ describe('EventPluginRegistry', function() {
};
var OnePlugin = createPlugin({
abstractEventTypes: {
eventTypes: {
click: clickDispatchConfig,
magic: magicDispatchConfig
}
});
var clickEvent = {reactEventType: clickDispatchConfig};
var magicEvent = {reactEventType: magicDispatchConfig};
var clickEvent = {dispatchConfig: clickDispatchConfig};
var magicEvent = {dispatchConfig: magicDispatchConfig};
expect(EventPluginRegistry.getPluginModuleForEvent(clickEvent)).toBe(null);
expect(EventPluginRegistry.getPluginModuleForEvent(magicEvent)).toBe(null);
+3 -1
View File
@@ -14,9 +14,11 @@
* limitations under the License.
*
* @providesModule SyntheticEvent
* @typechecks
* @typechecks static-only
*/
"use strict";
var PooledClass = require('PooledClass');
var emptyFunction = require('emptyFunction');
+3 -1
View File
@@ -14,9 +14,11 @@
* limitations under the License.
*
* @providesModule SyntheticFocusEvent
* @typechecks
* @typechecks static-only
*/
"use strict";
var SyntheticUIEvent = require('SyntheticUIEvent');
/**
@@ -14,9 +14,11 @@
* limitations under the License.
*
* @providesModule SyntheticKeyboardEvent
* @typechecks
* @typechecks static-only
*/
"use strict";
var SyntheticUIEvent = require('SyntheticUIEvent');
/**
+6 -4
View File
@@ -14,11 +14,13 @@
* limitations under the License.
*
* @providesModule SyntheticMouseEvent
* @typechecks
* @typechecks static-only
*/
var BrowserEnv = require('BrowserEnv');
"use strict";
var SyntheticUIEvent = require('SyntheticUIEvent');
var ViewportMetrics = require('ViewportMetrics');
/**
* @interface MouseEvent
@@ -59,12 +61,12 @@ var MouseEventInterface = {
pageX: function(event) {
return 'pageX' in event ?
event.pageX :
event.clientX + BrowserEnv.currentPageScrollLeft;
event.clientX + ViewportMetrics.currentScrollLeft;
},
pageY: function(event) {
return 'pageY' in event ?
event.pageY :
event.clientY + BrowserEnv.currentPageScrollTop;
event.clientY + ViewportMetrics.currentScrollTop;
}
};
@@ -14,9 +14,11 @@
* limitations under the License.
*
* @providesModule SyntheticMutationEvent
* @typechecks
* @typechecks static-only
*/
"use strict";
var SyntheticEvent = require('SyntheticEvent');
/**
+3 -1
View File
@@ -14,9 +14,11 @@
* limitations under the License.
*
* @providesModule SyntheticTouchEvent
* @typechecks
* @typechecks static-only
*/
"use strict";
var SyntheticUIEvent = require('SyntheticUIEvent');
/**
+3 -1
View File
@@ -14,9 +14,11 @@
* limitations under the License.
*
* @providesModule SyntheticUIEvent
* @typechecks
* @typechecks static-only
*/
"use strict";
var SyntheticEvent = require('SyntheticEvent');
/**
+3 -1
View File
@@ -14,9 +14,11 @@
* limitations under the License.
*
* @providesModule SyntheticWheelEvent
* @typechecks
* @typechecks static-only
*/
"use strict";
var SyntheticMouseEvent = require('SyntheticMouseEvent');
/**
@@ -133,14 +133,14 @@ if (__DEV__) {
}
/**
* This plugin does not really extract any abstract events. Rather it just looks
* at the top level event and bumps up counters as appropriate
* This plugin does not really extract any synthetic events. Rather it just
* looks at the top-level event and bumps up counters as appropriate
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of `AbstractEvent`s.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
function extractEvents(
+383
View File
@@ -0,0 +1,383 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule ChangeEventPlugin
*/
"use strict";
var EventConstants = require('EventConstants');
var EventPluginHub = require('EventPluginHub');
var EventPropagators = require('EventPropagators');
var ExecutionEnvironment = require('ExecutionEnvironment');
var SyntheticEvent = require('SyntheticEvent');
var isEventSupported = require('isEventSupported');
var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
var eventTypes = {
change: {
phasedRegistrationNames: {
bubbled: keyOf({onChange: null}),
captured: keyOf({onChangeCapture: null})
}
}
};
/**
* For IE shims
*/
var activeElement = null;
var activeElementID = null;
var activeElementValue = null;
var activeElementValueProp = null;
/**
* SECTION: handle `change` event
*/
function shouldUseChangeEvent(elem) {
return (
elem.nodeName === 'SELECT' ||
(elem.nodeName === 'INPUT' && elem.type === 'file')
);
}
var doesChangeEventBubble = false;
if (ExecutionEnvironment.canUseDOM) {
// See `handleChange` comment below
doesChangeEventBubble = isEventSupported('change') && (
!('documentMode' in document) || document.documentMode > 8
);
}
function manualDispatchChangeEvent(nativeEvent) {
var event = SyntheticEvent.getPooled(
eventTypes.change,
activeElementID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(event);
// If change bubbled, we'd just bind to it like all the other events
// and have it go through ReactEventTopLevelCallback. Since it doesn't, we
// manually listen for the change event and so we have to enqueue and
// process the abstract event manually.
EventPluginHub.enqueueEvents(event);
EventPluginHub.processEventQueue();
}
function startWatchingForChangeEventIE8(target, targetID) {
activeElement = target;
activeElementID = targetID;
activeElement.attachEvent('onchange', manualDispatchChangeEvent);
}
function stopWatchingForChangeEventIE8() {
if (!activeElement) {
return;
}
activeElement.detachEvent('onchange', manualDispatchChangeEvent);
activeElement = null;
activeElementID = null;
}
function getTargetIDForChangeEvent(
topLevelType,
topLevelTarget,
topLevelTargetID) {
if (topLevelType === topLevelTypes.topChange) {
return topLevelTargetID;
}
}
function handleEventsForChangeEventIE8(
topLevelType,
topLevelTarget,
topLevelTargetID) {
if (topLevelType === topLevelTypes.topFocus) {
// stopWatching() should be a noop here but we call it just in case we
// missed a blur event somehow.
stopWatchingForChangeEventIE8();
startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID);
} else if (topLevelType === topLevelTypes.topBlur) {
stopWatchingForChangeEventIE8();
}
}
/**
* SECTION: handle `input` event
*/
var isInputEventSupported = false;
if (ExecutionEnvironment.canUseDOM) {
// IE9 claims to support the input event but fails to trigger it when
// deleting text, so we ignore its input events
isInputEventSupported = isEventSupported('input') && (
!('documentMode' in document) || document.documentMode > 9
);
}
/**
* @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
*/
var supportedInputTypes = {
'color': true,
'date': true,
'datetime': true,
'datetime-local': true,
'email': true,
'month': true,
'number': true,
'password': true,
'range': true,
'search': true,
'tel': true,
'text': true,
'time': true,
'url': true,
'week': true
};
function shouldUseInputEvent(elem) {
return (
(elem.nodeName === 'INPUT' && supportedInputTypes[elem.type]) ||
elem.nodeName === 'TEXTAREA'
);
}
/**
* (For old IE.) Replacement getter/setter for the `value` property that gets
* set on the active element.
*/
var newValueProp = {
get: function() {
return activeElementValueProp.get.call(this);
},
set: function(val) {
activeElementValue = val;
activeElementValueProp.set.call(this, val);
}
};
/**
* (For old IE.) Starts tracking propertychange events on the passed-in element
* and override the value property so that we can distinguish user events from
* value changes in JS.
*/
function startWatchingForValueChange(target, targetID) {
activeElement = target;
activeElementID = targetID;
activeElementValue = target.value;
activeElementValueProp = Object.getOwnPropertyDescriptor(
target.constructor.prototype,
'value'
);
Object.defineProperty(activeElement, 'value', newValueProp);
activeElement.attachEvent('onpropertychange', handlePropertyChange);
}
/**
* (For old IE.) Removes the event listeners from the currently-tracked element,
* if any exists.
*/
function stopWatchingForValueChange() {
if (!activeElement) {
return;
}
// delete restores the original property definition
delete activeElement.value;
activeElement.detachEvent('onpropertychange', handlePropertyChange);
activeElement = null;
activeElementID = null;
activeElementValue = null;
activeElementValueProp = null;
}
/**
* (For old IE.) Handles a propertychange event, sending a `change` event if
* the value of the active element has changed.
*/
function handlePropertyChange(nativeEvent) {
if (nativeEvent.propertyName !== 'value') {
return;
}
var value = nativeEvent.srcElement.value;
if (value === activeElementValue) {
return;
}
activeElementValue = value;
manualDispatchChangeEvent(nativeEvent);
}
/**
* If a `change` event should be fired, returns the target's ID.
*/
function getTargetIDForInputEvent(
topLevelType,
topLevelTarget,
topLevelTargetID) {
if (topLevelType === topLevelTypes.topInput) {
// In modern browsers (i.e., not IE8 or IE9), the input event is exactly
// what we want so fall through here and trigger an abstract event
return topLevelTargetID;
}
}
// For IE8 and IE9
function handleEventsForInputEventIE(
topLevelType,
topLevelTarget,
topLevelTargetID) {
if (topLevelType === topLevelTypes.topFocus) {
// In IE8, we can capture almost all .value changes by adding a
// propertychange handler and looking for events with propertyName
// equal to 'value'
// In IE9, propertychange fires for most input events but is buggy and
// doesn't fire when text is deleted, but conveniently, selectionchange
// appears to fire in all of the remaining cases so we catch those and
// forward the event if the value has changed
// In either case, we don't want to call the event handler if the value
// is changed from JS so we redefine a setter for `.value` that updates
// our activeElementValue variable, allowing us to ignore those changes
//
// stopWatching() should be a noop here but we call it just in case we
// missed a blur event somehow.
stopWatchingForValueChange();
startWatchingForValueChange(topLevelTarget, topLevelTargetID);
} else if (topLevelType === topLevelTypes.topBlur) {
stopWatchingForValueChange();
} else if (
topLevelType === topLevelTypes.topSelectionChange ||
topLevelType === topLevelTypes.topKeyUp ||
topLevelType === topLevelTypes.topKeyDown) {
// On the selectionchange event, the target is just document which isn't
// helpful for us so just check activeElement instead.
//
// 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
// propertychange on the first input event after setting `value` from a
// script and fires only keydown, keypress, keyup. Catching keyup usually
// gets it and catching keydown lets us fire an event for the first
// keystroke if user does a key repeat (it'll be a little delayed: right
// before the second keystroke). Other input methods (e.g., paste) seem to
// fire selectionchange normally.
if (activeElement && activeElement.value !== activeElementValue) {
activeElementValue = activeElement.value;
return activeElementID;
}
}
}
/**
* SECTION: handle `click` event
*/
function shouldUseClickEvent(elem) {
// Use the `click` event to detect changes to checkbox and radio inputs.
// This approach works across all browsers, whereas `change` does not fire
// until `blur` in IE8.
return (
elem.nodeName === 'INPUT' &&
(elem.type === 'checkbox' || elem.type === 'radio')
);
}
function getTargetIDForClickEvent(
topLevelType,
topLevelTarget,
topLevelTargetID) {
if (topLevelType === topLevelTypes.topClick) {
return topLevelTargetID;
}
}
/**
* This plugin creates an `onChange` event that normalizes change events
* across form elements. This event fires at a time when it's possible to
* change the element's value without seeing a flicker.
*
* Supported elements are:
* - input (see `supportedInputTypes`)
* - textarea
* - select
*/
var ChangeEventPlugin = {
eventTypes: eventTypes,
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var getTargetIDFunc, handleEventFunc;
if (shouldUseChangeEvent(topLevelTarget)) {
if (doesChangeEventBubble) {
getTargetIDFunc = getTargetIDForChangeEvent;
} else {
handleEventFunc = handleEventsForChangeEventIE8;
}
} else if (shouldUseInputEvent(topLevelTarget)) {
if (isInputEventSupported) {
getTargetIDFunc = getTargetIDForInputEvent;
} else {
handleEventFunc = handleEventsForInputEventIE;
}
} else if (shouldUseClickEvent(topLevelTarget)) {
getTargetIDFunc = getTargetIDForClickEvent;
}
if (getTargetIDFunc) {
var targetID = getTargetIDFunc(
topLevelType,
topLevelTarget,
topLevelTargetID
);
if (targetID) {
var event = SyntheticEvent.getPooled(
eventTypes.change,
targetID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}
} else if (handleEventFunc) {
handleEventFunc(
topLevelType,
topLevelTarget,
topLevelTargetID
);
}
}
};
module.exports = ChangeEventPlugin;
+1 -1
View File
@@ -34,7 +34,7 @@ var DefaultEventPluginOrder = [
keyOf({SimpleEventPlugin: null}),
keyOf({TapEventPlugin: null}),
keyOf({EnterLeaveEventPlugin: null}),
keyOf({TextChangeEventPlugin: null}),
keyOf({ChangeEventPlugin: null}),
keyOf({AnalyticsEventPlugin: null})
];
+71 -71
View File
@@ -14,95 +14,95 @@
* limitations under the License.
*
* @providesModule EnterLeaveEventPlugin
* @typechecks
* @typechecks static-only
*/
"use strict";
var EventConstants = require('EventConstants');
var EventPropagators = require('EventPropagators');
var ExecutionEnvironment = require('ExecutionEnvironment');
var AbstractEvent = require('AbstractEvent');
var EventConstants = require('EventConstants');
var ReactInstanceHandles = require('ReactInstanceHandles');
var SyntheticMouseEvent = require('SyntheticMouseEvent');
var getDOMNodeID = require('getDOMNodeID');
var ReactID = require('ReactID');
var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
var getFirstReactDOM = ReactInstanceHandles.getFirstReactDOM;
var abstractEventTypes = {
var eventTypes = {
mouseEnter: {registrationName: keyOf({onMouseEnter: null})},
mouseLeave: {registrationName: keyOf({onMouseLeave: null})}
};
/**
* For almost every interaction we care about, there will be a top-level
* `mouseover` and `mouseout` event that occurs so only pay attention to one of
* the two (to avoid duplicate events). We use the `mouseout` event.
*
* However, there's one interaction where there will be no `mouseout` event to
* rely on - mousing from outside the browser *into* the chrome. We detect this
* scenario and only in that case, we use the `mouseover` event.
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of `AbstractEvent`s.
* @see {EventPluginHub.extractEvents}
*/
var extractEvents = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (topLevelType === topLevelTypes.topMouseOver &&
(nativeEvent.relatedTarget || nativeEvent.fromElement)) {
return null;
}
if (topLevelType !== topLevelTypes.topMouseOut &&
topLevelType !== topLevelTypes.topMouseOver) {
return null; // Must not be a mouse in or mouse out - ignoring.
}
var to, from;
if (topLevelType === topLevelTypes.topMouseOut) {
to = getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement) ||
ExecutionEnvironment.global;
from = topLevelTarget;
} else {
to = topLevelTarget;
from = ExecutionEnvironment.global;
}
// Nothing pertains to our managed components.
if (from === to) {
return null;
}
var fromID = from ? getDOMNodeID(from) : '';
var toID = to ? getDOMNodeID(to) : '';
var leave = AbstractEvent.getPooled(
abstractEventTypes.mouseLeave,
fromID,
nativeEvent
);
var enter = AbstractEvent.getPooled(
abstractEventTypes.mouseEnter,
toID,
nativeEvent
);
EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
return [leave, enter];
};
var EnterLeaveEventPlugin = {
abstractEventTypes: abstractEventTypes,
extractEvents: extractEvents
eventTypes: eventTypes,
/**
* For almost every interaction we care about, there will be both a top-level
* `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
* we do not extract duplicate events. However, moving the mouse into the
* browser from outside will not fire a `mouseout` event. In this case, we use
* the `mouseover` top-level event.
*
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (topLevelType === topLevelTypes.topMouseOver &&
(nativeEvent.relatedTarget || nativeEvent.fromElement)) {
return null;
}
if (topLevelType !== topLevelTypes.topMouseOut &&
topLevelType !== topLevelTypes.topMouseOver) {
// Must not be a mouse in or mouse out - ignoring.
return null;
}
var from, to;
if (topLevelType === topLevelTypes.topMouseOut) {
from = topLevelTarget;
to =
getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement) ||
ExecutionEnvironment.global;
} else {
from = ExecutionEnvironment.global;
to = topLevelTarget;
}
if (from === to) {
// Nothing pertains to our managed components.
return null;
}
var fromID = from ? ReactID.getID(from) : '';
var toID = to ? ReactID.getID(to) : '';
var leave = SyntheticMouseEvent.getPooled(
eventTypes.mouseLeave,
fromID,
nativeEvent
);
var enter = SyntheticMouseEvent.getPooled(
eventTypes.mouseEnter,
toID,
nativeEvent
);
EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
return [leave, enter];
}
};
module.exports = EnterLeaveEventPlugin;
+142 -145
View File
@@ -18,10 +18,10 @@
"use strict";
var AbstractEvent = require('AbstractEvent');
var EventConstants = require('EventConstants');
var EventPluginUtils = require('EventPluginUtils');
var EventPropagators = require('EventPropagators');
var SyntheticEvent = require('SyntheticEvent');
var accumulate = require('accumulate');
var keyOf = require('keyOf');
@@ -41,11 +41,7 @@ var executeDispatchesInOrderStopAtTrue =
var responderID = null;
var isPressing = false;
var getResponderID = function() {
return responderID;
};
var abstractEventTypes = {
var eventTypes = {
/**
* On a `touchStart`/`mouseDown`, is it desired that this element become the
* responder?
@@ -116,22 +112,22 @@ var abstractEventTypes = {
* `extractEvents()`.
* - These events that are returned from `extractEvents` are "deferred
* dispatched events".
* - When returned from `extractEvents`, deferred dispatched events
* contain an "accumulation" of deferred dispatches.
* -- These deferred dispatches are accumulated/collected before they are
* returned, but processed at a later time by the `EventPluginHub` (hence the
* name deferred).
* - When returned from `extractEvents`, deferred-dispatched events contain an
* "accumulation" of deferred dispatches.
* - These deferred dispatches are accumulated/collected before they are
* returned, but processed at a later time by the `EventPluginHub` (hence the
* name deferred).
*
* In the process of returning their deferred dispatched events, event plugins
* In the process of returning their deferred-dispatched events, event plugins
* themselves can dispatch events on-demand without returning them from
* `extractEvents`. Plugins might want to do this, so that they can use
* event dispatching as a tool that helps them decide which events should be
* extracted in the first place.
* `extractEvents`. Plugins might want to do this, so that they can use event
* dispatching as a tool that helps them decide which events should be extracted
* in the first place.
*
* "On-Demand-Dispatched Events":
*
* - On-demand dispatched are not returned from `extractEvents`.
* - On-demand dispatched events are dispatched during the process of returning
* - On-demand-dispatched events are not returned from `extractEvents`.
* - On-demand-dispatched events are dispatched during the process of returning
* the deferred-dispatched events.
* - They should not have side effects.
* - They should be avoided, and/or eventually be replaced with another
@@ -158,81 +154,81 @@ var abstractEventTypes = {
* - `touchStart` (`EventPluginHub` dispatches as usual)
* - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
*
* @returns {Accumulation<AbstractEvent>}
*/
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {string} renderedTargetID ID of deepest React rendered element.
* @param {string} topLevelTargetID ID of deepest React rendered element.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of extracted `AbstractEvent`s.
* @return {*} An accumulation of synthetic events.
*/
var setResponderAndExtractTransfer =
function(topLevelType, renderedTargetID, nativeEvent) {
var type;
var shouldSetEventType =
isStartish(topLevelType) ? abstractEventTypes.startShouldSetResponder :
isMoveish(topLevelType) ? abstractEventTypes.moveShouldSetResponder :
abstractEventTypes.scrollShouldSetResponder;
function setResponderAndExtractTransfer(
topLevelType,
topLevelTargetID,
nativeEvent) {
var shouldSetEventType =
isStartish(topLevelType) ? eventTypes.startShouldSetResponder :
isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder :
eventTypes.scrollShouldSetResponder;
var bubbleShouldSetFrom = responderID || renderedTargetID;
var shouldSetEvent = AbstractEvent.getPooled(
shouldSetEventType,
bubbleShouldSetFrom,
topLevelType,
nativeEvent,
AbstractEvent.normalizePointerData(nativeEvent)
);
EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
AbstractEvent.release(shouldSetEvent);
var bubbleShouldSetFrom = responderID || topLevelTargetID;
var shouldSetEvent = SyntheticEvent.getPooled(
shouldSetEventType,
bubbleShouldSetFrom,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
if (!shouldSetEvent.isPersistent()) {
shouldSetEvent.constructor.release(shouldSetEvent);
}
if (!wantsResponderID || wantsResponderID === responderID) {
return null;
}
var extracted;
var grantEvent = AbstractEvent.getPooled(
abstractEventTypes.responderGrant,
wantsResponderID,
topLevelType,
if (!wantsResponderID || wantsResponderID === responderID) {
return null;
}
var extracted;
var grantEvent = SyntheticEvent.getPooled(
eventTypes.responderGrant,
wantsResponderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(grantEvent);
if (responderID) {
var terminationRequestEvent = SyntheticEvent.getPooled(
eventTypes.responderTerminationRequest,
responderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(grantEvent);
if (responderID) {
type = abstractEventTypes.responderTerminationRequest;
var terminationRequestEvent = AbstractEvent.getPooled(type, responderID);
EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
executeDirectDispatch(terminationRequestEvent);
AbstractEvent.release(terminationRequestEvent);
if (shouldSwitch) {
var terminateType = abstractEventTypes.responderTerminate;
var terminateEvent = AbstractEvent.getPooled(
terminateType,
responderID,
topLevelType,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(terminateEvent);
extracted = accumulate(extracted, [grantEvent, terminateEvent]);
responderID = wantsResponderID;
} else {
var rejectEvent = AbstractEvent.getPooled(
abstractEventTypes.responderReject,
wantsResponderID,
topLevelType,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(rejectEvent);
extracted = accumulate(extracted, rejectEvent);
}
} else {
extracted = accumulate(extracted, grantEvent);
responderID = wantsResponderID;
EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
executeDirectDispatch(terminationRequestEvent);
if (!terminationRequestEvent.isPersistent()) {
terminationRequestEvent.constructor.release(terminationRequestEvent);
}
return extracted;
};
if (shouldSwitch) {
var terminateType = eventTypes.responderTerminate;
var terminateEvent = SyntheticEvent.getPooled(
terminateType,
responderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(terminateEvent);
extracted = accumulate(extracted, [grantEvent, terminateEvent]);
responderID = wantsResponderID;
} else {
var rejectEvent = SyntheticEvent.getPooled(
eventTypes.responderReject,
wantsResponderID,
nativeEvent
);
EventPropagators.accumulateDirectDispatches(rejectEvent);
extracted = accumulate(extracted, rejectEvent);
}
} else {
extracted = accumulate(extracted, grantEvent);
responderID = wantsResponderID;
}
return extracted;
}
/**
* A transfer is a negotiation between a currently set responder and the next
@@ -241,9 +237,8 @@ var setResponderAndExtractTransfer =
* currently a responder set (in other words as long as the user is pressing
* down).
*
* @param {EventConstants.topLevelTypes} topLevelType
* @return {boolean} Whether or not a transfer of responder could possibly
* occur.
* @param {string} topLevelType Record from `EventConstants`.
* @return {boolean} True if a transfer of responder could possibly occur.
*/
function canTriggerTransfer(topLevelType) {
return topLevelType === EventConstants.topLevelTypes.topScroll ||
@@ -251,69 +246,71 @@ function canTriggerTransfer(topLevelType) {
(isPressing && isMoveish(topLevelType));
}
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of `AbstractEvent`s.
* @see {EventPluginHub.extractEvents}
*/
var extractEvents = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var extracted;
// Must have missed an end event - reset the state here.
if (responderID && isStartish(topLevelType)) {
responderID = null;
}
if (isStartish(topLevelType)) {
isPressing = true;
} else if (isEndish(topLevelType)) {
isPressing = false;
}
if (canTriggerTransfer(topLevelType)) {
var transfer = setResponderAndExtractTransfer(
topLevelType,
topLevelTargetID,
nativeEvent
);
if (transfer) {
extracted = accumulate(extracted, transfer);
}
}
// Now that we know the responder is set correctly, we can dispatch
// responder type events (directly to the responder).
var type = isMoveish(topLevelType) ? abstractEventTypes.responderMove :
isEndish(topLevelType) ? abstractEventTypes.responderRelease :
isStartish(topLevelType) ? abstractEventTypes.responderStart : null;
if (type) {
var data = AbstractEvent.normalizePointerData(nativeEvent);
var gesture = AbstractEvent.getPooled(
type,
responderID,
nativeEvent,
data
);
EventPropagators.accumulateDirectDispatches(gesture);
extracted = accumulate(extracted, gesture);
}
if (type === abstractEventTypes.responderRelease) {
responderID = null;
}
return extracted;
};
/**
* Event plugin for formalizing the negotiation between claiming locks on
* receiving touches.
*/
var ResponderEventPlugin = {
abstractEventTypes: abstractEventTypes,
extractEvents: extractEvents,
getResponderID: getResponderID
getResponderID: function() {
return responderID;
},
eventTypes: eventTypes,
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var extracted;
// Must have missed an end event - reset the state here.
if (responderID && isStartish(topLevelType)) {
responderID = null;
}
if (isStartish(topLevelType)) {
isPressing = true;
} else if (isEndish(topLevelType)) {
isPressing = false;
}
if (canTriggerTransfer(topLevelType)) {
var transfer = setResponderAndExtractTransfer(
topLevelType,
topLevelTargetID,
nativeEvent
);
if (transfer) {
extracted = accumulate(extracted, transfer);
}
}
// Now that we know the responder is set correctly, we can dispatch
// responder type events (directly to the responder).
var type = isMoveish(topLevelType) ? eventTypes.responderMove :
isEndish(topLevelType) ? eventTypes.responderRelease :
isStartish(topLevelType) ? eventTypes.responderStart : null;
if (type) {
var gesture = SyntheticEvent.getPooled(
type,
responderID || '',
nativeEvent
);
EventPropagators.accumulateDirectDispatches(gesture);
extracted = accumulate(extracted, gesture);
}
if (type === eventTypes.responderRelease) {
responderID = null;
}
return extracted;
}
};
module.exports = ResponderEventPlugin;
+261 -238
View File
@@ -18,201 +18,236 @@
"use strict";
var AbstractEvent = require('AbstractEvent');
var EventConstants = require('EventConstants');
var EventPropagators = require('EventPropagators');
var SyntheticEvent = require('SyntheticEvent');
var SyntheticFocusEvent = require('SyntheticFocusEvent');
var SyntheticKeyboardEvent = require('SyntheticKeyboardEvent');
var SyntheticMouseEvent = require('SyntheticMouseEvent');
var SyntheticMutationEvent = require('SyntheticMutationEvent');
var SyntheticTouchEvent = require('SyntheticTouchEvent');
var SyntheticUIEvent = require('SyntheticUIEvent');
var SyntheticWheelEvent = require('SyntheticWheelEvent');
var invariant = require('invariant');
var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
var SimpleEventPlugin = {
abstractEventTypes: {
// Note: We do not allow listening to mouseOver events. Instead, use the
// onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`.
mouseDown: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseDown: true}),
captured: keyOf({onMouseDownCapture: true})
}
},
mouseUp: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseUp: true}),
captured: keyOf({onMouseUpCapture: true})
}
},
mouseMove: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseMove: true}),
captured: keyOf({onMouseMoveCapture: true})
}
},
doubleClick: {
phasedRegistrationNames: {
bubbled: keyOf({onDoubleClick: true}),
captured: keyOf({onDoubleClickCapture: true})
}
},
click: {
phasedRegistrationNames: {
bubbled: keyOf({onClick: true}),
captured: keyOf({onClickCapture: true})
}
},
wheel: {
phasedRegistrationNames: {
bubbled: keyOf({onWheel: true}),
captured: keyOf({onWheelCapture: true})
}
},
touchStart: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchStart: true}),
captured: keyOf({onTouchStartCapture: true})
}
},
touchEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchEnd: true}),
captured: keyOf({onTouchEndCapture: true})
}
},
touchCancel: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchCancel: true}),
captured: keyOf({onTouchCancelCapture: true})
}
},
touchMove: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchMove: true}),
captured: keyOf({onTouchMoveCapture: true})
}
},
keyUp: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyUp: true}),
captured: keyOf({onKeyUpCapture: true})
}
},
keyPress: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyPress: true}),
captured: keyOf({onKeyPressCapture: true})
}
},
keyDown: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyDown: true}),
captured: keyOf({onKeyDownCapture: true})
}
},
input: {
phasedRegistrationNames: {
bubbled: keyOf({onInput: true}),
captured: keyOf({onInputCapture: true})
}
},
focus: {
phasedRegistrationNames: {
bubbled: keyOf({onFocus: true}),
captured: keyOf({onFocusCapture: true})
}
},
blur: {
phasedRegistrationNames: {
bubbled: keyOf({onBlur: true}),
captured: keyOf({onBlurCapture: true})
}
},
scroll: {
phasedRegistrationNames: {
bubbled: keyOf({onScroll: true}),
captured: keyOf({onScrollCapture: true})
}
},
change: {
phasedRegistrationNames: {
bubbled: keyOf({onChange: true}),
captured: keyOf({onChangeCapture: true})
}
},
submit: {
phasedRegistrationNames: {
bubbled: keyOf({onSubmit: true}),
captured: keyOf({onSubmitCapture: true})
}
},
DOMCharacterDataModified: {
phasedRegistrationNames: {
bubbled: keyOf({onDOMCharacterDataModified: true}),
captured: keyOf({onDOMCharacterDataModifiedCapture: true})
}
},
drag: {
phasedRegistrationNames: {
bubbled: keyOf({onDrag: true}),
captured: keyOf({onDragCapture: true})
}
},
dragEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onDragEnd: true}),
captured: keyOf({onDragEndCapture: true})
}
},
dragEnter: {
phasedRegistrationNames: {
bubbled: keyOf({onDragEnter: true}),
captured: keyOf({onDragEnterCapture: true})
}
},
dragExit: {
phasedRegistrationNames: {
bubbled: keyOf({onDragExit: true}),
captured: keyOf({onDragExitCapture: true})
}
},
dragLeave: {
phasedRegistrationNames: {
bubbled: keyOf({onDragLeave: true}),
captured: keyOf({onDragLeaveCapture: true})
}
},
dragOver: {
phasedRegistrationNames: {
bubbled: keyOf({onDragOver: true}),
captured: keyOf({onDragOverCapture: true})
}
},
dragStart: {
phasedRegistrationNames: {
bubbled: keyOf({onDragStart: true}),
captured: keyOf({onDragStartCapture: true})
}
},
drop: {
phasedRegistrationNames: {
bubbled: keyOf({onDrop: true}),
captured: keyOf({onDropCapture: true})
}
var eventTypes = {
blur: {
phasedRegistrationNames: {
bubbled: keyOf({onBlur: true}),
captured: keyOf({onBlurCapture: true})
}
},
click: {
phasedRegistrationNames: {
bubbled: keyOf({onClick: true}),
captured: keyOf({onClickCapture: true})
}
},
doubleClick: {
phasedRegistrationNames: {
bubbled: keyOf({onDoubleClick: true}),
captured: keyOf({onDoubleClickCapture: true})
}
},
drag: {
phasedRegistrationNames: {
bubbled: keyOf({onDrag: true}),
captured: keyOf({onDragCapture: true})
}
},
dragEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onDragEnd: true}),
captured: keyOf({onDragEndCapture: true})
}
},
dragEnter: {
phasedRegistrationNames: {
bubbled: keyOf({onDragEnter: true}),
captured: keyOf({onDragEnterCapture: true})
}
},
dragExit: {
phasedRegistrationNames: {
bubbled: keyOf({onDragExit: true}),
captured: keyOf({onDragExitCapture: true})
}
},
dragLeave: {
phasedRegistrationNames: {
bubbled: keyOf({onDragLeave: true}),
captured: keyOf({onDragLeaveCapture: true})
}
},
dragOver: {
phasedRegistrationNames: {
bubbled: keyOf({onDragOver: true}),
captured: keyOf({onDragOverCapture: true})
}
},
dragStart: {
phasedRegistrationNames: {
bubbled: keyOf({onDragStart: true}),
captured: keyOf({onDragStartCapture: true})
}
},
drop: {
phasedRegistrationNames: {
bubbled: keyOf({onDrop: true}),
captured: keyOf({onDropCapture: true})
}
},
DOMCharacterDataModified: {
phasedRegistrationNames: {
bubbled: keyOf({onDOMCharacterDataModified: true}),
captured: keyOf({onDOMCharacterDataModifiedCapture: true})
}
},
focus: {
phasedRegistrationNames: {
bubbled: keyOf({onFocus: true}),
captured: keyOf({onFocusCapture: true})
}
},
input: {
phasedRegistrationNames: {
bubbled: keyOf({onInput: true}),
captured: keyOf({onInputCapture: true})
}
},
keyDown: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyDown: true}),
captured: keyOf({onKeyDownCapture: true})
}
},
keyPress: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyPress: true}),
captured: keyOf({onKeyPressCapture: true})
}
},
keyUp: {
phasedRegistrationNames: {
bubbled: keyOf({onKeyUp: true}),
captured: keyOf({onKeyUpCapture: true})
}
},
// Note: We do not allow listening to mouseOver events. Instead, use the
// onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`.
mouseDown: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseDown: true}),
captured: keyOf({onMouseDownCapture: true})
}
},
mouseMove: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseMove: true}),
captured: keyOf({onMouseMoveCapture: true})
}
},
mouseUp: {
phasedRegistrationNames: {
bubbled: keyOf({onMouseUp: true}),
captured: keyOf({onMouseUpCapture: true})
}
},
scroll: {
phasedRegistrationNames: {
bubbled: keyOf({onScroll: true}),
captured: keyOf({onScrollCapture: true})
}
},
submit: {
phasedRegistrationNames: {
bubbled: keyOf({onSubmit: true}),
captured: keyOf({onSubmitCapture: true})
}
},
touchCancel: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchCancel: true}),
captured: keyOf({onTouchCancelCapture: true})
}
},
touchEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchEnd: true}),
captured: keyOf({onTouchEndCapture: true})
}
},
touchMove: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchMove: true}),
captured: keyOf({onTouchMoveCapture: true})
}
},
touchStart: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchStart: true}),
captured: keyOf({onTouchStartCapture: true})
}
},
wheel: {
phasedRegistrationNames: {
bubbled: keyOf({onWheel: true}),
captured: keyOf({onWheelCapture: true})
}
}
};
var topLevelEventsToDispatchConfig = {
topBlur: eventTypes.blur,
topClick: eventTypes.click,
topDoubleClick: eventTypes.doubleClick,
topDOMCharacterDataModified: eventTypes.DOMCharacterDataModified,
topDrag: eventTypes.drag,
topDragEnd: eventTypes.dragEnd,
topDragEnter: eventTypes.dragEnter,
topDragExit: eventTypes.dragExit,
topDragLeave: eventTypes.dragLeave,
topDragOver: eventTypes.dragOver,
topDragStart: eventTypes.dragStart,
topDrop: eventTypes.drop,
topFocus: eventTypes.focus,
topInput: eventTypes.input,
topKeyDown: eventTypes.keyDown,
topKeyPress: eventTypes.keyPress,
topKeyUp: eventTypes.keyUp,
topMouseDown: eventTypes.mouseDown,
topMouseMove: eventTypes.mouseMove,
topMouseUp: eventTypes.mouseUp,
topScroll: eventTypes.scroll,
topSubmit: eventTypes.submit,
topTouchCancel: eventTypes.touchCancel,
topTouchEnd: eventTypes.touchEnd,
topTouchMove: eventTypes.touchMove,
topTouchStart: eventTypes.touchStart,
topWheel: eventTypes.wheel
};
var SimpleEventPlugin = {
eventTypes: eventTypes,
/**
* Same as the default implementation, except cancels the event when return
* value is false.
*
* @param {AbstractEvent} AbstractEvent to handle
* @param {function} Application-level callback
* @param {string} domID DOM id to pass to the callback.
* @param {object} Event to be dispatched.
* @param {function} Application-level callback.
* @param {string} domID DOM ID to pass to the callback.
*/
executeDispatch: function(abstractEvent, listener, domID) {
var returnValue = listener(abstractEvent, domID);
executeDispatch: function(event, listener, domID) {
var returnValue = listener(event, domID);
if (returnValue === false) {
abstractEvent.stopPropagation();
abstractEvent.preventDefault();
event.stopPropagation();
event.preventDefault();
}
},
@@ -221,7 +256,7 @@ var SimpleEventPlugin = {
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of `AbstractEvent`s.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
@@ -229,29 +264,29 @@ var SimpleEventPlugin = {
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var data;
var abstractEventType =
SimpleEventPlugin.topLevelTypesToAbstract[topLevelType];
if (!abstractEventType) {
var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
if (!dispatchConfig) {
return null;
}
var EventConstructor;
switch(topLevelType) {
case topLevelTypes.topWheel:
data = AbstractEvent.normalizeMouseWheelData(nativeEvent);
case topLevelTypes.topInput:
case topLevelTypes.topSubmit:
// HTML Events
// @see http://www.w3.org/TR/html5/index.html#events-0
EventConstructor = SyntheticEvent;
break;
case topLevelTypes.topScroll:
data = AbstractEvent.normalizeScrollDataFromTarget(topLevelTarget);
case topLevelTypes.topKeyDown:
case topLevelTypes.topKeyPress:
case topLevelTypes.topKeyUp:
EventConstructor = SyntheticKeyboardEvent;
break;
case topLevelTypes.topBlur:
case topLevelTypes.topFocus:
EventConstructor = SyntheticFocusEvent;
break;
case topLevelTypes.topClick:
case topLevelTypes.topDoubleClick:
case topLevelTypes.topChange:
case topLevelTypes.topDOMCharacterDataModified:
case topLevelTypes.topMouseDown:
case topLevelTypes.topMouseUp:
case topLevelTypes.topMouseMove:
case topLevelTypes.topTouchMove:
case topLevelTypes.topTouchStart:
case topLevelTypes.topTouchEnd:
case topLevelTypes.topDrag:
case topLevelTypes.topDragEnd:
case topLevelTypes.topDragEnter:
@@ -260,53 +295,41 @@ var SimpleEventPlugin = {
case topLevelTypes.topDragOver:
case topLevelTypes.topDragStart:
case topLevelTypes.topDrop:
data = AbstractEvent.normalizePointerData(nativeEvent);
// todo: Use AbstractEvent.normalizeDragEventData for drag/drop?
case topLevelTypes.topMouseDown:
case topLevelTypes.topMouseMove:
case topLevelTypes.topMouseUp:
EventConstructor = SyntheticMouseEvent;
break;
case topLevelTypes.topDOMCharacterDataModified:
EventConstructor = SyntheticMutationEvent;
break;
case topLevelTypes.topTouchCancel:
case topLevelTypes.topTouchEnd:
case topLevelTypes.topTouchMove:
case topLevelTypes.topTouchStart:
EventConstructor = SyntheticTouchEvent;
break;
case topLevelTypes.topScroll:
EventConstructor = SyntheticUIEvent;
break;
case topLevelTypes.topWheel:
EventConstructor = SyntheticWheelEvent;
break;
default:
data = null;
}
var abstractEvent = AbstractEvent.getPooled(
abstractEventType,
topLevelTargetID,
nativeEvent,
data
invariant(
EventConstructor,
'SimpleEventPlugin: Unhandled event type, `%s`.',
topLevelType
);
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
return abstractEvent;
var event = EventConstructor.getPooled(
dispatchConfig,
topLevelTargetID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}
};
SimpleEventPlugin.topLevelTypesToAbstract = {
topMouseDown: SimpleEventPlugin.abstractEventTypes.mouseDown,
topMouseUp: SimpleEventPlugin.abstractEventTypes.mouseUp,
topMouseMove: SimpleEventPlugin.abstractEventTypes.mouseMove,
topClick: SimpleEventPlugin.abstractEventTypes.click,
topDoubleClick: SimpleEventPlugin.abstractEventTypes.doubleClick,
topWheel: SimpleEventPlugin.abstractEventTypes.wheel,
topTouchStart: SimpleEventPlugin.abstractEventTypes.touchStart,
topTouchEnd: SimpleEventPlugin.abstractEventTypes.touchEnd,
topTouchMove: SimpleEventPlugin.abstractEventTypes.touchMove,
topTouchCancel: SimpleEventPlugin.abstractEventTypes.touchCancel,
topKeyUp: SimpleEventPlugin.abstractEventTypes.keyUp,
topKeyPress: SimpleEventPlugin.abstractEventTypes.keyPress,
topKeyDown: SimpleEventPlugin.abstractEventTypes.keyDown,
topInput: SimpleEventPlugin.abstractEventTypes.input,
topFocus: SimpleEventPlugin.abstractEventTypes.focus,
topBlur: SimpleEventPlugin.abstractEventTypes.blur,
topScroll: SimpleEventPlugin.abstractEventTypes.scroll,
topChange: SimpleEventPlugin.abstractEventTypes.change,
topSubmit: SimpleEventPlugin.abstractEventTypes.submit,
topDOMCharacterDataModified:
SimpleEventPlugin.abstractEventTypes.DOMCharacterDataModified,
topDrag: SimpleEventPlugin.abstractEventTypes.drag,
topDragEnd: SimpleEventPlugin.abstractEventTypes.dragEnd,
topDragEnter: SimpleEventPlugin.abstractEventTypes.dragEnter,
topDragExit: SimpleEventPlugin.abstractEventTypes.dragExit,
topDragLeave: SimpleEventPlugin.abstractEventTypes.dragLeave,
topDragOver: SimpleEventPlugin.abstractEventTypes.dragOver,
topDragStart: SimpleEventPlugin.abstractEventTypes.dragStart,
topDrop: SimpleEventPlugin.abstractEventTypes.drop
};
module.exports = SimpleEventPlugin;
+71 -45
View File
@@ -14,30 +14,54 @@
* limitations under the License.
*
* @providesModule TapEventPlugin
* @typechecks
* @typechecks static-only
*/
"use strict";
var AbstractEvent = require('AbstractEvent');
var EventPluginUtils = require('EventPluginUtils');
var EventPropagators = require('EventPropagators');
var SyntheticUIEvent = require('SyntheticUIEvent');
var TouchEventUtils = require('TouchEventUtils');
var ViewportMetrics = require('ViewportMetrics');
var keyOf = require('keyOf');
var isStartish = EventPluginUtils.isStartish;
var isEndish = EventPluginUtils.isEndish;
var storePageCoordsIn = EventPluginUtils.storePageCoordsIn;
var eventDistance = EventPluginUtils.eventDistance;
/**
* The number of pixels that are tolerated in between a touchStart and
* touchEnd in order to still be considered a 'tap' event.
* Number of pixels that are tolerated in between a `touchStart` and `touchEnd`
* in order to still be considered a 'tap' event.
*/
var tapMoveThreshold = 10;
var startCoords = {x: null, y: null};
var abstractEventTypes = {
var Axis = {
x: {page: 'pageX', client: 'clientX', envScroll: 'currentPageScrollLeft'},
y: {page: 'pageY', client: 'clientY', envScroll: 'currentPageScrollTop'}
};
function getAxisCoordOfEvent(axis, nativeEvent) {
var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
if (singleTouch) {
return singleTouch[axis.page];
}
return axis.page in nativeEvent ?
nativeEvent[axis.page] :
nativeEvent[axis.client] + ViewportMetrics[axis.envScroll];
}
function getDistance(coords, nativeEvent) {
var pageX = getAxisCoordOfEvent(Axis.x, nativeEvent);
var pageY = getAxisCoordOfEvent(Axis.y, nativeEvent);
return Math.pow(
Math.pow(pageX - coords.x, 2) + Math.pow(pageY - coords.y, 2),
0.5
);
}
var eventTypes = {
touchTap: {
phasedRegistrationNames: {
bubbled: keyOf({onTouchTap: null}),
@@ -46,46 +70,48 @@ var abstractEventTypes = {
}
};
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of `AbstractEvent`s.
* @see {EventPluginHub.extractEvents}
*/
var extractEvents = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
return;
}
var abstractEvent;
var dist = eventDistance(startCoords, nativeEvent);
if (isEndish(topLevelType) && dist < tapMoveThreshold) {
abstractEvent = AbstractEvent.getPooled(
abstractEventTypes.touchTap,
topLevelTargetID,
nativeEvent
);
}
if (isStartish(topLevelType)) {
storePageCoordsIn(startCoords, nativeEvent);
} else if (isEndish(topLevelType)) {
startCoords.x = 0;
startCoords.y = 0;
}
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
return abstractEvent;
};
var TapEventPlugin = {
tapMoveThreshold: tapMoveThreshold,
startCoords: startCoords,
abstractEventTypes: abstractEventTypes,
extractEvents: extractEvents
eventTypes: eventTypes,
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of synthetic events.
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
return null;
}
var event = null;
var distance = getDistance(startCoords, nativeEvent);
if (isEndish(topLevelType) && distance < tapMoveThreshold) {
event = SyntheticUIEvent.getPooled(
eventTypes.touchTap,
topLevelTargetID,
nativeEvent
);
}
if (isStartish(topLevelType)) {
startCoords.x = getAxisCoordOfEvent(Axis.x, nativeEvent);
startCoords.y = getAxisCoordOfEvent(Axis.y, nativeEvent);
} else if (isEndish(topLevelType)) {
startCoords.x = 0;
startCoords.y = 0;
}
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}
};
module.exports = TapEventPlugin;
-253
View File
@@ -1,253 +0,0 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule TextChangeEventPlugin
*/
"use strict";
var AbstractEvent = require('AbstractEvent');
var EventConstants = require('EventConstants');
var EventPluginHub = require('EventPluginHub');
var EventPropagators = require('EventPropagators');
var ExecutionEnvironment = require('ExecutionEnvironment');
var isEventSupported = require('isEventSupported');
var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
var abstractEventTypes = {
textChange: {
phasedRegistrationNames: {
bubbled: keyOf({onTextChange: null}),
captured: keyOf({onTextChangeCapture: null})
}
}
};
var isInputSupported;
if (ExecutionEnvironment.canUseDOM) {
// IE9 claims to support the input event but fails to trigger it when
// deleting text, so we ignore its input events
isInputSupported = isEventSupported('input') && (
!("documentMode" in document) || document.documentMode > 9
);
}
var hasInputCapabilities = function(elem) {
// The HTML5 spec lists many more types than `text` and `password` on which
// the input event is triggered but none of them exist in old IE, so we don't
// check them here.
// TODO: <textarea> should be supported too but IE seems to reset the
// selection when changing textarea contents during a selectionchange event
// so it's not listed here for now.
return (
elem.nodeName === 'INPUT' &&
(elem.type === 'text' || elem.type === 'password')
);
};
var activeElement = null;
var activeElementID = null;
var activeElementValue = null;
var activeElementValueProp = null;
/**
* (For old IE.) Replacement getter/setter for the `value` property that gets
* set on the active element.
*/
var newValueProp = {
get: function() {
return activeElementValueProp.get.call(this);
},
set: function(val) {
activeElementValue = val;
activeElementValueProp.set.call(this, val);
}
};
/**
* (For old IE.) Starts tracking propertychange events on the passed-in element
* and override the value property so that we can distinguish user events from
* value changes in JS.
*/
var startWatching = function(target, targetID) {
activeElement = target;
activeElementID = targetID;
activeElementValue = target.value;
activeElementValueProp = Object.getOwnPropertyDescriptor(
target.constructor.prototype,
'value'
);
Object.defineProperty(activeElement, 'value', newValueProp);
activeElement.attachEvent('onpropertychange', handlePropertyChange);
};
/**
* (For old IE.) Removes the event listeners from the currently-tracked element,
* if any exists.
*/
var stopWatching = function() {
if (!activeElement) {
return;
}
// delete restores the original property definition
delete activeElement.value;
activeElement.detachEvent('onpropertychange', handlePropertyChange);
activeElement = null;
activeElementID = null;
activeElementValue = null;
activeElementValueProp = null;
};
/**
* (For old IE.) Handles a propertychange event, sending a textChange event if
* the value of the active element has changed.
*/
var handlePropertyChange = function(nativeEvent) {
var value;
var abstractEvent;
if (nativeEvent.propertyName === "value") {
value = nativeEvent.srcElement.value;
if (value !== activeElementValue) {
activeElementValue = value;
abstractEvent = AbstractEvent.getPooled(
abstractEventTypes.textChange,
activeElementID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
// If propertychange bubbled, we'd just bind to it like all the other
// events and have it go through ReactEventTopLevelCallback. Since it
// doesn't, we manually listen for the propertychange event and so we
// have to enqueue and process the abstract event manually.
EventPluginHub.enqueueEvents(abstractEvent);
EventPluginHub.processEventQueue();
}
}
};
/**
* If a textChange event should be fired, returns the target's ID.
*/
var targetIDForTextChangeEvent;
if (isInputSupported) {
targetIDForTextChangeEvent = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (topLevelType === topLevelTypes.topInput) {
// In modern browsers (i.e., not IE8 or IE9), the input event is exactly
// what we want so fall through here and trigger an abstract event...
if (topLevelTarget.nodeName === 'TEXTAREA') {
// ...unless it's a textarea, in which case we don't fire an event (so
// that we have consistency with our old-IE shim).
return;
}
return topLevelTargetID;
}
};
} else {
targetIDForTextChangeEvent = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
if (topLevelType === topLevelTypes.topFocus) {
// In IE8, we can capture almost all .value changes by adding a
// propertychange handler and looking for events with propertyName
// equal to 'value'
// In IE9, propertychange fires for most input events but is buggy and
// doesn't fire when text is deleted, but conveniently, selectionchange
// appears to fire in all of the remaining cases so we catch those and
// forward the event if the value has changed
// In either case, we don't want to call the event handler if the value
// is changed from JS so we redefine a setter for `.value` that updates
// our activeElementValue variable, allowing us to ignore those changes
if (hasInputCapabilities(topLevelTarget)) {
// stopWatching() should be a noop here but we call it just in case we
// missed a blur event somehow.
stopWatching();
startWatching(topLevelTarget, topLevelTargetID);
}
} else if (topLevelType === topLevelTypes.topBlur) {
stopWatching();
} else if (
topLevelType === topLevelTypes.topSelectionChange ||
topLevelType === topLevelTypes.topKeyUp ||
topLevelType === topLevelTypes.topKeyDown) {
// On the selectionchange event, the target is just document which isn't
// helpful for us so just check activeElement instead.
//
// 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
// propertychange on the first input event after setting `value` from a
// script and fires only keydown, keypress, keyup. Catching keyup usually
// gets it and catching keydown lets us fire an event for the first
// keystroke if user does a key repeat (it'll be a little delayed: right
// before the second keystroke). Other input methods (e.g., paste) seem to
// fire selectionchange normally.
if (activeElement && activeElement.value !== activeElementValue) {
activeElementValue = activeElement.value;
return activeElementID;
}
}
};
}
/**
* @param {string} topLevelType Record from `EventConstants`.
* @param {DOMEventTarget} topLevelTarget The listening component root node.
* @param {string} topLevelTargetID ID of `topLevelTarget`.
* @param {object} nativeEvent Native browser event.
* @return {*} An accumulation of `AbstractEvent`s.
* @see {EventPluginHub.extractEvents}
*/
var extractEvents = function(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent) {
var targetID = targetIDForTextChangeEvent(
topLevelType,
topLevelTarget,
topLevelTargetID,
nativeEvent
);
if (targetID) {
var abstractEvent = AbstractEvent.getPooled(
abstractEventTypes.textChange,
targetID,
nativeEvent
);
EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
return abstractEvent;
}
};
var TextChangeEventPlugin = {
abstractEventTypes: abstractEventTypes,
extractEvents: extractEvents
};
module.exports = TextChangeEventPlugin;

Some files were not shown because too many files have changed in this diff Show More