├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── __tests__ ├── ReactChildren-test.js ├── ReactClass-test.js ├── ReactClassMixin-test.js ├── ReactComponent-test.js ├── ReactComponentLifeCycle-test.js ├── ReactCompositeComponentState-test.js ├── ReactDOM-test.js ├── ReactDOMComponent-test.js ├── ReactES6Class-test.js ├── ReactElement-test.js ├── ReactElementClone-test.js ├── ReactEmptyComponent-test.js ├── ReactJSXElement-test.js ├── ReactMount-test.js ├── ReactMountDestruction-test.js ├── ReactMultiChild-test.js ├── ReactPureComponent-test.js ├── ReactStatelessComponent-test.js ├── SelectValueElement-test.js ├── findDOMNode-test.js ├── onlyChild-test.js └── refs-destruction-test.js ├── addons ├── LinkedStateMixin.js ├── ReactCSSTransitionGroup.js ├── ReactCSSTransitionGroupChild.js ├── ReactChildren.js ├── ReactComponentWithPureRenderMixin.js ├── ReactFragment.js ├── ReactLink.js ├── ReactStateSetters.js ├── ReactTransitionChildMapping.js ├── ReactTransitionEvents.js ├── ReactTransitionGroup.js ├── ReactWithAddons.js ├── react-tap-event-plugin.js ├── renderSubtreeIntoContainer.js ├── shallowCompare.js ├── update.js └── utils │ ├── CSSCore.js │ ├── PooledClass.js │ ├── emptyFunction.js │ ├── escapeTextContentForBrowser.js │ ├── flattenChildren.js │ ├── getIteratorFn.js │ ├── keyOf.js │ ├── quoteAttributeValueForBrowser.js │ ├── shallowEqual.js │ └── traverseAllChildren.js ├── build.js ├── dist ├── react-lite.common.js ├── react-lite.js ├── react-lite.min.js └── react-lite.min.js.gz ├── examples ├── js-repaint-perf │ ├── ENV.js │ ├── ga.js │ ├── react │ │ ├── app-component.js │ │ ├── app.js │ │ ├── index.html │ │ └── lite.html │ ├── styles.css │ └── vendor │ │ ├── JSXTransformer.js │ │ ├── bootstrap.min.css │ │ ├── memory-stats.js │ │ ├── monitor.js │ │ ├── react-dom.min.js │ │ ├── react-with-addons.js │ │ ├── react-with-addons.min.js │ │ ├── react.addons.12.2.2.js │ │ └── react.min.js └── simple │ ├── app.js │ ├── index.html │ ├── index.js │ ├── react.js │ └── react.min.js ├── jest └── preprocessor.js ├── package.json ├── src ├── CSSPropertyOperations.js ├── Children.js ├── Component.js ├── DOM.js ├── DOMConfig.js ├── DOMPropertyOperations.js ├── PropTypes.js ├── PureComponent.js ├── ReactDOM.js ├── constant.js ├── createClass.js ├── createElement.js ├── event-system.js ├── index.js ├── shallowEqual.js ├── util.js └── virtual-dom.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | coverage 5 | _book 6 | lib 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | examples 4 | test 5 | coverage 6 | _book 7 | book.json 8 | docs 9 | src 10 | jest 11 | __tests__ 12 | webpack.config.js 13 | build.js 14 | addons 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "5" 5 | branches: 6 | only: 7 | - master 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 工业聚 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-lite 2 | 3 | [![Travis](https://travis-ci.org/Lucifier129/react-lite.svg?branch=master)](https://travis-ci.org/Lucifier129/react-lite) 4 | [![npm](https://img.shields.io/npm/v/react-lite.svg)](https://www.npmjs.com/package/react-lite) 5 | [![Join the chat at https://gitter.im/Lucifier129/react-lite](https://badges.gitter.im/Lucifier129/react-lite.svg)](https://gitter.im/Lucifier129/react-lite?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | 7 | ## Introduction 8 | react-lite is an implementation of React that optimizes for small script size. 9 | 10 | Note: react-lite dose not support React v16.x now. 11 | 12 | ### Size Comparison 13 | 14 | | Framework | Version | Minified Size | 15 | |------------------------|------------|---------------| 16 | | Ember | 2.2.0 | 446kb | 17 | | Polymer | 1.0.6 | 183kb | 18 | | Angular | 1.4.8 | 148kb | 19 | | React | 0.14.3 | 136kb | 20 | | Web Components Polyfill| 0.7.19 | 118kb | 21 | | Riot | 2.3.11 | 20kb | 22 | | React-lite | 0.15.6 | 25kb | 23 | | preact + preact-compat | 8.2.1 | 5kb | 24 | 25 | React-lite supports the core APIs of React, such as Virtual DOM, intended as a drop-in 26 | replacement for React, when you don't need server-side rendering in browser(no `ReactDOM.renderToString` & `ReactDOM.renderToStaticMarkup`). 27 | 28 | ### Usage 29 | If you are using webpack, it's so easy to use react-lite, just [config alias](http://webpack.github.io/docs/configuration.html#resolve-alias) in webpack.config.js: 30 | 31 | ```javascript 32 | // webpack.config.js 33 | { 34 | resolve: { 35 | alias: { 36 | 'react': 'react-lite', 37 | 'react-dom': 'react-lite' 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | Note: feel free to try react-lite, if something happen and we can't fix it in time, then use [regular react](https://github.com/facebook/react) instead. 44 | ## Installation 45 | 46 | You can install react-lite from npm: 47 | 48 | ```shell 49 | npm install react-lite --save 50 | ``` 51 | 52 | ## Browser compatibility 53 | 54 | supports IE9+ / ES5 enviroment 55 | 56 | ## Documentation 57 | 58 | learn react-lite from [React official documentation](https://reactjs.org) 59 | 60 | ## What can react-lite do? 61 | 62 | just the same as what react does, see some demos below(I just add the alias to webpack.config.js, no need to do anything else): 63 | 64 | - works with material-ui: [docs demo](https://lucifier129.github.io/material-ui/build) 65 | - works with react-bootstrap: [docs demo](http://react-lite-with-bootstrap.herokuapp.com/) 66 | - works with ant-design: [demo](http://lucifier129.github.io/ant-design/) 67 | - works with react-router: [examples](http://react-lite-with-react-router.coding.io/) 68 | - works with redux: 69 | * [async](http://lucifier129.github.io/redux-with-react-lite/async/index.html) 70 | * [counter](http://lucifier129.github.io/redux-with-react-lite/counter/index.html) 71 | * [shopping-cart](http://lucifier129.github.io/redux-with-react-lite/shopping-cart/index.html) 72 | * [todomvc](http://lucifier129.github.io/redux-with-react-lite/todomvc/index.html) 73 | * [todos-with-undo](http://lucifier129.github.io/redux-with-react-lite/todos-with-undo/index.html) 74 | - works with react-motion: [demos](http://lucifier129.github.io/react-motion-with-react-lite/index.html) 75 | - works with react-d3-components: [demos](http://lucifier129.github.io/react-d3-components-demos/) 76 | - works with react-d3: [demos](http://lucifier129.github.io/react-d3-demos/) 77 | - react-lite [vdom-benchmark](http://vdom-benchmark.github.io/vdom-benchmark/) 78 | - js-repaint-perf: 79 | * [react](http://lucifier129.github.io/react-lite-repaint-perf/react/index.html) 80 | * [react-lite](http://lucifier129.github.io/react-lite-repaint-perf/react/lite.html) 81 | 82 | ## React-lite vs React 83 | 84 | via react-lite: 85 | - all of React.PropTypes method is no-op(empty function) 86 | - use React in server side rendering, and use React-lite in browser 87 | * react-lite will replace the dom tree with new dom tree 88 | * you had better avoid `script|head|link` tag in client side 89 | - can not use react-dev-tool inspect react-lite, should switch to regular react for debugging 90 | - react-lite only works with a JSX toolchain([issue](https://github.com/Lucifier129/react-lite/issues/51)) 91 | - unlike react, `event` object in react-lite is always persistent, and `event.persist` is set as no-op to avoid throwing error. 92 | - react-lite can't work with `react-tap-event-plugin`, please use `fastclick` instead. or add alias `'react-tap-event-plugin': 'react-lite/lib/react-tap-event-plugin'`, just like [here](https://github.com/Lucifier129/material-ui/blob/master/docs/webpack-production.config.js#L21) 93 | - can't work with `transform-react-inline-elements`, you will get a bundle include both `react` and `react-lite`. 94 | - `react-lite` just follow the best practice of `React`. 95 | 96 | ## Test 97 | react-lite reuses react's unitest(170), you can see them in `__test__`, and run the tests with: 98 | 99 | ```shell 100 | npm test 101 | ``` 102 | 103 | License: MIT (See LICENSE file for details) 104 | -------------------------------------------------------------------------------- /__tests__/ReactCompositeComponentState-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | var mocks = { 15 | getMockFunction: function() { 16 | return jest.genMockFunction() 17 | } 18 | } 19 | 20 | var React; 21 | var ReactDOM; 22 | 23 | var TestComponent; 24 | 25 | describe('ReactCompositeComponent-state', function() { 26 | 27 | beforeEach(function() { 28 | React = require('../src'); 29 | ReactDOM = require('../src'); 30 | 31 | TestComponent = React.createClass({ 32 | peekAtState: function(from, state) { 33 | state = state || this.state; 34 | this.props.stateListener(from, state && state.color); 35 | }, 36 | 37 | peekAtCallback: function(from) { 38 | return () => this.peekAtState(from); 39 | }, 40 | 41 | setFavoriteColor: function(nextColor) { 42 | this.setState( 43 | {color: nextColor}, 44 | this.peekAtCallback('setFavoriteColor') 45 | ); 46 | }, 47 | 48 | getInitialState: function() { 49 | this.peekAtState('getInitialState'); 50 | return {color: 'red'}; 51 | }, 52 | 53 | render: function() { 54 | this.peekAtState('render'); 55 | return
{this.state.color}
; 56 | }, 57 | 58 | componentWillMount: function() { 59 | this.peekAtState('componentWillMount-start'); 60 | this.setState(function(state) { 61 | this.peekAtState('before-setState-sunrise', state); 62 | }); 63 | this.setState( 64 | {color: 'sunrise'}, 65 | this.peekAtCallback('setState-sunrise') 66 | ); 67 | this.setState(function(state) { 68 | this.peekAtState('after-setState-sunrise', state); 69 | }); 70 | this.peekAtState('componentWillMount-after-sunrise'); 71 | this.setState( 72 | {color: 'orange'}, 73 | this.peekAtCallback('setState-orange') 74 | ); 75 | this.setState(function(state) { 76 | this.peekAtState('after-setState-orange', state); 77 | }); 78 | this.peekAtState('componentWillMount-end'); 79 | }, 80 | 81 | componentDidMount: function() { 82 | this.peekAtState('componentDidMount-start'); 83 | this.setState( 84 | {color: 'yellow'}, 85 | this.peekAtCallback('setState-yellow') 86 | ); 87 | this.peekAtState('componentDidMount-end'); 88 | }, 89 | 90 | componentWillReceiveProps: function(newProps) { 91 | this.peekAtState('componentWillReceiveProps-start'); 92 | if (newProps.nextColor) { 93 | this.setState(function(state) { 94 | this.peekAtState('before-setState-receiveProps', state); 95 | return {color: newProps.nextColor}; 96 | }); 97 | this.replaceState({color: undefined}); 98 | this.setState( 99 | function(state) { 100 | this.peekAtState('before-setState-again-receiveProps', state); 101 | return {color: newProps.nextColor}; 102 | }, 103 | this.peekAtCallback('setState-receiveProps') 104 | ); 105 | this.setState(function(state) { 106 | this.peekAtState('after-setState-receiveProps', state); 107 | }); 108 | } 109 | this.peekAtState('componentWillReceiveProps-end'); 110 | }, 111 | 112 | shouldComponentUpdate: function(nextProps, nextState) { 113 | this.peekAtState('shouldComponentUpdate-currentState'); 114 | this.peekAtState('shouldComponentUpdate-nextState', nextState); 115 | return true; 116 | }, 117 | 118 | componentWillUpdate: function(nextProps, nextState) { 119 | this.peekAtState('componentWillUpdate-currentState'); 120 | this.peekAtState('componentWillUpdate-nextState', nextState); 121 | }, 122 | 123 | componentDidUpdate: function(prevProps, prevState) { 124 | this.peekAtState('componentDidUpdate-currentState'); 125 | this.peekAtState('componentDidUpdate-prevState', prevState); 126 | }, 127 | 128 | componentWillUnmount: function() { 129 | this.peekAtState('componentWillUnmount'); 130 | }, 131 | }); 132 | }); 133 | 134 | it('should support setting state', function() { 135 | var container = document.createElement('div'); 136 | document.body.appendChild(container); 137 | 138 | var stateListener = mocks.getMockFunction(); 139 | var instance = ReactDOM.render( 140 | , 141 | container, 142 | function peekAtInitialCallback() { 143 | this.peekAtState('initial-callback'); 144 | } 145 | ); 146 | ReactDOM.render( 147 | , 148 | container, 149 | instance.peekAtCallback('setProps') 150 | ); 151 | instance.setFavoriteColor('blue'); 152 | instance.forceUpdate(instance.peekAtCallback('forceUpdate')); 153 | 154 | ReactDOM.unmountComponentAtNode(container); 155 | 156 | expect(stateListener.mock.calls.join('\n')).toEqual([ 157 | // there is no state when getInitialState() is called 158 | ['getInitialState', null], 159 | ['componentWillMount-start', 'red'], 160 | // setState()'s only enqueue pending states. 161 | ['componentWillMount-after-sunrise', 'red'], 162 | ['componentWillMount-end', 'red'], 163 | // pending state queue is processed 164 | ['before-setState-sunrise', 'red'], 165 | ['after-setState-sunrise', 'sunrise'], 166 | ['after-setState-orange', 'orange'], 167 | // pending state has been applied 168 | ['render', 'orange'], 169 | ['componentDidMount-start', 'orange'], 170 | // setState-sunrise and setState-orange should be called here, 171 | // after the bug in #1740 172 | // componentDidMount() called setState({color:'yellow'}), which is async. 173 | // The update doesn't happen until the next flush. 174 | ['componentDidMount-end', 'orange'], 175 | ['shouldComponentUpdate-currentState', 'orange'], 176 | ['shouldComponentUpdate-nextState', 'yellow'], 177 | ['componentWillUpdate-currentState', 'orange'], 178 | ['componentWillUpdate-nextState', 'yellow'], 179 | ['render', 'yellow'], 180 | ['componentDidUpdate-currentState', 'yellow'], 181 | ['componentDidUpdate-prevState', 'orange'], 182 | ['setState-sunrise', 'yellow'], 183 | ['setState-orange', 'yellow'], 184 | ['setState-yellow', 'yellow'], 185 | ['initial-callback', 'yellow'], 186 | ['componentWillReceiveProps-start', 'yellow'], 187 | // setState({color:'green'}) only enqueues a pending state. 188 | ['componentWillReceiveProps-end', 'yellow'], 189 | // pending state queue is processed 190 | // before-setState-receiveProps never called, due to replaceState. 191 | ['before-setState-again-receiveProps', undefined], 192 | ['after-setState-receiveProps', 'green'], 193 | ['shouldComponentUpdate-currentState', 'yellow'], 194 | ['shouldComponentUpdate-nextState', 'green'], 195 | ['componentWillUpdate-currentState', 'yellow'], 196 | ['componentWillUpdate-nextState', 'green'], 197 | ['render', 'green'], 198 | ['componentDidUpdate-currentState', 'green'], 199 | ['componentDidUpdate-prevState', 'yellow'], 200 | ['setState-receiveProps', 'green'], 201 | ['setProps', 'green'], 202 | // setFavoriteColor('blue') 203 | ['shouldComponentUpdate-currentState', 'green'], 204 | ['shouldComponentUpdate-nextState', 'blue'], 205 | ['componentWillUpdate-currentState', 'green'], 206 | ['componentWillUpdate-nextState', 'blue'], 207 | ['render', 'blue'], 208 | ['componentDidUpdate-currentState', 'blue'], 209 | ['componentDidUpdate-prevState', 'green'], 210 | ['setFavoriteColor', 'blue'], 211 | // forceUpdate() 212 | ['componentWillUpdate-currentState', 'blue'], 213 | ['componentWillUpdate-nextState', 'blue'], 214 | ['render', 'blue'], 215 | ['componentDidUpdate-currentState', 'blue'], 216 | ['componentDidUpdate-prevState', 'blue'], 217 | ['forceUpdate', 'blue'], 218 | // unmountComponent() 219 | // state is available within `componentWillUnmount()` 220 | ['componentWillUnmount', 'blue'], 221 | ].join('\n')); 222 | }); 223 | 224 | it('should batch unmounts', function() { 225 | var outer; 226 | var Inner = React.createClass({ 227 | render: function() { 228 | return
; 229 | }, 230 | componentWillUnmount: function() { 231 | // This should get silently ignored (maybe with a warning), but it 232 | // shouldn't break React. 233 | outer.setState({showInner: false}); 234 | }, 235 | }); 236 | var Outer = React.createClass({ 237 | getInitialState: function() { 238 | return {showInner: true}; 239 | }, 240 | render: function() { 241 | return
{this.state.showInner && }
; 242 | }, 243 | }); 244 | 245 | var container = document.createElement('div'); 246 | outer = ReactDOM.render(, container); 247 | expect(() => { 248 | ReactDOM.unmountComponentAtNode(container); 249 | }).not.toThrow(); 250 | }); 251 | }); 252 | -------------------------------------------------------------------------------- /__tests__/ReactDOM-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | 14 | jest.dontMock('../src'); 15 | var React = require('../src'); 16 | var ReactDOM = require('../src'); 17 | var ReactTestUtils = { 18 | renderIntoDocument: function(instance) { 19 | var div = document.createElement('div'); 20 | // None of our tests actually require attaching the container to the 21 | // DOM, and doing so creates a mess that we rely on test isolation to 22 | // clean up, so we're going to stop honoring the name of this method 23 | // (and probably rename it eventually) if no problems arise. 24 | // document.documentElement.appendChild(div); 25 | return ReactDOM.render(instance, div); 26 | } 27 | }; 28 | var div = React.createFactory('div'); 29 | 30 | describe('ReactDOM', function() { 31 | // TODO: uncomment this test once we can run in phantom, which 32 | // supports real submit events. 33 | /* 34 | it('should bubble onSubmit', function() { 35 | var count = 0; 36 | var form; 37 | var Parent = React.createClass({ 38 | handleSubmit: function() { 39 | count++; 40 | return false; 41 | }, 42 | render: function() { 43 | return ; 44 | } 45 | }); 46 | var Child = React.createClass({ 47 | render: function() { 48 | return
; 49 | }, 50 | componentDidMount: function() { 51 | form = ReactDOM.findDOMNode(this); 52 | } 53 | }); 54 | var instance = ReactTestUtils.renderIntoDocument(); 55 | form.submit(); 56 | expect(count).toEqual(1); 57 | }); 58 | */ 59 | 60 | it('allows a DOM element to be used with a string', function() { 61 | var element = React.createElement('div', {className: 'foo'}); 62 | var instance = ReactTestUtils.renderIntoDocument(element); 63 | expect(ReactDOM.findDOMNode(instance).tagName).toBe('DIV'); 64 | }); 65 | 66 | it('should allow children to be passed as an argument', function() { 67 | var argDiv = ReactTestUtils.renderIntoDocument( 68 | div(null, 'child') 69 | ); 70 | var argNode = ReactDOM.findDOMNode(argDiv); 71 | expect(argNode.innerHTML).toBe('child'); 72 | }); 73 | 74 | it('should overwrite props.children with children argument', function() { 75 | var conflictDiv = ReactTestUtils.renderIntoDocument( 76 | div({children: 'fakechild'}, 'child') 77 | ); 78 | var conflictNode = ReactDOM.findDOMNode(conflictDiv); 79 | expect(conflictNode.innerHTML).toBe('child'); 80 | }); 81 | 82 | /** 83 | * We need to make sure that updates occur to the actual node that's in the 84 | * DOM, instead of a stale cache. 85 | */ 86 | it('should purge the DOM cache when removing nodes', function() { 87 | var myDiv = ReactTestUtils.renderIntoDocument( 88 |
89 |
, 90 |
91 |
92 | ); 93 | // Warm the cache with theDog 94 | myDiv = ReactTestUtils.renderIntoDocument( 95 |
96 |
, 97 |
, 98 |
99 | ); 100 | // Remove theDog - this should purge the cache 101 | myDiv = ReactTestUtils.renderIntoDocument( 102 |
103 |
, 104 |
105 | ); 106 | // Now, put theDog back. It's now a different DOM node. 107 | myDiv = ReactTestUtils.renderIntoDocument( 108 |
109 |
, 110 |
, 111 |
112 | ); 113 | // Change the className of theDog. It will use the same element 114 | myDiv = ReactTestUtils.renderIntoDocument( 115 |
116 |
, 117 |
, 118 |
119 | ); 120 | var root = ReactDOM.findDOMNode(myDiv); 121 | var dog = root.childNodes[0]; 122 | expect(dog.className).toBe('bigdog'); 123 | }); 124 | 125 | it('allow React.DOM factories to be called without warnings', function() { 126 | spyOn(console, 'error'); 127 | var element = div(); 128 | expect(element.type).toBe('div'); 129 | expect(console.error.argsForCall.length).toBe(0); 130 | }); 131 | 132 | }); 133 | -------------------------------------------------------------------------------- /__tests__/ReactElementClone-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | 14 | jest.dontMock('../src'); 15 | var React; 16 | var ReactDOM; 17 | var ReactTestUtils; 18 | 19 | React = require('../src'); 20 | ReactDOM = require('../src'); 21 | ReactTestUtils = ReactTestUtils = { 22 | renderIntoDocument: function(instance) { 23 | var div = document.createElement('div'); 24 | // None of our tests actually require attaching the container to the 25 | // DOM, and doing so creates a mess that we rely on test isolation to 26 | // clean up, so we're going to stop honoring the name of this method 27 | // (and probably rename it eventually) if no problems arise. 28 | // document.documentElement.appendChild(div); 29 | return ReactDOM.render(instance, div); 30 | } 31 | } 32 | 33 | describe('ReactElementClone', function() { 34 | 35 | beforeEach(function() { 36 | }); 37 | 38 | it('should clone a DOM component with new props', function() { 39 | var Grandparent = React.createClass({ 40 | render: function() { 41 | return } />; 42 | }, 43 | }); 44 | var Parent = React.createClass({ 45 | render: function() { 46 | return ( 47 |
48 | {React.cloneElement(this.props.child, { className: 'xyz' })} 49 |
50 | ); 51 | }, 52 | }); 53 | var component = ReactTestUtils.renderIntoDocument(); 54 | expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz'); 55 | }); 56 | 57 | it('should clone a composite component with new props', function() { 58 | var Child = React.createClass({ 59 | render: function() { 60 | return
; 61 | }, 62 | }); 63 | var Grandparent = React.createClass({ 64 | render: function() { 65 | return } />; 66 | }, 67 | }); 68 | var Parent = React.createClass({ 69 | render: function() { 70 | return ( 71 |
72 | {React.cloneElement(this.props.child, { className: 'xyz' })} 73 |
74 | ); 75 | }, 76 | }); 77 | var component = ReactTestUtils.renderIntoDocument(); 78 | expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz'); 79 | }); 80 | 81 | it('should keep the original ref if it is not overridden', function() { 82 | var Grandparent = React.createClass({ 83 | render: function() { 84 | return } />; 85 | }, 86 | }); 87 | 88 | var Parent = React.createClass({ 89 | render: function() { 90 | return ( 91 |
92 | {React.cloneElement(this.props.child, { className: 'xyz' })} 93 |
94 | ); 95 | }, 96 | }); 97 | 98 | var component = ReactTestUtils.renderIntoDocument(); 99 | expect(component.refs.yolo.tagName).toBe('DIV'); 100 | }); 101 | 102 | it('should transfer the key property', function() { 103 | var Component = React.createClass({ 104 | render: function() { 105 | return null; 106 | }, 107 | }); 108 | var clone = React.cloneElement(, {key: 'xyz'}); 109 | expect(clone.key).toBe('xyz'); 110 | }); 111 | 112 | it('should transfer children', function() { 113 | var Component = React.createClass({ 114 | render: function() { 115 | expect(this.props.children).toBe('xyz'); 116 | return
; 117 | }, 118 | }); 119 | 120 | ReactTestUtils.renderIntoDocument( 121 | React.cloneElement(, {children: 'xyz'}) 122 | ); 123 | }); 124 | 125 | it('should shallow clone children', function() { 126 | var Component = React.createClass({ 127 | render: function() { 128 | expect(this.props.children).toBe('xyz'); 129 | return
; 130 | }, 131 | }); 132 | 133 | ReactTestUtils.renderIntoDocument( 134 | React.cloneElement(xyz, {}) 135 | ); 136 | }); 137 | 138 | it('should accept children as rest arguments', function() { 139 | var Component = React.createClass({ 140 | render: function() { 141 | return null; 142 | }, 143 | }); 144 | 145 | var clone = React.cloneElement( 146 | xyz, 147 | { children: }, 148 |
, 149 | 150 | ); 151 | 152 | expect(clone.props.children).toEqual([ 153 |
, 154 | , 155 | ]); 156 | }); 157 | 158 | it('should support keys and refs', function() { 159 | var Parent = React.createClass({ 160 | render: function() { 161 | var clone = 162 | React.cloneElement(this.props.children, {key: 'xyz', ref: 'xyz'}); 163 | expect(clone.key).toBe('xyz'); 164 | expect(clone.ref).toBe('xyz'); 165 | return
{clone}
; 166 | }, 167 | }); 168 | 169 | var Grandparent = React.createClass({ 170 | render: function() { 171 | return ; 172 | }, 173 | }); 174 | 175 | var component = ReactTestUtils.renderIntoDocument(); 176 | expect(component.refs.parent.refs.xyz.tagName).toBe('SPAN'); 177 | }); 178 | 179 | it('should steal the ref if a new ref is specified', function() { 180 | var Parent = React.createClass({ 181 | render: function() { 182 | var clone = React.cloneElement(this.props.children, {ref: 'xyz'}); 183 | return
{clone}
; 184 | }, 185 | }); 186 | 187 | var Grandparent = React.createClass({ 188 | render: function() { 189 | return ; 190 | }, 191 | }); 192 | 193 | var component = ReactTestUtils.renderIntoDocument(); 194 | expect(component.refs.child).toBeUndefined(); 195 | expect(component.refs.parent.refs.xyz.tagName).toBe('SPAN'); 196 | }); 197 | 198 | it('should overwrite props', function() { 199 | var Component = React.createClass({ 200 | render: function() { 201 | expect(this.props.myprop).toBe('xyz'); 202 | return
; 203 | }, 204 | }); 205 | 206 | ReactTestUtils.renderIntoDocument( 207 | React.cloneElement(, {myprop: 'xyz'}) 208 | ); 209 | }); 210 | 211 | // it('warns for keys for arrays of elements in rest args', function() { 212 | // spyOn(console, 'error'); 213 | 214 | // React.cloneElement(
, null, [
,
]); 215 | 216 | // expect(console.error.argsForCall.length).toBe(1); 217 | // expect(console.error.argsForCall[0][0]).toContain( 218 | // 'Each child in an array or iterator should have a unique "key" prop.' 219 | // ); 220 | // }); 221 | 222 | // it('does not warns for arrays of elements with keys', function() { 223 | // spyOn(console, 'error'); 224 | 225 | // React.cloneElement(
, null, [
,
]); 226 | 227 | // expect(console.error.argsForCall.length).toBe(0); 228 | // }); 229 | 230 | // it('does not warn when the element is directly in rest args', function() { 231 | // spyOn(console, 'error'); 232 | 233 | // React.cloneElement(
, null,
,
); 234 | 235 | // expect(console.error.argsForCall.length).toBe(0); 236 | // }); 237 | 238 | // it('does not warn when the array contains a non-element', function() { 239 | // spyOn(console, 'error'); 240 | 241 | // React.cloneElement(
, null, [{}, {}]); 242 | 243 | // expect(console.error.argsForCall.length).toBe(0); 244 | // }); 245 | 246 | // it('should check declared prop types after clone', function() { 247 | // spyOn(console, 'error'); 248 | // var Component = React.createClass({ 249 | // propTypes: { 250 | // color: React.PropTypes.string.isRequired, 251 | // }, 252 | // render: function() { 253 | // return React.createElement('div', null, 'My color is ' + this.color); 254 | // }, 255 | // }); 256 | // var Parent = React.createClass({ 257 | // render: function() { 258 | // return React.cloneElement(this.props.child, {color: 123}); 259 | // }, 260 | // }); 261 | // var GrandParent = React.createClass({ 262 | // render: function() { 263 | // return React.createElement( 264 | // Parent, 265 | // { child: React.createElement(Component, {color: 'red'}) } 266 | // ); 267 | // }, 268 | // }); 269 | // ReactTestUtils.renderIntoDocument(React.createElement(GrandParent)); 270 | // expect(console.error.argsForCall.length).toBe(1); 271 | // expect(console.error.calls[0].args[0]).toBe( 272 | // 'Warning: Failed propType: ' + 273 | // 'Invalid prop `color` of type `number` supplied to `Component`, ' + 274 | // 'expected `string`. Check the render method of `Parent`.' 275 | // ); 276 | // }); 277 | 278 | }); 279 | -------------------------------------------------------------------------------- /__tests__/ReactJSXElement-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | var React; 15 | var ReactDOM; 16 | var ReactTestUtils; 17 | 18 | React = require('../src'); 19 | ReactDOM = require('../src'); 20 | ReactTestUtils = ReactTestUtils = { 21 | renderIntoDocument: function(instance) { 22 | var div = document.createElement('div'); 23 | // None of our tests actually require attaching the container to the 24 | // DOM, and doing so creates a mess that we rely on test isolation to 25 | // clean up, so we're going to stop honoring the name of this method 26 | // (and probably rename it eventually) if no problems arise. 27 | // document.documentElement.appendChild(div); 28 | return ReactDOM.render(instance, div); 29 | } 30 | } 31 | describe('ReactJSXElement', function() { 32 | var Component; 33 | 34 | beforeEach(function() { 35 | Component = class extends React.Component { 36 | render() { 37 | return
; 38 | } 39 | }; 40 | }); 41 | 42 | it('returns a complete element according to spec', function() { 43 | var element = ; 44 | expect(element.type).toBe(Component); 45 | expect(element.key).toBe(null); 46 | expect(element.ref).toBe(null); 47 | var expectation = {}; 48 | Object.freeze(expectation); 49 | expect(element.props).toEqual(expectation); 50 | }); 51 | 52 | it('allows a lower-case to be passed as the string type', function() { 53 | var element =
; 54 | expect(element.type).toBe('div'); 55 | expect(element.key).toBe(null); 56 | expect(element.ref).toBe(null); 57 | var expectation = {}; 58 | Object.freeze(expectation); 59 | expect(element.props).toEqual(expectation); 60 | }); 61 | 62 | it('allows a string to be passed as the type', function() { 63 | var TagName = 'div'; 64 | var element = ; 65 | expect(element.type).toBe('div'); 66 | expect(element.key).toBe(null); 67 | expect(element.ref).toBe(null); 68 | var expectation = {}; 69 | Object.freeze(expectation); 70 | expect(element.props).toEqual(expectation); 71 | }); 72 | 73 | // it('returns an immutable element', function() { 74 | // var element = ; 75 | // expect(() => element.type = 'div').toThrow(); 76 | // }); 77 | 78 | it('does not reuse the object that is spread into props', function() { 79 | var config = {foo: 1}; 80 | var element = ; 81 | expect(element.props.foo).toBe(1); 82 | config.foo = 2; 83 | expect(element.props.foo).toBe(1); 84 | }); 85 | 86 | // it('extracts key and ref from the rest of the props', function() { 87 | // var element = ; 88 | // expect(element.type).toBe(Component); 89 | // expect(element.key).toBe('12'); 90 | // expect(element.ref).toBe('34'); 91 | // var expectation = {foo:'56'}; 92 | // Object.freeze(expectation); 93 | // expect(element.props).toEqual(expectation); 94 | // }); 95 | 96 | // it('coerces the key to a string', function() { 97 | // var element = ; 98 | // expect(element.type).toBe(Component); 99 | // expect(element.key).toBe('12'); 100 | // expect(element.ref).toBe(null); 101 | // var expectation = {foo:'56'}; 102 | // Object.freeze(expectation); 103 | // expect(element.props).toEqual(expectation); 104 | // }); 105 | 106 | // it('merges JSX children onto the children prop', function() { 107 | // spyOn(console, 'error'); 108 | // var a = 1; 109 | // var element = {a}; 110 | // expect(element.props.children).toBe(a); 111 | // expect(console.error.argsForCall.length).toBe(0); 112 | // }); 113 | 114 | it('does not override children if no JSX children are provided', function() { 115 | spyOn(console, 'error'); 116 | var element = ; 117 | expect(element.props.children).toBe('text'); 118 | expect(console.error.argsForCall.length).toBe(0); 119 | }); 120 | 121 | // it('overrides children if null is provided as a JSX child', function() { 122 | // spyOn(console, 'error'); 123 | // var element = {null}; 124 | // expect(element.props.children).toBe(null); 125 | // expect(console.error.argsForCall.length).toBe(0); 126 | // }); 127 | 128 | // it('merges JSX children onto the children prop in an array', function() { 129 | // spyOn(console, 'error'); 130 | // var a = 1; 131 | // var b = 2; 132 | // var c = 3; 133 | // var element = {a}{b}{c}; 134 | // expect(element.props.children).toEqual([1, 2, 3]); 135 | // expect(console.error.argsForCall.length).toBe(0); 136 | // }); 137 | 138 | it('allows static methods to be called using the type property', function() { 139 | spyOn(console, 'error'); 140 | 141 | class StaticMethodComponent { 142 | static someStaticMethod() { 143 | return 'someReturnValue'; 144 | } 145 | render() { 146 | return
; 147 | } 148 | } 149 | 150 | var element = ; 151 | expect(element.type.someStaticMethod()).toBe('someReturnValue'); 152 | expect(console.error.argsForCall.length).toBe(0); 153 | }); 154 | 155 | it('identifies valid elements', function() { 156 | expect(React.isValidElement(
)).toEqual(true); 157 | expect(React.isValidElement()).toEqual(true); 158 | 159 | expect(React.isValidElement(null)).toEqual(false); 160 | expect(React.isValidElement(true)).toEqual(false); 161 | expect(React.isValidElement({})).toEqual(false); 162 | expect(React.isValidElement('string')).toEqual(false); 163 | expect(React.isValidElement(Component)).toEqual(false); 164 | expect(React.isValidElement({ type: 'div', props: {} })).toEqual(false); 165 | }); 166 | 167 | // it('is indistinguishable from a plain object', function() { 168 | // var element =
; 169 | // var object = {}; 170 | // expect(element.constructor).toBe(object.constructor); 171 | // }); 172 | 173 | it('should use default prop value when removing a prop', function() { 174 | Component.defaultProps = {fruit: 'persimmon'}; 175 | 176 | var container = document.createElement('div'); 177 | var instance = ReactDOM.render( 178 | , 179 | container 180 | ); 181 | expect(instance.props.fruit).toBe('mango'); 182 | 183 | ReactDOM.render(, container); 184 | expect(instance.props.fruit).toBe('persimmon'); 185 | }); 186 | 187 | it('should normalize props with default values', function() { 188 | class NormalizingComponent extends React.Component { 189 | render() { 190 | return {this.props.prop}; 191 | } 192 | } 193 | NormalizingComponent.defaultProps = {prop: 'testKey'}; 194 | 195 | var instance = ReactTestUtils.renderIntoDocument(); 196 | expect(instance.props.prop).toBe('testKey'); 197 | 198 | var inst2 = 199 | ReactTestUtils.renderIntoDocument(); 200 | expect(inst2.props.prop).toBe(null); 201 | }); 202 | 203 | }); 204 | -------------------------------------------------------------------------------- /__tests__/ReactMountDestruction-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | 14 | jest.dontMock('../src'); 15 | var React = require('../src'); 16 | var ReactDOM = require('../src'); 17 | 18 | describe('ReactMount', function() { 19 | it('should destroy a react root upon request', function() { 20 | var mainContainerDiv = document.createElement('div'); 21 | document.body.appendChild(mainContainerDiv); 22 | 23 | var instanceOne =
; 24 | var firstRootDiv = document.createElement('div'); 25 | mainContainerDiv.appendChild(firstRootDiv); 26 | ReactDOM.render(instanceOne, firstRootDiv); 27 | 28 | var instanceTwo =
; 29 | var secondRootDiv = document.createElement('div'); 30 | mainContainerDiv.appendChild(secondRootDiv); 31 | ReactDOM.render(instanceTwo, secondRootDiv); 32 | 33 | // Test that two react roots are rendered in isolation 34 | expect(firstRootDiv.firstChild.className).toBe('firstReactDiv'); 35 | expect(secondRootDiv.firstChild.className).toBe('secondReactDiv'); 36 | 37 | // Test that after unmounting each, they are no longer in the document. 38 | ReactDOM.unmountComponentAtNode(firstRootDiv); 39 | expect(firstRootDiv.firstChild).toBeNull(); 40 | ReactDOM.unmountComponentAtNode(secondRootDiv); 41 | expect(secondRootDiv.firstChild).toBeNull(); 42 | }); 43 | 44 | // it('should warn when unmounting a non-container root node', function() { 45 | // var mainContainerDiv = document.createElement('div'); 46 | 47 | // var component = 48 | //
49 | //
50 | //
; 51 | // ReactDOM.render(component, mainContainerDiv); 52 | 53 | // // Test that unmounting at a root node gives a helpful warning 54 | // var rootDiv = mainContainerDiv.firstChild; 55 | // spyOn(console, 'error'); 56 | // ReactDOM.unmountComponentAtNode(rootDiv); 57 | // expect(console.error.callCount).toBe(1); 58 | // expect(console.error.mostRecentCall.args[0]).toBe( 59 | // 'Warning: unmountComponentAtNode(): The node you\'re attempting to ' + 60 | // 'unmount was rendered by React and is not a top-level container. You ' + 61 | // 'may have accidentally passed in a React root node instead of its ' + 62 | // 'container.' 63 | // ); 64 | // }); 65 | 66 | // it('should warn when unmounting a non-container, non-root node', function() { 67 | // var mainContainerDiv = document.createElement('div'); 68 | 69 | // var component = 70 | //
71 | //
72 | //
73 | //
74 | //
; 75 | // ReactDOM.render(component, mainContainerDiv); 76 | 77 | // // Test that unmounting at a non-root node gives a different warning 78 | // var nonRootDiv = mainContainerDiv.firstChild.firstChild; 79 | // spyOn(console, 'error'); 80 | // ReactDOM.unmountComponentAtNode(nonRootDiv); 81 | // expect(console.error.callCount).toBe(1); 82 | // expect(console.error.mostRecentCall.args[0]).toBe( 83 | // 'Warning: unmountComponentAtNode(): The node you\'re attempting to ' + 84 | // 'unmount was rendered by React and is not a top-level container. ' + 85 | // 'Instead, have the parent component update its state and rerender in ' + 86 | // 'order to remove this component.' 87 | // ); 88 | // }); 89 | }); 90 | -------------------------------------------------------------------------------- /__tests__/ReactMultiChild-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | var mocks = { 15 | getMockFunction: function() { 16 | return jest.genMockFunction() 17 | } 18 | } 19 | 20 | describe('ReactMultiChild', function() { 21 | var React; 22 | 23 | var ReactDOM; 24 | 25 | beforeEach(function() { 26 | React = require('../src'); 27 | ReactDOM = require('../src'); 28 | }); 29 | 30 | describe('reconciliation', function() { 31 | it('should update children when possible', function() { 32 | var container = document.createElement('div'); 33 | 34 | var mockMount = mocks.getMockFunction(); 35 | var mockUpdate = mocks.getMockFunction(); 36 | var mockUnmount = mocks.getMockFunction(); 37 | 38 | var MockComponent = React.createClass({ 39 | componentDidMount: mockMount, 40 | componentDidUpdate: mockUpdate, 41 | componentWillUnmount: mockUnmount, 42 | render: function() { 43 | return ; 44 | }, 45 | }); 46 | 47 | expect(mockMount.mock.calls.length).toBe(0); 48 | expect(mockUpdate.mock.calls.length).toBe(0); 49 | expect(mockUnmount.mock.calls.length).toBe(0); 50 | 51 | ReactDOM.render(
, container); 52 | 53 | expect(mockMount.mock.calls.length).toBe(1); 54 | expect(mockUpdate.mock.calls.length).toBe(0); 55 | expect(mockUnmount.mock.calls.length).toBe(0); 56 | 57 | ReactDOM.render(
, container); 58 | 59 | expect(mockMount.mock.calls.length).toBe(1); 60 | expect(mockUpdate.mock.calls.length).toBe(1); 61 | expect(mockUnmount.mock.calls.length).toBe(0); 62 | }); 63 | 64 | it('should replace children with different constructors', function() { 65 | var container = document.createElement('div'); 66 | 67 | var mockMount = mocks.getMockFunction(); 68 | var mockUnmount = mocks.getMockFunction(); 69 | 70 | var MockComponent = React.createClass({ 71 | componentDidMount: mockMount, 72 | componentWillUnmount: mockUnmount, 73 | render: function() { 74 | return ; 75 | }, 76 | }); 77 | 78 | expect(mockMount.mock.calls.length).toBe(0); 79 | expect(mockUnmount.mock.calls.length).toBe(0); 80 | 81 | ReactDOM.render(
, container); 82 | 83 | expect(mockMount.mock.calls.length).toBe(1); 84 | expect(mockUnmount.mock.calls.length).toBe(0); 85 | 86 | ReactDOM.render(
, container); 87 | 88 | expect(mockMount.mock.calls.length).toBe(1); 89 | expect(mockUnmount.mock.calls.length).toBe(1); 90 | }); 91 | 92 | it('should NOT replace children with different owners', function() { 93 | var container = document.createElement('div'); 94 | 95 | var mockMount = mocks.getMockFunction(); 96 | var mockUnmount = mocks.getMockFunction(); 97 | 98 | var MockComponent = React.createClass({ 99 | componentDidMount: mockMount, 100 | componentWillUnmount: mockUnmount, 101 | render: function() { 102 | return ; 103 | }, 104 | }); 105 | 106 | var WrapperComponent = React.createClass({ 107 | render: function() { 108 | return this.props.children || ; 109 | }, 110 | }); 111 | 112 | expect(mockMount.mock.calls.length).toBe(0); 113 | expect(mockUnmount.mock.calls.length).toBe(0); 114 | 115 | ReactDOM.render(, container); 116 | 117 | expect(mockMount.mock.calls.length).toBe(1); 118 | expect(mockUnmount.mock.calls.length).toBe(0); 119 | 120 | ReactDOM.render( 121 | , 122 | container 123 | ); 124 | 125 | expect(mockMount.mock.calls.length).toBe(1); 126 | expect(mockUnmount.mock.calls.length).toBe(0); 127 | }); 128 | 129 | it('should replace children with different keys', function() { 130 | var container = document.createElement('div'); 131 | 132 | var mockMount = mocks.getMockFunction(); 133 | var mockUnmount = mocks.getMockFunction(); 134 | 135 | var MockComponent = React.createClass({ 136 | componentDidMount: mockMount, 137 | componentWillUnmount: mockUnmount, 138 | render: function() { 139 | return ; 140 | }, 141 | }); 142 | 143 | expect(mockMount.mock.calls.length).toBe(0); 144 | expect(mockUnmount.mock.calls.length).toBe(0); 145 | 146 | ReactDOM.render(
, container); 147 | 148 | expect(mockMount.mock.calls.length).toBe(1); 149 | expect(mockUnmount.mock.calls.length).toBe(0); 150 | 151 | ReactDOM.render(
, container); 152 | 153 | expect(mockMount.mock.calls.length).toBe(2); 154 | expect(mockUnmount.mock.calls.length).toBe(1); 155 | }); 156 | }); 157 | 158 | // describe('innerHTML', function() { 159 | // var setInnerHTML; 160 | 161 | // // Only run this suite if `Element.prototype.innerHTML` can be spied on. 162 | // var innerHTMLDescriptor = Object.getOwnPropertyDescriptor( 163 | // Element.prototype, 164 | // 'innerHTML' 165 | // ); 166 | // if (!innerHTMLDescriptor) { 167 | // return; 168 | // } 169 | 170 | // beforeEach(function() { 171 | // var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); 172 | // ReactDOMFeatureFlags.useCreateElement = false; 173 | 174 | // Object.defineProperty(Element.prototype, 'innerHTML', { 175 | // set: setInnerHTML = jasmine.createSpy().andCallFake( 176 | // innerHTMLDescriptor.set 177 | // ), 178 | // }); 179 | // }); 180 | 181 | // it('should only set `innerHTML` once on update', function() { 182 | // var container = document.createElement('div'); 183 | 184 | // ReactDOM.render( 185 | //
186 | //

187 | //

188 | //

189 | //
, 190 | // container 191 | // ); 192 | // // Warm the cache used by `getMarkupWrap`. 193 | // ReactDOM.render( 194 | //
195 | //

196 | //

197 | //

198 | //
, 199 | // container 200 | // ); 201 | // expect(setInnerHTML).toHaveBeenCalled(); 202 | // var callCountOnMount = setInnerHTML.calls.length; 203 | 204 | // ReactDOM.render( 205 | //
206 | //

207 | //

208 | //

209 | //
, 210 | // container 211 | // ); 212 | // expect(setInnerHTML.calls.length).toBe(callCountOnMount + 1); 213 | // }); 214 | // }); 215 | }); 216 | -------------------------------------------------------------------------------- /__tests__/ReactPureComponent-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | var React = require('../src'); 15 | var ReactDOM = require('../src'); 16 | 17 | describe('ReactPureComponent', function() { 18 | beforeEach(function() { 19 | // React = require('React'); 20 | // ReactDOM = require('ReactDOM'); 21 | }); 22 | 23 | it('should render', function() { 24 | var renders = 0; 25 | class Component extends React.PureComponent { 26 | constructor() { 27 | super(); 28 | this.state = {type: 'mushrooms'}; 29 | } 30 | render() { 31 | renders++; 32 | return
{this.props.text[0]}
; 33 | } 34 | } 35 | 36 | var container = document.createElement('div'); 37 | var text; 38 | var component; 39 | 40 | text = ['porcini']; 41 | component = ReactDOM.render(, container); 42 | expect(container.textContent).toBe('porcini'); 43 | expect(renders).toBe(1); 44 | 45 | text = ['morel']; 46 | component = ReactDOM.render(, container); 47 | expect(container.textContent).toBe('morel'); 48 | expect(renders).toBe(2); 49 | 50 | text[0] = 'portobello'; 51 | component = ReactDOM.render(, container); 52 | expect(container.textContent).toBe('morel'); 53 | expect(renders).toBe(2); 54 | 55 | // Setting state without changing it doesn't cause a rerender. 56 | component.setState({type: 'mushrooms'}); 57 | expect(container.textContent).toBe('morel'); 58 | expect(renders).toBe(2); 59 | 60 | // But changing state does. 61 | component.setState({type: 'portobello mushrooms'}); 62 | expect(container.textContent).toBe('portobello'); 63 | expect(renders).toBe(3); 64 | }); 65 | 66 | it('can override shouldComponentUpdate', function() { 67 | var renders = 0; 68 | class Component extends React.PureComponent { 69 | render() { 70 | renders++; 71 | return
; 72 | } 73 | shouldComponentUpdate() { 74 | return true; 75 | } 76 | } 77 | var container = document.createElement('div'); 78 | ReactDOM.render(, container); 79 | ReactDOM.render(, container); 80 | expect(renders).toBe(2); 81 | }); 82 | 83 | it('extends React.Component', function() { 84 | var renders = 0; 85 | class Component extends React.PureComponent { 86 | render() { 87 | expect(this instanceof React.Component).toBe(true); 88 | expect(this instanceof React.PureComponent).toBe(true); 89 | renders++; 90 | return
; 91 | } 92 | } 93 | ReactDOM.render(, document.createElement('div')); 94 | expect(renders).toBe(1); 95 | }); 96 | 97 | }); 98 | -------------------------------------------------------------------------------- /__tests__/ReactStatelessComponent-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | var React; 15 | var ReactDOM; 16 | var ReactTestUtils; 17 | 18 | function StatelessComponent(props) { 19 | return
{props.name}
; 20 | } 21 | 22 | describe('ReactStatelessComponent', function() { 23 | 24 | beforeEach(function() { 25 | React = require('../src'); 26 | ReactDOM = require('../src'); 27 | ReactTestUtils = { 28 | renderIntoDocument: function(instance) { 29 | var div = document.createElement('div'); 30 | // None of our tests actually require attaching the container to the 31 | // DOM, and doing so creates a mess that we rely on test isolation to 32 | // clean up, so we're going to stop honoring the name of this method 33 | // (and probably rename it eventually) if no problems arise. 34 | // document.documentElement.appendChild(div); 35 | return ReactDOM.render(instance, div); 36 | } 37 | }; 38 | }); 39 | 40 | it('should render stateless component', function() { 41 | var el = document.createElement('div'); 42 | ReactDOM.render(, el); 43 | 44 | expect(el.textContent).toBe('A'); 45 | }); 46 | 47 | it('should update stateless component', function() { 48 | var Parent = React.createClass({ 49 | render() { 50 | return ; 51 | }, 52 | }); 53 | 54 | var el = document.createElement('div'); 55 | ReactDOM.render(, el); 56 | expect(el.textContent).toBe('A'); 57 | 58 | ReactDOM.render(, el); 59 | expect(el.textContent).toBe('B'); 60 | }); 61 | 62 | it('should unmount stateless component', function() { 63 | var container = document.createElement('div'); 64 | 65 | ReactDOM.render(, container); 66 | expect(container.textContent).toBe('A'); 67 | 68 | ReactDOM.unmountComponentAtNode(container); 69 | expect(container.textContent).toBe(''); 70 | }); 71 | 72 | it('should pass context thru stateless component', function() { 73 | var Child = React.createClass({ 74 | contextTypes: { 75 | test: React.PropTypes.string.isRequired, 76 | }, 77 | 78 | render: function() { 79 | return
{this.context.test}
; 80 | }, 81 | }); 82 | 83 | function Parent() { 84 | return ; 85 | } 86 | 87 | var GrandParent = React.createClass({ 88 | childContextTypes: { 89 | test: React.PropTypes.string.isRequired, 90 | }, 91 | 92 | getChildContext() { 93 | return {test: this.props.test}; 94 | }, 95 | 96 | render: function() { 97 | return ; 98 | }, 99 | }); 100 | 101 | var el = document.createElement('div'); 102 | ReactDOM.render(, el); 103 | 104 | expect(el.textContent).toBe('test'); 105 | 106 | ReactDOM.render(, el); 107 | 108 | expect(el.textContent).toBe('mest'); 109 | }); 110 | 111 | it('should warn when stateless component returns array', function() { 112 | spyOn(console, 'error'); 113 | function NotAComponent() { 114 | return [
,
]; 115 | } 116 | expect(function() { 117 | ReactTestUtils.renderIntoDocument(
); 118 | }).toThrow(); 119 | // expect(console.error.calls.length).toBe(1); 120 | // expect(console.error.argsForCall[0][0]).toContain( 121 | // 'NotAComponent(...): A valid React element (or null) must be returned. '+ 122 | // 'You may have returned undefined, an array or some other invalid object.' 123 | // ); 124 | }); 125 | 126 | // it('should throw on string refs in pure functions', function() { 127 | // function Child() { 128 | // return
; 129 | // } 130 | 131 | // expect(function() { 132 | // ReactTestUtils.renderIntoDocument(); 133 | // }).toThrow( 134 | // 'Stateless function components cannot have refs.' 135 | // ); 136 | // }); 137 | 138 | // it('should warn when given a ref', function() { 139 | // spyOn(console, 'error'); 140 | 141 | // var Parent = React.createClass({ 142 | // displayName: 'Parent', 143 | // render: function() { 144 | // return ; 145 | // }, 146 | // }); 147 | // ReactTestUtils.renderIntoDocument(); 148 | 149 | // expect(console.error.argsForCall.length).toBe(1); 150 | // expect(console.error.argsForCall[0][0]).toContain( 151 | // 'Stateless function components cannot be given refs ' + 152 | // '(See ref "stateless" in StatelessComponent created by Parent). ' + 153 | // 'Attempts to access this ref will fail.' 154 | // ); 155 | // }); 156 | 157 | it('should provide a null ref', function() { 158 | function Child() { 159 | return
; 160 | } 161 | 162 | var comp = ReactTestUtils.renderIntoDocument(); 163 | expect(comp).toBe(null); 164 | }); 165 | 166 | // it('should use correct name in key warning', function() { 167 | // function Child() { 168 | // return
{[]}
; 169 | // } 170 | 171 | // spyOn(console, 'error'); 172 | // ReactTestUtils.renderIntoDocument(); 173 | // expect(console.error.argsForCall.length).toBe(1); 174 | // expect(console.error.argsForCall[0][0]).toContain('a unique "key" prop'); 175 | // expect(console.error.argsForCall[0][0]).toContain('Child'); 176 | // }); 177 | 178 | // it('should support default props and prop types', function() { 179 | // function Child(props) { 180 | // return
{props.test}
; 181 | // } 182 | // Child.defaultProps = {test: 2}; 183 | // Child.propTypes = {test: React.PropTypes.string}; 184 | 185 | // spyOn(console, 'error'); 186 | // ReactTestUtils.renderIntoDocument(); 187 | // expect(console.error.argsForCall.length).toBe(1); 188 | // expect(console.error.argsForCall[0][0]).toBe( 189 | // 'Warning: Failed propType: Invalid prop `test` of type `number` ' + 190 | // 'supplied to `Child`, expected `string`.' 191 | // ); 192 | // }); 193 | 194 | it('should receive context', function() { 195 | var Parent = React.createClass({ 196 | childContextTypes: { 197 | lang: React.PropTypes.string, 198 | }, 199 | getChildContext: function() { 200 | return {lang: 'en'}; 201 | }, 202 | render: function() { 203 | return ; 204 | }, 205 | }); 206 | function Child(props, context) { 207 | return
{context.lang}
; 208 | } 209 | Child.contextTypes = {lang: React.PropTypes.string}; 210 | 211 | var el = document.createElement('div'); 212 | ReactDOM.render(, el); 213 | expect(el.textContent).toBe('en'); 214 | }); 215 | 216 | it('should work with arrow functions', function() { 217 | var Child = function() { 218 | return
; 219 | }; 220 | // Will create a new bound function without a prototype, much like a native 221 | // arrow function. 222 | Child = Child.bind(this); 223 | 224 | expect(() => ReactTestUtils.renderIntoDocument()).not.toThrow(); 225 | }); 226 | 227 | it('should allow simple functions to return null', function() { 228 | var Child = function() { 229 | return null; 230 | }; 231 | expect(() => ReactTestUtils.renderIntoDocument()).not.toThrow(); 232 | }); 233 | 234 | it('should allow simple functions to return false', function() { 235 | function Child() { 236 | return false; 237 | } 238 | expect(() => ReactTestUtils.renderIntoDocument()).not.toThrow(); 239 | }); 240 | 241 | // it('should warn when using non-React functions in JSX', function() { 242 | // spyOn(console, 'error'); 243 | // function NotAComponent() { 244 | // return [
,
]; 245 | // } 246 | // expect(function() { 247 | // ReactTestUtils.renderIntoDocument(
); 248 | // }).toThrow(); // has no method 'render' 249 | // expect(console.error.calls.length).toBe(1); 250 | // expect(console.error.argsForCall[0][0]).toContain( 251 | // 'NotAComponent(...): A valid React element (or null) must be returned. You may ' + 252 | // 'have returned undefined, an array or some other invalid object.' 253 | // ); 254 | // }); 255 | }); 256 | -------------------------------------------------------------------------------- /__tests__/SelectValueElement-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | var React = require('../src'); 15 | var ReactDOM = require('../src'); 16 | var ReactTestUtils = { 17 | renderIntoDocument: function(instance) { 18 | var div = document.createElement('div'); 19 | return ReactDOM.render(instance, div); 20 | } 21 | }; 22 | 23 | describe('Render Select with multiple values', function() { 24 | class Component extends React.Component { 25 | constructor(props, context) { 26 | super(props, context); 27 | this.state = {selectedValue: [1,3,4]}; 28 | } 29 | 30 | render() { 31 | return
32 | 38 | {this.state.selectedValue} 39 |
; 40 | } 41 | } 42 | 43 | it('should mark correct option as selected', function() { 44 | var instance = ReactTestUtils.renderIntoDocument(); 45 | var root = ReactDOM.findDOMNode(instance); 46 | expect(root.childNodes[0].options[0].selected).toBe(true); 47 | expect(root.childNodes[0].options[1].selected).toBe(false); 48 | expect(root.childNodes[0].options[2].selected).toBe(true); 49 | expect(root.childNodes[0].options[3].selected).toBe(true); 50 | }); 51 | 52 | }); 53 | 54 | describe('Render Select with single value', function() { 55 | class Component extends React.Component { 56 | constructor(props, context) { 57 | super(props, context); 58 | this.state = {selectedValue: 2}; 59 | } 60 | 61 | render() { 62 | return
63 | 69 | {this.state.selectedValue} 70 |
; 71 | } 72 | } 73 | 74 | it('should mark correct option as selected', function() { 75 | var instance = ReactTestUtils.renderIntoDocument(); 76 | var root = ReactDOM.findDOMNode(instance); 77 | 78 | expect(root.childNodes[0].options[0].selected).toBe(false); 79 | expect(root.childNodes[0].options[1].selected).toBe(true); 80 | expect(root.childNodes[0].options[2].selected).toBe(false); 81 | expect(root.childNodes[0].options[3].selected).toBe(false); 82 | }); 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /__tests__/findDOMNode-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | var React = require('../src'); 15 | var ReactDOM = require('../src'); 16 | var ReactTestUtils = { 17 | renderIntoDocument: function(instance) { 18 | var div = document.createElement('div'); 19 | // None of our tests actually require attaching the container to the 20 | // DOM, and doing so creates a mess that we rely on test isolation to 21 | // clean up, so we're going to stop honoring the name of this method 22 | // (and probably rename it eventually) if no problems arise. 23 | // document.documentElement.appendChild(div); 24 | return ReactDOM.render(instance, div); 25 | } 26 | }; 27 | 28 | describe('findDOMNode', function() { 29 | it('findDOMNode should return null if passed null', function() { 30 | expect(ReactDOM.findDOMNode(null)).toBe(null); 31 | }); 32 | 33 | it('findDOMNode should find dom element', function() { 34 | var MyNode = React.createClass({ 35 | render: function() { 36 | return
Noise
; 37 | }, 38 | }); 39 | 40 | var myNode = ReactTestUtils.renderIntoDocument(); 41 | var myDiv = ReactDOM.findDOMNode(myNode); 42 | var mySameDiv = ReactDOM.findDOMNode(myDiv); 43 | expect(myDiv.tagName).toBe('DIV'); 44 | expect(mySameDiv).toBe(myDiv); 45 | }); 46 | 47 | it('findDOMNode should reject random objects', function() { 48 | expect(function() { 49 | ReactDOM.findDOMNode({foo: 'bar'}); 50 | }) 51 | .toThrow(); 52 | }); 53 | 54 | it('findDOMNode should reject unmounted objects with render func', function() { 55 | var Foo = React.createClass({ 56 | render: function() { 57 | return
; 58 | }, 59 | }); 60 | 61 | var container = document.createElement('div'); 62 | var inst = ReactDOM.render(, container); 63 | ReactDOM.unmountComponentAtNode(container); 64 | 65 | expect(() => ReactDOM.findDOMNode(inst)).toThrow(); 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /__tests__/onlyChild-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | describe('onlyChild', function() { 15 | 16 | var React; 17 | var ReactFragment; 18 | var onlyChild; 19 | var WrapComponent; 20 | 21 | beforeEach(function() { 22 | React = require('../src'); 23 | onlyChild = React.Children.only; 24 | WrapComponent = React.createClass({ 25 | render: function() { 26 | return ( 27 |
28 | {onlyChild(this.props.children, this.props.mapFn, this)} 29 |
30 | ); 31 | }, 32 | }); 33 | }); 34 | 35 | it('should fail when passed two children', function() { 36 | expect(function() { 37 | var instance = 38 | 39 |
40 | 41 | ; 42 | onlyChild(instance.props.children); 43 | }).toThrow(); 44 | }); 45 | 46 | it('should fail when passed nully values', function() { 47 | expect(function() { 48 | var instance = 49 | 50 | {null} 51 | ; 52 | onlyChild(instance.props.children); 53 | }).toThrow(); 54 | 55 | expect(function() { 56 | var instance = 57 | 58 | {undefined} 59 | ; 60 | onlyChild(instance.props.children); 61 | }).toThrow(); 62 | }); 63 | 64 | // it('should fail when key/value objects', function() { 65 | // expect(function() { 66 | // var instance = 67 | // 68 | // {ReactFragment.create({oneThing: })} 69 | // ; 70 | // onlyChild(instance.props.children); 71 | // }).toThrow(); 72 | // }); 73 | 74 | 75 | it('should not fail when passed interpolated single child', function() { 76 | expect(function() { 77 | var instance = 78 | 79 | {} 80 | ; 81 | onlyChild(instance.props.children); 82 | }).not.toThrow(); 83 | }); 84 | 85 | 86 | it('should return the only child', function() { 87 | expect(function() { 88 | var instance = 89 | 90 | 91 | ; 92 | onlyChild(instance.props.children); 93 | }).not.toThrow(); 94 | }); 95 | 96 | }); 97 | -------------------------------------------------------------------------------- /__tests__/refs-destruction-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @emails react-core 10 | */ 11 | 12 | 'use strict'; 13 | jest.dontMock('../src'); 14 | var React; 15 | var ReactDOM; 16 | var ReactTestUtils; 17 | 18 | var TestComponent; 19 | 20 | describe('refs-destruction', function() { 21 | beforeEach(function() { 22 | React = require('../src'); 23 | ReactDOM = require('../src'); 24 | ReactTestUtils = { 25 | renderIntoDocument: function(instance) { 26 | var div = document.createElement('div'); 27 | // None of our tests actually require attaching the container to the 28 | // DOM, and doing so creates a mess that we rely on test isolation to 29 | // clean up, so we're going to stop honoring the name of this method 30 | // (and probably rename it eventually) if no problems arise. 31 | // document.documentElement.appendChild(div); 32 | return ReactDOM.render(instance, div); 33 | } 34 | }; 35 | 36 | TestComponent = React.createClass({ 37 | render: function() { 38 | return ( 39 |
40 | {this.props.destroy ? null : 41 |
42 | Lets try to destroy this. 43 |
44 | } 45 |
46 | ); 47 | }, 48 | }); 49 | }); 50 | 51 | it('should remove refs when destroying the parent', function() { 52 | var container = document.createElement('div'); 53 | var testInstance = ReactDOM.render(, container); 54 | expect(testInstance.refs.theInnerDiv.tagName === 'DIV') 55 | .toBe(true); 56 | expect(testInstance.refs.theInnerDiv.getDOMNode().tagName === 'DIV') 57 | .toBe(true); 58 | expect(Object.keys(testInstance.refs || {}).length).toEqual(1); 59 | ReactDOM.unmountComponentAtNode(container); 60 | expect(Object.keys(testInstance.refs || {}).length).toEqual(0); 61 | }); 62 | 63 | it('should remove refs when destroying the child', function() { 64 | var container = document.createElement('div'); 65 | var testInstance = ReactDOM.render(, container); 66 | expect(testInstance.refs.theInnerDiv.tagName === 'DIV') 67 | .toBe(true); 68 | expect(testInstance.refs.theInnerDiv.getDOMNode().tagName === 'DIV') 69 | .toBe(true); 70 | expect(Object.keys(testInstance.refs || {}).length).toEqual(1); 71 | ReactDOM.render(, container); 72 | expect(Object.keys(testInstance.refs || {}).length).toEqual(0); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /addons/LinkedStateMixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule LinkedStateMixin 10 | * @typechecks static-only 11 | */ 12 | var ReactLink = require('./ReactLink') 13 | var ReactStateSetters = require('./ReactStateSetters') 14 | 15 | /** 16 | * A simple mixin around ReactLink.forState(). 17 | */ 18 | var LinkedStateMixin = { 19 | /** 20 | * Create a ReactLink that's linked to part of this component's state. The 21 | * ReactLink will have the current value of this.state[key] and will call 22 | * setState() when a change is requested. 23 | * 24 | * @param {string} key state key to update. Note: you may want to use keyOf() 25 | * if you're using Google Closure Compiler advanced mode. 26 | * @return {ReactLink} ReactLink instance linking to the state. 27 | */ 28 | linkState: function(key) { 29 | return new ReactLink( 30 | this.state[key], 31 | ReactStateSetters.createStateKeySetter(this, key) 32 | ); 33 | }, 34 | }; 35 | 36 | module.exports = LinkedStateMixin; 37 | -------------------------------------------------------------------------------- /addons/ReactCSSTransitionGroup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @typechecks 10 | * @providesModule ReactCSSTransitionGroup 11 | */ 12 | 13 | var React = require('../dist/react-lite.common') 14 | var ReactTransitionGroup = require('./ReactTransitionGroup') 15 | var ReactCSSTransitionGroupChild = require('./ReactCSSTransitionGroupChild') 16 | var assign = Object.assign 17 | 18 | function createTransitionTimeoutPropValidator(transitionType) { 19 | var timeoutPropName = 'transition' + transitionType + 'Timeout'; 20 | var enabledPropName = 'transition' + transitionType; 21 | 22 | return function(props) { 23 | // If the transition is enabled 24 | if (props[enabledPropName]) { 25 | // If no timeout duration is provided 26 | if (props[timeoutPropName] == null) { 27 | return new Error( 28 | timeoutPropName + ' wasn\'t supplied to ReactCSSTransitionGroup: ' + 29 | 'this can cause unreliable animations and won\'t be supported in ' + 30 | 'a future version of React. See ' + 31 | 'https://fb.me/react-animation-transition-group-timeout for more ' + 32 | 'information.' 33 | ); 34 | 35 | // If the duration isn't a number 36 | } else if (typeof props[timeoutPropName] !== 'number') { 37 | return new Error(timeoutPropName + ' must be a number (in milliseconds)'); 38 | } 39 | } 40 | }; 41 | } 42 | 43 | var ReactCSSTransitionGroup = React.createClass({ 44 | displayName: 'ReactCSSTransitionGroup', 45 | 46 | propTypes: { 47 | transitionName: ReactCSSTransitionGroupChild.propTypes.name, 48 | 49 | transitionAppear: React.PropTypes.bool, 50 | transitionEnter: React.PropTypes.bool, 51 | transitionLeave: React.PropTypes.bool, 52 | transitionAppearTimeout: createTransitionTimeoutPropValidator('Appear'), 53 | transitionEnterTimeout: createTransitionTimeoutPropValidator('Enter'), 54 | transitionLeaveTimeout: createTransitionTimeoutPropValidator('Leave'), 55 | }, 56 | 57 | getDefaultProps: function() { 58 | return { 59 | transitionAppear: false, 60 | transitionEnter: true, 61 | transitionLeave: true, 62 | }; 63 | }, 64 | 65 | _wrapChild: function(child) { 66 | // We need to provide this childFactory so that 67 | // ReactCSSTransitionGroupChild can receive updates to name, enter, and 68 | // leave while it is leaving. 69 | return React.createElement( 70 | ReactCSSTransitionGroupChild, 71 | { 72 | name: this.props.transitionName, 73 | appear: this.props.transitionAppear, 74 | enter: this.props.transitionEnter, 75 | leave: this.props.transitionLeave, 76 | appearTimeout: this.props.transitionAppearTimeout, 77 | enterTimeout: this.props.transitionEnterTimeout, 78 | leaveTimeout: this.props.transitionLeaveTimeout, 79 | }, 80 | child 81 | ); 82 | }, 83 | 84 | render: function() { 85 | return React.createElement( 86 | ReactTransitionGroup, 87 | assign({}, this.props, {childFactory: this._wrapChild}) 88 | ); 89 | }, 90 | }); 91 | 92 | module.exports = ReactCSSTransitionGroup; 93 | -------------------------------------------------------------------------------- /addons/ReactCSSTransitionGroupChild.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @typechecks 10 | * @providesModule ReactCSSTransitionGroupChild 11 | */ 12 | var React = require('../dist/react-lite.common') 13 | var ReactDOM = React 14 | var Children = ReactDOM.Children 15 | var CSSCore = require('./utils/CSSCore') 16 | var ReactTransitionEvents = require('./ReactTransitionEvents') 17 | 18 | var onlyChild = Children.only 19 | 20 | // We don't remove the element from the DOM until we receive an animationend or 21 | // transitionend event. If the user screws up and forgets to add an animation 22 | // their node will be stuck in the DOM forever, so we detect if an animation 23 | // does not start and if it doesn't, we just call the end listener immediately. 24 | var TICK = 17; 25 | 26 | var ReactCSSTransitionGroupChild = React.createClass({ 27 | displayName: 'ReactCSSTransitionGroupChild', 28 | 29 | propTypes: { 30 | name: React.PropTypes.oneOfType([ 31 | React.PropTypes.string, 32 | React.PropTypes.shape({ 33 | enter: React.PropTypes.string, 34 | leave: React.PropTypes.string, 35 | active: React.PropTypes.string, 36 | }), 37 | React.PropTypes.shape({ 38 | enter: React.PropTypes.string, 39 | enterActive: React.PropTypes.string, 40 | leave: React.PropTypes.string, 41 | leaveActive: React.PropTypes.string, 42 | appear: React.PropTypes.string, 43 | appearActive: React.PropTypes.string, 44 | }), 45 | ]).isRequired, 46 | 47 | // Once we require timeouts to be specified, we can remove the 48 | // boolean flags (appear etc.) and just accept a number 49 | // or a bool for the timeout flags (appearTimeout etc.) 50 | appear: React.PropTypes.bool, 51 | enter: React.PropTypes.bool, 52 | leave: React.PropTypes.bool, 53 | appearTimeout: React.PropTypes.number, 54 | enterTimeout: React.PropTypes.number, 55 | leaveTimeout: React.PropTypes.number, 56 | }, 57 | 58 | transition: function(animationType, finishCallback, userSpecifiedDelay) { 59 | var node = ReactDOM.findDOMNode(this); 60 | 61 | if (!node) { 62 | if (finishCallback) { 63 | finishCallback(); 64 | } 65 | return; 66 | } 67 | 68 | var className = this.props.name[animationType] || this.props.name + '-' + animationType; 69 | var activeClassName = this.props.name[animationType + 'Active'] || className + '-active'; 70 | var timeout = null; 71 | 72 | var endListener = function(e) { 73 | if (e && e.target !== node) { 74 | return; 75 | } 76 | 77 | clearTimeout(timeout); 78 | 79 | CSSCore.removeClass(node, className); 80 | CSSCore.removeClass(node, activeClassName); 81 | 82 | ReactTransitionEvents.removeEndEventListener(node, endListener); 83 | 84 | // Usually this optional callback is used for informing an owner of 85 | // a leave animation and telling it to remove the child. 86 | if (finishCallback) { 87 | finishCallback(); 88 | } 89 | }; 90 | 91 | CSSCore.addClass(node, className); 92 | 93 | // Need to do this to actually trigger a transition. 94 | this.queueClass(activeClassName); 95 | 96 | // If the user specified a timeout delay. 97 | if (userSpecifiedDelay) { 98 | // Clean-up the animation after the specified delay 99 | timeout = setTimeout(endListener, userSpecifiedDelay); 100 | this.transitionTimeouts.push(timeout); 101 | } else { 102 | // DEPRECATED: this listener will be removed in a future version of react 103 | ReactTransitionEvents.addEndEventListener(node, endListener); 104 | } 105 | }, 106 | 107 | queueClass: function(className) { 108 | this.classNameQueue.push(className); 109 | 110 | if (!this.timeout) { 111 | this.timeout = setTimeout(this.flushClassNameQueue, TICK); 112 | } 113 | }, 114 | 115 | flushClassNameQueue: function() { 116 | if (this.isMounted()) { 117 | this.classNameQueue.forEach( 118 | CSSCore.addClass.bind(CSSCore, ReactDOM.findDOMNode(this)) 119 | ); 120 | } 121 | this.classNameQueue.length = 0; 122 | this.timeout = null; 123 | }, 124 | 125 | componentWillMount: function() { 126 | this.classNameQueue = []; 127 | this.transitionTimeouts = []; 128 | }, 129 | 130 | componentWillUnmount: function() { 131 | if (this.timeout) { 132 | clearTimeout(this.timeout); 133 | } 134 | this.transitionTimeouts.forEach(function(timeout) { 135 | clearTimeout(timeout); 136 | }); 137 | }, 138 | 139 | componentWillAppear: function(done) { 140 | if (this.props.appear) { 141 | this.transition('appear', done, this.props.appearTimeout); 142 | } else { 143 | done(); 144 | } 145 | }, 146 | 147 | componentWillEnter: function(done) { 148 | if (this.props.enter) { 149 | this.transition('enter', done, this.props.enterTimeout); 150 | } else { 151 | done(); 152 | } 153 | }, 154 | 155 | componentWillLeave: function(done) { 156 | if (this.props.leave) { 157 | this.transition('leave', done, this.props.leaveTimeout); 158 | } else { 159 | done(); 160 | } 161 | }, 162 | 163 | render: function() { 164 | return onlyChild(this.props.children); 165 | }, 166 | }); 167 | 168 | module.exports = ReactCSSTransitionGroupChild; 169 | -------------------------------------------------------------------------------- /addons/ReactChildren.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactChildren 10 | */ 11 | var React = require('../dist/react-lite.common') 12 | var PooledClass = require('./utils/PooledClass') 13 | var emptyFunction = require('./utils/emptyFunction') 14 | var traverseAllChildren = require('./utils/traverseAllChildren') 15 | var isValidElement = React.isValidElement 16 | var cloneElement = React.cloneElement 17 | 18 | var twoArgumentPooler = PooledClass.twoArgumentPooler; 19 | var fourArgumentPooler = PooledClass.fourArgumentPooler; 20 | 21 | var cloneAndReplaceKey = function(oldElement, newKey) { 22 | var newElement = cloneElement( 23 | oldElement, 24 | { key: newKey } 25 | ); 26 | 27 | return newElement; 28 | } 29 | 30 | var userProvidedKeyEscapeRegex = /\/(?!\/)/g; 31 | function escapeUserProvidedKey(text) { 32 | return ('' + text).replace(userProvidedKeyEscapeRegex, '//'); 33 | } 34 | 35 | 36 | /** 37 | * PooledClass representing the bookkeeping associated with performing a child 38 | * traversal. Allows avoiding binding callbacks. 39 | * 40 | * @constructor ForEachBookKeeping 41 | * @param {!function} forEachFunction Function to perform traversal with. 42 | * @param {?*} forEachContext Context to perform context with. 43 | */ 44 | function ForEachBookKeeping(forEachFunction, forEachContext) { 45 | this.func = forEachFunction; 46 | this.context = forEachContext; 47 | this.count = 0; 48 | } 49 | ForEachBookKeeping.prototype.destructor = function() { 50 | this.func = null; 51 | this.context = null; 52 | this.count = 0; 53 | }; 54 | PooledClass.addPoolingTo(ForEachBookKeeping, twoArgumentPooler); 55 | 56 | function forEachSingleChild(bookKeeping, child, name) { 57 | var func = bookKeeping.func; 58 | var context = bookKeeping.context; 59 | func.call(context, child, bookKeeping.count++); 60 | } 61 | 62 | /** 63 | * Iterates through children that are typically specified as `props.children`. 64 | * 65 | * The provided forEachFunc(child, index) will be called for each 66 | * leaf child. 67 | * 68 | * @param {?*} children Children tree container. 69 | * @param {function(*, int)} forEachFunc 70 | * @param {*} forEachContext Context for forEachContext. 71 | */ 72 | function forEachChildren(children, forEachFunc, forEachContext) { 73 | if (children == null) { 74 | return children; 75 | } 76 | var traverseContext = 77 | ForEachBookKeeping.getPooled(forEachFunc, forEachContext); 78 | traverseAllChildren(children, forEachSingleChild, traverseContext); 79 | ForEachBookKeeping.release(traverseContext); 80 | } 81 | 82 | 83 | /** 84 | * PooledClass representing the bookkeeping associated with performing a child 85 | * mapping. Allows avoiding binding callbacks. 86 | * 87 | * @constructor MapBookKeeping 88 | * @param {!*} mapResult Object containing the ordered map of results. 89 | * @param {!function} mapFunction Function to perform mapping with. 90 | * @param {?*} mapContext Context to perform mapping with. 91 | */ 92 | function MapBookKeeping(mapResult, keyPrefix, mapFunction, mapContext) { 93 | this.result = mapResult; 94 | this.keyPrefix = keyPrefix; 95 | this.func = mapFunction; 96 | this.context = mapContext; 97 | this.count = 0; 98 | } 99 | MapBookKeeping.prototype.destructor = function() { 100 | this.result = null; 101 | this.keyPrefix = null; 102 | this.func = null; 103 | this.context = null; 104 | this.count = 0; 105 | }; 106 | PooledClass.addPoolingTo(MapBookKeeping, fourArgumentPooler); 107 | 108 | function mapSingleChildIntoContext(bookKeeping, child, childKey) { 109 | var {result, keyPrefix, func, context} = bookKeeping; 110 | 111 | var mappedChild = func.call(context, child, bookKeeping.count++); 112 | if (Array.isArray(mappedChild)) { 113 | mapIntoWithKeyPrefixInternal( 114 | mappedChild, 115 | result, 116 | childKey, 117 | emptyFunction.thatReturnsArgument 118 | ); 119 | } else if (mappedChild != null) { 120 | if (isValidElement(mappedChild)) { 121 | mappedChild = cloneAndReplaceKey( 122 | mappedChild, 123 | // Keep both the (mapped) and old keys if they differ, just as 124 | // traverseAllChildren used to do for objects as children 125 | keyPrefix + 126 | ( 127 | mappedChild !== child ? 128 | escapeUserProvidedKey(mappedChild.key || '') + '/' : 129 | '' 130 | ) + 131 | childKey 132 | ); 133 | } 134 | result.push(mappedChild); 135 | } 136 | } 137 | 138 | function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { 139 | var escapedPrefix = ''; 140 | if (prefix != null) { 141 | escapedPrefix = escapeUserProvidedKey(prefix) + '/'; 142 | } 143 | var traverseContext = MapBookKeeping.getPooled( 144 | array, 145 | escapedPrefix, 146 | func, 147 | context 148 | ); 149 | traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); 150 | MapBookKeeping.release(traverseContext); 151 | } 152 | 153 | /** 154 | * Maps children that are typically specified as `props.children`. 155 | * 156 | * The provided mapFunction(child, key, index) will be called for each 157 | * leaf child. 158 | * 159 | * @param {?*} children Children tree container. 160 | * @param {function(*, int)} func The map function. 161 | * @param {*} context Context for mapFunction. 162 | * @return {object} Object containing the ordered map of results. 163 | */ 164 | function mapChildren(children, func, context) { 165 | if (children == null) { 166 | return children; 167 | } 168 | var result = []; 169 | mapIntoWithKeyPrefixInternal(children, result, null, func, context); 170 | return result; 171 | } 172 | 173 | 174 | 175 | function forEachSingleChildDummy(traverseContext, child, name) { 176 | return null; 177 | } 178 | 179 | /** 180 | * Count the number of children that are typically specified as 181 | * `props.children`. 182 | * 183 | * @param {?*} children Children tree container. 184 | * @return {number} The number of children. 185 | */ 186 | function countChildren(children, context) { 187 | return traverseAllChildren(children, forEachSingleChildDummy, null); 188 | } 189 | 190 | 191 | /** 192 | * Flatten a children object (typically specified as `props.children`) and 193 | * return an array with appropriately re-keyed children. 194 | */ 195 | function toArray(children) { 196 | var result = []; 197 | mapIntoWithKeyPrefixInternal( 198 | children, 199 | result, 200 | null, 201 | emptyFunction.thatReturnsArgument 202 | ); 203 | return result; 204 | } 205 | 206 | 207 | var ReactChildren = { 208 | forEach: forEachChildren, 209 | map: mapChildren, 210 | mapIntoWithKeyPrefixInternal: mapIntoWithKeyPrefixInternal, 211 | count: countChildren, 212 | toArray: toArray, 213 | }; 214 | 215 | module.exports = ReactChildren; 216 | -------------------------------------------------------------------------------- /addons/ReactComponentWithPureRenderMixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactComponentWithPureRenderMixin 10 | */ 11 | var shallowCompare = require('./shallowCompare') 12 | 13 | /** 14 | * If your React component's render function is "pure", e.g. it will render the 15 | * same result given the same props and state, provide this Mixin for a 16 | * considerable performance boost. 17 | * 18 | * Most React components have pure render functions. 19 | * 20 | * Example: 21 | * 22 | * var ReactComponentWithPureRenderMixin = 23 | * require('ReactComponentWithPureRenderMixin'); 24 | * React.createClass({ 25 | * mixins: [ReactComponentWithPureRenderMixin], 26 | * 27 | * render: function() { 28 | * return
foo
; 29 | * } 30 | * }); 31 | * 32 | * Note: This only checks shallow equality for props and state. If these contain 33 | * complex data structures this mixin may have false-negatives for deeper 34 | * differences. Only mixin to components which have simple props and state, or 35 | * use `forceUpdate()` when you know deep data structures have changed. 36 | */ 37 | var ReactComponentWithPureRenderMixin = { 38 | shouldComponentUpdate: function(nextProps, nextState) { 39 | return shallowCompare(this, nextProps, nextState); 40 | }, 41 | }; 42 | 43 | module.exports = ReactComponentWithPureRenderMixin; 44 | -------------------------------------------------------------------------------- /addons/ReactFragment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactFragment 10 | */ 11 | var React = require('../dist/react-lite.common') 12 | var isValidElement = React.isValidElement 13 | var ReactChildren = require('./ReactChildren') 14 | var emptyFunction = require('./utils/emptyFunction') 15 | var invariant = function() {} 16 | var warning = function() {} 17 | /** 18 | * We used to allow keyed objects to serve as a collection of ReactElements, 19 | * or nested sets. This allowed us a way to explicitly key a set a fragment of 20 | * components. This is now being replaced with an opaque data structure. 21 | * The upgrade path is to call React.addons.createFragment({ key: value }) to 22 | * create a keyed fragment. The resulting data structure is an array. 23 | */ 24 | 25 | var numericPropertyRegex = /^\d+$/; 26 | 27 | var warnedAboutNumeric = false; 28 | 29 | var ReactFragment = { 30 | // Wrap a keyed object in an opaque proxy that warns you if you access any 31 | // of its properties. 32 | create: function(object) { 33 | if (typeof object !== 'object' || !object || Array.isArray(object)) { 34 | warning( 35 | false, 36 | 'React.addons.createFragment only accepts a single object. Got: %s', 37 | object 38 | ); 39 | return object; 40 | } 41 | if (isValidElement(object)) { 42 | warning( 43 | false, 44 | 'React.addons.createFragment does not accept a ReactElement ' + 45 | 'without a wrapper object.' 46 | ); 47 | return object; 48 | } 49 | 50 | invariant( 51 | object.nodeType !== 1, 52 | 'React.addons.createFragment(...): Encountered an invalid child; DOM ' + 53 | 'elements are not valid children of React components.' 54 | ); 55 | 56 | var result = []; 57 | 58 | for (var key in object) { 59 | ReactChildren.mapIntoWithKeyPrefixInternal( 60 | object[key], 61 | result, 62 | key, 63 | emptyFunction.thatReturnsArgument 64 | ); 65 | } 66 | 67 | return result; 68 | }, 69 | }; 70 | 71 | module.exports = ReactFragment; 72 | -------------------------------------------------------------------------------- /addons/ReactLink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactLink 10 | * @typechecks static-only 11 | */ 12 | 13 | /** 14 | * ReactLink encapsulates a common pattern in which a component wants to modify 15 | * a prop received from its parent. ReactLink allows the parent to pass down a 16 | * value coupled with a callback that, when invoked, expresses an intent to 17 | * modify that value. For example: 18 | * 19 | * React.createClass({ 20 | * getInitialState: function() { 21 | * return {value: ''}; 22 | * }, 23 | * render: function() { 24 | * var valueLink = new ReactLink(this.state.value, this._handleValueChange); 25 | * return ; 26 | * }, 27 | * _handleValueChange: function(newValue) { 28 | * this.setState({value: newValue}); 29 | * } 30 | * }); 31 | * 32 | * We have provided some sugary mixins to make the creation and 33 | * consumption of ReactLink easier; see LinkedValueUtils and LinkedStateMixin. 34 | */ 35 | var React = require('../dist/react-lite.common') 36 | 37 | /** 38 | * @param {*} value current value of the link 39 | * @param {function} requestChange callback to request a change 40 | */ 41 | function ReactLink(value, requestChange) { 42 | this.value = value; 43 | this.requestChange = requestChange; 44 | } 45 | 46 | /** 47 | * Creates a PropType that enforces the ReactLink API and optionally checks the 48 | * type of the value being passed inside the link. Example: 49 | * 50 | * MyComponent.propTypes = { 51 | * tabIndexLink: ReactLink.PropTypes.link(React.PropTypes.number) 52 | * } 53 | */ 54 | function createLinkTypeChecker(linkType) { 55 | var shapes = { 56 | value: typeof linkType === 'undefined' ? 57 | React.PropTypes.any.isRequired : 58 | linkType.isRequired, 59 | requestChange: React.PropTypes.func.isRequired, 60 | }; 61 | return React.PropTypes.shape(shapes); 62 | } 63 | 64 | ReactLink.PropTypes = { 65 | link: createLinkTypeChecker, 66 | }; 67 | 68 | module.exports = ReactLink; 69 | -------------------------------------------------------------------------------- /addons/ReactStateSetters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactStateSetters 10 | */ 11 | 12 | 'use strict'; 13 | 14 | var ReactStateSetters = { 15 | /** 16 | * Returns a function that calls the provided function, and uses the result 17 | * of that to set the component's state. 18 | * 19 | * @param {ReactCompositeComponent} component 20 | * @param {function} funcReturningState Returned callback uses this to 21 | * determine how to update state. 22 | * @return {function} callback that when invoked uses funcReturningState to 23 | * determined the object literal to setState. 24 | */ 25 | createStateSetter: function(component, funcReturningState) { 26 | return function(a, b, c, d, e, f) { 27 | var partialState = funcReturningState.call(component, a, b, c, d, e, f); 28 | if (partialState) { 29 | component.setState(partialState); 30 | } 31 | }; 32 | }, 33 | 34 | /** 35 | * Returns a single-argument callback that can be used to update a single 36 | * key in the component's state. 37 | * 38 | * Note: this is memoized function, which makes it inexpensive to call. 39 | * 40 | * @param {ReactCompositeComponent} component 41 | * @param {string} key The key in the state that you should update. 42 | * @return {function} callback of 1 argument which calls setState() with 43 | * the provided keyName and callback argument. 44 | */ 45 | createStateKeySetter: function(component, key) { 46 | // Memoize the setters. 47 | var cache = component.__keySetters || (component.__keySetters = {}); 48 | return cache[key] || (cache[key] = createStateKeySetter(component, key)); 49 | }, 50 | }; 51 | 52 | function createStateKeySetter(component, key) { 53 | // Partial state is allocated outside of the function closure so it can be 54 | // reused with every call, avoiding memory allocation when this function 55 | // is called. 56 | var partialState = {}; 57 | return function stateKeySetter(value) { 58 | partialState[key] = value; 59 | component.setState(partialState); 60 | }; 61 | } 62 | 63 | ReactStateSetters.Mixin = { 64 | /** 65 | * Returns a function that calls the provided function, and uses the result 66 | * of that to set the component's state. 67 | * 68 | * For example, these statements are equivalent: 69 | * 70 | * this.setState({x: 1}); 71 | * this.createStateSetter(function(xValue) { 72 | * return {x: xValue}; 73 | * })(1); 74 | * 75 | * @param {function} funcReturningState Returned callback uses this to 76 | * determine how to update state. 77 | * @return {function} callback that when invoked uses funcReturningState to 78 | * determined the object literal to setState. 79 | */ 80 | createStateSetter: function(funcReturningState) { 81 | return ReactStateSetters.createStateSetter(this, funcReturningState); 82 | }, 83 | 84 | /** 85 | * Returns a single-argument callback that can be used to update a single 86 | * key in the component's state. 87 | * 88 | * For example, these statements are equivalent: 89 | * 90 | * this.setState({x: 1}); 91 | * this.createStateKeySetter('x')(1); 92 | * 93 | * Note: this is memoized function, which makes it inexpensive to call. 94 | * 95 | * @param {string} key The key in the state that you should update. 96 | * @return {function} callback of 1 argument which calls setState() with 97 | * the provided keyName and callback argument. 98 | */ 99 | createStateKeySetter: function(key) { 100 | return ReactStateSetters.createStateKeySetter(this, key); 101 | }, 102 | }; 103 | 104 | module.exports = ReactStateSetters; 105 | -------------------------------------------------------------------------------- /addons/ReactTransitionChildMapping.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @typechecks static-only 10 | * @providesModule ReactTransitionChildMapping 11 | */ 12 | 13 | 'use strict'; 14 | 15 | var flattenChildren = require('./utils/flattenChildren') 16 | 17 | var ReactTransitionChildMapping = { 18 | /** 19 | * Given `this.props.children`, return an object mapping key to child. Just 20 | * simple syntactic sugar around flattenChildren(). 21 | * 22 | * @param {*} children `this.props.children` 23 | * @return {object} Mapping of key to child 24 | */ 25 | getChildMapping: function(children) { 26 | if (!children) { 27 | return children; 28 | } 29 | return flattenChildren(children); 30 | }, 31 | 32 | /** 33 | * When you're adding or removing children some may be added or removed in the 34 | * same render pass. We want to show *both* since we want to simultaneously 35 | * animate elements in and out. This function takes a previous set of keys 36 | * and a new set of keys and merges them with its best guess of the correct 37 | * ordering. In the future we may expose some of the utilities in 38 | * ReactMultiChild to make this easy, but for now React itself does not 39 | * directly have this concept of the union of prevChildren and nextChildren 40 | * so we implement it here. 41 | * 42 | * @param {object} prev prev children as returned from 43 | * `ReactTransitionChildMapping.getChildMapping()`. 44 | * @param {object} next next children as returned from 45 | * `ReactTransitionChildMapping.getChildMapping()`. 46 | * @return {object} a key set that contains all keys in `prev` and all keys 47 | * in `next` in a reasonable order. 48 | */ 49 | mergeChildMappings: function(prev, next) { 50 | prev = prev || {}; 51 | next = next || {}; 52 | 53 | function getValueForKey(key) { 54 | if (next.hasOwnProperty(key)) { 55 | return next[key]; 56 | } else { 57 | return prev[key]; 58 | } 59 | } 60 | 61 | // For each key of `next`, the list of keys to insert before that key in 62 | // the combined list 63 | var nextKeysPending = {}; 64 | 65 | var pendingKeys = []; 66 | for (var prevKey in prev) { 67 | if (next.hasOwnProperty(prevKey)) { 68 | if (pendingKeys.length) { 69 | nextKeysPending[prevKey] = pendingKeys; 70 | pendingKeys = []; 71 | } 72 | } else { 73 | pendingKeys.push(prevKey); 74 | } 75 | } 76 | 77 | var i; 78 | var childMapping = {}; 79 | for (var nextKey in next) { 80 | if (nextKeysPending.hasOwnProperty(nextKey)) { 81 | for (i = 0; i < nextKeysPending[nextKey].length; i++) { 82 | var pendingNextKey = nextKeysPending[nextKey][i]; 83 | childMapping[nextKeysPending[nextKey][i]] = getValueForKey( 84 | pendingNextKey 85 | ); 86 | } 87 | } 88 | childMapping[nextKey] = getValueForKey(nextKey); 89 | } 90 | 91 | // Finally, add the keys which didn't appear before any key in `next` 92 | for (i = 0; i < pendingKeys.length; i++) { 93 | childMapping[pendingKeys[i]] = getValueForKey(pendingKeys[i]); 94 | } 95 | 96 | return childMapping; 97 | }, 98 | }; 99 | 100 | module.exports = ReactTransitionChildMapping; 101 | -------------------------------------------------------------------------------- /addons/ReactTransitionEvents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactTransitionEvents 10 | */ 11 | 12 | 'use strict'; 13 | 14 | /** 15 | * EVENT_NAME_MAP is used to determine which event fired when a 16 | * transition/animation ends, based on the style property used to 17 | * define that event. 18 | */ 19 | var EVENT_NAME_MAP = { 20 | transitionend: { 21 | 'transition': 'transitionend', 22 | 'WebkitTransition': 'webkitTransitionEnd', 23 | 'MozTransition': 'mozTransitionEnd', 24 | 'OTransition': 'oTransitionEnd', 25 | 'msTransition': 'MSTransitionEnd', 26 | }, 27 | 28 | animationend: { 29 | 'animation': 'animationend', 30 | 'WebkitAnimation': 'webkitAnimationEnd', 31 | 'MozAnimation': 'mozAnimationEnd', 32 | 'OAnimation': 'oAnimationEnd', 33 | 'msAnimation': 'MSAnimationEnd', 34 | }, 35 | }; 36 | 37 | var endEvents = []; 38 | 39 | function detectEvents() { 40 | var testEl = document.createElement('div'); 41 | var style = testEl.style; 42 | 43 | // On some platforms, in particular some releases of Android 4.x, 44 | // the un-prefixed "animation" and "transition" properties are defined on the 45 | // style object but the events that fire will still be prefixed, so we need 46 | // to check if the un-prefixed events are useable, and if not remove them 47 | // from the map 48 | if (!('AnimationEvent' in window)) { 49 | delete EVENT_NAME_MAP.animationend.animation; 50 | } 51 | 52 | if (!('TransitionEvent' in window)) { 53 | delete EVENT_NAME_MAP.transitionend.transition; 54 | } 55 | 56 | for (var baseEventName in EVENT_NAME_MAP) { 57 | var baseEvents = EVENT_NAME_MAP[baseEventName]; 58 | for (var styleName in baseEvents) { 59 | if (styleName in style) { 60 | endEvents.push(baseEvents[styleName]); 61 | break; 62 | } 63 | } 64 | } 65 | } 66 | 67 | detectEvents(); 68 | 69 | // We use the raw {add|remove}EventListener() call because EventListener 70 | // does not know how to remove event listeners and we really should 71 | // clean up. Also, these events are not triggered in older browsers 72 | // so we should be A-OK here. 73 | 74 | function addEventListener(node, eventName, eventListener) { 75 | node.addEventListener(eventName, eventListener, false); 76 | } 77 | 78 | function removeEventListener(node, eventName, eventListener) { 79 | node.removeEventListener(eventName, eventListener, false); 80 | } 81 | 82 | var ReactTransitionEvents = { 83 | addEndEventListener: function(node, eventListener) { 84 | if (endEvents.length === 0) { 85 | // If CSS transitions are not supported, trigger an "end animation" 86 | // event immediately. 87 | window.setTimeout(eventListener, 0); 88 | return; 89 | } 90 | endEvents.forEach(function(endEvent) { 91 | addEventListener(node, endEvent, eventListener); 92 | }); 93 | }, 94 | 95 | removeEndEventListener: function(node, eventListener) { 96 | if (endEvents.length === 0) { 97 | return; 98 | } 99 | endEvents.forEach(function(endEvent) { 100 | removeEventListener(node, endEvent, eventListener); 101 | }); 102 | }, 103 | }; 104 | 105 | module.exports = ReactTransitionEvents; 106 | -------------------------------------------------------------------------------- /addons/ReactTransitionGroup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactTransitionGroup 10 | */ 11 | 12 | var React = require('../dist/react-lite.common') 13 | var ReactTransitionChildMapping = require('./ReactTransitionChildMapping') 14 | var emptyFunction = require('./utils/emptyFunction') 15 | var assign = Object.assign 16 | var ReactTransitionGroup = React.createClass({ 17 | displayName: 'ReactTransitionGroup', 18 | 19 | propTypes: { 20 | component: React.PropTypes.any, 21 | childFactory: React.PropTypes.func, 22 | }, 23 | 24 | getDefaultProps: function() { 25 | return { 26 | component: 'span', 27 | childFactory: emptyFunction.thatReturnsArgument, 28 | }; 29 | }, 30 | 31 | getInitialState: function() { 32 | return { 33 | children: ReactTransitionChildMapping.getChildMapping(this.props.children), 34 | }; 35 | }, 36 | 37 | componentWillMount: function() { 38 | this.currentlyTransitioningKeys = {}; 39 | this.keysToEnter = []; 40 | this.keysToLeave = []; 41 | }, 42 | 43 | componentDidMount: function() { 44 | var initialChildMapping = this.state.children; 45 | for (var key in initialChildMapping) { 46 | if (initialChildMapping[key]) { 47 | this.performAppear(key); 48 | } 49 | } 50 | }, 51 | 52 | componentWillReceiveProps: function(nextProps) { 53 | var nextChildMapping = ReactTransitionChildMapping.getChildMapping( 54 | nextProps.children 55 | ); 56 | var prevChildMapping = this.state.children; 57 | 58 | this.setState({ 59 | children: ReactTransitionChildMapping.mergeChildMappings( 60 | prevChildMapping, 61 | nextChildMapping 62 | ), 63 | }); 64 | 65 | var key; 66 | 67 | for (key in nextChildMapping) { 68 | var hasPrev = prevChildMapping && prevChildMapping.hasOwnProperty(key); 69 | if (nextChildMapping[key] && !hasPrev && 70 | !this.currentlyTransitioningKeys[key]) { 71 | this.keysToEnter.push(key); 72 | } 73 | } 74 | 75 | for (key in prevChildMapping) { 76 | var hasNext = nextChildMapping && nextChildMapping.hasOwnProperty(key); 77 | if (prevChildMapping[key] && !hasNext && 78 | !this.currentlyTransitioningKeys[key]) { 79 | this.keysToLeave.push(key); 80 | } 81 | } 82 | 83 | // If we want to someday check for reordering, we could do it here. 84 | }, 85 | 86 | componentDidUpdate: function() { 87 | var keysToEnter = this.keysToEnter; 88 | this.keysToEnter = []; 89 | keysToEnter.forEach(this.performEnter); 90 | 91 | var keysToLeave = this.keysToLeave; 92 | this.keysToLeave = []; 93 | keysToLeave.forEach(this.performLeave); 94 | }, 95 | 96 | performAppear: function(key) { 97 | this.currentlyTransitioningKeys[key] = true; 98 | 99 | var component = this.refs[key]; 100 | 101 | if (component.componentWillAppear) { 102 | component.componentWillAppear( 103 | this._handleDoneAppearing.bind(this, key) 104 | ); 105 | } else { 106 | this._handleDoneAppearing(key); 107 | } 108 | }, 109 | 110 | _handleDoneAppearing: function(key) { 111 | var component = this.refs[key]; 112 | if (component.componentDidAppear) { 113 | component.componentDidAppear(); 114 | } 115 | 116 | delete this.currentlyTransitioningKeys[key]; 117 | 118 | var currentChildMapping = ReactTransitionChildMapping.getChildMapping( 119 | this.props.children 120 | ); 121 | 122 | if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) { 123 | // This was removed before it had fully appeared. Remove it. 124 | this.performLeave(key); 125 | } 126 | }, 127 | 128 | performEnter: function(key) { 129 | this.currentlyTransitioningKeys[key] = true; 130 | 131 | var component = this.refs[key]; 132 | 133 | if (component.componentWillEnter) { 134 | component.componentWillEnter( 135 | this._handleDoneEntering.bind(this, key) 136 | ); 137 | } else { 138 | this._handleDoneEntering(key); 139 | } 140 | }, 141 | 142 | _handleDoneEntering: function(key) { 143 | var component = this.refs[key]; 144 | if (component.componentDidEnter) { 145 | component.componentDidEnter(); 146 | } 147 | 148 | delete this.currentlyTransitioningKeys[key]; 149 | 150 | var currentChildMapping = ReactTransitionChildMapping.getChildMapping( 151 | this.props.children 152 | ); 153 | 154 | if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) { 155 | // This was removed before it had fully entered. Remove it. 156 | this.performLeave(key); 157 | } 158 | }, 159 | 160 | performLeave: function(key) { 161 | this.currentlyTransitioningKeys[key] = true; 162 | 163 | var component = this.refs[key]; 164 | if (component.componentWillLeave) { 165 | component.componentWillLeave(this._handleDoneLeaving.bind(this, key)); 166 | } else { 167 | // Note that this is somewhat dangerous b/c it calls setState() 168 | // again, effectively mutating the component before all the work 169 | // is done. 170 | this._handleDoneLeaving(key); 171 | } 172 | }, 173 | 174 | _handleDoneLeaving: function(key) { 175 | var component = this.refs[key]; 176 | 177 | if (component.componentDidLeave) { 178 | component.componentDidLeave(); 179 | } 180 | 181 | delete this.currentlyTransitioningKeys[key]; 182 | 183 | var currentChildMapping = ReactTransitionChildMapping.getChildMapping( 184 | this.props.children 185 | ); 186 | 187 | if (currentChildMapping && currentChildMapping.hasOwnProperty(key)) { 188 | // This entered again before it fully left. Add it again. 189 | this.performEnter(key); 190 | } else { 191 | this.setState(function(state) { 192 | var newChildren = assign({}, state.children); 193 | delete newChildren[key]; 194 | return {children: newChildren}; 195 | }); 196 | } 197 | }, 198 | 199 | render: function() { 200 | // TODO: we could get rid of the need for the wrapper node 201 | // by cloning a single child 202 | var childrenToRender = []; 203 | for (var key in this.state.children) { 204 | var child = this.state.children[key]; 205 | if (child) { 206 | // You may need to apply reactive updates to a child as it is leaving. 207 | // The normal React way to do it won't work since the child will have 208 | // already been removed. In case you need this behavior you can provide 209 | // a childFactory function to wrap every child, even the ones that are 210 | // leaving. 211 | childrenToRender.push(React.cloneElement( 212 | this.props.childFactory(child), 213 | {ref: key, key: key} 214 | )); 215 | } 216 | } 217 | return React.createElement( 218 | this.props.component, 219 | this.props, 220 | childrenToRender 221 | ); 222 | }, 223 | }); 224 | 225 | module.exports = ReactTransitionGroup; 226 | -------------------------------------------------------------------------------- /addons/ReactWithAddons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactWithAddons 10 | */ 11 | 12 | /** 13 | * This module exists purely in the open source project, and is meant as a way 14 | * to create a separate standalone build of React. This build has "addons", or 15 | * functionality we've built and think might be useful but doesn't have a good 16 | * place to live inside React core. 17 | */ 18 | var React = require('../dist/react-lite.common') 19 | var LinkedStateMixin = require('./LinkedStateMixin') 20 | var ReactComponentWithPureRenderMixin = require('./ReactComponentWithPureRenderMixin') 21 | var ReactCSSTransitionGroup = require('./ReactCSSTransitionGroup') 22 | var ReactFragment = require('./ReactFragment') 23 | var ReactTransitionGroup = require('./ReactTransitionGroup') 24 | var ReactUpdates = React 25 | var shallowCompare = require('./shallowCompare') 26 | var update = require('./update') 27 | 28 | var warning = function() {} 29 | 30 | var cloneWithProps = React.cloneElement 31 | 32 | var warnedAboutBatchedUpdates = false; 33 | 34 | React.addons = { 35 | CSSTransitionGroup: ReactCSSTransitionGroup, 36 | LinkedStateMixin: LinkedStateMixin, 37 | PureRenderMixin: ReactComponentWithPureRenderMixin, 38 | TransitionGroup: ReactTransitionGroup, 39 | batchedUpdates: function() { 40 | return ReactUpdates.batchedUpdates.apply(this, arguments); 41 | }, 42 | cloneWithProps: cloneWithProps, 43 | createFragment: ReactFragment.create, 44 | shallowCompare: shallowCompare, 45 | update: update, 46 | }; 47 | 48 | module.exports = React; 49 | -------------------------------------------------------------------------------- /addons/react-tap-event-plugin.js: -------------------------------------------------------------------------------- 1 | var Fastclick = require('fastclick') 2 | module.exports = function injectTapEventPlugin() { 3 | var supportTouch = 'ontouchstart' in document 4 | if (supportTouch) { 5 | Fastclick.attach(document.body) 6 | } 7 | } -------------------------------------------------------------------------------- /addons/renderSubtreeIntoContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule renderSubtreeIntoContainer 10 | */ 11 | 12 | 'use strict'; 13 | var React = require('../dist/react-lite.common') 14 | var unstable_renderSubtreeIntoContainer = React.unstable_renderSubtreeIntoContainer 15 | module.exports = unstable_renderSubtreeIntoContainer -------------------------------------------------------------------------------- /addons/shallowCompare.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule shallowCompare 10 | */ 11 | var shallowEqual = require('./utils/shallowEqual') 12 | 13 | /** 14 | * Does a shallow comparison for props and state. 15 | * See ReactComponentWithPureRenderMixin 16 | */ 17 | function shallowCompare(instance, nextProps, nextState) { 18 | return ( 19 | !shallowEqual(instance.props, nextProps) || 20 | !shallowEqual(instance.state, nextState) 21 | ); 22 | } 23 | 24 | module.exports = shallowCompare; 25 | -------------------------------------------------------------------------------- /addons/update.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule update 10 | */ 11 | 12 | /* global hasOwnProperty:true */ 13 | 14 | var React = require('../dist/react-lite.common') 15 | var assign = Object.assign 16 | var keyOf = require('./utils/keyOf') 17 | var invariant = function() {} 18 | var hasOwnProperty = {}.hasOwnProperty; 19 | 20 | function shallowCopy(x) { 21 | if (Array.isArray(x)) { 22 | return x.concat(); 23 | } else if (x && typeof x === 'object') { 24 | return assign(new x.constructor(), x); 25 | } else { 26 | return x; 27 | } 28 | } 29 | 30 | var COMMAND_PUSH = keyOf({$push: null}); 31 | var COMMAND_UNSHIFT = keyOf({$unshift: null}); 32 | var COMMAND_SPLICE = keyOf({$splice: null}); 33 | var COMMAND_SET = keyOf({$set: null}); 34 | var COMMAND_MERGE = keyOf({$merge: null}); 35 | var COMMAND_APPLY = keyOf({$apply: null}); 36 | 37 | var ALL_COMMANDS_LIST = [ 38 | COMMAND_PUSH, 39 | COMMAND_UNSHIFT, 40 | COMMAND_SPLICE, 41 | COMMAND_SET, 42 | COMMAND_MERGE, 43 | COMMAND_APPLY, 44 | ]; 45 | 46 | var ALL_COMMANDS_SET = {}; 47 | 48 | ALL_COMMANDS_LIST.forEach(function(command) { 49 | ALL_COMMANDS_SET[command] = true; 50 | }); 51 | 52 | function invariantArrayCase(value, spec, command) { 53 | invariant( 54 | Array.isArray(value), 55 | 'update(): expected target of %s to be an array; got %s.', 56 | command, 57 | value 58 | ); 59 | var specValue = spec[command]; 60 | invariant( 61 | Array.isArray(specValue), 62 | 'update(): expected spec of %s to be an array; got %s. ' + 63 | 'Did you forget to wrap your parameter in an array?', 64 | command, 65 | specValue 66 | ); 67 | } 68 | 69 | function update(value, spec) { 70 | invariant( 71 | typeof spec === 'object', 72 | 'update(): You provided a key path to update() that did not contain one ' + 73 | 'of %s. Did you forget to include {%s: ...}?', 74 | ALL_COMMANDS_LIST.join(', '), 75 | COMMAND_SET 76 | ); 77 | 78 | if (hasOwnProperty.call(spec, COMMAND_SET)) { 79 | invariant( 80 | Object.keys(spec).length === 1, 81 | 'Cannot have more than one key in an object with %s', 82 | COMMAND_SET 83 | ); 84 | 85 | return spec[COMMAND_SET]; 86 | } 87 | 88 | var nextValue = shallowCopy(value); 89 | 90 | if (hasOwnProperty.call(spec, COMMAND_MERGE)) { 91 | var mergeObj = spec[COMMAND_MERGE]; 92 | invariant( 93 | mergeObj && typeof mergeObj === 'object', 94 | 'update(): %s expects a spec of type \'object\'; got %s', 95 | COMMAND_MERGE, 96 | mergeObj 97 | ); 98 | invariant( 99 | nextValue && typeof nextValue === 'object', 100 | 'update(): %s expects a target of type \'object\'; got %s', 101 | COMMAND_MERGE, 102 | nextValue 103 | ); 104 | assign(nextValue, spec[COMMAND_MERGE]); 105 | } 106 | 107 | if (hasOwnProperty.call(spec, COMMAND_PUSH)) { 108 | invariantArrayCase(value, spec, COMMAND_PUSH); 109 | spec[COMMAND_PUSH].forEach(function(item) { 110 | nextValue.push(item); 111 | }); 112 | } 113 | 114 | if (hasOwnProperty.call(spec, COMMAND_UNSHIFT)) { 115 | invariantArrayCase(value, spec, COMMAND_UNSHIFT); 116 | spec[COMMAND_UNSHIFT].forEach(function(item) { 117 | nextValue.unshift(item); 118 | }); 119 | } 120 | 121 | if (hasOwnProperty.call(spec, COMMAND_SPLICE)) { 122 | invariant( 123 | Array.isArray(value), 124 | 'Expected %s target to be an array; got %s', 125 | COMMAND_SPLICE, 126 | value 127 | ); 128 | invariant( 129 | Array.isArray(spec[COMMAND_SPLICE]), 130 | 'update(): expected spec of %s to be an array of arrays; got %s. ' + 131 | 'Did you forget to wrap your parameters in an array?', 132 | COMMAND_SPLICE, 133 | spec[COMMAND_SPLICE] 134 | ); 135 | spec[COMMAND_SPLICE].forEach(function(args) { 136 | invariant( 137 | Array.isArray(args), 138 | 'update(): expected spec of %s to be an array of arrays; got %s. ' + 139 | 'Did you forget to wrap your parameters in an array?', 140 | COMMAND_SPLICE, 141 | spec[COMMAND_SPLICE] 142 | ); 143 | nextValue.splice.apply(nextValue, args); 144 | }); 145 | } 146 | 147 | if (hasOwnProperty.call(spec, COMMAND_APPLY)) { 148 | invariant( 149 | typeof spec[COMMAND_APPLY] === 'function', 150 | 'update(): expected spec of %s to be a function; got %s.', 151 | COMMAND_APPLY, 152 | spec[COMMAND_APPLY] 153 | ); 154 | nextValue = spec[COMMAND_APPLY](nextValue); 155 | } 156 | 157 | for (var k in spec) { 158 | if (!(ALL_COMMANDS_SET.hasOwnProperty(k) && ALL_COMMANDS_SET[k])) { 159 | nextValue[k] = update(value[k], spec[k]); 160 | } 161 | } 162 | 163 | return nextValue; 164 | } 165 | 166 | module.exports = update; 167 | -------------------------------------------------------------------------------- /addons/utils/CSSCore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule CSSCore 10 | * @typechecks 11 | */ 12 | 13 | 'use strict'; 14 | 15 | var invariant = function() {} 16 | 17 | /** 18 | * The CSSCore module specifies the API (and implements most of the methods) 19 | * that should be used when dealing with the display of elements (via their 20 | * CSS classes and visibility on screen. It is an API focused on mutating the 21 | * display and not reading it as no logical state should be encoded in the 22 | * display of elements. 23 | */ 24 | 25 | var CSSCore = { 26 | 27 | /** 28 | * Adds the class passed in to the element if it doesn't already have it. 29 | * 30 | * @param {DOMElement} element the element to set the class on 31 | * @param {string} className the CSS className 32 | * @return {DOMElement} the element passed in 33 | */ 34 | addClass: function (element, className) { 35 | !!/\s/.test(className) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'CSSCore.addClass takes only a single class name. "%s" contains ' + 'multiple classes.', className) : invariant(false) : undefined; 36 | 37 | if (className) { 38 | if (element.classList) { 39 | element.classList.add(className); 40 | } else if (!CSSCore.hasClass(element, className)) { 41 | element.className = element.className + ' ' + className; 42 | } 43 | } 44 | return element; 45 | }, 46 | 47 | /** 48 | * Removes the class passed in from the element 49 | * 50 | * @param {DOMElement} element the element to set the class on 51 | * @param {string} className the CSS className 52 | * @return {DOMElement} the element passed in 53 | */ 54 | removeClass: function (element, className) { 55 | !!/\s/.test(className) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'CSSCore.removeClass takes only a single class name. "%s" contains ' + 'multiple classes.', className) : invariant(false) : undefined; 56 | 57 | if (className) { 58 | if (element.classList) { 59 | element.classList.remove(className); 60 | } else if (CSSCore.hasClass(element, className)) { 61 | element.className = element.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), '$1').replace(/\s+/g, ' ') // multiple spaces to one 62 | .replace(/^\s*|\s*$/g, ''); // trim the ends 63 | } 64 | } 65 | return element; 66 | }, 67 | 68 | /** 69 | * Helper to add or remove a class from an element based on a condition. 70 | * 71 | * @param {DOMElement} element the element to set the class on 72 | * @param {string} className the CSS className 73 | * @param {*} bool condition to whether to add or remove the class 74 | * @return {DOMElement} the element passed in 75 | */ 76 | conditionClass: function (element, className, bool) { 77 | return (bool ? CSSCore.addClass : CSSCore.removeClass)(element, className); 78 | }, 79 | 80 | /** 81 | * Tests whether the element has the class specified. 82 | * 83 | * @param {DOMNode|DOMWindow} element the element to set the class on 84 | * @param {string} className the CSS className 85 | * @return {boolean} true if the element has the class, false if not 86 | */ 87 | hasClass: function (element, className) { 88 | !!/\s/.test(className) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'CSS.hasClass takes only a single class name.') : invariant(false) : undefined; 89 | if (element.classList) { 90 | return !!className && element.classList.contains(className); 91 | } 92 | return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1; 93 | } 94 | 95 | }; 96 | 97 | module.exports = CSSCore; -------------------------------------------------------------------------------- /addons/utils/PooledClass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule PooledClass 10 | */ 11 | 12 | 'use strict'; 13 | 14 | var invariant = function() {} 15 | 16 | /** 17 | * Static poolers. Several custom versions for each potential number of 18 | * arguments. A completely generic pooler is easy to implement, but would 19 | * require accessing the `arguments` object. In each of these, `this` refers to 20 | * the Class itself, not an instance. If any others are needed, simply add them 21 | * here, or in their own files. 22 | */ 23 | var oneArgumentPooler = function(copyFieldsFrom) { 24 | var Klass = this; 25 | if (Klass.instancePool.length) { 26 | var instance = Klass.instancePool.pop(); 27 | Klass.call(instance, copyFieldsFrom); 28 | return instance; 29 | } else { 30 | return new Klass(copyFieldsFrom); 31 | } 32 | }; 33 | 34 | var twoArgumentPooler = function(a1, a2) { 35 | var Klass = this; 36 | if (Klass.instancePool.length) { 37 | var instance = Klass.instancePool.pop(); 38 | Klass.call(instance, a1, a2); 39 | return instance; 40 | } else { 41 | return new Klass(a1, a2); 42 | } 43 | }; 44 | 45 | var threeArgumentPooler = function(a1, a2, a3) { 46 | var Klass = this; 47 | if (Klass.instancePool.length) { 48 | var instance = Klass.instancePool.pop(); 49 | Klass.call(instance, a1, a2, a3); 50 | return instance; 51 | } else { 52 | return new Klass(a1, a2, a3); 53 | } 54 | }; 55 | 56 | var fourArgumentPooler = function(a1, a2, a3, a4) { 57 | var Klass = this; 58 | if (Klass.instancePool.length) { 59 | var instance = Klass.instancePool.pop(); 60 | Klass.call(instance, a1, a2, a3, a4); 61 | return instance; 62 | } else { 63 | return new Klass(a1, a2, a3, a4); 64 | } 65 | }; 66 | 67 | var fiveArgumentPooler = function(a1, a2, a3, a4, a5) { 68 | var Klass = this; 69 | if (Klass.instancePool.length) { 70 | var instance = Klass.instancePool.pop(); 71 | Klass.call(instance, a1, a2, a3, a4, a5); 72 | return instance; 73 | } else { 74 | return new Klass(a1, a2, a3, a4, a5); 75 | } 76 | }; 77 | 78 | var standardReleaser = function(instance) { 79 | var Klass = this; 80 | invariant( 81 | instance instanceof Klass, 82 | 'Trying to release an instance into a pool of a different type.' 83 | ); 84 | instance.destructor(); 85 | if (Klass.instancePool.length < Klass.poolSize) { 86 | Klass.instancePool.push(instance); 87 | } 88 | }; 89 | 90 | var DEFAULT_POOL_SIZE = 10; 91 | var DEFAULT_POOLER = oneArgumentPooler; 92 | 93 | /** 94 | * Augments `CopyConstructor` to be a poolable class, augmenting only the class 95 | * itself (statically) not adding any prototypical fields. Any CopyConstructor 96 | * you give this may have a `poolSize` property, and will look for a 97 | * prototypical `destructor` on instances (optional). 98 | * 99 | * @param {Function} CopyConstructor Constructor that can be used to reset. 100 | * @param {Function} pooler Customizable pooler. 101 | */ 102 | var addPoolingTo = function(CopyConstructor, pooler) { 103 | var NewKlass = CopyConstructor; 104 | NewKlass.instancePool = []; 105 | NewKlass.getPooled = pooler || DEFAULT_POOLER; 106 | if (!NewKlass.poolSize) { 107 | NewKlass.poolSize = DEFAULT_POOL_SIZE; 108 | } 109 | NewKlass.release = standardReleaser; 110 | return NewKlass; 111 | }; 112 | 113 | var PooledClass = { 114 | addPoolingTo: addPoolingTo, 115 | oneArgumentPooler: oneArgumentPooler, 116 | twoArgumentPooler: twoArgumentPooler, 117 | threeArgumentPooler: threeArgumentPooler, 118 | fourArgumentPooler: fourArgumentPooler, 119 | fiveArgumentPooler: fiveArgumentPooler, 120 | }; 121 | 122 | module.exports = PooledClass; 123 | -------------------------------------------------------------------------------- /addons/utils/emptyFunction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule emptyFunction 10 | */ 11 | 12 | "use strict"; 13 | 14 | function makeEmptyFunction(arg) { 15 | return function () { 16 | return arg; 17 | }; 18 | } 19 | 20 | /** 21 | * This function accepts and discards inputs; it has no side effects. This is 22 | * primarily useful idiomatically for overridable function endpoints which 23 | * always need to be callable, since JS lacks a null-call idiom ala Cocoa. 24 | */ 25 | function emptyFunction() {} 26 | 27 | emptyFunction.thatReturns = makeEmptyFunction; 28 | emptyFunction.thatReturnsFalse = makeEmptyFunction(false); 29 | emptyFunction.thatReturnsTrue = makeEmptyFunction(true); 30 | emptyFunction.thatReturnsNull = makeEmptyFunction(null); 31 | emptyFunction.thatReturnsThis = function () { 32 | return this; 33 | }; 34 | emptyFunction.thatReturnsArgument = function (arg) { 35 | return arg; 36 | }; 37 | 38 | module.exports = emptyFunction; -------------------------------------------------------------------------------- /addons/utils/escapeTextContentForBrowser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule escapeTextContentForBrowser 10 | */ 11 | 12 | 'use strict'; 13 | 14 | var ESCAPE_LOOKUP = { 15 | '&': '&', 16 | '>': '>', 17 | '<': '<', 18 | '"': '"', 19 | '\'': ''', 20 | }; 21 | 22 | var ESCAPE_REGEX = /[&><"']/g; 23 | 24 | function escaper(match) { 25 | return ESCAPE_LOOKUP[match]; 26 | } 27 | 28 | /** 29 | * Escapes text to prevent scripting attacks. 30 | * 31 | * @param {*} text Text value to escape. 32 | * @return {string} An escaped string. 33 | */ 34 | function escapeTextContentForBrowser(text) { 35 | return ('' + text).replace(ESCAPE_REGEX, escaper); 36 | } 37 | 38 | module.exports = escapeTextContentForBrowser; 39 | -------------------------------------------------------------------------------- /addons/utils/flattenChildren.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule flattenChildren 10 | */ 11 | var traverseAllChildren = require('./traverseAllChildren') 12 | 13 | /** 14 | * @param {function} traverseContext Context passed through traversal. 15 | * @param {?ReactComponent} child React child component. 16 | * @param {!string} name String name of key path to child. 17 | */ 18 | function flattenSingleChildIntoContext(traverseContext, child, name) { 19 | // We found a component instance. 20 | var result = traverseContext; 21 | var keyUnique = (result[name] === undefined); 22 | if (keyUnique && child != null) { 23 | result[name] = child; 24 | } 25 | } 26 | 27 | /** 28 | * Flattens children that are typically specified as `props.children`. Any null 29 | * children will not be included in the resulting object. 30 | * @return {!object} flattened children keyed by name. 31 | */ 32 | function flattenChildren(children) { 33 | if (children == null) { 34 | return children; 35 | } 36 | var result = {}; 37 | traverseAllChildren(children, flattenSingleChildIntoContext, result); 38 | return result; 39 | } 40 | 41 | module.exports = flattenChildren; 42 | -------------------------------------------------------------------------------- /addons/utils/getIteratorFn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule getIteratorFn 10 | * @typechecks static-only 11 | */ 12 | 13 | 'use strict'; 14 | 15 | /* global Symbol */ 16 | var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; 17 | var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec. 18 | 19 | /** 20 | * Returns the iterator method function contained on the iterable object. 21 | * 22 | * Be sure to invoke the function with the iterable as context: 23 | * 24 | * var iteratorFn = getIteratorFn(myIterable); 25 | * if (iteratorFn) { 26 | * var iterator = iteratorFn.call(myIterable); 27 | * ... 28 | * } 29 | * 30 | * @param {?object} maybeIterable 31 | * @return {?function} 32 | */ 33 | function getIteratorFn(maybeIterable) { 34 | var iteratorFn = maybeIterable && ( 35 | (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL]) || 36 | maybeIterable[FAUX_ITERATOR_SYMBOL] 37 | ); 38 | if (typeof iteratorFn === 'function') { 39 | return iteratorFn; 40 | } 41 | } 42 | 43 | module.exports = getIteratorFn; 44 | -------------------------------------------------------------------------------- /addons/utils/keyOf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule keyOf 10 | */ 11 | 12 | /** 13 | * Allows extraction of a minified key. Let's the build system minify keys 14 | * without losing the ability to dynamically use key strings as values 15 | * themselves. Pass in an object with a single key/val pair and it will return 16 | * you the string key of that single record. Suppose you want to grab the 17 | * value for a key 'className' inside of an object. Key/val minification may 18 | * have aliased that key to be 'xa12'. keyOf({className: null}) will return 19 | * 'xa12' in that case. Resolve keys you want to use once at startup time, then 20 | * reuse those resolutions. 21 | */ 22 | "use strict"; 23 | 24 | var keyOf = function (oneKeyObj) { 25 | var key; 26 | for (key in oneKeyObj) { 27 | if (!oneKeyObj.hasOwnProperty(key)) { 28 | continue; 29 | } 30 | return key; 31 | } 32 | return null; 33 | }; 34 | 35 | module.exports = keyOf; -------------------------------------------------------------------------------- /addons/utils/quoteAttributeValueForBrowser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule quoteAttributeValueForBrowser 10 | */ 11 | 12 | var escapeTextContentForBrowser = require('./escapeTextContentForBrowser') 13 | 14 | /** 15 | * Escapes attribute value to prevent scripting attacks. 16 | * 17 | * @param {*} value Value to escape. 18 | * @return {string} An escaped string. 19 | */ 20 | function quoteAttributeValueForBrowser(value) { 21 | return '"' + escapeTextContentForBrowser(value) + '"'; 22 | } 23 | 24 | module.exports = quoteAttributeValueForBrowser; 25 | -------------------------------------------------------------------------------- /addons/utils/shallowEqual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule shallowEqual 10 | * @typechecks 11 | * 12 | */ 13 | 14 | 'use strict'; 15 | 16 | var hasOwnProperty = Object.prototype.hasOwnProperty; 17 | 18 | /** 19 | * Performs equality by iterating through keys on an object and returning false 20 | * when any key has values which are not strictly equal between the arguments. 21 | * Returns true when the values of all keys are strictly equal. 22 | */ 23 | function shallowEqual(objA, objB) { 24 | if (objA === objB) { 25 | return true; 26 | } 27 | 28 | if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { 29 | return false; 30 | } 31 | 32 | var keysA = Object.keys(objA); 33 | var keysB = Object.keys(objB); 34 | 35 | if (keysA.length !== keysB.length) { 36 | return false; 37 | } 38 | 39 | // Test for A's keys different from B. 40 | var bHasOwnProperty = hasOwnProperty.bind(objB); 41 | for (var i = 0; i < keysA.length; i++) { 42 | if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) { 43 | return false; 44 | } 45 | } 46 | 47 | return true; 48 | } 49 | 50 | module.exports = shallowEqual; -------------------------------------------------------------------------------- /addons/utils/traverseAllChildren.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule traverseAllChildren 10 | */ 11 | var React = require('../../dist/react-lite.common') 12 | var isValidElement = React.isValidElement 13 | var getIteratorFn = require('./getIteratorFn') 14 | 15 | var invariant = function() {} 16 | var SEPARATOR = '.'; 17 | var SUBSEPARATOR = ':'; 18 | 19 | /** 20 | * TODO: Test that a single child and an array with one item have the same key 21 | * pattern. 22 | */ 23 | 24 | var userProvidedKeyEscaperLookup = { 25 | '=': '=0', 26 | '.': '=1', 27 | ':': '=2', 28 | }; 29 | 30 | var userProvidedKeyEscapeRegex = /[=.:]/g; 31 | 32 | var didWarnAboutMaps = false; 33 | 34 | function userProvidedKeyEscaper(match) { 35 | return userProvidedKeyEscaperLookup[match]; 36 | } 37 | 38 | /** 39 | * Generate a key string that identifies a component within a set. 40 | * 41 | * @param {*} component A component that could contain a manual key. 42 | * @param {number} index Index that is used if a manual key is not provided. 43 | * @return {string} 44 | */ 45 | function getComponentKey(component, index) { 46 | if (component && component.key != null) { 47 | // Explicit key 48 | return wrapUserProvidedKey(component.key); 49 | } 50 | // Implicit key determined by the index in the set 51 | return index.toString(36); 52 | } 53 | 54 | /** 55 | * Escape a component key so that it is safe to use in a reactid. 56 | * 57 | * @param {*} text Component key to be escaped. 58 | * @return {string} An escaped string. 59 | */ 60 | function escapeUserProvidedKey(text) { 61 | return ('' + text).replace( 62 | userProvidedKeyEscapeRegex, 63 | userProvidedKeyEscaper 64 | ); 65 | } 66 | 67 | /** 68 | * Wrap a `key` value explicitly provided by the user to distinguish it from 69 | * implicitly-generated keys generated by a component's index in its parent. 70 | * 71 | * @param {string} key Value of a user-provided `key` attribute 72 | * @return {string} 73 | */ 74 | function wrapUserProvidedKey(key) { 75 | return '$' + escapeUserProvidedKey(key); 76 | } 77 | 78 | /** 79 | * @param {?*} children Children tree container. 80 | * @param {!string} nameSoFar Name of the key path so far. 81 | * @param {!function} callback Callback to invoke with each child found. 82 | * @param {?*} traverseContext Used to pass information throughout the traversal 83 | * process. 84 | * @return {!number} The number of children in this subtree. 85 | */ 86 | function traverseAllChildrenImpl( 87 | children, 88 | nameSoFar, 89 | callback, 90 | traverseContext 91 | ) { 92 | var type = typeof children; 93 | 94 | if (type === 'undefined' || type === 'boolean') { 95 | // All of the above are perceived as null. 96 | children = null; 97 | } 98 | 99 | if (children === null || 100 | type === 'string' || 101 | type === 'number' || 102 | isValidElement(children)) { 103 | callback( 104 | traverseContext, 105 | children, 106 | // If it's the only child, treat the name as if it was wrapped in an array 107 | // so that it's consistent if the number of children grows. 108 | nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar 109 | ); 110 | return 1; 111 | } 112 | 113 | var child; 114 | var nextName; 115 | var subtreeCount = 0; // Count of children found in the current subtree. 116 | var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR; 117 | 118 | if (Array.isArray(children)) { 119 | for (var i = 0; i < children.length; i++) { 120 | child = children[i]; 121 | nextName = nextNamePrefix + getComponentKey(child, i); 122 | subtreeCount += traverseAllChildrenImpl( 123 | child, 124 | nextName, 125 | callback, 126 | traverseContext 127 | ); 128 | } 129 | } else { 130 | var iteratorFn = getIteratorFn(children); 131 | if (iteratorFn) { 132 | var iterator = iteratorFn.call(children); 133 | var step; 134 | if (iteratorFn !== children.entries) { 135 | var ii = 0; 136 | while (!(step = iterator.next()).done) { 137 | child = step.value; 138 | nextName = nextNamePrefix + getComponentKey(child, ii++); 139 | subtreeCount += traverseAllChildrenImpl( 140 | child, 141 | nextName, 142 | callback, 143 | traverseContext 144 | ); 145 | } 146 | } else { 147 | // Iterator will provide entry [k,v] tuples rather than values. 148 | while (!(step = iterator.next()).done) { 149 | var entry = step.value; 150 | if (entry) { 151 | child = entry[1]; 152 | nextName = ( 153 | nextNamePrefix + 154 | wrapUserProvidedKey(entry[0]) + SUBSEPARATOR + 155 | getComponentKey(child, 0) 156 | ); 157 | subtreeCount += traverseAllChildrenImpl( 158 | child, 159 | nextName, 160 | callback, 161 | traverseContext 162 | ); 163 | } 164 | } 165 | } 166 | } else if (type === 'object') { 167 | var addendum = ''; 168 | var childrenString = String(children); 169 | invariant( 170 | false, 171 | 'Objects are not valid as a React child (found: %s).%s', 172 | childrenString === '[object Object]' ? 173 | 'object with keys {' + Object.keys(children).join(', ') + '}' : 174 | childrenString, 175 | addendum 176 | ); 177 | } 178 | } 179 | 180 | return subtreeCount; 181 | } 182 | 183 | /** 184 | * Traverses children that are typically specified as `props.children`, but 185 | * might also be specified through attributes: 186 | * 187 | * - `traverseAllChildren(this.props.children, ...)` 188 | * - `traverseAllChildren(this.props.leftPanelChildren, ...)` 189 | * 190 | * The `traverseContext` is an optional argument that is passed through the 191 | * entire traversal. It can be used to store accumulations or anything else that 192 | * the callback might find relevant. 193 | * 194 | * @param {?*} children Children tree object. 195 | * @param {!function} callback To invoke upon traversing each child. 196 | * @param {?*} traverseContext Context for traversal. 197 | * @return {!number} The number of children in this subtree. 198 | */ 199 | function traverseAllChildren(children, callback, traverseContext) { 200 | if (children == null) { 201 | return 0; 202 | } 203 | 204 | return traverseAllChildrenImpl(children, '', callback, traverseContext); 205 | } 206 | 207 | module.exports = traverseAllChildren; 208 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | // copy from Vue 2 | var fs = require('fs') 3 | var zlib = require('zlib') 4 | var rollup = require('rollup') 5 | var uglify = require('uglify-js') 6 | var babel = require('rollup-plugin-babel') 7 | var replace = require('rollup-plugin-replace') 8 | var version = process.env.VERSION || require('./package.json').version 9 | 10 | var banner = 11 | '/*!\n' + 12 | ' * react-lite.js v' + version + '\n' + 13 | ' * (c) ' + new Date().getFullYear() + ' Jade Gu\n' + 14 | ' * Released under the MIT License.\n' + 15 | ' */' 16 | 17 | runRollupTask('./src/index', 'react-lite') 18 | // runRollupTask('./addons/ReactWithAddons', 'react-lite-with-addons') 19 | 20 | function runRollupTask(entry, filename) { 21 | 22 | // CommonJS build. 23 | // this is used as the "main" field in package.json 24 | // and used by bundlers like Webpack and Browserify. 25 | rollup.rollup({ 26 | entry: entry, 27 | plugins: [ 28 | babel({ 29 | loose: 'all' 30 | }) 31 | ] 32 | }) 33 | .then(function(bundle) { 34 | return write('dist/' + filename + '.common.js', bundle.generate({ 35 | format: 'cjs', 36 | banner: banner 37 | }).code) 38 | }) 39 | // Standalone Dev Build 40 | .then(function() { 41 | return rollup.rollup({ 42 | entry: entry, 43 | plugins: [ 44 | replace({ 45 | 'process.env.NODE_ENV': "'development'" 46 | }), 47 | babel({ 48 | loose: 'all' 49 | }) 50 | ] 51 | }) 52 | .then(function(bundle) { 53 | return write('dist/' + filename + '.js', bundle.generate({ 54 | format: 'umd', 55 | banner: banner, 56 | moduleName: 'React' 57 | }).code) 58 | }) 59 | }) 60 | .then(function() { 61 | // Standalone Production Build 62 | return rollup.rollup({ 63 | entry: entry, 64 | plugins: [ 65 | replace({ 66 | 'process.env.NODE_ENV': "'production'" 67 | }), 68 | babel({ 69 | loose: 'all' 70 | }) 71 | ] 72 | }) 73 | .then(function(bundle) { 74 | var code = bundle.generate({ 75 | format: 'umd', 76 | moduleName: 'React' 77 | }).code 78 | var minified = banner + '\n' + uglify.minify(code, { 79 | fromString: true 80 | }).code 81 | return write('dist/' + filename + '.min.js', minified) 82 | }) 83 | .then(zip) 84 | }) 85 | .catch(logError) 86 | 87 | function zip() { 88 | return new Promise(function(resolve, reject) { 89 | fs.readFile('dist/' + filename + '.min.js', function(err, buf) { 90 | if (err) return reject(err) 91 | zlib.gzip(buf, function(err, buf) { 92 | if (err) return reject(err) 93 | write('dist/' + filename + '.min.js.gz', buf).then(resolve) 94 | }) 95 | }) 96 | }) 97 | } 98 | } 99 | 100 | function write(dest, code) { 101 | return new Promise(function(resolve, reject) { 102 | fs.writeFile(dest, code, function(err) { 103 | if (err) return reject(err) 104 | console.log(blue(dest) + ' ' + getSize(code)) 105 | resolve() 106 | }) 107 | }) 108 | } 109 | 110 | function getSize(code) { 111 | return (code.length / 1024).toFixed(2) + 'kb' 112 | } 113 | 114 | function logError(e) { 115 | console.log(e) 116 | } 117 | 118 | function blue(str) { 119 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m' 120 | } 121 | -------------------------------------------------------------------------------- /dist/react-lite.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifier129/react-lite/b7586ae247615f2d4c4373f206e6c284d7931f81/dist/react-lite.min.js.gz -------------------------------------------------------------------------------- /examples/js-repaint-perf/ENV.js: -------------------------------------------------------------------------------- 1 | var ENV = ENV || (function() { 2 | 3 | var _base; 4 | 5 | (_base = String.prototype).lpad || (_base.lpad = function(padding, toLength) { 6 | return padding.repeat((toLength - this.length) / padding.length).concat(this); 7 | }); 8 | 9 | function formatElapsed(value) { 10 | str = parseFloat(value).toFixed(2); 11 | if (value > 60) { 12 | minutes = Math.floor(value / 60); 13 | comps = (value % 60).toFixed(2).split('.'); 14 | seconds = comps[0].lpad('0', 2); 15 | ms = comps[1]; 16 | str = minutes + ":" + seconds + "." + ms; 17 | } 18 | return str; 19 | } 20 | 21 | function getElapsedClassName(elapsed) { 22 | var className = 'Query elapsed'; 23 | if (elapsed >= 10.0) { 24 | className += ' warn_long'; 25 | } 26 | else if (elapsed >= 1.0) { 27 | className += ' warn'; 28 | } 29 | else { 30 | className += ' short'; 31 | } 32 | return className; 33 | } 34 | 35 | var lastGeneratedDatabases = []; 36 | 37 | function getData() { 38 | // generate some dummy data 39 | data = { 40 | start_at: new Date().getTime() / 1000, 41 | databases: {} 42 | }; 43 | 44 | for (var i = 1; i <= ENV.rows; i++) { 45 | data.databases["cluster" + i] = { 46 | queries: [] 47 | }; 48 | 49 | data.databases["cluster" + i + "slave"] = { 50 | queries: [] 51 | }; 52 | } 53 | 54 | Object.keys(data.databases).forEach(function(dbname) { 55 | 56 | if (lastGeneratedDatabases.length == 0 || Math.random() < ENV.mutations()) { 57 | var info = data.databases[dbname]; 58 | var r = Math.floor((Math.random() * 10) + 1); 59 | for (var i = 0; i < r; i++) { 60 | var elapsed = Math.random() * 15; 61 | var q = { 62 | canvas_action: null, 63 | canvas_context_id: null, 64 | canvas_controller: null, 65 | canvas_hostname: null, 66 | canvas_job_tag: null, 67 | canvas_pid: null, 68 | elapsed: elapsed, 69 | formatElapsed: formatElapsed(elapsed), 70 | elapsedClassName: getElapsedClassName(elapsed), 71 | query: "SELECT blah FROM something", 72 | waiting: Math.random() < 0.5 73 | }; 74 | 75 | if (Math.random() < 0.2) { 76 | q.query = " in transaction"; 77 | } 78 | 79 | if (Math.random() < 0.1) { 80 | q.query = "vacuum"; 81 | } 82 | 83 | info.queries.push(q); 84 | } 85 | 86 | info.queries = info.queries.sort(function (a, b) { 87 | return b.elapsed - a.elapsed; 88 | }); 89 | } else { 90 | data.databases[dbname] = lastGeneratedDatabases[dbname]; 91 | } 92 | }); 93 | 94 | lastGeneratedDatabases = data.databases; 95 | 96 | return data; 97 | } 98 | 99 | var lastDatabases = { 100 | toArray: function() { 101 | return Object.keys(this).filter(function(k) { return k !== 'toArray'; }).map(function(k) { return this[k]; }.bind(this)) 102 | } 103 | }; 104 | 105 | function generateData() { 106 | var databases = []; 107 | var newData = getData(); 108 | Object.keys(newData.databases).forEach(function(dbname) { 109 | var sampleInfo = newData.databases[dbname]; 110 | var database = { 111 | dbname: dbname, 112 | samples: [] 113 | }; 114 | 115 | function countClassName(queries) { 116 | var countClassName = "label"; 117 | if (queries.length >= 20) { 118 | countClassName += " label-important"; 119 | } 120 | else if (queries.length >= 10) { 121 | countClassName += " label-warning"; 122 | } 123 | else { 124 | countClassName += " label-success"; 125 | } 126 | return countClassName; 127 | } 128 | 129 | function topFiveQueries(queries) { 130 | var tfq = queries.slice(0, 5); 131 | while (tfq.length < 5) { 132 | tfq.push({ query: "", formatElapsed: '', elapsedClassName: '' }); 133 | } 134 | return tfq; 135 | } 136 | 137 | var samples = database.samples; 138 | samples.push({ 139 | time: newData.start_at, 140 | queries: sampleInfo.queries, 141 | topFiveQueries: topFiveQueries(sampleInfo.queries), 142 | countClassName: countClassName(sampleInfo.queries) 143 | }); 144 | if (samples.length > 5) { 145 | samples.splice(0, samples.length - 5); 146 | } 147 | var samples = database.samples; 148 | database.lastSample = database.samples[database.samples.length - 1]; 149 | databases.push(database); 150 | }); 151 | return { 152 | toArray: function() { 153 | return databases; 154 | } 155 | }; 156 | } 157 | 158 | var mutationsValue = 0.5; 159 | 160 | function mutations(value) { 161 | if (value) { 162 | mutationsValue = value; 163 | return mutationsValue; 164 | } else { 165 | return mutationsValue; 166 | } 167 | } 168 | 169 | var body = document.querySelector('body'); 170 | var theFirstChild = body.firstChild; 171 | 172 | var sliderContainer = document.createElement( 'div' ); 173 | sliderContainer.style.cssText = "display: flex"; 174 | var slider = document.createElement('input'); 175 | var text = document.createElement('label'); 176 | text.innerHTML = 'mutations : ' + (mutationsValue * 100).toFixed(0) + '%'; 177 | text.id = "ratioval"; 178 | slider.setAttribute("type", "range"); 179 | slider.style.cssText = 'margin-bottom: 10px; margin-top: 5px'; 180 | slider.addEventListener('change', function(e) { 181 | ENV.mutations(e.target.value / 100); 182 | document.querySelector('#ratioval').innerHTML = 'mutations : ' + (ENV.mutations() * 100).toFixed(0) + '%'; 183 | }); 184 | sliderContainer.appendChild( text ); 185 | sliderContainer.appendChild( slider ); 186 | body.insertBefore( sliderContainer, theFirstChild ); 187 | 188 | return { 189 | generateData: generateData, 190 | rows: 50, 191 | timeout: 0, 192 | mutations: mutations 193 | }; 194 | })(); 195 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/ga.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 3 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 4 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 5 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 6 | 7 | ga('create', 'UA-63822970-1', 'auto'); 8 | ga('send', 'pageview'); 9 | })(); 10 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/app-component.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var Query = React.createClass({ 4 | render: function() { 5 | return ( 6 | 7 | {this.props.formatElapsed} 8 |
9 |
{this.props.query}
10 |
11 |
12 | 13 | ); 14 | } 15 | }) 16 | 17 | var sample = function (database) { 18 | var _queries = []; 19 | database.lastSample.topFiveQueries.forEach(function(query, index) { 20 | _queries.push( 21 | 26 | ); 27 | }); 28 | return [ 29 | 30 | 31 | {database.lastSample.queries.length} 32 | 33 | , 34 | _queries 35 | ]; 36 | }; 37 | 38 | var Database = React.createClass({ 39 | render: function() { 40 | var lastSample = this.props.lastSample; 41 | return ( 42 | 43 | 44 | {this.props.dbname} 45 | 46 | {sample(this.props)} 47 | 48 | ); 49 | } 50 | }); 51 | 52 | var DBMon = React.createClass({ 53 | getInitialState: function() { 54 | return { 55 | databases: [] 56 | }; 57 | }, 58 | 59 | loadSamples: function () { 60 | this.setState({ 61 | databases: ENV.generateData().toArray() 62 | }); 63 | Monitoring.renderRate.ping(); 64 | setTimeout(this.loadSamples, ENV.timeout); 65 | }, 66 | 67 | componentDidMount: function() { 68 | this.loadSamples(); 69 | }, 70 | 71 | render: function() { 72 | var databases = []; 73 | Object.keys(this.state.databases).forEach(function(dbname) { 74 | databases.push( 75 | 78 | ); 79 | }.bind(this)); 80 | 81 | var databases = this.state.databases.map(function(database) { 82 | return 86 | }); 87 | 88 | return ( 89 |
90 | 91 | 92 | {databases} 93 | 94 |
95 |
96 | ); 97 | } 98 | }); 99 | 100 | React.render(, document.getElementById('dbmon')); 101 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/app.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var DBMon = React.createClass({ 4 | getInitialState: function() { 5 | return { 6 | databases: [] 7 | }; 8 | }, 9 | 10 | loadSamples: function () { 11 | this.setState({ databases: ENV.generateData().toArray() }); 12 | Monitoring.renderRate.ping(); 13 | setTimeout(this.loadSamples.bind(this), ENV.timeout); 14 | }, 15 | 16 | componentDidMount: function() { 17 | this.loadSamples(); 18 | }, 19 | 20 | render: function() { 21 | return ( 22 |
23 | 24 | 25 | { 26 | this.state.databases.map(function(database) { 27 | return ( 28 | 29 | 32 | 37 | { 38 | database.lastSample.topFiveQueries.map(function(query, index) { 39 | return ( 40 | 47 | ); 48 | }) 49 | } 50 | 51 | ); 52 | }) 53 | } 54 | 55 |
30 | {database.dbname} 31 | 33 | 34 | {database.lastSample.queries.length} 35 | 36 | 41 | {query.formatElapsed} 42 |
43 |
{query.query}
44 |
45 |
46 |
56 |
57 | ); 58 | } 59 | }); 60 | 61 | console.time('mount') 62 | ReactDOM.render(, document.getElementById('dbmon')); 63 | console.timeEnd('mount') 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | dbmon (react) 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/lite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | dbmon (react) 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/styles.css: -------------------------------------------------------------------------------- 1 | .Query { 2 | position: relative; 3 | } 4 | 5 | .Query:hover .popover { 6 | left: -100%; 7 | width: 100%; 8 | display: block; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/vendor/memory-stats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author jetienne / http://jetienne.com/ 4 | * @author paulirish / http://paulirish.com/ 5 | */ 6 | var MemoryStats = function (){ 7 | 8 | var msMin = 100; 9 | var msMax = 0; 10 | 11 | var container = document.createElement( 'div' ); 12 | container.id = 'stats'; 13 | container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer'; 14 | 15 | var msDiv = document.createElement( 'div' ); 16 | msDiv.id = 'ms'; 17 | msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;'; 18 | container.appendChild( msDiv ); 19 | 20 | var msText = document.createElement( 'div' ); 21 | msText.id = 'msText'; 22 | msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; 23 | msText.innerHTML= 'Memory'; 24 | msDiv.appendChild( msText ); 25 | 26 | var msGraph = document.createElement( 'div' ); 27 | msGraph.id = 'msGraph'; 28 | msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0'; 29 | msDiv.appendChild( msGraph ); 30 | 31 | while ( msGraph.children.length < 74 ) { 32 | 33 | var bar = document.createElement( 'span' ); 34 | bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131'; 35 | msGraph.appendChild( bar ); 36 | 37 | } 38 | 39 | var updateGraph = function ( dom, height, color ) { 40 | 41 | var child = dom.appendChild( dom.firstChild ); 42 | child.style.height = height + 'px'; 43 | if( color ) child.style.backgroundColor = color; 44 | 45 | } 46 | 47 | var perf = window.performance || {}; 48 | // polyfill usedJSHeapSize 49 | if (!perf && !perf.memory){ 50 | perf.memory = { usedJSHeapSize : 0 }; 51 | } 52 | if (perf && !perf.memory){ 53 | perf.memory = { usedJSHeapSize : 0 }; 54 | } 55 | 56 | // support of the API? 57 | if( perf.memory.totalJSHeapSize === 0 ){ 58 | console.warn('totalJSHeapSize === 0... performance.memory is only available in Chrome .') 59 | } 60 | 61 | // TODO, add a sanity check to see if values are bucketed. 62 | // If so, reminde user to adopt the --enable-precise-memory-info flag. 63 | // open -a "/Applications/Google Chrome.app" --args --enable-precise-memory-info 64 | 65 | var lastTime = Date.now(); 66 | var lastUsedHeap= perf.memory.usedJSHeapSize; 67 | return { 68 | domElement: container, 69 | 70 | update: function () { 71 | 72 | // refresh only 30time per second 73 | if( Date.now() - lastTime < 1000/30 ) return; 74 | lastTime = Date.now() 75 | 76 | var delta = perf.memory.usedJSHeapSize - lastUsedHeap; 77 | lastUsedHeap = perf.memory.usedJSHeapSize; 78 | var color = delta < 0 ? '#830' : '#131'; 79 | 80 | var ms = perf.memory.usedJSHeapSize; 81 | msMin = Math.min( msMin, ms ); 82 | msMax = Math.max( msMax, ms ); 83 | msText.textContent = "Mem: " + bytesToSize(ms, 2); 84 | 85 | var normValue = ms / (30*1024*1024); 86 | var height = Math.min( 30, 30 - normValue * 30 ); 87 | updateGraph( msGraph, height, color); 88 | 89 | function bytesToSize( bytes, nFractDigit ){ 90 | var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; 91 | if (bytes == 0) return 'n/a'; 92 | nFractDigit = nFractDigit !== undefined ? nFractDigit : 0; 93 | var precision = Math.pow(10, nFractDigit); 94 | var i = Math.floor(Math.log(bytes) / Math.log(1024)); 95 | return Math.round(bytes*precision / Math.pow(1024, i))/precision + ' ' + sizes[i]; 96 | }; 97 | } 98 | 99 | } 100 | 101 | }; -------------------------------------------------------------------------------- /examples/js-repaint-perf/vendor/monitor.js: -------------------------------------------------------------------------------- 1 | var Monitoring = Monitoring || (function() { 2 | 3 | var stats = new MemoryStats(); 4 | stats.domElement.style.position = 'fixed'; 5 | stats.domElement.style.right = '0px'; 6 | stats.domElement.style.bottom = '0px'; 7 | document.body.appendChild( stats.domElement ); 8 | requestAnimationFrame(function rAFloop(){ 9 | stats.update(); 10 | requestAnimationFrame(rAFloop); 11 | }); 12 | 13 | var RenderRate = function () { 14 | var container = document.createElement( 'div' ); 15 | container.id = 'stats'; 16 | container.style.cssText = 'width:150px;opacity:0.9;cursor:pointer;position:fixed;right:80px;bottom:0px;'; 17 | 18 | var msDiv = document.createElement( 'div' ); 19 | msDiv.id = 'ms'; 20 | msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;'; 21 | container.appendChild( msDiv ); 22 | 23 | var msText = document.createElement( 'div' ); 24 | msText.id = 'msText'; 25 | msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; 26 | msText.innerHTML= 'Repaint rate: 0/sec'; 27 | msDiv.appendChild( msText ); 28 | 29 | var bucketSize = 20; 30 | var bucket = []; 31 | var lastTime = Date.now(); 32 | return { 33 | domElement: container, 34 | ping: function () { 35 | var start = lastTime; 36 | var stop = Date.now(); 37 | var rate = 1000 / (stop - start); 38 | bucket.push(rate); 39 | if (bucket.length > bucketSize) { 40 | bucket.shift(); 41 | } 42 | var sum = 0; 43 | for (var i = 0; i < bucket.length; i++) { 44 | sum = sum + bucket[i]; 45 | } 46 | msText.textContent = "Repaint rate: " + (sum / bucket.length).toFixed(2) + "/sec"; 47 | lastTime = stop; 48 | } 49 | } 50 | }; 51 | 52 | var renderRate = new RenderRate(); 53 | document.body.appendChild( renderRate.domElement ); 54 | 55 | return { 56 | memoryStats: stats, 57 | renderRate: renderRate 58 | }; 59 | 60 | })(); 61 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/vendor/react-dom.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ReactDOM v15.3.1 3 | * 4 | * Copyright 2013-present, Facebook, Inc. 5 | * All rights reserved. 6 | * 7 | * This source code is licensed under the BSD-style license found in the 8 | * LICENSE file in the root directory of this source tree. An additional grant 9 | * of patent rights can be found in the PATENTS file in the same directory. 10 | * 11 | */ 12 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e(require("react"));else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;f="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,f.ReactDOM=e(f.React)}}(function(e){return e.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED}); -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | simple test 6 | 7 | 8 |
9 |
10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/simple/index.js: -------------------------------------------------------------------------------- 1 | if (typeof requestAnimationFrame === 'undefined') { 2 | window.requestAnimationFrame = fn => setTimeout(fn, 100 / 6) 3 | window.cancelAnimationFrame = id => clearTimeout(id) 4 | } 5 | 6 | if (typeof console === 'undefined') { 7 | console = { log() {}, time() {}, timeEnd() {} } 8 | } 9 | 10 | const Counter = React.createClass({ 11 | getInitialState() { 12 | return { 13 | text: '123123123' 14 | } 15 | }, 16 | componentWillMount() { 17 | console.time('Counter mount') 18 | }, 19 | componentDidMount() { 20 | console.timeEnd('Counter mount') 21 | }, 22 | toNum(num, callback) { 23 | cancelAnimationFrame(this.rid) 24 | let { COUNT } = this.props 25 | let counting = () => { 26 | let { count } = this.props 27 | switch (true) { 28 | case count > num: 29 | COUNT('DECREMENT') 30 | break 31 | case count < num: 32 | COUNT('INCREMENT') 33 | break 34 | case count === num: 35 | return callback && callback() 36 | } 37 | this.rid = requestAnimationFrame(counting) 38 | } 39 | counting() 40 | }, 41 | componentWillUpdate() { 42 | // debugger 43 | console.log('willUpdate', 'Counter') 44 | if (this.state.text === '123123123') { 45 | this.setState({ 46 | text: '[text set by willUpdate]' 47 | }) 48 | } 49 | console.log(this.state.text, 'WillUpdate') 50 | 51 | }, 52 | componentDidUpdate() { 53 | console.log(this.state.text, 'DidUpdate') 54 | this; 55 | //debugger 56 | console.log('DidUpdate', 'Counter') 57 | }, 58 | componentWillReceiveProps(nextProps) { 59 | let state = this.state 60 | console.log('Counter: receiveProps:setState', state === this.state) 61 | }, 62 | shouldComponentUpdate(nextProps, nextState) { 63 | console.log('Counter: shouldComponentUpdate') 64 | return true 65 | }, 66 | componentWillUnmount() { 67 | console.log('unmount', 'Counter') 68 | }, 69 | getNum(e) { 70 | let num = parseInt(this.input.value, 10) 71 | if (typeof num === 'number') { 72 | this.toNum(num) 73 | } 74 | }, 75 | getInput(input) { 76 | this.input = input 77 | }, 78 | handleChange(e) { 79 | let text = e.target.value.replace(/[^\d]+/, '') 80 | this.setState({ text }) 81 | }, 82 | render() { 83 | let { props } = this 84 | let { COUNT } = props 85 | var img = { 89 | console.log('onload this.refs', this.refs) 90 | }} 91 | onError={()=>{ 92 | console.log('onerror this.refs', this.refs) 93 | }} 94 | /> 95 | return ( 96 |
97 | { Math.random() > 0.5 && img } 98 | 0.5 ? '' : 'test-ref'} data-test="abaasdf">count: { props.count } 99 | {' '} 100 | 101 | {' '} 102 | 103 | {' '} 104 | 105 | {' '} 106 | 118 | 119 | test link 120 |

121 |
122 | ) 123 | } 124 | }) 125 | 126 | var Example = React.createClass({ 127 | getInitialState() { 128 | return { 129 | val: 0, 130 | test: 0 131 | }; 132 | }, 133 | 134 | componentDidMount() { 135 | this.setState({val: this.state.val + 1, test1: 1}) 136 | console.log('didMount', this.state); // log 1 137 | this.setState({val: this.state.val + 1, test2: 2}); 138 | console.log('didMount', this.state); // log 2 139 | 140 | setTimeout(() => { 141 | this.setState({val: this.state.val + 1}); 142 | console.log('setTimeout:', this.state); // log 3 143 | this.setState({val: this.state.val + 1}); 144 | console.log('setTimeout:', this.state); // log 4 145 | }, 4); 146 | }, 147 | 148 | render() { 149 | return

{this.state.val}

; 150 | } 151 | }); 152 | 153 | const Wrap = React.createClass({ 154 | getInitialState() { 155 | return { count: 0 } 156 | }, 157 | COUNT(type) { 158 | let { count } = this.state 159 | switch(type) { 160 | case 'INCREMENT': 161 | count += 1 162 | break 163 | case 'DECREMENT': 164 | count -= 1 165 | break 166 | case 'INCREMENT_IF_ODD': 167 | if (count % 2 === 0) { 168 | return 169 | } 170 | count += 1 171 | break 172 | } 173 | this.setState({ count }) 174 | 175 | }, 176 | componentWillMount() { 177 | console.time('Wrap mount') 178 | // let state = this.state 179 | // this.setState({ 180 | // count: this.props.count 181 | // }) 182 | // console.log('componentWillMount:setState', state === this.state) 183 | }, 184 | componentDidMount() { 185 | console.timeEnd('Wrap mount') 186 | let state = this.state 187 | this.setState({ 188 | count: this.props.count * 2 189 | }) 190 | console.log('componentDidMount:setState', state === this.state) 191 | console.log(this) 192 | }, 193 | componentWillUpdate() { 194 | // debugger 195 | console.log('willUpdate', 'Wrap') 196 | }, 197 | componentDidUpdate() { 198 | //debugger 199 | console.log('DidUpdate', 'Wrap') 200 | }, 201 | componentWillReceiveProps(props) { 202 | this.setState({ 203 | count: props.count 204 | }) 205 | console.log('Wrap:receiveProps') 206 | }, 207 | shouldComponentUpdate() { 208 | console.log('Wrap: shouldComponentUpdate') 209 | return true 210 | }, 211 | componentWillUnmount() { 212 | console.log('unmount', 'wrap') 213 | }, 214 | render() { 215 | let example = 216 | // let count = Math.random() > 0.5 217 | // ? 218 | // : null 219 | return
220 | 221 | {example} 222 | {example} 223 |
224 | } 225 | }) 226 | 227 | 228 | var wrap = 229 | 230 | let update = count => { 231 | return React.render( 232 | wrap, 233 | document.getElementById('container') 234 | ) 235 | } 236 | 237 | let num = 10 238 | update() 239 | 240 | // const context = { 241 | // THIS_IS_IMPORTANT: function() {} 242 | // } 243 | 244 | // class ContextProvider extends React.Component { 245 | // static childContextTypes = context; 246 | 247 | // getChildContext() { 248 | // return this.props.context 249 | // } 250 | 251 | // render() { 252 | // return this.props.children 253 | // } 254 | // } 255 | 256 | // class Test extends React.Component { 257 | 258 | // static contextTypes = context; 259 | 260 | // // We added THIS constructor 261 | // constructor(props) { 262 | // super(props) 263 | // } 264 | 265 | // render() { 266 | // console.info('This should NOT be undefined:', this.context.THIS_IS_IMPORTANT) 267 | // return
Test1
268 | // } 269 | // } 270 | 271 | // // Render HTML on the browser 272 | // React.render(, document.body) 273 | 274 | 275 | 276 | // class TestRootUpdateAtDidMount extends React.Component { 277 | // componentDidMount() { 278 | // console.log('TestRootUpdateAtDidMount didMount') 279 | // updateName('TestRootUpdateAtDidMount1', () => console.log('TestRootUpdateAtDidMount1 done')) 280 | // updateName('TestRootUpdateAtDidMount2', () => console.log('TestRootUpdateAtDidMount2 done')) 281 | // updateName('TestRootUpdateAtDidMount3', () => console.log('TestRootUpdateAtDidMount3 done')) 282 | // } 283 | // render() { 284 | // let { props } = this 285 | // console.log('render count', props.name) 286 | // return
{props.name} asdfsdf
287 | // } 288 | // } 289 | 290 | // class TestRootUpdateAtDidMountWrapper extends React.Component { 291 | // state = { 292 | // text: 'TestRootUpdateAtDidMountWrapper text' 293 | // }; 294 | // componentDidMount() { 295 | // console.log('TestRootUpdateAtDidMountWrapper didMount') 296 | // this.setState({ 297 | // text: 'change at didMount' 298 | // }) 299 | // this.setState({ 300 | // text: 'change at didMount1' 301 | // }) 302 | // updateName('TestRootUpdateAtDidMountWrapper') 303 | // } 304 | // render() { 305 | // let { props } = this 306 | // let children = testCount++ > 0 ? : 'init' 307 | // return (
308 | // {this.state.text + ' ' + (this.props.name || 'default name')} 309 | // {children} 310 | //
) 311 | // } 312 | // } 313 | 314 | // let testCount = 0 315 | 316 | 317 | // let Root = props => { 318 | 319 | // return

placeholder

320 | // } 321 | 322 | // var globalState = { 323 | // name: 'init' 324 | // } 325 | 326 | // var updateName = (name, callback) => { 327 | // console.log('updateName', name) 328 | // globalState = { 329 | // ...globalState, 330 | // name 331 | // } 332 | // renderTest(callback) 333 | // } 334 | 335 | // let renderTest = (callback) => { 336 | // React.render( 337 | // , 338 | // document.getElementById('container'), 339 | // callback 340 | // ) 341 | // } 342 | 343 | // updateName('init') 344 | // updateName('update') 345 | 346 | 347 | 348 | 349 | 350 | // class Test extends React.Component { 351 | // componentWillMount() { 352 | // console.log(this.props.index, 'willMount') 353 | // debugger 354 | // } 355 | // componentDidMount() { 356 | // console.log(this.props.index, 'didMount') 357 | // debugger 358 | // } 359 | // componentWillUnmount() { 360 | // console.log(this.props.index, 'willUnmount') 361 | // } 362 | // render() { 363 | // return
{this.props.index}
364 | // } 365 | // } 366 | 367 | // var root = ( 368 | //
369 | // 370 | // 371 | // 372 | //
) 373 | 374 | // React.render(root, document.getElementById('container')) 375 | 376 | 377 | 378 | 379 | 380 | -------------------------------------------------------------------------------- /jest/preprocessor.js: -------------------------------------------------------------------------------- 1 | // jest/preprocessor.js 2 | var babelJest = require('babel-jest') 3 | var webpackAlias = require('jest-webpack-alias') 4 | 5 | module.exports = { 6 | process: function(src, filename) { 7 | if (filename.indexOf('node_modules') === -1) { 8 | src = babelJest.process(src, filename) 9 | src = webpackAlias.process(src, filename) 10 | } 11 | return src 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-lite", 3 | "version": "0.15.40", 4 | "description": "an implementation of React that optimizes for small script size", 5 | "main": "dist/react-lite.common.js", 6 | "jsnext:main": "src/index.js", 7 | "scripts": { 8 | "test": "jest", 9 | "build:addons": "babel ./addons --out-dir ./lib", 10 | "build": "node build.js && npm run build:addons", 11 | "prepublish": "npm test && npm run build" 12 | }, 13 | "publishConfig": { 14 | "registry": "https://registry.npmjs.org" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/Lucifier129/react-lite.git" 19 | }, 20 | "jest": { 21 | "scriptPreprocessor": "/jest/preprocessor.js", 22 | "persistModuleRegistryBetweenSpecs": true, 23 | "unmockedModulePathPatterns": [ 24 | "" 25 | ] 26 | }, 27 | "keywords": [ 28 | "react", 29 | "lite", 30 | "react-lite", 31 | "component", 32 | "virtual-dom" 33 | ], 34 | "author": "Jade Gu (https://github.com/Lucifier129)", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/Lucifier129/react-lite/issues" 38 | }, 39 | "homepage": "https://github.com/Lucifier129/react-lite", 40 | "devDependencies": { 41 | "babel": "^5.8.35", 42 | "babel-core": "^5.8.25", 43 | "babel-jest": "^5.3.0", 44 | "babel-loader": "^5.3.2", 45 | "babel-runtime": "^5.8.25", 46 | "jest-cli": "^0.8.1", 47 | "jest-webpack-alias": "^2.0.0", 48 | "rollup": "^0.21.0", 49 | "rollup-plugin-babel": "^1.0.0", 50 | "rollup-plugin-replace": "^1.1.0", 51 | "uglify-js": "^2.6.1", 52 | "webpack": "^1.12.2" 53 | }, 54 | "npmName": "react-lite", 55 | "npmFileMap": [{ 56 | "basePath": "/dist/", 57 | "files": [ 58 | "*.js" 59 | ] 60 | }] 61 | } -------------------------------------------------------------------------------- /src/CSSPropertyOperations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS Property Operations 3 | */ 4 | 5 | export function setStyle(elemStyle, styles) { 6 | for (let styleName in styles) { 7 | if (styles.hasOwnProperty(styleName)) { 8 | setStyleValue(elemStyle, styleName, styles[styleName]) 9 | } 10 | } 11 | } 12 | 13 | export function removeStyle(elemStyle, styles) { 14 | for (let styleName in styles) { 15 | if (styles.hasOwnProperty(styleName)) { 16 | elemStyle[styleName] = '' 17 | } 18 | } 19 | } 20 | 21 | export function patchStyle(elemStyle, style, newStyle) { 22 | if (style === newStyle) { 23 | return 24 | } 25 | if (!newStyle && style) { 26 | removeStyle(elemStyle, style) 27 | return 28 | } else if (newStyle && !style) { 29 | setStyle(elemStyle, newStyle) 30 | return 31 | } 32 | 33 | for (let key in style) { 34 | if (newStyle.hasOwnProperty(key)) { 35 | if (newStyle[key] !== style[key]) { 36 | setStyleValue(elemStyle, key, newStyle[key]) 37 | } 38 | } else { 39 | elemStyle[key] = '' 40 | } 41 | } 42 | for (let key in newStyle) { 43 | if (!style.hasOwnProperty(key)) { 44 | setStyleValue(elemStyle, key, newStyle[key]) 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * CSS properties which accept numbers but are not in units of "px". 51 | */ 52 | const isUnitlessNumber = { 53 | animationIterationCount: 1, 54 | borderImageOutset: 1, 55 | borderImageSlice: 1, 56 | borderImageWidth: 1, 57 | boxFlex: 1, 58 | boxFlexGroup: 1, 59 | boxOrdinalGroup: 1, 60 | columnCount: 1, 61 | flex: 1, 62 | flexGrow: 1, 63 | flexPositive: 1, 64 | flexShrink: 1, 65 | flexNegative: 1, 66 | flexOrder: 1, 67 | gridRow: 1, 68 | gridColumn: 1, 69 | fontWeight: 1, 70 | lineClamp: 1, 71 | lineHeight: 1, 72 | opacity: 1, 73 | order: 1, 74 | orphans: 1, 75 | tabSize: 1, 76 | widows: 1, 77 | zIndex: 1, 78 | zoom: 1, 79 | 80 | // SVG-related properties 81 | fillOpacity: 1, 82 | floodOpacity: 1, 83 | stopOpacity: 1, 84 | strokeDasharray: 1, 85 | strokeDashoffset: 1, 86 | strokeMiterlimit: 1, 87 | strokeOpacity: 1, 88 | strokeWidth: 1, 89 | } 90 | 91 | function prefixKey(prefix, key) { 92 | return prefix + key.charAt(0).toUpperCase() + key.substring(1) 93 | } 94 | 95 | let prefixes = ['Webkit', 'ms', 'Moz', 'O'] 96 | 97 | Object.keys(isUnitlessNumber).forEach(function(prop) { 98 | prefixes.forEach(function(prefix) { 99 | isUnitlessNumber[prefixKey(prefix, prop)] = 1 100 | }) 101 | }) 102 | 103 | let RE_NUMBER = /^-?\d+(\.\d+)?$/ 104 | function setStyleValue(elemStyle, styleName, styleValue) { 105 | 106 | if (!isUnitlessNumber[styleName] && RE_NUMBER.test(styleValue)) { 107 | elemStyle[styleName] = styleValue + 'px' 108 | return 109 | } 110 | 111 | if (styleName === 'float') { 112 | styleName = 'cssFloat' 113 | } 114 | 115 | if (styleValue == null || typeof styleValue === 'boolean') { 116 | styleValue = '' 117 | } 118 | 119 | elemStyle[styleName] = styleValue 120 | } -------------------------------------------------------------------------------- /src/Children.js: -------------------------------------------------------------------------------- 1 | import * as _ from './util' 2 | import { cloneElement, isValidElement } from './createElement' 3 | 4 | export function only(children) { 5 | if (isValidElement(children)) { 6 | return children 7 | } 8 | throw new Error('expect only one child') 9 | } 10 | 11 | export function forEach(children, iteratee, context) { 12 | if (children == null) { 13 | return children 14 | } 15 | let index = 0 16 | if (_.isArr(children)) { 17 | _.flatEach(children, child => { 18 | // from traverseAllChildrenImpl in react 19 | var type = typeof child 20 | if (type === 'undefined' || type === 'boolean') { 21 | // All of the above are perceived as null. 22 | child = null 23 | } 24 | 25 | iteratee.call(context, child, index++) 26 | }) 27 | } else { 28 | // from traverseAllChildrenImpl in react 29 | var type = typeof children 30 | if (type === 'undefined' || type === 'boolean') { 31 | // All of the above are perceived as null. 32 | children = null 33 | } 34 | iteratee.call(context, children, index) 35 | } 36 | } 37 | 38 | export function map(children, iteratee, context) { 39 | if (children == null) { 40 | return children 41 | } 42 | let store = [] 43 | let keyMap = {} 44 | forEach(children, (child, index) => { 45 | let data = {} 46 | data.child = iteratee.call(context, child, index) || child 47 | data.isEqual = data.child === child 48 | let key = data.key = getKey(child, index) 49 | if (keyMap.hasOwnProperty(key)) { 50 | keyMap[key] += 1 51 | } else { 52 | keyMap[key] = 0 53 | } 54 | data.index = keyMap[key] 55 | _.addItem(store, data) 56 | }) 57 | let result = [] 58 | store.forEach(({ child, key, index, isEqual }) => { 59 | if (child == null || typeof child === 'boolean') { 60 | return 61 | } 62 | if (!isValidElement(child) || key == null) { 63 | _.addItem(result, child) 64 | return 65 | } 66 | if (keyMap[key] !== 0) { 67 | key += ':' + index 68 | } 69 | if (!isEqual) { 70 | key = escapeUserProvidedKey(child.key || '') + '/' + key 71 | } 72 | child = cloneElement(child, { key }) 73 | _.addItem(result, child) 74 | }) 75 | return result 76 | } 77 | 78 | export function count(children) { 79 | let count = 0 80 | forEach(children, () => { 81 | count++ 82 | }) 83 | return count 84 | } 85 | 86 | export function toArray(children) { 87 | return map(children, _.identity) || [] 88 | } 89 | 90 | function getKey(child, index) { 91 | let key 92 | if (isValidElement(child) && typeof child.key === 'string') { 93 | key = '.$' + child.key 94 | } else { 95 | key = '.' + index.toString(36) 96 | } 97 | return key 98 | } 99 | 100 | let userProvidedKeyEscapeRegex = /\/(?!\/)/g; 101 | function escapeUserProvidedKey(text) { 102 | return ('' + text).replace(userProvidedKeyEscapeRegex, '//') 103 | } -------------------------------------------------------------------------------- /src/Component.js: -------------------------------------------------------------------------------- 1 | import * as _ from './util' 2 | import { 3 | renderComponent, 4 | clearPending, 5 | compareTwoVnodes, 6 | getChildContext, 7 | syncCache 8 | } from './virtual-dom' 9 | 10 | export let updateQueue = { 11 | updaters: [], 12 | isPending: false, 13 | add(updater) { 14 | _.addItem(this.updaters, updater) 15 | }, 16 | batchUpdate() { 17 | if (this.isPending) { 18 | return 19 | } 20 | this.isPending = true 21 | /* 22 | each updater.update may add new updater to updateQueue 23 | clear them with a loop 24 | event bubbles from bottom-level to top-level 25 | reverse the updater order can merge some props and state and reduce the refresh times 26 | see Updater.update method below to know why 27 | */ 28 | let { updaters } = this 29 | let updater 30 | while (updater = updaters.pop()) { 31 | updater.updateComponent() 32 | } 33 | this.isPending = false 34 | } 35 | } 36 | 37 | function Updater(instance) { 38 | this.instance = instance 39 | this.pendingStates = [] 40 | this.pendingCallbacks = [] 41 | this.isPending = false 42 | this.nextProps = this.nextContext = null 43 | this.clearCallbacks = this.clearCallbacks.bind(this) 44 | } 45 | 46 | Updater.prototype = { 47 | emitUpdate(nextProps, nextContext) { 48 | this.nextProps = nextProps 49 | this.nextContext = nextContext 50 | // receive nextProps!! should update immediately 51 | nextProps || !updateQueue.isPending 52 | ? this.updateComponent() 53 | : updateQueue.add(this) 54 | }, 55 | updateComponent() { 56 | let { instance, pendingStates, nextProps, nextContext } = this 57 | if (nextProps || pendingStates.length > 0) { 58 | nextProps = nextProps || instance.props 59 | nextContext = nextContext || instance.context 60 | this.nextProps = this.nextContext = null 61 | // merge the nextProps and nextState and update by one time 62 | shouldUpdate(instance, nextProps, this.getState(), nextContext, this.clearCallbacks) 63 | } 64 | }, 65 | addState(nextState) { 66 | if (nextState) { 67 | _.addItem(this.pendingStates, nextState) 68 | if (!this.isPending) { 69 | this.emitUpdate() 70 | } 71 | } 72 | }, 73 | replaceState(nextState) { 74 | let { pendingStates } = this 75 | pendingStates.pop() 76 | // push special params to point out should replace state 77 | _.addItem(pendingStates, [nextState]) 78 | }, 79 | getState() { 80 | let { instance, pendingStates } = this 81 | let { state, props } = instance 82 | if (pendingStates.length) { 83 | state = _.extend({}, state) 84 | pendingStates.forEach(nextState => { 85 | let isReplace = _.isArr(nextState) 86 | if (isReplace) { 87 | nextState = nextState[0] 88 | } 89 | if (_.isFn(nextState)) { 90 | nextState = nextState.call(instance, state, props) 91 | } 92 | // replace state 93 | if (isReplace) { 94 | state = _.extend({}, nextState) 95 | } else { 96 | _.extend(state, nextState) 97 | } 98 | }) 99 | pendingStates.length = 0 100 | } 101 | return state 102 | }, 103 | clearCallbacks() { 104 | let { pendingCallbacks, instance } = this 105 | if (pendingCallbacks.length > 0) { 106 | this.pendingCallbacks = [] 107 | pendingCallbacks.forEach(callback => callback.call(instance)) 108 | } 109 | }, 110 | addCallback(callback) { 111 | if (_.isFn(callback)) { 112 | _.addItem(this.pendingCallbacks, callback) 113 | } 114 | } 115 | } 116 | 117 | export default function Component(props, context) { 118 | this.$updater = new Updater(this) 119 | this.$cache = { isMounted: false } 120 | this.props = props 121 | this.state = {} 122 | this.refs = {} 123 | this.context = context 124 | } 125 | 126 | const ReactComponentSymbol = {} 127 | 128 | Component.prototype = { 129 | constructor: Component, 130 | isReactComponent: ReactComponentSymbol, 131 | // getChildContext: _.noop, 132 | // componentWillUpdate: _.noop, 133 | // componentDidUpdate: _.noop, 134 | // componentWillReceiveProps: _.noop, 135 | // componentWillMount: _.noop, 136 | // componentDidMount: _.noop, 137 | // componentWillUnmount: _.noop, 138 | // shouldComponentUpdate(nextProps, nextState) { 139 | // return true 140 | // }, 141 | forceUpdate(callback) { 142 | let { $updater, $cache, props, state, context } = this 143 | if (!$cache.isMounted) { 144 | return 145 | } 146 | // if updater is pending, add state to trigger nexttick update 147 | if ($updater.isPending) { 148 | $updater.addState(state) 149 | return; 150 | } 151 | let nextProps = $cache.props || props 152 | let nextState = $cache.state || state 153 | let nextContext = $cache.context || context 154 | let parentContext = $cache.parentContext 155 | let node = $cache.node 156 | let vnode = $cache.vnode 157 | $cache.props = $cache.state = $cache.context = null 158 | $updater.isPending = true 159 | if (this.componentWillUpdate) { 160 | this.componentWillUpdate(nextProps, nextState, nextContext) 161 | } 162 | this.state = nextState 163 | this.props = nextProps 164 | this.context = nextContext 165 | let newVnode = renderComponent(this) 166 | let newNode = compareTwoVnodes(vnode, newVnode, node, getChildContext(this, parentContext)) 167 | if (newNode !== node) { 168 | newNode.cache = newNode.cache || {} 169 | syncCache(newNode.cache, node.cache, newNode) 170 | } 171 | $cache.vnode = newVnode 172 | $cache.node = newNode 173 | clearPending() 174 | if (this.componentDidUpdate) { 175 | this.componentDidUpdate(props, state, context) 176 | } 177 | if (callback) { 178 | callback.call(this) 179 | } 180 | $updater.isPending = false 181 | $updater.emitUpdate() 182 | }, 183 | setState(nextState, callback) { 184 | let { $updater } = this 185 | $updater.addCallback(callback) 186 | $updater.addState(nextState) 187 | }, 188 | replaceState(nextState, callback) { 189 | let { $updater } = this 190 | $updater.addCallback(callback) 191 | $updater.replaceState(nextState) 192 | }, 193 | getDOMNode() { 194 | let node = this.$cache.node 195 | return node && (node.nodeName === '#comment') ? null : node 196 | }, 197 | isMounted() { 198 | return this.$cache.isMounted 199 | } 200 | } 201 | 202 | function shouldUpdate(component, nextProps, nextState, nextContext, callback) { 203 | let shouldComponentUpdate = true 204 | if (component.shouldComponentUpdate) { 205 | shouldComponentUpdate = component.shouldComponentUpdate(nextProps, nextState, nextContext) 206 | } 207 | if (shouldComponentUpdate === false) { 208 | component.props = nextProps 209 | component.state = nextState 210 | component.context = nextContext || {} 211 | if(callback) { 212 | callback.call(component) 213 | } 214 | return 215 | } 216 | let cache = component.$cache 217 | cache.props = nextProps 218 | cache.state = nextState 219 | cache.context = nextContext || {} 220 | component.forceUpdate(callback) 221 | } 222 | -------------------------------------------------------------------------------- /src/DOM.js: -------------------------------------------------------------------------------- 1 | import * as _ from './util' 2 | import { createFactory } from './createElement' 3 | 4 | let tagNames = 'a|abbr|address|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|main|map|mark|menu|menuitem|meta|meter|nav|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|u|ul|var|video|wbr|circle|clipPath|defs|ellipse|g|image|line|linearGradient|mask|path|pattern|polygon|polyline|radialGradient|rect|stop|svg|text|tspan' 5 | let DOM = {} 6 | tagNames.split('|').forEach(tagName => { 7 | DOM[tagName] = createFactory(tagName) 8 | }) 9 | 10 | export default DOM -------------------------------------------------------------------------------- /src/DOMPropertyOperations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DOM Property Operations 3 | */ 4 | 5 | import { 6 | properties, 7 | isCustomAttribute, 8 | VALID_ATTRIBUTE_NAME_REGEX 9 | } from './DOMConfig' 10 | /** 11 | * Sets the value for a property on a node. 12 | * 13 | * @param {DOMElement} node 14 | * @param {string} name 15 | * @param {*} value 16 | */ 17 | export function setPropValue(node, name, value) { 18 | let propInfo = properties.hasOwnProperty(name) && properties[name] 19 | if (propInfo) { 20 | // should delete value from dom 21 | if (value == null || 22 | (propInfo.hasBooleanValue && !value) || 23 | (propInfo.hasNumericValue && isNaN(value)) || 24 | (propInfo.hasPositiveNumericValue && (value < 1)) || 25 | (propInfo.hasOverloadedBooleanValue && value === false)) { 26 | removePropValue(node, name) 27 | } else if (propInfo.mustUseProperty) { 28 | node[propInfo.propertyName] = value 29 | } else { 30 | let attributeName = propInfo.attributeName 31 | let namespace = propInfo.attributeNamespace 32 | 33 | // `setAttribute` with objects becomes only `[object]` in IE8/9, 34 | // ('' + value) makes it output the correct toString()-value. 35 | if (namespace) { 36 | node.setAttributeNS(namespace, attributeName, '' + value) 37 | } else if (propInfo.hasBooleanValue || (propInfo.hasOverloadedBooleanValue && value === true)) { 38 | node.setAttribute(attributeName, '') 39 | } else { 40 | node.setAttribute(attributeName, '' + value) 41 | } 42 | } 43 | } else if (isCustomAttribute(name) && VALID_ATTRIBUTE_NAME_REGEX.test(name)) { 44 | if (value == null) { 45 | node.removeAttribute(name) 46 | } else { 47 | node.setAttribute(name, '' + value) 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Deletes the value for a property on a node. 54 | * 55 | * @param {DOMElement} node 56 | * @param {string} name 57 | */ 58 | export function removePropValue(node, name) { 59 | let propInfo = properties.hasOwnProperty(name) && properties[name] 60 | if (propInfo) { 61 | if (propInfo.mustUseProperty) { 62 | let propName = propInfo.propertyName; 63 | if (propInfo.hasBooleanValue) { 64 | node[propName] = false 65 | } else { 66 | node[propName] = '' 67 | } 68 | } else { 69 | node.removeAttribute(propInfo.attributeName) 70 | } 71 | } else if (isCustomAttribute(name)) { 72 | node.removeAttribute(name) 73 | } 74 | } 75 | 76 | export function updateSelectOptions(select, multiple, propValue) { 77 | var selectedValue, i 78 | var options = select.options 79 | 80 | if (multiple) { 81 | select.multiple = true 82 | if (!Array.isArray(propValue)) { 83 | throw new Error('The value prop supplied to must be a scalar value if `multiple` is false.') 99 | } 100 | // Do not set `select.value` as exact behavior isn't consistent across all 101 | // browsers for all cases. 102 | selectedValue = '' + propValue 103 | for (i = 0; i < options.length; i++) { 104 | var option = options[i] 105 | if (option.value === selectedValue) { 106 | if (!option.selected) { 107 | option.selected = true 108 | } 109 | } else { 110 | if (option.selected) { 111 | option.selected = false 112 | } 113 | } 114 | } 115 | 116 | if (options.selectedIndex < 0 && options.length) { 117 | options[0].selected = true 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /src/PropTypes.js: -------------------------------------------------------------------------------- 1 | let check = () => check 2 | check.isRequired = check 3 | let PropTypes = { 4 | "array": check, 5 | "bool": check, 6 | "func": check, 7 | "number": check, 8 | "object": check, 9 | "string": check, 10 | "any": check, 11 | "arrayOf": check, 12 | "element": check, 13 | "instanceOf": check, 14 | "node": check, 15 | "objectOf": check, 16 | "oneOf": check, 17 | "oneOfType": check, 18 | "shape": check 19 | } 20 | 21 | export default PropTypes -------------------------------------------------------------------------------- /src/PureComponent.js: -------------------------------------------------------------------------------- 1 | import shallowEqual from './shallowEqual' 2 | import Component from './Component' 3 | 4 | export default function PureComponent(props, context) { 5 | Component.call(this, props, context) 6 | } 7 | 8 | PureComponent.prototype = Object.create(Component.prototype) 9 | PureComponent.prototype.constructor = PureComponent 10 | PureComponent.prototype.isPureReactComponent = true 11 | PureComponent.prototype.shouldComponentUpdate = shallowCompare 12 | 13 | function shallowCompare(nextProps, nextState) { 14 | return !shallowEqual(this.props, nextProps) || 15 | !shallowEqual(this.state, nextState) 16 | } -------------------------------------------------------------------------------- /src/ReactDOM.js: -------------------------------------------------------------------------------- 1 | import * as _ from './util' 2 | import { 3 | COMPONENT_ID, 4 | VELEMENT, 5 | VCOMPONENT, 6 | ELEMENT_NODE_TYPE, 7 | DOC_NODE_TYPE, 8 | DOCUMENT_FRAGMENT_NODE_TYPE 9 | } from './constant' 10 | import { initVnode, destroyVnode, clearPending, compareTwoVnodes } from './virtual-dom' 11 | import { updateQueue } from './Component' 12 | 13 | function isValidContainer(node) { 14 | return !!(node && ( 15 | node.nodeType === ELEMENT_NODE_TYPE || 16 | node.nodeType === DOC_NODE_TYPE || 17 | node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE 18 | )) 19 | } 20 | 21 | let pendingRendering = {} 22 | let vnodeStore = {} 23 | function renderTreeIntoContainer(vnode, container, callback, parentContext) { 24 | if (!vnode.vtype) { 25 | throw new Error(`cannot render ${ vnode } to container`) 26 | } 27 | if (!isValidContainer(container)) { 28 | throw new Error(`container ${container} is not a DOM element`) 29 | } 30 | let id = container[COMPONENT_ID] || (container[COMPONENT_ID] = _.getUid()) 31 | let argsCache = pendingRendering[id] 32 | 33 | // component lify cycle method maybe call root rendering 34 | // should bundle them and render by only one time 35 | if (argsCache) { 36 | if (argsCache === true) { 37 | pendingRendering[id] = argsCache = { vnode, callback, parentContext } 38 | } else { 39 | argsCache.vnode = vnode 40 | argsCache.parentContext = parentContext 41 | argsCache.callback = argsCache.callback ? _.pipe(argsCache.callback, callback) : callback 42 | } 43 | return 44 | } 45 | 46 | pendingRendering[id] = true 47 | let oldVnode = null 48 | let rootNode = null 49 | if (oldVnode = vnodeStore[id]) { 50 | rootNode = compareTwoVnodes(oldVnode, vnode, container.firstChild, parentContext) 51 | } else { 52 | rootNode = initVnode(vnode, parentContext, container.namespaceURI) 53 | var childNode = null 54 | while (childNode = container.lastChild) { 55 | container.removeChild(childNode) 56 | } 57 | container.appendChild(rootNode) 58 | } 59 | vnodeStore[id] = vnode 60 | let isPending = updateQueue.isPending 61 | updateQueue.isPending = true 62 | clearPending() 63 | argsCache = pendingRendering[id] 64 | delete pendingRendering[id] 65 | 66 | let result = null 67 | if (typeof argsCache === 'object') { 68 | result = renderTreeIntoContainer(argsCache.vnode, container, argsCache.callback, argsCache.parentContext) 69 | } else if (vnode.vtype === VELEMENT) { 70 | result = rootNode 71 | } else if (vnode.vtype === VCOMPONENT) { 72 | result = rootNode.cache[vnode.uid] 73 | } 74 | 75 | if (!isPending) { 76 | updateQueue.isPending = false 77 | updateQueue.batchUpdate() 78 | } 79 | 80 | if (callback) { 81 | callback.call(result) 82 | } 83 | 84 | return result 85 | } 86 | 87 | export function render(vnode, container, callback) { 88 | return renderTreeIntoContainer(vnode, container, callback) 89 | } 90 | 91 | export function unstable_renderSubtreeIntoContainer(parentComponent, subVnode, container, callback) { 92 | let context = parentComponent.$cache.parentContext 93 | return renderTreeIntoContainer(subVnode, container, callback, context) 94 | } 95 | 96 | export function unmountComponentAtNode(container) { 97 | if (!container.nodeName) { 98 | throw new Error('expect node') 99 | } 100 | let id = container[COMPONENT_ID] 101 | let vnode = null 102 | if (vnode = vnodeStore[id]) { 103 | destroyVnode(vnode, container.firstChild) 104 | container.removeChild(container.firstChild) 105 | delete vnodeStore[id] 106 | return true 107 | } 108 | return false 109 | } 110 | 111 | export function findDOMNode(node) { 112 | if (node == null) { 113 | return null 114 | } 115 | if (node.nodeName) { 116 | return node 117 | } 118 | let component = node 119 | // if component.node equal to false, component must be unmounted 120 | if (component.getDOMNode && component.$cache.isMounted) { 121 | return component.getDOMNode() 122 | } 123 | throw new Error('findDOMNode can not find Node') 124 | } -------------------------------------------------------------------------------- /src/constant.js: -------------------------------------------------------------------------------- 1 | /* 2 | key/value configs 3 | */ 4 | export const HTML_KEY = 'dangerouslySetInnerHTML' 5 | export const SVGNamespaceURI = 'http://www.w3.org/2000/svg' 6 | export const COMPONENT_ID = 'liteid' 7 | export const VTEXT = 1 8 | export const VELEMENT = 2 9 | export const VSTATELESS = 3 10 | export const VCOMPONENT = 4 11 | export const VCOMMENT = 5 12 | export const CREATE = 1 13 | export const REMOVE = 2 14 | export const UPDATE = 3 15 | export const ELEMENT_NODE_TYPE = 1 16 | export const DOC_NODE_TYPE = 9 17 | export const DOCUMENT_FRAGMENT_NODE_TYPE = 11 18 | -------------------------------------------------------------------------------- /src/createClass.js: -------------------------------------------------------------------------------- 1 | import * as _ from './util' 2 | import Component from './Component' 3 | 4 | function eachMixin(mixins, iteratee) { 5 | mixins.forEach(mixin => { 6 | if (mixin) { 7 | if (_.isArr(mixin.mixins)) { 8 | eachMixin(mixin.mixins, iteratee) 9 | } 10 | iteratee(mixin) 11 | } 12 | }) 13 | } 14 | 15 | function combineMixinToProto(proto, mixin) { 16 | for (let key in mixin) { 17 | if (!mixin.hasOwnProperty(key)) { 18 | continue 19 | } 20 | let value = mixin[key] 21 | if (key === 'getInitialState') { 22 | _.addItem(proto.$getInitialStates, value) 23 | continue 24 | } 25 | let curValue = proto[key] 26 | if (_.isFn(curValue) && _.isFn(value)) { 27 | proto[key] = _.pipe(curValue, value) 28 | } else { 29 | proto[key] = value 30 | } 31 | } 32 | } 33 | 34 | function combineMixinToClass(Component, mixin) { 35 | if (mixin.propTypes) { 36 | Component.propTypes = Component.propTypes || {} 37 | _.extend(Component.propTypes, mixin.propTypes) 38 | } 39 | if (mixin.contextTypes) { 40 | Component.contextTypes = Component.contextTypes || {} 41 | _.extend(Component.contextTypes, mixin.contextTypes) 42 | } 43 | _.extend(Component, mixin.statics) 44 | if (_.isFn(mixin.getDefaultProps)) { 45 | Component.defaultProps = Component.defaultProps || {} 46 | _.extend(Component.defaultProps, mixin.getDefaultProps()) 47 | } 48 | } 49 | 50 | function bindContext(obj, source) { 51 | for (let key in source) { 52 | if (source.hasOwnProperty(key)) { 53 | if (_.isFn(source[key])) { 54 | obj[key] = source[key].bind(obj) 55 | } 56 | } 57 | } 58 | } 59 | 60 | let Facade = function() {} 61 | Facade.prototype = Component.prototype 62 | 63 | function getInitialState() { 64 | let state = {} 65 | let setState = this.setState 66 | this.setState = Facade 67 | this.$getInitialStates.forEach(getInitialState => { 68 | if (_.isFn(getInitialState)) { 69 | _.extend(state, getInitialState.call(this)) 70 | } 71 | }) 72 | this.setState = setState 73 | return state 74 | } 75 | 76 | export default function createClass(spec) { 77 | if (!_.isFn(spec.render)) { 78 | throw new Error('createClass: spec.render is not function') 79 | } 80 | let specMixins = spec.mixins || [] 81 | let mixins = specMixins.concat(spec) 82 | spec.mixins = null 83 | function Klass(props, context) { 84 | Component.call(this, props, context) 85 | this.constructor = Klass 86 | spec.autobind !== false && bindContext(this, Klass.prototype) 87 | this.state = this.getInitialState() || this.state 88 | } 89 | Klass.displayName = spec.displayName 90 | let proto = Klass.prototype = new Facade() 91 | proto.$getInitialStates = [] 92 | eachMixin(mixins, mixin => { 93 | combineMixinToProto(proto, mixin) 94 | combineMixinToClass(Klass, mixin) 95 | }) 96 | proto.getInitialState = getInitialState 97 | spec.mixins = specMixins 98 | return Klass 99 | } -------------------------------------------------------------------------------- /src/createElement.js: -------------------------------------------------------------------------------- 1 | import * as _ from './util' 2 | import { 3 | VELEMENT, 4 | VSTATELESS, 5 | VCOMPONENT, 6 | VCOMMENT 7 | } from './constant' 8 | import { createVnode } from './virtual-dom' 9 | 10 | export default function createElement(type, props, children) { 11 | let vtype = null 12 | if (typeof type === 'string') { 13 | vtype = VELEMENT 14 | } else if (typeof type === 'function') { 15 | if (type.prototype && type.prototype.isReactComponent) { 16 | vtype = VCOMPONENT 17 | } else { 18 | vtype = VSTATELESS 19 | } 20 | } else { 21 | throw new Error(`React.createElement: unexpect type [ ${type} ]`) 22 | } 23 | 24 | let key = null 25 | let ref = null 26 | let finalProps = {} 27 | if (props != null) { 28 | for (let propKey in props) { 29 | if (!props.hasOwnProperty(propKey)) { 30 | continue 31 | } 32 | if (propKey === 'key') { 33 | if (props.key !== undefined) { 34 | key = '' + props.key 35 | } 36 | } else if (propKey === 'ref') { 37 | if (props.ref !== undefined) { 38 | ref = props.ref 39 | } 40 | } else { 41 | finalProps[propKey] = props[propKey] 42 | } 43 | } 44 | } 45 | 46 | let defaultProps = type.defaultProps 47 | 48 | if (defaultProps) { 49 | for (let propKey in defaultProps) { 50 | if (finalProps[propKey] === undefined) { 51 | finalProps[propKey] = defaultProps[propKey] 52 | } 53 | } 54 | } 55 | 56 | let argsLen = arguments.length 57 | let finalChildren = children 58 | 59 | if (argsLen > 3) { 60 | finalChildren = Array(argsLen - 2) 61 | for (let i = 2; i < argsLen; i++) { 62 | finalChildren[i - 2] = arguments[i] 63 | } 64 | } 65 | 66 | if (finalChildren !== undefined) { 67 | finalProps.children = finalChildren 68 | } 69 | 70 | return createVnode(vtype, type, finalProps, key, ref) 71 | } 72 | 73 | export function isValidElement(obj) { 74 | return obj != null && !!obj.vtype 75 | } 76 | 77 | export function cloneElement(originElem, props, ...children) { 78 | let { type, key, ref } = originElem 79 | let newProps = _.extend(_.extend({ key, ref }, originElem.props), props) 80 | let vnode = createElement(type, newProps, ...children) 81 | if (vnode.ref === originElem.ref) { 82 | vnode.refs = originElem.refs 83 | } 84 | return vnode 85 | } 86 | 87 | export function createFactory(type) { 88 | let factory = (...args) => createElement(type, ...args) 89 | factory.type = type 90 | return factory 91 | } -------------------------------------------------------------------------------- /src/event-system.js: -------------------------------------------------------------------------------- 1 | import { updateQueue } from './Component' 2 | import * as _ from './util' 3 | 4 | // event config 5 | export const unbubbleEvents = { 6 | /** 7 | * should not bind mousemove in document scope 8 | * even though mousemove event can bubble 9 | */ 10 | onmousemove: 1, 11 | ontouchmove: 1, 12 | onmouseleave: 1, 13 | onmouseenter: 1, 14 | onload: 1, 15 | onunload: 1, 16 | onscroll: 1, 17 | onfocus: 1, 18 | onblur: 1, 19 | onrowexit: 1, 20 | onbeforeunload: 1, 21 | onstop: 1, 22 | ondragdrop: 1, 23 | ondragenter: 1, 24 | ondragexit: 1, 25 | ondraggesture: 1, 26 | ondragover: 1, 27 | oncontextmenu: 1, 28 | onerror: 1, 29 | 30 | // media event 31 | onabort: 1, 32 | oncanplay: 1, 33 | oncanplaythrough: 1, 34 | ondurationchange: 1, 35 | onemptied: 1, 36 | onended: 1, 37 | onloadeddata: 1, 38 | onloadedmetadata: 1, 39 | onloadstart: 1, 40 | onencrypted: 1, 41 | onpause: 1, 42 | onplay: 1, 43 | onplaying: 1, 44 | onprogress: 1, 45 | onratechange: 1, 46 | onseeking: 1, 47 | onseeked: 1, 48 | onstalled: 1, 49 | onsuspend: 1, 50 | ontimeupdate: 1, 51 | onvolumechange: 1, 52 | onwaiting: 1, 53 | } 54 | 55 | export function getEventName(key) { 56 | if (key === 'onDoubleClick') { 57 | key = 'ondblclick' 58 | } else if (key === 'onTouchTap') { 59 | key = 'onclick' 60 | } 61 | 62 | return key.toLowerCase() 63 | } 64 | 65 | 66 | // Mobile Safari does not fire properly bubble click events on 67 | // non-interactive elements, which means delegated click listeners do not 68 | // fire. The workaround for this bug involves attaching an empty click 69 | // listener on the target node. 70 | let inMobile = 'ontouchstart' in document 71 | let emptyFunction = () => {} 72 | let ON_CLICK_KEY = 'onclick' 73 | 74 | let eventTypes = {} 75 | export function addEvent(elem, eventType, listener) { 76 | eventType = getEventName(eventType) 77 | 78 | let eventStore = elem.eventStore || (elem.eventStore = {}) 79 | eventStore[eventType] = listener 80 | 81 | if (unbubbleEvents[eventType] === 1) { 82 | elem[eventType] = dispatchUnbubbleEvent 83 | return 84 | } else if (!eventTypes[eventType]) { 85 | // onclick -> click 86 | document.addEventListener(eventType.substr(2), dispatchEvent, false) 87 | eventTypes[eventType] = true 88 | } 89 | 90 | if (inMobile && eventType === ON_CLICK_KEY) { 91 | elem.addEventListener('click', emptyFunction, false) 92 | return 93 | } 94 | 95 | let nodeName = elem.nodeName 96 | 97 | if (eventType === 'onchange' && supportInputEvent(elem)) { 98 | addEvent(elem, 'oninput', listener) 99 | } 100 | } 101 | 102 | export function removeEvent(elem, eventType) { 103 | eventType = getEventName(eventType) 104 | 105 | let eventStore = elem.eventStore || (elem.eventStore = {}) 106 | delete eventStore[eventType] 107 | 108 | if (unbubbleEvents[eventType] === 1) { 109 | elem[eventType] = null 110 | return 111 | } else if (inMobile && eventType === ON_CLICK_KEY) { 112 | elem.removeEventListener('click', emptyFunction, false) 113 | return 114 | } 115 | 116 | let nodeName = elem.nodeName 117 | 118 | if (eventType === 'onchange' && supportInputEvent(elem)) { 119 | delete eventStore['oninput'] 120 | } 121 | } 122 | 123 | function dispatchEvent(event) { 124 | let { target, type } = event 125 | let eventType = 'on' + type 126 | let syntheticEvent 127 | 128 | updateQueue.isPending = true 129 | while (target) { 130 | let { eventStore } = target 131 | let listener = eventStore && eventStore[eventType] 132 | if (!listener) { 133 | target = target.parentNode 134 | continue 135 | } 136 | if (!syntheticEvent) { 137 | syntheticEvent = createSyntheticEvent(event) 138 | } 139 | syntheticEvent.currentTarget = target 140 | listener.call(target, syntheticEvent) 141 | if (syntheticEvent.$cancelBubble) { 142 | break 143 | } 144 | target = target.parentNode 145 | } 146 | updateQueue.isPending = false 147 | updateQueue.batchUpdate() 148 | } 149 | 150 | function dispatchUnbubbleEvent(event) { 151 | let target = event.currentTarget || event.target 152 | let eventType = 'on' + event.type 153 | let syntheticEvent = createSyntheticEvent(event) 154 | 155 | syntheticEvent.currentTarget = target 156 | updateQueue.isPending = true 157 | 158 | let { eventStore } = target 159 | let listener = eventStore && eventStore[eventType] 160 | if (listener) { 161 | listener.call(target, syntheticEvent) 162 | } 163 | 164 | updateQueue.isPending = false 165 | updateQueue.batchUpdate() 166 | } 167 | 168 | function createSyntheticEvent(nativeEvent) { 169 | let syntheticEvent = {} 170 | let cancelBubble = () => syntheticEvent.$cancelBubble = true 171 | syntheticEvent.nativeEvent = nativeEvent 172 | syntheticEvent.persist = _.noop 173 | for (let key in nativeEvent) { 174 | if (typeof nativeEvent[key] !== 'function') { 175 | syntheticEvent[key] = nativeEvent[key] 176 | } else if (key === 'stopPropagation' || key === 'stopImmediatePropagation') { 177 | syntheticEvent[key] = cancelBubble 178 | } else { 179 | syntheticEvent[key] = nativeEvent[key].bind(nativeEvent) 180 | } 181 | } 182 | return syntheticEvent 183 | } 184 | 185 | function supportInputEvent(elem) { 186 | let nodeName = elem.nodeName && elem.nodeName.toLowerCase() 187 | return nodeName !== 'select' && !(nodeName === 'input' && elem.type === 'file') 188 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Component from './Component' 2 | import PureComponent from './PureComponent' 3 | import createClass from './createClass' 4 | import createElement, { isValidElement, cloneElement, createFactory } from './createElement' 5 | import * as Children from './Children' 6 | import * as ReactDOM from './ReactDOM' 7 | import PropTypes from './PropTypes' 8 | import DOM from './DOM' 9 | import * as _ from './util' 10 | 11 | let React = _.extend({ 12 | version: '0.15.1', 13 | cloneElement, 14 | isValidElement, 15 | createElement, 16 | createFactory, 17 | Component, 18 | PureComponent, 19 | createClass, 20 | Children, 21 | PropTypes, 22 | DOM 23 | }, ReactDOM) 24 | 25 | React.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactDOM 26 | 27 | export default React -------------------------------------------------------------------------------- /src/shallowEqual.js: -------------------------------------------------------------------------------- 1 | export default function shallowEqual(objA, objB) { 2 | if (objA === objB) { 3 | return true 4 | } 5 | 6 | if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { 7 | return false 8 | } 9 | 10 | var keysA = Object.keys(objA) 11 | var keysB = Object.keys(objB) 12 | 13 | if (keysA.length !== keysB.length) { 14 | return false 15 | } 16 | 17 | // Test for A's keys different from B. 18 | for (var i = 0; i < keysA.length; i++) { 19 | if (!objB.hasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) { 20 | return false 21 | } 22 | } 23 | 24 | return true 25 | } -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | // util 2 | import { addEvent, removeEvent } from './event-system' 3 | import { 4 | setStyle, 5 | removeStyle, 6 | patchStyle 7 | } from './CSSPropertyOperations.js' 8 | import { 9 | setPropValue, 10 | removePropValue, 11 | updateSelectOptions 12 | } from './DOMPropertyOperations' 13 | import { HTML_KEY } from './constant' 14 | 15 | export function isFn(obj) { 16 | return typeof obj === 'function' 17 | } 18 | 19 | export let isArr = Array.isArray 20 | 21 | export function noop() {} 22 | export function identity(obj) { 23 | return obj 24 | } 25 | export function pipe(fn1, fn2) { 26 | return function() { 27 | fn1.apply(this, arguments) 28 | return fn2.apply(this, arguments) 29 | } 30 | } 31 | 32 | export function addItem(list, item) { 33 | list[list.length] = item 34 | } 35 | 36 | export function flatEach(list, iteratee, a) { 37 | let len = list.length 38 | let i = -1 39 | 40 | while (len--) { 41 | let item = list[++i] 42 | if (isArr(item)) { 43 | flatEach(item, iteratee, a) 44 | } else { 45 | iteratee(item, a) 46 | } 47 | } 48 | } 49 | 50 | export function extend(to, from) { 51 | if (!from) { 52 | return to 53 | } 54 | var keys = Object.keys(from) 55 | var i = keys.length 56 | while (i--) { 57 | to[keys[i]] = from[keys[i]] 58 | } 59 | return to 60 | } 61 | 62 | 63 | let uid = 0 64 | export function getUid() { 65 | return ++uid 66 | } 67 | 68 | export let EVENT_KEYS = /^on/i 69 | 70 | function setProp(elem, key, value, isCustomComponent) { 71 | if (EVENT_KEYS.test(key)) { 72 | addEvent(elem, key, value) 73 | } else if (key === 'style') { 74 | setStyle(elem.style, value) 75 | } else if (key === HTML_KEY) { 76 | if (value && value.__html != null) { 77 | elem.innerHTML = value.__html 78 | } 79 | } else if (isCustomComponent) { 80 | if (value == null) { 81 | elem.removeAttribute(key) 82 | } else { 83 | elem.setAttribute(key, '' + value) 84 | } 85 | } else { 86 | setPropValue(elem, key, value) 87 | } 88 | } 89 | 90 | function removeProp(elem, key, oldValue, isCustomComponent) { 91 | if (EVENT_KEYS.test(key)) { 92 | removeEvent(elem, key) 93 | } else if (key === 'style') { 94 | removeStyle(elem.style, oldValue) 95 | } else if (key === HTML_KEY) { 96 | elem.innerHTML = '' 97 | } else if (isCustomComponent) { 98 | elem.removeAttribute(key) 99 | } else { 100 | removePropValue(elem, key) 101 | } 102 | } 103 | 104 | function patchProp(elem, key, value, oldValue, isCustomComponent) { 105 | if (key === 'value' || key === 'checked') { 106 | oldValue = elem[key] 107 | } 108 | if (value === oldValue) { 109 | return 110 | } 111 | if (value === undefined) { 112 | removeProp(elem, key, oldValue, isCustomComponent) 113 | return 114 | } 115 | if (key === 'style') { 116 | patchStyle(elem.style, oldValue, value) 117 | } else { 118 | setProp(elem, key, value, isCustomComponent) 119 | } 120 | } 121 | 122 | export function setProps(elem, props, isCustomComponent) { 123 | var isSelect = elem.nodeName === 'SELECT' 124 | for (let key in props) { 125 | if (key !== 'children') { 126 | if (isSelect && (key === 'value' || key === 'defaultValue')) { 127 | updateSelectOptions(elem, props.multiple, props[key]) 128 | } else { 129 | setProp(elem, key, props[key], isCustomComponent) 130 | } 131 | } 132 | } 133 | } 134 | 135 | export function patchProps(elem, props, newProps, isCustomComponent) { 136 | var isSelect = elem.nodeName === 'SELECT' 137 | for (let key in props) { 138 | if (key !== 'children') { 139 | if (newProps.hasOwnProperty(key)) { 140 | if (isSelect && (key === 'value' || key === 'defaultValue')) { 141 | updateSelectOptions(elem, newProps.multiple, newProps[key]) 142 | } else { 143 | patchProp(elem, key, newProps[key], props[key], isCustomComponent) 144 | } 145 | } else { 146 | removeProp(elem, key, props[key], isCustomComponent) 147 | } 148 | } 149 | } 150 | for (let key in newProps) { 151 | if (key !== 'children' && !props.hasOwnProperty(key)) { 152 | if (isSelect && (key === 'value' || key === 'defaultValue')) { 153 | updateSelectOptions(elem, newProps.multiple, newProps[key]) 154 | } else { 155 | setProp(elem, key, newProps[key], isCustomComponent) 156 | } 157 | } 158 | } 159 | } 160 | 161 | if (!Object.freeze) { 162 | Object.freeze = identity 163 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | watch: true, 6 | entry: { 7 | simple: './examples/simple/' 8 | }, 9 | output: { 10 | path: './examples/', 11 | filename: '[name]/app.js' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | loader: 'babel-loader', 17 | query: { 18 | stage: 0 19 | }, 20 | exclude: /node_modules/ 21 | }] 22 | }, 23 | resolve: { 24 | extensions: ['', '.js'], 25 | root: __dirname 26 | } 27 | }; 28 | --------------------------------------------------------------------------------