summaryrefslogtreecommitdiff
path: root/frontend/delta/js
Side-by-side diff
Diffstat (limited to 'frontend/delta/js') (more/less context) (ignore whitespace changes)
-rw-r--r--frontend/delta/js/Clipperz/PM/DataModel/DevicePreferences.js90
-rw-r--r--frontend/delta/js/Clipperz/PM/Proxy.js1
-rwxr-xr-xfrontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js4
-rw-r--r--frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js4
-rw-r--r--frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js85
-rw-r--r--frontend/delta/js/Clipperz/PM/UI/Components/CardList.js13
-rw-r--r--frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js44
-rw-r--r--frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js6
-rw-r--r--frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js7
-rw-r--r--frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js88
-rw-r--r--frontend/delta/js/Clipperz/PM/UI/MainController.js130
-rw-r--r--frontend/delta/js/React/react-0.5.0-alpha.js13239
-rw-r--r--frontend/delta/js/main.js3
13 files changed, 13667 insertions, 47 deletions
diff --git a/frontend/delta/js/Clipperz/PM/DataModel/DevicePreferences.js b/frontend/delta/js/Clipperz/PM/DataModel/DevicePreferences.js
new file mode 100644
index 0000000..ff3b33f
--- a/dev/null
+++ b/frontend/delta/js/Clipperz/PM/DataModel/DevicePreferences.js
@@ -0,0 +1,90 @@
+/*
+
+Copyright 2008-2013 Clipperz Srl
+
+This file is part of Clipperz, the online password manager.
+For further information about its features and functionalities please
+refer to http://www.clipperz.com.
+
+* Clipperz is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+* Clipperz is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU Affero General Public License for more details.
+
+* You should have received a copy of the GNU Affero General Public
+ License along with Clipperz. If not, see http://www.gnu.org/licenses/.
+
+*/
+
+Clipperz.PM.DataModel.DevicePreferences = function (args) {
+ args = args || {};
+
+ this._data = null;
+
+ Clipperz.PM.DataModel.DevicePreferences.superclass.constructor.apply(this, arguments);
+
+ return this;
+}
+
+Clipperz.Base.extend(Clipperz.PM.DataModel.DevicePreferences, Object, {
+
+ toString: function () {
+ return "Clipperz.PM.DataModel.DevicePreferences";
+ },
+
+ //-------------------------------------------------------------------------
+
+ shouldStoreDataLocally: function () {
+ return (localStorage.getItem('shouldStoreDataLocally') === 'true');
+ },
+
+ setShouldStoreDataLocally: function (aValue) {
+ localStorage.setItem('shouldStoreDataLocally', aValue);
+ },
+
+ //-------------------------------------------------------------------------
+
+ setAccountDataWityResponse: function (aResponse) {
+ localStorage.setItem('clipperz_dump_data', aResponse['data']);
+ localStorage.setItem('clipperz_dump_version', aResponse['version']);
+ localStorage.setItem('clipperz_dump_date', new Date());
+
+ this._data = null;
+ },
+
+ accountData: function () {
+ if (this._data == null) {
+ var data;
+
+ data = localStorage.getItem('clipperz_dump_data');
+ if (data != null) {
+ this._data = JSON.parse(data);
+ }
+ }
+
+ return this._data;
+ },
+
+ latestDownload: function () {
+ var result;
+ var date;
+
+ date = localStorage.getItem('clipperz_dump_date');
+ if (date != null) {
+ result = new Date(date);
+ } else {
+ result = null;
+ }
+
+ return result;
+ },
+
+ //=========================================================================
+ __syntaxFix__: "syntax fix"
+});
+
diff --git a/frontend/delta/js/Clipperz/PM/Proxy.js b/frontend/delta/js/Clipperz/PM/Proxy.js
index 2ac684a..71c784f 100644
--- a/frontend/delta/js/Clipperz/PM/Proxy.js
+++ b/frontend/delta/js/Clipperz/PM/Proxy.js
@@ -1,186 +1,187 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
if (typeof(Clipperz) == 'undefined') { Clipperz = {}; }
if (typeof(Clipperz.PM) == 'undefined') { Clipperz.PM = {}; }
//=============================================================================
Clipperz.PM.Proxy = function(args) {
args = args || {};
this._shouldPayTolls = args.shouldPayTolls || false;
this._tolls = {
'CONNECT': [],
'REGISTER': [],
'MESSAGE': []
};
if (args.isDefault === true) {
Clipperz.PM.Proxy.defaultProxy = this;
}
return this;
}
Clipperz.PM.Proxy.prototype = MochiKit.Base.update(null, {
'toString': function() {
return "Clipperz.PM.Proxy";
},
//=========================================================================
'shouldPayTolls': function() {
return this._shouldPayTolls;
},
//-------------------------------------------------------------------------
'tolls': function() {
return this._tolls;
},
//-------------------------------------------------------------------------
'payToll': function(aRequestType, someParameters) {
var deferredResult;
if (this.shouldPayTolls()) {
deferredResult = new Clipperz.Async.Deferred("Proxy.payToll", {trace:false});
if (this.tolls()[aRequestType].length == 0) {
deferredResult.addMethod(this, 'sendMessage', 'knock', {requestType:aRequestType});
deferredResult.addMethod(this, 'setTollCallback');
}
deferredResult.addMethod(this.tolls()[aRequestType], 'pop');
deferredResult.addCallback(MochiKit.Base.methodcaller('deferredPay'));
deferredResult.addCallback(function(aToll) {
var result;
result = {
parameters: someParameters,
toll: aToll
}
return result;
});
deferredResult.callback();
} else {
deferredResult = MochiKit.Async.succeed({parameters:someParameters});
}
return deferredResult;
},
//-------------------------------------------------------------------------
'addToll': function(aToll) {
this.tolls()[aToll.requestType()].push(aToll);
},
//=========================================================================
'setTollCallback': function(someParameters) {
if (typeof(someParameters['toll']) != 'undefined') {
this.addToll(new Clipperz.PM.Toll(someParameters['toll']));
}
return someParameters['result'];
},
//=========================================================================
'registration': function (someParameters) {
return this.processMessage('registration', someParameters, 'REGISTER');
},
'handshake': function (someParameters) {
return this.processMessage('handshake', someParameters, 'CONNECT');
},
'message': function (someParameters) {
return this.processMessage('message', someParameters, 'MESSAGE');
},
'logout': function (someParameters) {
return this.processMessage('logout', someParameters, 'MESSAGE');
},
//=========================================================================
'processMessage': function (aFunctionName, someParameters, aRequestType) {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred("Proxy.processMessage", {trace:false});
deferredResult.addMethod(this, 'payToll', aRequestType);
deferredResult.addMethod(this, 'sendMessage', aFunctionName);
deferredResult.addMethod(this, 'setTollCallback');
deferredResult.callback(someParameters);
return deferredResult;
},
//=========================================================================
'_sendMessage': function (aFunctionName, aVersion, someParameters) {
throw Clipperz.Base.exception.AbstractMethod;
},
'sendMessage': function (aFunctionName, someParameters) {
var deferredResult;
+console.log("PROXY.sendMessage", aFunctionName, someParameters);
// TODO: read actual application version for a property set at build time
deferredResult = new Clipperz.Async.Deferred("Proxy.sendMessage", {trace:false});
deferredResult.addMethod(this, '_sendMessage', aFunctionName, 'fake-app-version');
deferredResult.addErrback(MochiKit.Base.method(this, 'handleError'));
deferredResult.callback(someParameters);
return deferredResult;
},
//-------------------------------------------------------------------------
'handleError': function (anError) {
if (anError['message'] == 'Wrong application version') {
anError['isPermanent'] = true;
}
return anError;
},
//=========================================================================
'isReadOnly': function () {
return false;
},
'canRegisterNewUsers': function () {
return true;
},
//=========================================================================
__syntaxFix__: "syntax fix"
});
diff --git a/frontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js b/frontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js
index 1638d99..6deee3d 100755
--- a/frontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js
+++ b/frontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js
@@ -1,86 +1,86 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
if (typeof(Clipperz) == 'undefined') { Clipperz = {}; }
if (typeof(Clipperz.PM) == 'undefined') { Clipperz.PM = {}; }
//=============================================================================
Clipperz.PM.Proxy.JSON = function(args) {
Clipperz.PM.Proxy.JSON.superclass.constructor.call(this, args);
this._url = args.url || Clipperz.Base.exception.raise('MandatoryParameter');
return this;
}
Clipperz.Base.extend(Clipperz.PM.Proxy.JSON, Clipperz.PM.Proxy, {
'toString': function() {
return "Clipperz.PM.Proxy.JSON";
},
//=========================================================================
'url': function () {
return this._url;
},
//=========================================================================
'_sendMessage': function(aFunctionName, aVersion, someParameters) {
var deferredResult;
var parameters;
parameters = {
method: aFunctionName,
version: aVersion,
parameters: Clipperz.Base.serializeJSON(someParameters)
};
-
- deferredResult = new Clipperz.Async.Deferred("Proxy.JSON.sendMessage", {trace:false});
+console.log("PROXY.JSON._sendMessage", parameters);
+ deferredResult = new Clipperz.Async.Deferred("Proxy.JSON._sendMessage", {trace:false});
deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'remoteRequestSent');
deferredResult.addCallback(MochiKit.Async.doXHR, this.url(), {
method:'POST',
sendContent:MochiKit.Base.queryString(parameters),
headers:{"Content-Type":"application/x-www-form-urlencoded"}
});
deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'remoteRequestReceived');
deferredResult.addCallback(MochiKit.Base.itemgetter('responseText'));
deferredResult.addCallback(Clipperz.Base.evalJSON);
deferredResult.addCallback(function (someValues) {
if (someValues['result'] == 'EXCEPTION') {
throw someValues['message'];
}
return someValues;
})
deferredResult.callback();
return deferredResult;
},
//=========================================================================
__syntaxFix__: "syntax fix"
});
diff --git a/frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js b/frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js
index a3c238c..3f16f70 100644
--- a/frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js
+++ b/frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js
@@ -1,289 +1,291 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
try { if (typeof(Clipperz.PM.Proxy.Offline.DataStore) == 'undefined') { throw ""; }} catch (e) {
throw "Clipperz.PM.Proxy.Offline.LocalStorageDataStore depends on Clipperz.PM.Proxy.Offline.DataStore!";
}
//=============================================================================
Clipperz.PM.Proxy.Offline.LocalStorageDataStore = function(args) {
args = args || {};
- this._data = args.data || (typeof(_clipperz_dump_data_) != 'undefined' ? _clipperz_dump_data_ : null);
+// this._data = args.data || (typeof(_clipperz_dump_data_) != 'undefined' ? _clipperz_dump_data_ : null);
+ this._data = JSON.parse(localStorage.getItem('clipperz_dump_data'));
+
this._isReadOnly = (typeof(args.readOnly) == 'undefined' ? true : args.readOnly);
this._shouldPayTolls = args.shouldPayTolls || false;
this._tolls = {};
this._currentStaticConnection = null;
// Clipperz.PM.Proxy.Offline.LocalStorageDataStore.superclass.constructor.apply(this, arguments);
return this;
}
Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.LocalStorageDataStore, Clipperz.PM.Proxy.Offline.DataStore, {
//=========================================================================
'_knock': function(aConnection, someParameters) {
var result;
result = {
toll: this.getTollForRequestType(someParameters['requestType'])
}
return result;
},
//-------------------------------------------------------------------------
'_registration': function(aConnection, someParameters) {
throw Clipperz.PM.Proxy.Offline.DataStore.exception.ReadOnly;
},
//-------------------------------------------------------------------------
'_handshake': function(aConnection, someParameters) {
var result;
var nextTollRequestType;
result = {};
if (someParameters.message == "connect") {
var userData;
var randomBytes;
var v;
userData = this.data()['users'][someParameters.parameters.C];
if ((typeof(userData) != 'undefined') && (userData['version'] == someParameters.version)) {
aConnection['userData'] = userData;
aConnection['C'] = someParameters.parameters.C;
} else {
aConnection['userData'] = this.data()['users']['catchAllUser'];
}
randomBytes = Clipperz.Crypto.Base.generateRandomSeed();
aConnection['b'] = new Clipperz.Crypto.BigInt(randomBytes, 16);
v = new Clipperz.Crypto.BigInt(aConnection['userData']['v'], 16);
aConnection['B'] = v.add(Clipperz.Crypto.SRP.g().powerModule(aConnection['b'], Clipperz.Crypto.SRP.n()));
aConnection['A'] = someParameters.parameters.A;
result['s'] = aConnection['userData']['s'];
result['B'] = aConnection['B'].asString(16);
nextTollRequestType = 'CONNECT';
} else if (someParameters.message == "credentialCheck") {
var v, u, S, A, K, M1;
v = new Clipperz.Crypto.BigInt(aConnection['userData']['v'], 16);
u = new Clipperz.Crypto.BigInt(Clipperz.PM.Crypto.encryptingFunctions.versions[someParameters.version].hash(new Clipperz.ByteArray(aConnection['B'].asString(10))).toHexString(), 16);
A = new Clipperz.Crypto.BigInt(aConnection['A'], 16);
S = (A.multiply(v.powerModule(u, Clipperz.Crypto.SRP.n()))).powerModule(aConnection['b'], Clipperz.Crypto.SRP.n());
K = Clipperz.PM.Crypto.encryptingFunctions.versions[someParameters.version].hash(new Clipperz.ByteArray(S.asString(10))).toHexString().slice(2);
M1 = Clipperz.PM.Crypto.encryptingFunctions.versions[someParameters.version].hash(new Clipperz.ByteArray(A.asString(10) + aConnection['B'].asString(10) + K)).toHexString().slice(2);
if (someParameters.parameters.M1 == M1) {
var M2;
M2 = Clipperz.PM.Crypto.encryptingFunctions.versions[someParameters.version].hash(new Clipperz.ByteArray(A.asString(10) + someParameters.parameters.M1 + K)).toHexString().slice(2);
result['M2'] = M2;
} else {
throw new Error("Client checksum verification failed! Expected <" + M1 + ">, received <" + someParameters.parameters.M1 + ">.", "Error");
}
nextTollRequestType = 'MESSAGE';
} else if (someParameters.message == "oneTimePassword") {
var otpData;
otpData = this.data()['onetimePasswords'][someParameters.parameters.oneTimePasswordKey];
try {
if (typeof(otpData) != 'undefined') {
if (otpData['status'] == 'ACTIVE') {
if (otpData['key_checksum'] == someParameters.parameters.oneTimePasswordKeyChecksum) {
result = {
'data': otpData['data'],
'version': otpData['version']
}
otpData['status'] = 'REQUESTED';
} else {
otpData['status'] = 'DISABLED';
throw "The requested One Time Password has been disabled, due to a wrong keyChecksum";
}
} else {
throw "The requested One Time Password was not active";
}
} else {
throw "The requested One Time Password has not been found"
}
} catch (exception) {
result = {
'data': Clipperz.PM.Crypto.randomKey(),
'version': Clipperz.PM.Connection.communicationProtocol.currentVersion
}
}
nextTollRequestType = 'CONNECT';
} else {
Clipperz.logError("Clipperz.PM.Proxy.Test.handshake - unhandled message: " + someParameters.message);
}
result = {
result: result,
toll: this.getTollForRequestType(nextTollRequestType)
}
return result;
},
//-------------------------------------------------------------------------
'_message': function(aConnection, someParameters) {
var result;
result = {};
//=====================================================================
//
// R E A D - O N L Y M e t h o d s
//
//=====================================================================
if (someParameters.message == 'getUserDetails') {
var recordsStats;
var recordReference;
recordsStats = {};
for (recordReference in aConnection['userData']['records']) {
recordsStats[recordReference] = {
'updateDate': aConnection['userData']['records'][recordReference]['updateDate']
}
}
result['header'] = this.userDetails(aConnection);
result['statistics'] = this.statistics(aConnection);
result['maxNumberOfRecords'] = aConnection['userData']['maxNumberOfRecords'];
result['version'] = aConnection['userData']['userDetailsVersion'];
result['recordsStats'] = recordsStats;
if (this.isReadOnly() == false) {
var lock;
if (typeof(aConnection['userData']['lock']) == 'undefined') {
aConnection['userData']['lock'] = "<<LOCK>>";
}
result['lock'] = aConnection['userData']['lock'];
}
//=====================================================================
} else if (someParameters.message == 'getRecordDetail') {
/*
var recordData;
var currentVersionData;
recordData = this.userData()['records'][someParameters['parameters']['reference']];
result['reference'] = someParameters['parameters']['reference'];
result['data'] = recordData['data'];
result['version'] = recordData['version'];
result['creationData'] = recordData['creationDate'];
result['updateDate'] = recordData['updateDate'];
result['accessDate'] = recordData['accessDate'];
currentVersionData = recordData['versions'][recordData['currentVersion']];
result['currentVersion'] = {};
result['currentVersion']['reference'] = recordData['currentVersion'];
result['currentVersion']['version'] = currentVersionData['version'];
result['currentVersion']['header'] = currentVersionData['header'];
result['currentVersion']['data'] = currentVersionData['data'];
result['currentVersion']['creationData'] = currentVersionData['creationDate'];
result['currentVersion']['updateDate'] = currentVersionData['updateDate'];
result['currentVersion']['accessDate'] = currentVersionData['accessDate'];
if (typeof(currentVersionData['previousVersion']) != 'undefined') {
result['currentVersion']['previousVersionKey'] = currentVersionData['previousVersionKey'];
result['currentVersion']['previousVersion'] = currentVersionData['previousVersion'];
}
*/
MochiKit.Base.update(result, aConnection['userData']['records'][someParameters['parameters']['reference']]);
result['reference'] = someParameters['parameters']['reference'];
//=====================================================================
//
// R E A D - W R I T E M e t h o d s
//
//=====================================================================
} else if (someParameters.message == 'upgradeUserCredentials') {
if (this.isReadOnly() == false) {
var parameters;
var credentials;
parameters = someParameters['parameters'];
credentials = parameters['credentials'];
if ((credentials['C'] == null)
|| (credentials['s'] == null)
|| (credentials['v'] == null)
|| (credentials['version'] != Clipperz.PM.Connection.communicationProtocol.currentVersion)
) {
result = Clipperz.PM.DataModel.User.exception.CredentialUpgradeFailed;
} else {
var oldCValue;
oldCValue = aConnection['C'];
this.data()['users'][credentials['C']] = aConnection['userData'];
aConnection['C'] = credentials['C'];
aConnection['userData']['s'] = credentials['s'];
aConnection['userData']['v'] = credentials['v'];
aConnection['userData']['version'] = credentials['version'];
aConnection['userData']['userDetails'] = parameters['user']['header'];
aConnection['userData']['userDetailsVersion'] = parameters['user']['version'];
aConnection['userData']['statistics'] = parameters['user']['statistics'];
aConnection['userData']['lock'] = parameters['user']['lock'];
delete this.data()['users'][oldCValue];
result = {result:"done", parameters:parameters};
}
} else {
throw Clipperz.PM.Proxy.Offline.DataStore.exception.ReadOnly;
}
//=====================================================================
} else if (someParameters.message == 'saveChanges') {
if (this.isReadOnly() == false) {
var i, c;
if (aConnection['userData']['lock'] != someParameters['parameters']['user']['lock']) {
throw "the lock attribute is not processed correctly"
}
aConnection['userData']['userDetails'] = someParameters['parameters']['user']['header'];
aConnection['userData']['statistics'] = someParameters['parameters']['user']['statistics'];
aConnection['userData']['userDetailsVersion'] = someParameters['parameters']['user']['version'];
diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js b/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js
index df514a2..12ddce3 100644
--- a/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js
+++ b/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js
@@ -1,142 +1,195 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
Clipperz.PM.UI.Components.CardDetail = React.createClass({
getDefaultProps: function () {
return {
// searchDelay: 0.3
}
},
propTypes: {
card: React.PropTypes.object.isRequired
},
getInitialState: function () {
return {
// showSearch: false,
// searchTimer: null,
+ unmaskedFields: new Clipperz.Set(),
starred: false
};
},
handleDirectLoginClick: function (aDirectLoginReference, anEvent) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'runDirectLogin', {record:this.props.card['reference'], directLogin:aDirectLoginReference});
},
+ toggleFieldVisibility: function (aField, anEvent) {
+ var unmaskedFields;
+ var fieldReference;
+
+ unmaskedFields = this.state['unmaskedFields'];
+ fieldReference = aField['reference']
+ if (unmaskedFields.contains(fieldReference)) {
+ unmaskedFields.remove(fieldReference)
+ } else {
+ unmaskedFields.add(fieldReference)
+ }
+
+ this.setState({'unmaskedFields': unmaskedFields});
+ },
+
+ handleGoAction: function (aField, anEvent) {
+ var newWindow;
+
+ newWindow = MochiKit.DOM.currentWindow().open(aField['value'], '_blank');
+ newWindow.focus();
+ },
+
+ handleEmailAction: function (aField, anEvent) {
+ MochiKit.DOM.currentWindow().location = 'mailto:' + aField['value'];
+ },
+
//=========================================================================
normalizeFieldValue: function (aValue) {
var result = [];
var rows = aValue.split('\n');
for (var i = 0; i < rows.length; i++) {
if (i > 0) {
result.push(React.DOM.br());
}
result.push(rows[i].replace(/[\s]/g, '\u00A0'));
}
return result;
},
- renderField: function (aField) {
-//console.log("FIELD", aField);
- var actionLabel;
+ renderFieldActionButton: function (aField) {
+// var actionLabel;
+ var result;
if (aField['actionType'] == 'URL') {
- actionLabel = "go";
+ result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'handleGoAction', aField)}, [
+ React.DOM.a({className:aField['actionType']}, "go")
+ ]);
} else if (aField['actionType'] == 'PASSWORD') {
- actionLabel = "locked";
+ var icon;
+
+ if (this.state['unmaskedFields'].contains(aField['reference'])) {
+ icon = "unlocked";
+ } else {
+ icon = "locked";
+ }
+ result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'toggleFieldVisibility', aField)}, [
+ React.DOM.a({className:aField['actionType']}, icon)
+ ]);
} else if (aField['actionType'] == 'EMAIL') {
- actionLabel = "email";
+ result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'handleEmailAction', aField)}, [
+ React.DOM.a({className:aField['actionType']}, "email")
+ ]);
} else {
- actionLabel = "";
+ result = null;
+ }
+
+ return result;
+ },
+
+ renderField: function (aField) {
+//console.log("FIELD", aField);
+ var fieldExtraClass;
+
+ fieldExtraClass = aField['actionType'];
+ if (this.state['unmaskedFields'].contains(aField['reference'])) {
+ fieldExtraClass = fieldExtraClass + ' unlocked';
}
- return React.DOM.div({className:'listItem ' + aField['actionType']}, [
+ return React.DOM.div({className:'listItem ' + fieldExtraClass, key:aField['reference']}, [
React.DOM.div({className:'fieldWrapper'}, [
React.DOM.div({className:'fieldInnerWrapper'}, [
React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aField['label'])),
- React.DOM.div({className:'valueWrapper'}, React.DOM.span({className:'value ' + aField['actionType']}, this.normalizeFieldValue(aField['value'])))
+ React.DOM.div({className:'valueWrapper'}, React.DOM.span({className:'value ' + fieldExtraClass}, this.normalizeFieldValue(aField['value'])))
])
]),
- React.DOM.div({className:'actionWrapper'}, [
- React.DOM.div({className:aField['actionType']}, actionLabel)
- ])
+ this.renderFieldActionButton(aField)
+// React.DOM.div({className:'actionWrapper'}, [
+// React.DOM.div({className:aField['actionType']}, actionLabel)
+// ])
]);
},
renderDirectLogin: function (aDirectLogin) {
//console.log("DIRECT LOGIN", aDirectLogin);
return React.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleDirectLoginClick', aDirectLogin['reference'])}, [
React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aDirectLogin['label'])),
React.DOM.div({className:'faviconWrapper'}, React.DOM.img({className:'favicon', src:aDirectLogin['favicon']})),
React.DOM.div({className:'directLoginLinkWrapper'}, React.DOM.span({className:'directLoginLink'}, "go"))
]);
},
handleBackClick: function (anEvent) {
- window.history.back();
+// window.history.back();
+ MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack');
},
handleStarClick: function (anEvent) {
this.setState({starred: !this.state['starred']});
},
//=========================================================================
render: function () {
var card = this.props.card;
- var starredStatus = (this.state['starred'] ? "starred" : "unstarred");
+// var starredStatus = (this.state['starred'] ? "starred" : "unstarred");
if ((typeof(card['fields']) != 'undefined') && (card['notes'] != '')) {
card['fields'].push({ 'actionType': 'NOTES', 'isHidden': false, 'label': "notes", 'reference': "notes", 'value': card['notes'] })
}
return React.DOM.div({className:'cardDetail'}, [
React.DOM.div({className:'header'}, [
React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title)),
-// React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title + ' ' + card.title + ' ' + card.title + ' ' + card.title)),
React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")),
- React.DOM.div({className:'starWrapper'}, React.DOM.a({className:'star', onClick:this.handleStarClick}, starredStatus))
+// React.DOM.div({className:'starWrapper'}, React.DOM.a({className:'star', onClick:this.handleStarClick}, starredStatus))
]),
React.DOM.div({className:'content'}, [
card.fields ? React.DOM.div({className:'fields'}, MochiKit.Base.map(this.renderField, card.fields)) : null,
card.directLogins ? React.DOM.div({className:'directLogins'}, MochiKit.Base.map(this.renderDirectLogin, card.directLogins)): null
]),
React.DOM.div({className:'footer'}, [
/*
// React.DOM.a({className:'cancel'}, "cancel"),
// React.DOM.a({className:'save'}, "save")
React.DOM.a({className:'cancel button'}, "failed"),
React.DOM.a({className:'save button'}, "done")
*/
])
]);
}
//=========================================================================
});
diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js b/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js
index 66d20f1..5a44a4a 100644
--- a/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js
+++ b/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js
@@ -1,161 +1,168 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
Clipperz.PM.UI.Components.CardList = React.createClass({
getDefaultProps: function () {
return {
selectedCard: null,
searchDelay: 0.3
}
},
propTypes: {
searchDelay: React.PropTypes.number
},
getInitialState: function () {
return {
showSearch: false,
searchTimer: null,
searchText: '',
// passphrase: '',
// pin: ''
};
},
//=========================================================================
toggleSearch: function (anEvent) {
var showSearchBox;
showSearchBox = !this.state.showSearch;
this.setState({showSearch: showSearchBox});
if (showSearchBox) {
MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'focusOnSearchField'));
}
},
updateSearchText: function (anEvent) {
var searchText;
searchText = anEvent.target.value;
//console.log(">>> updateSearchText", searchText);
if ((this.state['searchTimer'] != null) && (searchText != this.state['searchText'])) {
this.state['searchTimer'].cancel();
}
if (searchText != this.state['searchText']) {
this.state['searchText'] = searchText;
this.state['searchTimer'] = MochiKit.Async.callLater(this.props['searchDelay'], MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'searchCards', searchText);
}
},
focusOnSearchField: function () {
console.log("focusOnSearchField", this.refs['searchField']);
this.refs['searchField'].getDOMNode.focus();
},
searchBox: function () {
var result;
if (this.state.showSearch) {
result = React.DOM.div({className:'searchBox'}, [
React.DOM.div(null, [
React.DOM.input({type:'search', placeholder:"search", ref:'searchField', onChange:this.updateSearchText})
])
]);
} else {
result = null;
}
return result;
},
//=========================================================================
+ showPreferences: function (anEvent) {
+ MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showPreferences', anEvent);
+ },
+
+ //=========================================================================
+
cardItem: function (aRecordReference) {
var reference = aRecordReference['_reference'];
var selectedCard = (reference == this.props.selectedCard);
- return React.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleClickOnCardDetail', reference)}, [
+ // TODO: verify if it is possible to put the onClick handler on the container 'div', instead of adding it to each 'div' item.
+ return React.DOM.div({className:'listItem', key:reference, onClick:MochiKit.Base.method(this, 'handleClickOnCardDetail', reference)}, [
React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label)),
// React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label)),
React.DOM.div({className:'faviconWrapper'}, aRecordReference.favicon ? React.DOM.img({className:'favicon', src:aRecordReference.favicon}) : React.DOM.div({className:'favicon'}, '\u00A0')),
React.DOM.div({className:'detailLinkWrapper'}, React.DOM.span({className:'detailLink ' + (selectedCard ? 'icon-spin' : '')}, (selectedCard ? "loading" : "detail")))
]);
},
handleClickOnCardDetail: function (aRecordReference, anEvent) {
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRecord', aRecordReference);
},
cardListItems: function () {
var list;
var result;
list = this.props['cardList'];
if (typeof(list) != 'undefined') {
result = MochiKit.Base.map(MochiKit.Base.method(this, 'cardItem'), list);
} else {
result = null;
}
return result;
},
//=========================================================================
handleChange: function (anEvent) {
// var refs = this.refs;
// var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0];
// var newState = {};
//
// newState[refName] = event.target.value;
// this.setState(newState);
},
//=========================================================================
render: function() {
return React.DOM.div(null, [
React.DOM.div({className:'header'}, [
React.DOM.a({className:'account'}, 'clipperz'),
React.DOM.div({className:'features'}, [
- React.DOM.a({className:'addCard'}, 'add'),
+// React.DOM.a({className:'addCard'}, 'add'),
React.DOM.a({className:'search ' + (this.state.showSearch ? 'selected' : ''), onClick:this.toggleSearch}, 'search'),
- React.DOM.a({className:'settings'}, 'settings')
+ React.DOM.a({className:'settings', onClick:this.showPreferences}, 'settings')
]),
// this.searchBox()
]),
this.searchBox(),
React.DOM.div({className:'content cardList'}, this.cardListItems()),
]);
}
//=========================================================================
});
diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js b/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js
new file mode 100644
index 0000000..9538063
--- a/dev/null
+++ b/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js
@@ -0,0 +1,44 @@
+/*
+
+Copyright 2008-2013 Clipperz Srl
+
+This file is part of Clipperz, the online password manager.
+For further information about its features and functionalities please
+refer to http://www.clipperz.com.
+
+* Clipperz is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+* Clipperz is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU Affero General Public License for more details.
+
+* You should have received a copy of the GNU Affero General Public
+ License along with Clipperz. If not, see http://www.gnu.org/licenses/.
+
+*/
+
+Clipperz.PM.UI.Components.Checkbox = React.createClass({
+// http://development.tobypitman.com/iphoneCheckboxes/iphoneCheckboxes2.html
+
+ propTypes: {
+ 'checked': React.PropTypes.bool.isRequired,
+ 'id': React.PropTypes.string.isRequired,
+ 'eventHandler': React.PropTypes.func.isRequired
+ },
+
+ //=========================================================================
+
+ render: function () {
+ return React.DOM.div({className:'checkbox', onClick:this.props['eventHandler']}, [
+ React.DOM.input({name:this.props['id'], id:this.props['id'], value:this.props['id'], type:'checkbox', checked:this.props['checked']}),
+ React.DOM.label({className:'check', 'for':this.props['id']}),
+ React.DOM.label({className:'info', 'for':this.props['id']}, "enable local storage")
+ ]);
+ }
+
+ //=========================================================================
+});
diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js b/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js
index 2b5b4a4..801549f 100644
--- a/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js
+++ b/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js
@@ -1,150 +1,150 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
Clipperz.PM.UI.Components.LoginForm = React.createClass({
getDefaultProps: function () {
return {
mode: 'CREDENTIALS',
isNewUserRegistrationAvailable: false,
disabled: false,
template: Clipperz.PM.UI.Components.PageTemplate
}
},
propTypes: {
mode: React.PropTypes.oneOf(['CREDENTIALS','PIN']),
isNewUserRegistrationAvailable: React.PropTypes.bool,
disabled: React.PropTypes.bool,
template: React.PropTypes.func
},
getInitialState: function () {
return {
username: '',
passphrase: '',
pin: ''
};
},
//=========================================================================
handleChange: function (anEvent) {
var refs = this.refs;
var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0];
var newState = {};
newState[refName] = event.target.value;
this.setState(newState);
},
//=========================================================================
handleCredentialSubmit: function (event) {
event.preventDefault();
this.refs['passphrase'].getDOMNode().blur();
var credentials = {
'username': this.refs['username'].getDOMNode().value,
'passphrase': this.refs['passphrase'].getDOMNode().value
}
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials);
},
handleRegistrationLinkClick: function (event) {
event.preventDefault();
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRegistrationForm');
},
//-------------------------------------------------------------------------
shouldEnableLoginButton: function () {
var result;
return (
((this.state['username'] != '') && (this.state['passphrase'] != ''))
||
(this.state['pin'] != '')
) && !this.props['disabled'];
},
loginForm: function () {
registrationLink = React.DOM.div({'className':'registrationLink'}, [
- React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Need an account")
+ React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Sign up")
]);
return React.DOM.div({'className':'loginForm credentials'},[
React.DOM.form({onChange: this.handleChange, onSubmit:this.handleCredentialSubmit}, [
React.DOM.div(null,[
- React.DOM.label({'for':'name'}, "username"),
+ React.DOM.label({'for' :'name'}, "username"),
React.DOM.input({'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'key':'username', 'autoCapitalize':'none'}),
- React.DOM.label({'for':'passphrase'}, "passphrase"),
+ React.DOM.label({'for' :'passphrase'}, "passphrase"),
React.DOM.input({'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase", 'key':'passphrase'})
]),
React.DOM.button({'type':'submit', 'disabled':!this.shouldEnableLoginButton(), 'className':'button'}, "login")
]),
this.props.isNewUserRegistrationAvailable ? registrationLink : null
]);
},
handlePINSubmit: function (event) {
event.preventDefault();
this.refs['pin'].getDOMNode().blur();
var credentials = {
pin: this.refs['pin'].getDOMNode().value
}
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials);
},
pinForm: function () {
return React.DOM.div({'className':'loginForm pin'},[
React.DOM.form({onChange: this.handleChange, onSubmit:this.handlePINSubmit}, [
React.DOM.div(null,[
React.DOM.label({'for':'pin'}, "pin"),
React.DOM.input({'type':'text', 'name':'pin', 'ref':'pin', placeholder:"PIN", 'key':'pin', 'autocapitalize':'none'})
]),
React.DOM.button({'type':'submit', 'disabled':this.props.disabled, 'className':'button'}, "login")
])
]);
},
setInitialFocus: function () {
if (this.props.mode == 'PIN') {
this.refs['pin'].getDOMNode().select();
} else {
if (this.refs['username'].getDOMNode().value == '') {
this.refs['username'].getDOMNode().focus();
} else{
this.refs['passphrase'].getDOMNode().select();
}
}
},
render: function() {
return new this.props.template({'innerComponent': this.props.mode == 'PIN' ? this.pinForm() : this.loginForm()});
}
});
diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js
index cc4a06c..cb5f81a 100644
--- a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js
+++ b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js
@@ -1,122 +1,123 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
Clipperz.Base.module('Clipperz.PM.UI.Components');
Clipperz.PM.UI.Components.Overlay = function(args) {
args = args || {};
this._defaultDelay = 2;
this._element = MochiKit.DOM.getElement('overlay');
return this;
}
//=============================================================================
Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
//-------------------------------------------------------------------------
'toString': function () {
return "Clipperz.PM.UI.Components.Overlay component";
},
'element': function () {
// return MochiKit.DOM.getElement('overlay');
return this._element;
},
'getElement': function (aClass) {
return MochiKit.Selector.findChildElements(this.element(), ['.'+aClass])[0];
},
//-------------------------------------------------------------------------
'show': function (aMessage) {
this.resetStatus();
this.setMessage(aMessage);
MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-hide');
MochiKit.DOM.addElementClass(this.element(), 'ios-overlay-show');
},
'done': function (aMessage, aDelayBeforeHiding) {
this.completed(this.showDoneIcon, aMessage, aDelayBeforeHiding);
},
'failed': function (aMessage, aDelayBeforeHiding) {
this.completed(this.showFailIcon, aMessage, aDelayBeforeHiding);
},
//-------------------------------------------------------------------------
'resetStatus': function () {
MochiKit.Style.showElement(this.element());
MochiKit.Style.showElement(this.getElement('spinner'));
MochiKit.Style.hideElement(this.getElement('done'));
MochiKit.Style.hideElement(this.getElement('failed'));
},
'setMessage': function (aMessage) {
if (typeof(aMessage) != 'undefined') {
this.getElement('title').innerHTML = aMessage;
}
},
'completed': function (aFunctionToShowResult, aMessage, aDelayBeforeHiding) {
var delay = aDelayBeforeHiding || this.defaultDelay();
this.hideSpinner();
MochiKit.Base.bind(aFunctionToShowResult, this)();
this.setMessage(aMessage);
MochiKit.Async.callLater(delay, MochiKit.Base.bind(this.hide, this))
},
'hide': function () {
- MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-show');
- MochiKit.DOM.addElementClass(this.element(), 'ios-overlay-hide');
- MochiKit.Async.callLater(1, MochiKit.Style.hideElement, this.element());
+ var element = this.element();
+ MochiKit.DOM.removeElementClass(element, 'ios-overlay-show');
+ MochiKit.DOM.addElementClass(element, 'ios-overlay-hide');
+ MochiKit.Async.callLater(1, MochiKit.Style.hideElement, element);
},
'hideSpinner': function () {
MochiKit.Style.hideElement(this.getElement('spinner'));
},
'showDoneIcon': function () {
MochiKit.Style.showElement(this.getElement('done'));
},
'showFailIcon': function () {
MochiKit.Style.showElement(this.getElement('failed'));
},
//-------------------------------------------------------------------------
'defaultDelay': function () {
return this._defaultDelay;
},
//-------------------------------------------------------------------------
__syntaxFix__: "syntax fix"
});
diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js b/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js
new file mode 100644
index 0000000..822acc2
--- a/dev/null
+++ b/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js
@@ -0,0 +1,88 @@
+/*
+
+Copyright 2008-2013 Clipperz Srl
+
+This file is part of Clipperz, the online password manager.
+For further information about its features and functionalities please
+refer to http://www.clipperz.com.
+
+* Clipperz is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+* Clipperz is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU Affero General Public License for more details.
+
+* You should have received a copy of the GNU Affero General Public
+ License along with Clipperz. If not, see http://www.gnu.org/licenses/.
+
+*/
+
+Clipperz.PM.UI.Components.PreferencePage = React.createClass({
+
+ getDefaultProps: function () {
+ return {
+ }
+ },
+
+ propTypes: {
+// card: React.PropTypes.object.isRequired
+// checked: React.PropTypes.boolean.isRequired
+ },
+
+ getInitialState: function () {
+// return {
+// shouldStoreDataLocally: false
+// };
+ },
+
+ handleBackClick: function (anEvent) {
+ MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack');
+ },
+
+ toggleShouldStoreDataLocally: function (anEvent) {
+// this.setState({shouldStoreDataLocally: !this.state['shouldStoreDataLocally']});
+ Clipperz.PM.DataModel.devicePreferences.setShouldStoreDataLocally(!Clipperz.PM.DataModel.devicePreferences.shouldStoreDataLocally());
+ this.setState({});
+ },
+
+ shouldStoreDataLocally: function () {
+ return Clipperz.PM.DataModel.devicePreferences.shouldStoreDataLocally();
+ },
+
+ syncNow: function (anEvent) {
+ MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'synchronizeLocalData');
+ },
+
+ //=========================================================================
+
+ render: function () {
+ return React.DOM.div({className:'preferences'}, [
+ React.DOM.div({className:'header'}, [
+ React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, "Preferences")),
+ React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")),
+ ]),
+ React.DOM.div({className:'content'}, [
+ React.DOM.form(null, [
+ React.DOM.div({className:'section'}, [
+ React.DOM.h4(null, "Local storage"),
+ React.DOM.p(null, "Store you account data locally for offline viewing"),
+ new Clipperz.PM.UI.Components.Checkbox({'id':'shouldStoreLocally_checkbox', 'checked':this.shouldStoreDataLocally(), 'eventHandler':this.toggleShouldStoreDataLocally}),
+ this.shouldStoreDataLocally() ? React.DOM.div({className:'syncInfo'}, [
+// React.DOM.h5(null, "data were never synchronized before"),
+ React.DOM.a({className:'button', onClick:this.syncNow}, "Sync now")
+ ]) : null
+ ])
+ ])
+ ]),
+ React.DOM.div({className:'footer'}, [
+
+ ])
+ ]);
+ }
+
+ //=========================================================================
+});
diff --git a/frontend/delta/js/Clipperz/PM/UI/MainController.js b/frontend/delta/js/Clipperz/PM/UI/MainController.js
index da7540e..20ff041 100644
--- a/frontend/delta/js/Clipperz/PM/UI/MainController.js
+++ b/frontend/delta/js/Clipperz/PM/UI/MainController.js
@@ -1,491 +1,583 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
Clipperz.Base.module('Clipperz.PM.UI');
Clipperz.PM.UI.MainController = function() {
var pages;
- this._proxy = null;
+// this._proxy = null;
this._user = null;
this._filter = '';
// this._currentPage = 'loadingPage';
this._pageStack = ['loadingPage'];
this._overlay = new Clipperz.PM.UI.Components.Overlay();
pages = {
'loginPage': new Clipperz.PM.UI.Components.LoginForm(),
'registrationPage': new Clipperz.PM.UI.Components.RegistrationWizard(),
'cardListPage': new Clipperz.PM.UI.Components.CardList(),
'cardDetailPage': new Clipperz.PM.UI.Components.CardDetail({card: {}}),
+ 'preferencePage': new Clipperz.PM.UI.Components.PreferencePage(),
'errorPage': new Clipperz.PM.UI.Components.ErrorPage({message:''})
};
MochiKit.Base.map(function (anId) {React.renderComponent(pages[anId], MochiKit.DOM.getElement(anId))}, MochiKit.Base.keys(pages));
this._pages = pages;
this.registerForNotificationCenterEvents();
+ MochiKit.Signal.connect(MochiKit.DOM.currentDocument(), 'onselectionchange', this, 'selectionChangeHandler');
return this;
}
MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, {
toString: function () {
return "Clipperz.PM.UI.MainController";
},
//=========================================================================
overlay: function () {
return this._overlay;
},
loginForm: function () {
return this._loginForm;
},
registrationWizard: function () {
return this._registrationWizard;
},
//=========================================================================
isOnline: function() {
return navigator.onLine;
+// return false;
},
hasLocalData: function() {
- return false;
+// return false;
+ return (Clipperz.PM.DataModel.devicePreferences.accountData() != null);
},
loginMode: function () {
// PIN is set using this command:
// Clipperz.PM.PIN.setCredentialsWithPIN('1234', {'username':'joe', 'passphrase':'clipperz'});
return Clipperz.PM.PIN.isSet() ? 'PIN' : 'CREDENTIALS';
},
//=========================================================================
pages: function () {
return this._pages;
},
pageStack: function () {
return this._pageStack;
},
//=========================================================================
+ showOfflineError: function () {
+console.log("THE BROWSER IS OFFLINE");
+ },
+
selectInitialProxy: function () {
if (this.isOnline()) {
- this._proxy = Clipperz.PM.Proxy.defaultProxy;
+// this._proxy = Clipperz.PM.Proxy.defaultProxy;
} else {
if (this.hasLocalData()) {
- this._proxy = new Clipperz.PM.Proxy.Offline({dataStore: new Clipperz.PM.Proxy.Offline.LocalStorageDataStore(), shouldPayTolls:false});
+// this._proxy = new Clipperz.PM.Proxy.Offline({dataStore: new Clipperz.PM.Proxy.Offline.LocalStorageDataStore(), shouldPayTolls:false});
+ Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline({dataStore: new Clipperz.PM.Proxy.Offline.LocalStorageDataStore(), shouldPayTolls:false});
} else {
this.showOfflineError();
}
}
},
- proxy: function () {
- return this._proxy;
- },
+// proxy: function () {
+// return this._proxy;
+// },
//=========================================================================
registerForNotificationCenterEvents: function () {
- var events = ['doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack', 'showRecord', 'searchCards', 'runDirectLogin'];
+ var events = [
+ 'doLogin',
+ 'registerNewUser',
+ 'showRegistrationForm',
+ 'goBack',
+ 'showRecord',
+ 'searchCards',
+ 'showPreferences',
+ 'runDirectLogin',
+ 'synchronizeLocalData'
+ ];
var self = this;
MochiKit.Base.map(function (anEvent) {
MochiKit.Signal.connect(Clipperz.Signal.NotificationCenter, anEvent, MochiKit.Base.method(self, anEvent));
}, events);
// MochiKit.Signal.connect(window, 'onpopstate', MochiKit.Base.method(this, 'historyGoBack'));
MochiKit.Signal.connect(window, 'onbeforeunload', MochiKit.Base.method(this, 'shouldExitApp'));
},
//-------------------------------------------------------------------------
+ selectionChangeHandler: function (anEvent) {
+ var selection;
+ var selectionRange;
+ var selectionNode;
+ var valueElement;
+// other hints: http://www.bearpanther.com/2013/05/27/easy-text-selection-in-mobile-safari/
+// SELECTION: https://developer.mozilla.org/en-US/docs/Web/API/Selection
+// RANGE: https://developer.mozilla.org/en-US/docs/Web/API/Range
+// NODE TYPES: https://developer.mozilla.org/en-US/docs/Web/API/Node.nodeType
+
+ selection = MochiKit.DOM.currentWindow().getSelection();
+//console.log("-- selection", selection);
+ selectionRange = selection.getRangeAt(0);
+ selectionNode = selectionRange.startContainer.childNodes[selectionRange.startOffset];
+//console.log("-- selectionNode", selectionNode);
+
+ if (selectionNode != undefined) {
+ valueElement = MochiKit.DOM.getFirstElementByTagAndClassName('*', 'value', selectionNode);
+//console.log("-- valueElement", valueElement);
+ }
+
+ if ((valueElement != null) && (valueElement != selectionNode)) {
+ var range;
+ range = MochiKit.DOM.currentDocument().createRange();
+ range.selectNodeContents(valueElement);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ anEvent.preventDefault();
+ anEvent.stopPropagation();
+
+//console.log("updated selection", MochiKit.DOM.currentWindow().getSelection());
+ }
+//console.log("-----------");
+ },
+
+ //-------------------------------------------------------------------------
+
run: function (parameters) {
var shouldShowRegistrationForm;
+ var canRegisterNewUsers;
+
+ canRegisterNewUsers = Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers();
this.selectInitialProxy();
- shouldShowRegistrationForm = parameters['shouldShowRegistrationForm'] && this.proxy().canRegisterNewUsers();
- this.pages()['loginPage'].setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable': this.proxy().canRegisterNewUsers()});
+ shouldShowRegistrationForm = parameters['shouldShowRegistrationForm'] && canRegisterNewUsers;
+ this.pages()['loginPage'].setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable':canRegisterNewUsers});
if (shouldShowRegistrationForm) {
this.showRegistrationForm();
} else {
this.showLoginForm();
}
this.overlay().done("", 0.5);
},
//-------------------------------------------------------------------------
showLoginForm: function () {
var loginFormPage;
loginFormPage = this.pages()['loginPage'];
- loginFormPage.setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable': this.proxy().canRegisterNewUsers()});
+ loginFormPage.setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable':Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers()});
this.moveInPage(this.currentPage(), 'loginPage');
MochiKit.Async.callLater(0.5, MochiKit.Base.method(loginFormPage, 'setInitialFocus'));
},
showRegistrationForm: function () {
var currentPage;
var registrationPage;
currentPage = this.currentPage();
registrationPage = this.pages()['registrationPage'];
this.setCurrentPage('loginPage');
registrationPage.setProps({});
this.moveInPage(currentPage, 'registrationPage');
MochiKit.Async.callLater(0.5, MochiKit.Base.method(registrationPage, 'setInitialFocus'));
},
//=========================================================================
doLogin: function (event) {
var credentials;
var getPassphraseDelegate;
var user;
user = null;
this.overlay().show("logging in");
this.pages()['loginPage'].setProps({disabled:true});
if ('pin' in event) {
credentials = Clipperz.PM.PIN.credentialsWithPIN(event['pin']);
} else {
credentials = event;
}
getPassphraseDelegate = MochiKit.Base.partial(MochiKit.Async.succeed, credentials.passphrase);
user = new Clipperz.PM.DataModel.User({'username':credentials.username, 'getPassphraseFunction':getPassphraseDelegate});
deferredResult = new Clipperz.Async.Deferred('MainController.doLogin', {trace:false});
deferredResult.addCallback(MochiKit.Async.wait, 0.1);
deferredResult.addMethod(Clipperz.Crypto.PRNG.defaultRandomGenerator(), 'deferredEntropyCollection');
deferredResult.addMethod(user, 'login');
deferredResult.addMethod(Clipperz.PM.PIN, 'resetFailedAttemptCount');
deferredResult.addMethod(this, 'setUser', user);
// deferredResult.addMethod(this, 'setupApplication');
deferredResult.addMethod(this, 'runApplication');
deferredResult.addMethod(this.overlay(), 'done', "", 1);
deferredResult.addErrback(MochiKit.Base.method(this, 'genericErrorHandler', event));
deferredResult.addErrback(MochiKit.Base.bind(function (anEvent, anError) {
if (anError['isPermanent'] != true) {
this.pages()['loginPage'].setProps({disabled:false, 'mode':this.loginMode()});
- this.pages()['loginPage'].setInitialFocus();
- }
- return anError;
+ this.pages()['loginPage'].setInitialFocus();
+ }
+ return anError;
}, this, event))
deferredResult.callback();
return deferredResult;
},
//-------------------------------------------------------------------------
registerNewUser: function (credentials) {
var deferredResult;
this.overlay().show("creating user");
this.pages()['registrationPage'].setProps({disabled:true});
deferredResult = new Clipperz.Async.Deferred('MainController.registerNewUser', {trace:false});
deferredResult.addCallback(Clipperz.PM.DataModel.User.registerNewAccount,
credentials['username'],
MochiKit.Base.partial(MochiKit.Async.succeed, credentials['passphrase'])
);
deferredResult.addMethod(this, 'doLogin', credentials);
deferredResult.addErrback(MochiKit.Base.method(this, 'genericErrorHandler', event));
deferredResult.addErrback(MochiKit.Base.bind(function (anError) {
if (anError['isPermanent'] != true) {
this.pages()['registrationPage'].setProps({disabled:false});
this.pages()['registrationPage'].setInitialFocus();
}
return anError;
}, this));
deferredResult.callback();
return deferredResult;
},
//-------------------------------------------------------------------------
user: function () {
return this._user;
},
setUser: function (aUser) {
this._user = aUser;
return this._user;
},
//=========================================================================
allCardInfo: function () {
var deferredResult;
var cardInfo;
cardInfo = {
'_rowObject': MochiKit.Async.succeed,
'_reference': MochiKit.Base.methodcaller('reference'),
'_searchableContent': MochiKit.Base.methodcaller('searchableContent'),
'label': MochiKit.Base.methodcaller('label'),
'favicon': MochiKit.Base.methodcaller('favicon')
};
deferredResult = new Clipperz.Async.Deferred('MainController.allCardInfo', {trace:false});
deferredResult.addMethod(this.user(), 'getRecords');
deferredResult.addCallback(MochiKit.Base.map, Clipperz.Async.collectResults("CardList.value - collectResults", cardInfo, {trace:false}));
deferredResult.addCallback(Clipperz.Async.collectAll);
deferredResult.callback();
return deferredResult;
},
filterCards: function (someCardInfo) {
var filter;
var filterRegExp;
var result;
filter = this.filter().replace(/[^A-Za-z0-9]/g, "\\$&");
filterRegExp = new RegExp(filter, "i");
result = MochiKit.Base.filter(function (aCardInfo) { return filterRegExp.test(aCardInfo['_searchableContent'])}, someCardInfo);
return result;
},
sortCards: function (someCardInfo) {
return someCardInfo.sort(Clipperz.Base.caseInsensitiveKeyComparator('label'));
},
showRecordList: function () {
var deferredResult;
deferredResult = new Clipperz.Async.Deferred('MainController.showRecordList', {trace:false});
deferredResult.addMethod(this, 'allCardInfo');
deferredResult.addMethod(this, 'filterCards');
deferredResult.addMethod(this, 'sortCards');
deferredResult.addCallback(MochiKit.Base.bind(function (someRecordInfo) {
this.pages()['cardListPage'].setProps({cardList: someRecordInfo});
}, this));
deferredResult.callback();
return deferredResult;
},
filter: function () {
return this._filter;
},
setFilter: function (aValue) {
this._filter = aValue;
},
searchCards: function (someParameters) {
//console.log("SEARCH CARDS", someParameters);
this.setFilter(someParameters);
this.showRecordList();
},
//=========================================================================
runApplication: function () {
MochiKit.Signal.connect(window, 'onpopstate', MochiKit.Base.method(this, 'historyGoBack'));
+/// TODO: remove this TEST HACK
this.moveInPage(this.currentPage(), 'cardListPage');
return this.showRecordList();
+
+// this.moveInPage(this.currentPage(), 'preferencePage');
},
showRecord: function (aRecordReference) {
//console.log("Show Record", aRecordReference);
var deferredResult;
this.pages()['cardListPage'].setProps({selectedCard:aRecordReference});
deferredResult = new Clipperz.Async.Deferred('MainController.runApplication', {trace:false});
-// deferredResult.addMethod(this.user(), 'getRecord', aRecordReference['_reference']);
deferredResult.addMethod(this.user(), 'getRecord', aRecordReference);
deferredResult.addMethodcaller('content');
deferredResult.addCallback(MochiKit.Base.bind(function (aCard) {
//console.log("CARD DETAILS", aCard);
this.pages()['cardDetailPage'].setProps({card: aCard});
this.pages()['cardListPage'].setProps({selectedCard: null});
}, this));
deferredResult.addMethod(this, 'moveInPage', this.currentPage(), 'cardDetailPage', true);
deferredResult.callback();
return deferredResult;
},
runDirectLogin: function (someParameters) {
-console.log("RUN DIRECT LOGIN", someParameters);
+//console.log("RUN DIRECT LOGIN", someParameters);
var deferredResult;
-// this.pages()['cardListPage'].setProps({selectedCard:aRecordReference});
deferredResult = new Clipperz.Async.Deferred('MainController.runDirectLogin', {trace:false});
-// deferredResult.addMethod(this.user(), 'getRecord', aRecordReference['_reference']);
deferredResult.addMethod(this.user(), 'getRecord', someParameters['record']);
deferredResult.addMethodcaller('directLoginWithReference', someParameters['directLogin']);
deferredResult.addCallback(Clipperz.PM.UI.DirectLoginRunner.openDirectLogin);
deferredResult.callback();
return deferredResult;
},
shouldExitApp: function (anEvent) {
-console.log("SHOULD EXIT APP");
+//console.log("SHOULD EXIT APP");
anEvent.preventDefault();
anEvent.stopPropagation();
},
//=========================================================================
+ showPreferences: function (anEvent) {
+ var deferredResult;
+
+ this.pages()['preferencePage'].setProps({});
+ deferredResult = new Clipperz.Async.Deferred('MainController.showPreferences', {trace:false});
+ deferredResult.addMethod(this, 'moveInPage', this.currentPage(), 'preferencePage', true);
+ deferredResult.callback();
+
+ return deferredResult;
+ },
+
+ //=========================================================================
+
genericErrorHandler: function (anEvent, anError) {
var errorMessage;
var result;
result = anError;
errorMessage = "login failed";
if (anError['isPermanent'] === true) {
this.pages()['errorPage'].setProps({message:anError.message});
this.moveInPage(this.currentPage(), 'errorPage');
errorMessage = "failure";
} else {
if ('pin' in anEvent) {
errorCount = Clipperz.PM.PIN.recordFailedAttempt();
if (errorCount == -1) {
errorMessage = "PIN resetted";
}
}
}
this.overlay().failed(errorMessage, 1);
return result;
},
//=========================================================================
slidePage: function (fromPage, toPage, direction) {
var fromPosition;
var toPosition;
if (direction == "LEFT") {
fromPosition = 'right';
toPosition = 'left'
} else {
fromPosition = 'left';
toPosition = 'right'
}
MochiKit.DOM.addElementClass(fromPage, toPosition + ' transition');
MochiKit.DOM.addElementClass(toPage, fromPosition);
MochiKit.DOM.removeElementClass(toPage, toPosition);
MochiKit.DOM.addElementClass(toPage, 'transition');
MochiKit.Async.callLater(0.1, function () {
MochiKit.DOM.removeElementClass(toPage, fromPosition);
})
MochiKit.Async.callLater(0.5, function () {
MochiKit.DOM.removeElementClass(fromPage, 'transition');
MochiKit.DOM.removeElementClass(toPage, 'transition');
})
},
rotateInPage: function (fromPage, toPage) {
// Broken! :(
MochiKit.DOM.addElementClass(MochiKit.DOM.getElement('mainDiv'), 'show-right');
},
//.........................................................................
goBack: function () {
var fromPage;
var toPage;
fromPage = this.pageStack().shift();
toPage = this.currentPage();
this.pages()[toPage].setProps({});
this.moveOutPage(fromPage, toPage);
},
historyGoBack: function (anEvent) {
anEvent.preventDefault();
anEvent.stopPropagation();
this.goBack();
},
currentPage: function () {
return this.pageStack()[0];
},
setCurrentPage: function (aPage) {
this.pageStack().unshift(aPage);
},
moveInPage: function (fromPage, toPage, addToHistory) {
var shouldAddItemToHistory;
shouldAddItemToHistory = typeof(addToHistory) == 'undefined' ? false : addToHistory;
this.slidePage(MochiKit.DOM.getElement(fromPage), MochiKit.DOM.getElement(toPage), 'LEFT');
this.setCurrentPage(toPage);
if (shouldAddItemToHistory) {
//console.log("ADD ITEM TO HISTORY");
//console.log("ADD ITEM TO HISTORY - window", window);
//console.log("ADD ITEM TO HISTORY - window.history", window.history);
window.history.pushState({'fromPage': fromPage, 'toPage': toPage});
//# window.history.pushState();
//console.log("ADDED ITEM TO HISTORY");
} else {
//console.log("Skip HISTORY");
}
},
moveOutPage: function (fromPage, toPage) {
this.slidePage(MochiKit.DOM.getElement(fromPage), MochiKit.DOM.getElement(toPage), 'RIGHT');
this.setCurrentPage(toPage);
},
//=========================================================================
+
+ synchronizeLocalData: function (anEvent) {
+ var deferredResult;
+
+ deferredResult = new Clipperz.Async.Deferred('MainController.synchronizeLocalData', {trace:true});
+// deferredResult.addMethod(this.proxy(), 'message', 'downloadAccountData', {});
+ deferredResult.addMethod(this.user().connection(), 'message', 'downloadAccountData', {});
+ deferredResult.addCallback(function (aResult) {
+ Clipperz.PM.DataModel.devicePreferences.setAccountDataWityResponse(aResult);
+// localStorage.setItem('clipperz_dump_data', aResult['data']);
+// localStorage.setItem('clipperz_dump_version', aResult['version']);
+// localStorage.setItem('clipperz_dump_date', new Date());
+ })
+ deferredResult.callback();
+
+ return deferredResult;
+ },
+
+ //=========================================================================
/*
wrongAppVersion: function (anError) {
// this.pages()['errorPage'].setProps({message:anError.message});
// this.moveInPage('errorPage', this.currentPage());
},
*/
//=========================================================================
__syntaxFix__: "syntax fix"
});
diff --git a/frontend/delta/js/React/react-0.5.0-alpha.js b/frontend/delta/js/React/react-0.5.0-alpha.js
new file mode 100644
index 0000000..375b430
--- a/dev/null
+++ b/frontend/delta/js/React/react-0.5.0-alpha.js
@@ -0,0 +1,13239 @@
+/*
+
+Copyright 2008-2013 Clipperz Srl
+
+This file is part of Clipperz, the online password manager.
+For further information about its features and functionalities please
+refer to http://www.clipperz.com.
+
+* Clipperz is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+* Clipperz is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU Affero General Public License for more details.
+
+* You should have received a copy of the GNU Affero General Public
+ License along with Clipperz. If not, see http://www.gnu.org/licenses/.
+
+*/
+
+/**
+ * React v0.5.0-alpha
+ */
+(function(e){if("function"==typeof bootstrap)bootstrap("react",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeReact=e}else"undefined"!=typeof window?window.React=e():global.React=e()})(function(){var define,ses,bootstrap,module,exports;
+return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule $
+ * @typechecks
+ */
+
+var ge = require("./ge");
+var ex = require("./ex");
+
+/**
+ * Find a node by ID.
+ *
+ * If your application code depends on the existence of the element, use $,
+ * which will throw if the element doesn't exist.
+ *
+ * If you're not sure whether or not the element exists, use ge instead, and
+ * manually check for the element's existence in your application code.
+ *
+ * @param {string|DOMDocument|DOMElement|DOMTextNode|Comment} id
+ * @return {DOMDocument|DOMElement|DOMTextNode|Comment}
+ */
+function $(id) {
+ var element = ge(id);
+ if (!element) {
+ throw new Error(ex(
+ 'Tried to get element with id of "%s" but it is not present on the page.',
+ id
+ ));
+ }
+ return element;
+}
+
+module.exports = $;
+
+},{"./ex":79,"./ge":83}],2:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule CSSProperty
+ */
+
+"use strict";
+
+/**
+ * CSS properties which accept numbers but are not in units of "px".
+ */
+var isUnitlessNumber = {
+ fillOpacity: true,
+ fontWeight: true,
+ opacity: true,
+ orphans: true,
+ zIndex: true,
+ zoom: true
+};
+
+/**
+ * Most style properties can be unset by doing .style[prop] = '' but IE8
+ * doesn't like doing that with shorthand properties so for the properties that
+ * IE8 breaks on, which are listed here, we instead unset each of the
+ * individual properties. See http://bugs.jquery.com/ticket/12385.
+ * The 4-value 'clock' properties like margin, padding, border-width seem to
+ * behave without any problems. Curiously, list-style works too without any
+ * special prodding.
+ */
+var shorthandPropertyExpansions = {
+ background: {
+ backgroundImage: true,
+ backgroundPosition: true,
+ backgroundRepeat: true,
+ backgroundColor: true
+ },
+ border: {
+ borderWidth: true,
+ borderStyle: true,
+ borderColor: true
+ },
+ borderBottom: {
+ borderBottomWidth: true,
+ borderBottomStyle: true,
+ borderBottomColor: true
+ },
+ borderLeft: {
+ borderLeftWidth: true,
+ borderLeftStyle: true,
+ borderLeftColor: true
+ },
+ borderRight: {
+ borderRightWidth: true,
+ borderRightStyle: true,
+ borderRightColor: true
+ },
+ borderTop: {
+ borderTopWidth: true,
+ borderTopStyle: true,
+ borderTopColor: true
+ },
+ font: {
+ fontStyle: true,
+ fontVariant: true,
+ fontWeight: true,
+ fontSize: true,
+ lineHeight: true,
+ fontFamily: true
+ }
+};
+
+var CSSProperty = {
+ isUnitlessNumber: isUnitlessNumber,
+ shorthandPropertyExpansions: shorthandPropertyExpansions
+};
+
+module.exports = CSSProperty;
+
+},{}],3:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule CSSPropertyOperations
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var CSSProperty = require("./CSSProperty");
+
+var dangerousStyleValue = require("./dangerousStyleValue");
+var escapeTextForBrowser = require("./escapeTextForBrowser");
+var hyphenate = require("./hyphenate");
+var memoizeStringOnly = require("./memoizeStringOnly");
+
+var processStyleName = memoizeStringOnly(function(styleName) {
+ return escapeTextForBrowser(hyphenate(styleName));
+});
+
+/**
+ * Operations for dealing with CSS properties.
+ */
+var CSSPropertyOperations = {
+
+ /**
+ * Serializes a mapping of style properties for use as inline styles:
+ *
+ * > createMarkupForStyles({width: '200px', height: 0})
+ * "width:200px;height:0;"
+ *
+ * Undefined values are ignored so that declarative programming is easier.
+ *
+ * @param {object} styles
+ * @return {?string}
+ */
+ createMarkupForStyles: function(styles) {
+ var serialized = '';
+ for (var styleName in styles) {
+ if (!styles.hasOwnProperty(styleName)) {
+ continue;
+ }
+ var styleValue = styles[styleName];
+ if (styleValue != null) {
+ serialized += processStyleName(styleName) + ':';
+ serialized += dangerousStyleValue(styleName, styleValue) + ';';
+ }
+ }
+ return serialized || null;
+ },
+
+ /**
+ * Sets the value for multiple styles on a node. If a value is specified as
+ * '' (empty string), the corresponding style property will be unset.
+ *
+ * @param {DOMElement} node
+ * @param {object} styles
+ */
+ setValueForStyles: function(node, styles) {
+ var style = node.style;
+ for (var styleName in styles) {
+ if (!styles.hasOwnProperty(styleName)) {
+ continue;
+ }
+ var styleValue = dangerousStyleValue(styleName, styles[styleName]);
+ if (styleValue) {
+ style[styleName] = styleValue;
+ } else {
+ var expansion = CSSProperty.shorthandPropertyExpansions[styleName];
+ if (expansion) {
+ // Shorthand property that IE8 won't like unsetting, so unset each
+ // component to placate it
+ for (var individualStyleName in expansion) {
+ style[individualStyleName] = '';
+ }
+ } else {
+ style[styleName] = '';
+ }
+ }
+ }
+ }
+
+};
+
+module.exports = CSSPropertyOperations;
+
+},{"./CSSProperty":2,"./dangerousStyleValue":76,"./escapeTextForBrowser":78,"./hyphenate":88,"./memoizeStringOnly":94}],4:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule CallbackRegistry
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var listenerBank = {};
+
+/**
+ * Stores "listeners" by `registrationName`/`id`. There should be at most one
+ * "listener" per `registrationName`/`id` in the `listenerBank`.
+ *
+ * Access listeners via `listenerBank[registrationName][id]`.
+ *
+ * @class CallbackRegistry
+ * @internal
+ */
+var CallbackRegistry = {
+
+ /**
+ * Stores `listener` at `listenerBank[registrationName][id]`. Is idempotent.
+ *
+ * @param {string} id ID of the DOM element.
+ * @param {string} registrationName Name of listener (e.g. `onClick`).
+ * @param {?function} listener The callback to store.
+ */
+ putListener: function(id, registrationName, listener) {
+ var bankForRegistrationName =
+ listenerBank[registrationName] || (listenerBank[registrationName] = {});
+ bankForRegistrationName[id] = listener;
+ },
+
+ /**
+ * @param {string} id ID of the DOM element.
+ * @param {string} registrationName Name of listener (e.g. `onClick`).
+ * @return {?function} The stored callback.
+ */
+ getListener: function(id, registrationName) {
+ var bankForRegistrationName = listenerBank[registrationName];
+ return bankForRegistrationName && bankForRegistrationName[id];
+ },
+
+ /**
+ * Deletes a listener from the registration bank.
+ *
+ * @param {string} id ID of the DOM element.
+ * @param {string} registrationName Name of listener (e.g. `onClick`).
+ */
+ deleteListener: function(id, registrationName) {
+ var bankForRegistrationName = listenerBank[registrationName];
+ if (bankForRegistrationName) {
+ delete bankForRegistrationName[id];
+ }
+ },
+
+ /**
+ * Deletes all listeners for the DOM element with the supplied ID.
+ *
+ * @param {string} id ID of the DOM element.
+ */
+ deleteAllListeners: function(id) {
+ for (var registrationName in listenerBank) {
+ delete listenerBank[registrationName][id];
+ }
+ },
+
+ /**
+ * This is needed for tests only. Do not use!
+ */
+ __purge: function() {
+ listenerBank = {};
+ }
+
+};
+
+module.exports = CallbackRegistry;
+
+},{}],5:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ChangeEventPlugin
+ */
+
+"use strict";
+
+var EventConstants = require("./EventConstants");
+var EventPluginHub = require("./EventPluginHub");
+var EventPropagators = require("./EventPropagators");
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+var SyntheticEvent = require("./SyntheticEvent");
+
+var isEventSupported = require("./isEventSupported");
+var keyOf = require("./keyOf");
+
+var topLevelTypes = EventConstants.topLevelTypes;
+
+var eventTypes = {
+ change: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onChange: null}),
+ captured: keyOf({onChangeCapture: null})
+ }
+ }
+};
+
+/**
+ * For IE shims
+ */
+var activeElement = null;
+var activeElementID = null;
+var activeElementValue = null;
+var activeElementValueProp = null;
+
+
+/**
+ * SECTION: handle `change` event
+ */
+function shouldUseChangeEvent(elem) {
+ return (
+ elem.nodeName === 'SELECT' ||
+ (elem.nodeName === 'INPUT' && elem.type === 'file')
+ );
+}
+
+var doesChangeEventBubble = false;
+if (ExecutionEnvironment.canUseDOM) {
+ // See `handleChange` comment below
+ doesChangeEventBubble = isEventSupported('change') && (
+ !('documentMode' in document) || document.documentMode > 8
+ );
+}
+
+function manualDispatchChangeEvent(nativeEvent) {
+ var event = SyntheticEvent.getPooled(
+ eventTypes.change,
+ activeElementID,
+ nativeEvent
+ );
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+
+ // If change bubbled, we'd just bind to it like all the other events
+ // and have it go through ReactEventTopLevelCallback. Since it doesn't, we
+ // manually listen for the change event and so we have to enqueue and
+ // process the abstract event manually.
+ EventPluginHub.enqueueEvents(event);
+ EventPluginHub.processEventQueue();
+}
+
+function startWatchingForChangeEventIE8(target, targetID) {
+ activeElement = target;
+ activeElementID = targetID;
+ activeElement.attachEvent('onchange', manualDispatchChangeEvent);
+}
+
+function stopWatchingForChangeEventIE8() {
+ if (!activeElement) {
+ return;
+ }
+ activeElement.detachEvent('onchange', manualDispatchChangeEvent);
+ activeElement = null;
+ activeElementID = null;
+}
+
+function getTargetIDForChangeEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topChange) {
+ return topLevelTargetID;
+ }
+}
+function handleEventsForChangeEventIE8(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topFocus) {
+ // stopWatching() should be a noop here but we call it just in case we
+ // missed a blur event somehow.
+ stopWatchingForChangeEventIE8();
+ startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID);
+ } else if (topLevelType === topLevelTypes.topBlur) {
+ stopWatchingForChangeEventIE8();
+ }
+}
+
+
+/**
+ * SECTION: handle `input` event
+ */
+var isInputEventSupported = false;
+if (ExecutionEnvironment.canUseDOM) {
+ // IE9 claims to support the input event but fails to trigger it when
+ // deleting text, so we ignore its input events
+ isInputEventSupported = isEventSupported('input') && (
+ !('documentMode' in document) || document.documentMode > 9
+ );
+}
+
+
+/**
+ * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
+ */
+var supportedInputTypes = {
+ 'color': true,
+ 'date': true,
+ 'datetime': true,
+ 'datetime-local': true,
+ 'email': true,
+ 'month': true,
+ 'number': true,
+ 'password': true,
+ 'range': true,
+ 'search': true,
+ 'tel': true,
+ 'text': true,
+ 'time': true,
+ 'url': true,
+ 'week': true
+};
+
+function shouldUseInputEvent(elem) {
+ return (
+ (elem.nodeName === 'INPUT' && supportedInputTypes[elem.type]) ||
+ elem.nodeName === 'TEXTAREA'
+ );
+}
+
+/**
+ * (For old IE.) Replacement getter/setter for the `value` property that gets
+ * set on the active element.
+ */
+var newValueProp = {
+ get: function() {
+ return activeElementValueProp.get.call(this);
+ },
+ set: function(val) {
+ // Cast to a string so we can do equality checks.
+ activeElementValue = '' + val;
+ activeElementValueProp.set.call(this, val);
+ }
+};
+
+/**
+ * (For old IE.) Starts tracking propertychange events on the passed-in element
+ * and override the value property so that we can distinguish user events from
+ * value changes in JS.
+ */
+function startWatchingForValueChange(target, targetID) {
+ activeElement = target;
+ activeElementID = targetID;
+ activeElementValue = target.value;
+ activeElementValueProp = Object.getOwnPropertyDescriptor(
+ target.constructor.prototype,
+ 'value'
+ );
+
+ Object.defineProperty(activeElement, 'value', newValueProp);
+ activeElement.attachEvent('onpropertychange', handlePropertyChange);
+}
+
+/**
+ * (For old IE.) Removes the event listeners from the currently-tracked element,
+ * if any exists.
+ */
+function stopWatchingForValueChange() {
+ if (!activeElement) {
+ return;
+ }
+
+ // delete restores the original property definition
+ delete activeElement.value;
+ activeElement.detachEvent('onpropertychange', handlePropertyChange);
+
+ activeElement = null;
+ activeElementID = null;
+ activeElementValue = null;
+ activeElementValueProp = null;
+}
+
+/**
+ * (For old IE.) Handles a propertychange event, sending a `change` event if
+ * the value of the active element has changed.
+ */
+function handlePropertyChange(nativeEvent) {
+ if (nativeEvent.propertyName !== 'value') {
+ return;
+ }
+ var value = nativeEvent.srcElement.value;
+ if (value === activeElementValue) {
+ return;
+ }
+ activeElementValue = value;
+
+ manualDispatchChangeEvent(nativeEvent);
+}
+
+/**
+ * If a `change` event should be fired, returns the target's ID.
+ */
+function getTargetIDForInputEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topInput) {
+ // In modern browsers (i.e., not IE8 or IE9), the input event is exactly
+ // what we want so fall through here and trigger an abstract event
+ return topLevelTargetID;
+ }
+}
+
+// For IE8 and IE9.
+function handleEventsForInputEventIE(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topFocus) {
+ // In IE8, we can capture almost all .value changes by adding a
+ // propertychange handler and looking for events with propertyName
+ // equal to 'value'
+ // In IE9, propertychange fires for most input events but is buggy and
+ // doesn't fire when text is deleted, but conveniently, selectionchange
+ // appears to fire in all of the remaining cases so we catch those and
+ // forward the event if the value has changed
+ // In either case, we don't want to call the event handler if the value
+ // is changed from JS so we redefine a setter for `.value` that updates
+ // our activeElementValue variable, allowing us to ignore those changes
+ //
+ // stopWatching() should be a noop here but we call it just in case we
+ // missed a blur event somehow.
+ stopWatchingForValueChange();
+ startWatchingForValueChange(topLevelTarget, topLevelTargetID);
+ } else if (topLevelType === topLevelTypes.topBlur) {
+ stopWatchingForValueChange();
+ }
+}
+
+// For IE8 and IE9.
+function getTargetIDForInputEventIE(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topSelectionChange ||
+ topLevelType === topLevelTypes.topKeyUp ||
+ topLevelType === topLevelTypes.topKeyDown) {
+ // On the selectionchange event, the target is just document which isn't
+ // helpful for us so just check activeElement instead.
+ //
+ // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
+ // propertychange on the first input event after setting `value` from a
+ // script and fires only keydown, keypress, keyup. Catching keyup usually
+ // gets it and catching keydown lets us fire an event for the first
+ // keystroke if user does a key repeat (it'll be a little delayed: right
+ // before the second keystroke). Other input methods (e.g., paste) seem to
+ // fire selectionchange normally.
+ if (activeElement && activeElement.value !== activeElementValue) {
+ activeElementValue = activeElement.value;
+ return activeElementID;
+ }
+ }
+}
+
+
+/**
+ * SECTION: handle `click` event
+ */
+function shouldUseClickEvent(elem) {
+ // Use the `click` event to detect changes to checkbox and radio inputs.
+ // This approach works across all browsers, whereas `change` does not fire
+ // until `blur` in IE8.
+ return (
+ elem.nodeName === 'INPUT' &&
+ (elem.type === 'checkbox' || elem.type === 'radio')
+ );
+}
+
+function getTargetIDForClickEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topClick) {
+ return topLevelTargetID;
+ }
+}
+
+/**
+ * This plugin creates an `onChange` event that normalizes change events
+ * across form elements. This event fires at a time when it's possible to
+ * change the element's value without seeing a flicker.
+ *
+ * Supported elements are:
+ * - input (see `supportedInputTypes`)
+ * - textarea
+ * - select
+ */
+var ChangeEventPlugin = {
+
+ eventTypes: eventTypes,
+
+ /**
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {*} An accumulation of synthetic events.
+ * @see {EventPluginHub.extractEvents}
+ */
+ extractEvents: function(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent) {
+
+ var getTargetIDFunc, handleEventFunc;
+ if (shouldUseChangeEvent(topLevelTarget)) {
+ if (doesChangeEventBubble) {
+ getTargetIDFunc = getTargetIDForChangeEvent;
+ } else {
+ handleEventFunc = handleEventsForChangeEventIE8;
+ }
+ } else if (shouldUseInputEvent(topLevelTarget)) {
+ if (isInputEventSupported) {
+ getTargetIDFunc = getTargetIDForInputEvent;
+ } else {
+ getTargetIDFunc = getTargetIDForInputEventIE;
+ handleEventFunc = handleEventsForInputEventIE;
+ }
+ } else if (shouldUseClickEvent(topLevelTarget)) {
+ getTargetIDFunc = getTargetIDForClickEvent;
+ }
+
+ if (getTargetIDFunc) {
+ var targetID = getTargetIDFunc(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID
+ );
+ if (targetID) {
+ var event = SyntheticEvent.getPooled(
+ eventTypes.change,
+ targetID,
+ nativeEvent
+ );
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+ return event;
+ }
+ }
+
+ if (handleEventFunc) {
+ handleEventFunc(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID
+ );
+ }
+ }
+
+};
+
+module.exports = ChangeEventPlugin;
+
+},{"./EventConstants":13,"./EventPluginHub":15,"./EventPropagators":18,"./ExecutionEnvironment":19,"./SyntheticEvent":60,"./isEventSupported":90,"./keyOf":93}],6:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule DOMChildrenOperations
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var Danger = require("./Danger");
+var ReactMultiChildUpdateTypes = require("./ReactMultiChildUpdateTypes");
+
+var getTextContentAccessor = require("./getTextContentAccessor");
+
+/**
+ * The DOM property to use when setting text content.
+ *
+ * @type {string}
+ * @private
+ */
+var textContentAccessor = getTextContentAccessor() || 'NA';
+
+/**
+ * Inserts `childNode` as a child of `parentNode` at the `index`.
+ *
+ * @param {DOMElement} parentNode Parent node in which to insert.
+ * @param {DOMElement} childNode Child node to insert.
+ * @param {number} index Index at which to insert the child.
+ * @internal
+ */
+function insertChildAt(parentNode, childNode, index) {
+ var childNodes = parentNode.childNodes;
+ if (childNodes[index] === childNode) {
+ return;
+ }
+ // If `childNode` is already a child of `parentNode`, remove it so that
+ // computing `childNodes[index]` takes into account the removal.
+ if (childNode.parentNode === parentNode) {
+ parentNode.removeChild(childNode);
+ }
+ if (index >= childNodes.length) {
+ parentNode.appendChild(childNode);
+ } else {
+ parentNode.insertBefore(childNode, childNodes[index]);
+ }
+}
+
+/**
+ * Operations for updating with DOM children.
+ */
+var DOMChildrenOperations = {
+
+ dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup,
+
+ /**
+ * Updates a component's children by processing a series of updates. The
+ * update configurations are each expected to have a `parentNode` property.
+ *
+ * @param {array<object>} updates List of update configurations.
+ * @param {array<string>} markupList List of markup strings.
+ * @internal
+ */
+ processUpdates: function(updates, markupList) {
+ var update;
+ // Mapping from parent IDs to initial child orderings.
+ var initialChildren = null;
+ // List of children that will be moved or removed.
+ var updatedChildren = null;
+
+ for (var i = 0; update = updates[i]; i++) {
+ if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING ||
+ update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) {
+ var updatedIndex = update.fromIndex;
+ var updatedChild = update.parentNode.childNodes[updatedIndex];
+ var parentID = update.parentID;
+
+ initialChildren = initialChildren || {};
+ initialChildren[parentID] = initialChildren[parentID] || [];
+ initialChildren[parentID][updatedIndex] = updatedChild;
+
+ updatedChildren = updatedChildren || [];
+ updatedChildren.push(updatedChild);
+ }
+ }
+
+ var renderedMarkup = Danger.dangerouslyRenderMarkup(markupList);
+
+ // Remove updated children first so that `toIndex` is consistent.
+ if (updatedChildren) {
+ for (var j = 0; j < updatedChildren.length; j++) {
+ updatedChildren[j].parentNode.removeChild(updatedChildren[j]);
+ }
+ }
+
+ for (var k = 0; update = updates[k]; k++) {
+ switch (update.type) {
+ case ReactMultiChildUpdateTypes.INSERT_MARKUP:
+ insertChildAt(
+ update.parentNode,
+ renderedMarkup[update.markupIndex],
+ update.toIndex
+ );
+ break;
+ case ReactMultiChildUpdateTypes.MOVE_EXISTING:
+ insertChildAt(
+ update.parentNode,
+ initialChildren[update.parentID][update.fromIndex],
+ update.toIndex
+ );
+ break;
+ case ReactMultiChildUpdateTypes.TEXT_CONTENT:
+ update.parentNode[textContentAccessor] = update.textContent;
+ break;
+ case ReactMultiChildUpdateTypes.REMOVE_NODE:
+ // Already removed by the for-loop above.
+ break;
+ }
+ }
+ }
+
+};
+
+module.exports = DOMChildrenOperations;
+
+},{"./Danger":9,"./ReactMultiChildUpdateTypes":47,"./getTextContentAccessor":87}],7:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule DOMProperty
+ * @typechecks static-only
+ */
+
+/*jslint bitwise: true */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+var DOMPropertyInjection = {
+ /**
+ * Mapping from normalized, camelcased property names to a configuration that
+ * specifies how the associated DOM property should be accessed or rendered.
+ */
+ MUST_USE_ATTRIBUTE: 0x1,
+ MUST_USE_PROPERTY: 0x2,
+ HAS_BOOLEAN_VALUE: 0x4,
+ HAS_SIDE_EFFECTS: 0x8,
+
+ /**
+ * Inject some specialized knowledge about the DOM. This takes a config object
+ * with the following properties:
+ *
+ * isCustomAttribute: function that given an attribute name will return true
+ * if it can be inserted into the DOM verbatim. Useful for data-* or aria-*
+ * attributes where it's impossible to enumerate all of the possible
+ * attribute names,
+ *
+ * Properties: object mapping DOM property name to one of the
+ * DOMPropertyInjection constants or null. If your attribute isn't in here,
+ * it won't get written to the DOM.
+ *
+ * DOMAttributeNames: object mapping React attribute name to the DOM
+ * attribute name. Attribute names not specified use the **lowercase**
+ * normalized name.
+ *
+ * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties.
+ * Property names not specified use the normalized name.
+ *
+ * DOMMutationMethods: Properties that require special mutation methods. If
+ * `value` is undefined, the mutation method should unset the property.
+ *
+ * @param {object} domPropertyConfig the config as described above.
+ */
+ injectDOMPropertyConfig: function(domPropertyConfig) {
+ var Properties = domPropertyConfig.Properties || {};
+ var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {};
+ var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {};
+ var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {};
+
+ if (domPropertyConfig.isCustomAttribute) {
+ DOMProperty._isCustomAttributeFunctions.push(
+ domPropertyConfig.isCustomAttribute
+ );
+ }
+
+ for (var propName in Properties) {
+ invariant(
+ !DOMProperty.isStandardName[propName],
+ 'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' +
+ '\'%s\' which has already been injected. You may be accidentally ' +
+ 'injecting the same DOM property config twice, or you may be ' +
+ 'injecting two configs that have conflicting property names.',
+ propName
+ );
+
+ DOMProperty.isStandardName[propName] = true;
+
+ var lowerCased = propName.toLowerCase();
+ DOMProperty.getPossibleStandardName[lowerCased] = propName;
+
+ var attributeName = DOMAttributeNames[propName];
+ if (attributeName) {
+ DOMProperty.getPossibleStandardName[attributeName] = propName;
+ }
+
+ DOMProperty.getAttributeName[propName] = attributeName || lowerCased;
+
+ DOMProperty.getPropertyName[propName] =
+ DOMPropertyNames[propName] || propName;
+
+ var mutationMethod = DOMMutationMethods[propName];
+ if (mutationMethod) {
+ DOMProperty.getMutationMethod[propName] = mutationMethod;
+ }
+
+ var propConfig = Properties[propName];
+ DOMProperty.mustUseAttribute[propName] =
+ propConfig & DOMPropertyInjection.MUST_USE_ATTRIBUTE;
+ DOMProperty.mustUseProperty[propName] =
+ propConfig & DOMPropertyInjection.MUST_USE_PROPERTY;
+ DOMProperty.hasBooleanValue[propName] =
+ propConfig & DOMPropertyInjection.HAS_BOOLEAN_VALUE;
+ DOMProperty.hasSideEffects[propName] =
+ propConfig & DOMPropertyInjection.HAS_SIDE_EFFECTS;
+
+ invariant(
+ !DOMProperty.mustUseAttribute[propName] ||
+ !DOMProperty.mustUseProperty[propName],
+ 'DOMProperty: Cannot use require using both attribute and property: %s',
+ propName
+ );
+ invariant(
+ DOMProperty.mustUseProperty[propName] ||
+ !DOMProperty.hasSideEffects[propName],
+ 'DOMProperty: Properties that have side effects must use property: %s',
+ propName
+ );
+ }
+ }
+};
+var defaultValueCache = {};
+
+/**
+ * DOMProperty exports lookup objects that can be used like functions:
+ *
+ * > DOMProperty.isValid['id']
+ * true
+ * > DOMProperty.isValid['foobar']
+ * undefined
+ *
+ * Although this may be confusing, it performs better in general.
+ *
+ * @see http://jsperf.com/key-exists
+ * @see http://jsperf.com/key-missing
+ */
+var DOMProperty = {
+
+ /**
+ * Checks whether a property name is a standard property.
+ * @type {Object}
+ */
+ isStandardName: {},
+
+ /**
+ * Mapping from lowercase property names to the properly cased version, used
+ * to warn in the case of missing properties.
+ * @type {Object}
+ */
+ getPossibleStandardName: {},
+
+ /**
+ * Mapping from normalized names to attribute names that differ. Attribute
+ * names are used when rendering markup or with `*Attribute()`.
+ * @type {Object}
+ */
+ getAttributeName: {},
+
+ /**
+ * Mapping from normalized names to properties on DOM node instances.
+ * (This includes properties that mutate due to external factors.)
+ * @type {Object}
+ */
+ getPropertyName: {},
+
+ /**
+ * Mapping from normalized names to mutation methods. This will only exist if
+ * mutation cannot be set simply by the property or `setAttribute()`.
+ * @type {Object}
+ */
+ getMutationMethod: {},
+
+ /**
+ * Whether the property must be accessed and mutated as an object property.
+ * @type {Object}
+ */
+ mustUseAttribute: {},
+
+ /**
+ * Whether the property must be accessed and mutated using `*Attribute()`.
+ * (This includes anything that fails `<propName> in <element>`.)
+ * @type {Object}
+ */
+ mustUseProperty: {},
+
+ /**
+ * Whether the property should be removed when set to a falsey value.
+ * @type {Object}
+ */
+ hasBooleanValue: {},
+
+ /**
+ * Whether or not setting a value causes side effects such as triggering
+ * resources to be loaded or text selection changes. We must ensure that
+ * the value is only set if it has changed.
+ * @type {Object}
+ */
+ hasSideEffects: {},
+
+ /**
+ * All of the isCustomAttribute() functions that have been injected.
+ */
+ _isCustomAttributeFunctions: [],
+
+ /**
+ * Checks whether a property name is a custom attribute.
+ * @method
+ */
+ isCustomAttribute: function(attributeName) {
+ return DOMProperty._isCustomAttributeFunctions.some(
+ function(isCustomAttributeFn) {
+ return isCustomAttributeFn.call(null, attributeName);
+ }
+ );
+ },
+
+ /**
+ * Returns the default property value for a DOM property (i.e., not an
+ * attribute). Most default values are '' or false, but not all. Worse yet,
+ * some (in particular, `type`) vary depending on the type of element.
+ *
+ * TODO: Is it better to grab all the possible properties when creating an
+ * element to avoid having to create the same element twice?
+ */
+ getDefaultValueForProperty: function(nodeName, prop) {
+ var nodeDefaults = defaultValueCache[nodeName];
+ var testElement;
+ if (!nodeDefaults) {
+ defaultValueCache[nodeName] = nodeDefaults = {};
+ }
+ if (!(prop in nodeDefaults)) {
+ testElement = document.createElement(nodeName);
+ nodeDefaults[prop] = testElement[prop];
+ }
+ return nodeDefaults[prop];
+ },
+
+ injection: DOMPropertyInjection
+};
+
+module.exports = DOMProperty;
+
+},{"./invariant":89}],8:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule DOMPropertyOperations
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var DOMProperty = require("./DOMProperty");
+
+var escapeTextForBrowser = require("./escapeTextForBrowser");
+var memoizeStringOnly = require("./memoizeStringOnly");
+
+var processAttributeNameAndPrefix = memoizeStringOnly(function(name) {
+ return escapeTextForBrowser(name) + '="';
+});
+
+if (true) {
+ var reactProps = {
+ '{owner}': true,
+ children: true,
+ dangerouslySetInnerHTML: true,
+ key: true,
+ ref: true
+ };
+ var warnedProperties = {};
+
+ var warnUnknownProperty = function(name) {
+ if (reactProps[name] || warnedProperties[name]) {
+ return;
+ }
+
+ warnedProperties[name] = true;
+ var lowerCasedName = name.toLowerCase();
+
+ // data-* attributes should be lowercase; suggest the lowercase version
+ var standardName = DOMProperty.isCustomAttribute(lowerCasedName) ?
+ lowerCasedName : DOMProperty.getPossibleStandardName[lowerCasedName];
+
+ // For now, only warn when we have a suggested correction. This prevents
+ // logging too much when using transferPropsTo.
+ if (standardName != null) {
+ console.warn(
+ 'Unknown DOM property ' + name + '. Did you mean ' + standardName + '?'
+ );
+ }
+
+ };
+}
+
+/**
+ * Operations for dealing with DOM properties.
+ */
+var DOMPropertyOperations = {
+
+ /**
+ * Creates markup for a property.
+ *
+ * @param {string} name
+ * @param {*} value
+ * @return {?string} Markup string, or null if the property was invalid.
+ */
+ createMarkupForProperty: function(name, value) {
+ if (DOMProperty.isStandardName[name]) {
+ if (value == null || DOMProperty.hasBooleanValue[name] && !value) {
+ return '';
+ }
+ var attributeName = DOMProperty.getAttributeName[name];
+ return processAttributeNameAndPrefix(attributeName) +
+ escapeTextForBrowser(value) + '"';
+ } else if (DOMProperty.isCustomAttribute(name)) {
+ if (value == null) {
+ return '';
+ }
+ return processAttributeNameAndPrefix(name) +
+ escapeTextForBrowser(value) + '"';
+ } else {
+ if (true) {
+ warnUnknownProperty(name);
+ }
+ return null;
+ }
+ },
+
+ /**
+ * Sets the value for a property on a node.
+ *
+ * @param {DOMElement} node
+ * @param {string} name
+ * @param {*} value
+ */
+ setValueForProperty: function(node, name, value) {
+ if (DOMProperty.isStandardName[name]) {
+ var mutationMethod = DOMProperty.getMutationMethod[name];
+ if (mutationMethod) {
+ mutationMethod(node, value);
+ } else if (DOMProperty.mustUseAttribute[name]) {
+ if (DOMProperty.hasBooleanValue[name] && !value) {
+ node.removeAttribute(DOMProperty.getAttributeName[name]);
+ } else {
+ node.setAttribute(DOMProperty.getAttributeName[name], value);
+ }
+ } else {
+ var propName = DOMProperty.getPropertyName[name];
+ if (!DOMProperty.hasSideEffects[name] || node[propName] !== value) {
+ node[propName] = value;
+ }
+ }
+ } else if (DOMProperty.isCustomAttribute(name)) {
+ node.setAttribute(name, value);
+ } else {
+ if (true) {
+ warnUnknownProperty(name);
+ }
+ }
+ },
+
+ /**
+ * Deletes the value for a property on a node.
+ *
+ * @param {DOMElement} node
+ * @param {string} name
+ */
+ deleteValueForProperty: function(node, name) {
+ if (DOMProperty.isStandardName[name]) {
+ var mutationMethod = DOMProperty.getMutationMethod[name];
+ if (mutationMethod) {
+ mutationMethod(node, undefined);
+ } else if (DOMProperty.mustUseAttribute[name]) {
+ node.removeAttribute(DOMProperty.getAttributeName[name]);
+ } else {
+ var propName = DOMProperty.getPropertyName[name];
+ node[propName] = DOMProperty.getDefaultValueForProperty(
+ node.nodeName,
+ name
+ );
+ }
+ } else if (DOMProperty.isCustomAttribute(name)) {
+ node.removeAttribute(name);
+ } else {
+ if (true) {
+ warnUnknownProperty(name);
+ }
+ }
+ }
+
+};
+
+module.exports = DOMPropertyOperations;
+
+},{"./DOMProperty":7,"./escapeTextForBrowser":78,"./memoizeStringOnly":94}],9:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule Danger
+ * @typechecks static-only
+ */
+
+/*jslint evil: true, sub: true */
+
+"use strict";
+
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+
+var createNodesFromMarkup = require("./createNodesFromMarkup");
+var emptyFunction = require("./emptyFunction");
+var getMarkupWrap = require("./getMarkupWrap");
+var invariant = require("./invariant");
+var mutateHTMLNodeWithMarkup = require("./mutateHTMLNodeWithMarkup");
+
+var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/;
+var RESULT_INDEX_ATTR = 'data-danger-index';
+
+/**
+ * Extracts the `nodeName` from a string of markup.
+ *
+ * NOTE: Extracting the `nodeName` does not require a regular expression match
+ * because we make assumptions about React-generated markup (i.e. there are no
+ * spaces surrounding the opening tag and there is at least one attribute).
+ *
+ * @param {string} markup String of markup.
+ * @return {string} Node name of the supplied markup.
+ * @see http://jsperf.com/extract-nodename
+ */
+function getNodeName(markup) {
+ return markup.substring(1, markup.indexOf(' '));
+}
+
+var Danger = {
+
+ /**
+ * Renders markup into an array of nodes. The markup is expected to render
+ * into a list of root nodes. Also, the length of `resultList` and
+ * `markupList` should be the same.
+ *
+ * @param {array<string>} markupList List of markup strings to render.
+ * @return {array<DOMElement>} List of rendered nodes.
+ * @internal
+ */
+ dangerouslyRenderMarkup: function(markupList) {
+ invariant(
+ ExecutionEnvironment.canUseDOM,
+ 'dangerouslyRenderMarkup(...): Cannot render markup in a Worker ' +
+ 'thread. This is likely a bug in the framework. Please report ' +
+ 'immediately.'
+ );
+ var nodeName;
+ var markupByNodeName = {};
+ // Group markup by `nodeName` if a wrap is necessary, else by '*'.
+ for (var i = 0; i < markupList.length; i++) {
+ invariant(
+ markupList[i],
+ 'dangerouslyRenderMarkup(...): Missing markup.'
+ );
+ nodeName = getNodeName(markupList[i]);
+ nodeName = getMarkupWrap(nodeName) ? nodeName : '*';
+ markupByNodeName[nodeName] = markupByNodeName[nodeName] || [];
+ markupByNodeName[nodeName][i] = markupList[i];
+ }
+ var resultList = [];
+ var resultListAssignmentCount = 0;
+ for (nodeName in markupByNodeName) {
+ if (!markupByNodeName.hasOwnProperty(nodeName)) {
+ continue;
+ }
+ var markupListByNodeName = markupByNodeName[nodeName];
+
+ // This for-in loop skips the holes of the sparse array. The order of
+ // iteration should follow the order of assignment, which happens to match
+ // numerical index order, but we don't rely on that.
+ for (var resultIndex in markupListByNodeName) {
+ if (markupListByNodeName.hasOwnProperty(resultIndex)) {
+ var markup = markupListByNodeName[resultIndex];
+
+ // Push the requested markup with an additional RESULT_INDEX_ATTR
+ // attribute. If the markup does not start with a < character, it
+ // will be discarded below (with an appropriate console.error).
+ markupListByNodeName[resultIndex] = markup.replace(
+ OPEN_TAG_NAME_EXP,
+ // This index will be parsed back out below.
+ '$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" '
+ );
+ }
+ }
+
+ // Render each group of markup with similar wrapping `nodeName`.
+ var renderNodes = createNodesFromMarkup(
+ markupListByNodeName.join(''),
+ emptyFunction // Do nothing special with <script> tags.
+ );
+
+ for (i = 0; i < renderNodes.length; ++i) {
+ var renderNode = renderNodes[i];
+ if (renderNode.hasAttribute &&
+ renderNode.hasAttribute(RESULT_INDEX_ATTR)) {
+
+ resultIndex = +renderNode.getAttribute(RESULT_INDEX_ATTR);
+ renderNode.removeAttribute(RESULT_INDEX_ATTR);
+
+ invariant(
+ !resultList.hasOwnProperty(resultIndex),
+ 'Danger: Assigning to an already-occupied result index.'
+ );
+
+ resultList[resultIndex] = renderNode;
+
+ // This should match resultList.length and markupList.length when
+ // we're done.
+ resultListAssignmentCount += 1;
+
+ } else if (true) {
+ console.error(
+ "Danger: Discarding unexpected node:",
+ renderNode
+ );
+ }
+ }
+ }
+
+ // Although resultList was populated out of order, it should now be a dense
+ // array.
+ invariant(
+ resultListAssignmentCount === resultList.length,
+ 'Danger: Did not assign to every index of resultList.'
+ );
+
+ invariant(
+ resultList.length === markupList.length,
+ 'Danger: Expected markup to render %d nodes, but rendered %d.',
+ markupList.length,
+ resultList.length
+ );
+
+ return resultList;
+ },
+
+ /**
+ * Replaces a node with a string of markup at its current position within its
+ * parent. The markup must render into a single root node.
+ *
+ * @param {DOMElement} oldChild Child node to replace.
+ * @param {string} markup Markup to render in place of the child node.
+ * @internal
+ */
+ dangerouslyReplaceNodeWithMarkup: function(oldChild, markup) {
+ invariant(
+ ExecutionEnvironment.canUseDOM,
+ 'dangerouslyReplaceNodeWithMarkup(...): Cannot render markup in a ' +
+ 'worker thread. This is likely a bug in the framework. Please report ' +
+ 'immediately.'
+ );
+ invariant(markup, 'dangerouslyReplaceNodeWithMarkup(...): Missing markup.');
+ // createNodesFromMarkup() won't work if the markup is rooted by <html>
+ // since it has special semantic meaning. So we use an alternatie strategy.
+ if (oldChild.tagName.toLowerCase() === 'html') {
+ mutateHTMLNodeWithMarkup(oldChild, markup);
+ return;
+ }
+ var newChild = createNodesFromMarkup(markup, emptyFunction)[0];
+ oldChild.parentNode.replaceChild(newChild, oldChild);
+ }
+
+};
+
+module.exports = Danger;
+
+},{"./ExecutionEnvironment":19,"./createNodesFromMarkup":74,"./emptyFunction":77,"./getMarkupWrap":85,"./invariant":89,"./mutateHTMLNodeWithMarkup":99}],10:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule DefaultDOMPropertyConfig
+ */
+
+"use strict";
+
+var DOMProperty = require("./DOMProperty");
+
+var MUST_USE_ATTRIBUTE = DOMProperty.injection.MUST_USE_ATTRIBUTE;
+var MUST_USE_PROPERTY = DOMProperty.injection.MUST_USE_PROPERTY;
+var HAS_BOOLEAN_VALUE = DOMProperty.injection.HAS_BOOLEAN_VALUE;
+var HAS_SIDE_EFFECTS = DOMProperty.injection.HAS_SIDE_EFFECTS;
+
+var DefaultDOMPropertyConfig = {
+ isCustomAttribute: RegExp.prototype.test.bind(
+ /^(data|aria)-[a-z_][a-z\d_.\-]*$/
+ ),
+ Properties: {
+ /**
+ * Standard Properties
+ */
+ accessKey: null,
+ accept: null,
+ action: null,
+ ajaxify: MUST_USE_ATTRIBUTE,
+ allowFullScreen: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
+ allowTransparency: MUST_USE_ATTRIBUTE,
+ alt: null,
+ autoComplete: null,
+ autoFocus: HAS_BOOLEAN_VALUE,
+ autoPlay: HAS_BOOLEAN_VALUE,
+ cellPadding: null,
+ cellSpacing: null,
+ charSet: MUST_USE_ATTRIBUTE,
+ checked: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
+ className: MUST_USE_PROPERTY,
+ colSpan: null,
+ content: null,
+ contentEditable: null,
+ contextMenu: MUST_USE_ATTRIBUTE,
+ controls: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
+ data: null, // For `<object />` acts as `src`.
+ dateTime: MUST_USE_ATTRIBUTE,
+ dir: null,
+ disabled: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
+ draggable: null,
+ encType: null,
+ form: MUST_USE_ATTRIBUTE,
+ frameBorder: MUST_USE_ATTRIBUTE,
+ height: MUST_USE_ATTRIBUTE,
+ hidden: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
+ href: null,
+ htmlFor: null,
+ httpEquiv: null,
+ icon: null,
+ id: MUST_USE_PROPERTY,
+ label: null,
+ lang: null,
+ list: null,
+ max: null,
+ maxLength: MUST_USE_ATTRIBUTE,
+ method: null,
+ min: null,
+ multiple: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
+ name: null,
+ pattern: null,
+ poster: null,
+ preload: null,
+ placeholder: null,
+ radioGroup: null,
+ rel: null,
+ readOnly: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
+ required: HAS_BOOLEAN_VALUE,
+ role: MUST_USE_ATTRIBUTE,
+ rowSpan: null,
+ scrollLeft: MUST_USE_PROPERTY,
+ scrollTop: MUST_USE_PROPERTY,
+ selected: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
+ size: null,
+ spellCheck: null,
+ src: null,
+ step: null,
+ style: null,
+ tabIndex: null,
+ target: null,
+ title: null,
+ type: null,
+ value: MUST_USE_PROPERTY | HAS_SIDE_EFFECTS,
+ width: MUST_USE_ATTRIBUTE,
+ wmode: MUST_USE_ATTRIBUTE,
+
+ /**
+ * Non-standard Properties
+ */
+ autoCapitalize: null, // Supported in Mobile Safari for keyboard hints
+
+ /**
+ * SVG Properties
+ */
+ cx: MUST_USE_PROPERTY,
+ cy: MUST_USE_PROPERTY,
+ d: MUST_USE_PROPERTY,
+ fill: MUST_USE_PROPERTY,
+ fx: MUST_USE_PROPERTY,
+ fy: MUST_USE_PROPERTY,
+ points: MUST_USE_PROPERTY,
+ r: MUST_USE_PROPERTY,
+ stroke: MUST_USE_PROPERTY,
+ strokeLinecap: MUST_USE_PROPERTY,
+ strokeWidth: MUST_USE_PROPERTY,
+ transform: MUST_USE_PROPERTY,
+ x: MUST_USE_PROPERTY,
+ x1: MUST_USE_PROPERTY,
+ x2: MUST_USE_PROPERTY,
+ version: MUST_USE_PROPERTY,
+ viewBox: MUST_USE_PROPERTY,
+ y: MUST_USE_PROPERTY,
+ y1: MUST_USE_PROPERTY,
+ y2: MUST_USE_PROPERTY,
+ spreadMethod: MUST_USE_PROPERTY,
+ offset: MUST_USE_PROPERTY,
+ stopColor: MUST_USE_PROPERTY,
+ stopOpacity: MUST_USE_PROPERTY,
+ gradientUnits: MUST_USE_PROPERTY,
+ gradientTransform: MUST_USE_PROPERTY
+ },
+ DOMAttributeNames: {
+ className: 'class',
+ htmlFor: 'for',
+ strokeLinecap: 'stroke-linecap',
+ strokeWidth: 'stroke-width',
+ stopColor: 'stop-color',
+ stopOpacity: 'stop-opacity'
+ },
+ DOMPropertyNames: {
+ autoCapitalize: 'autocapitalize',
+ autoComplete: 'autocomplete',
+ autoFocus: 'autofocus',
+ autoPlay: 'autoplay',
+ encType: 'enctype',
+ radioGroup: 'radiogroup',
+ spellCheck: 'spellcheck'
+ },
+ DOMMutationMethods: {
+ /**
+ * Setting `className` to null may cause it to be set to the string "null".
+ *
+ * @param {DOMElement} node
+ * @param {*} value
+ */
+ className: function(node, value) {
+ node.className = value || '';
+ }
+ }
+};
+
+module.exports = DefaultDOMPropertyConfig;
+
+},{"./DOMProperty":7}],11:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule DefaultEventPluginOrder
+ */
+
+"use strict";
+
+ var keyOf = require("./keyOf");
+
+/**
+ * Module that is injectable into `EventPluginHub`, that specifies a
+ * deterministic ordering of `EventPlugin`s. A convenient way to reason about
+ * plugins, without having to package every one of them. This is better than
+ * having plugins be ordered in the same order that they are injected because
+ * that ordering would be influenced by the packaging order.
+ * `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
+ * preventing default on events is convenient in `SimpleEventPlugin` handlers.
+ */
+var DefaultEventPluginOrder = [
+ keyOf({ResponderEventPlugin: null}),
+ keyOf({SimpleEventPlugin: null}),
+ keyOf({TapEventPlugin: null}),
+ keyOf({EnterLeaveEventPlugin: null}),
+ keyOf({ChangeEventPlugin: null}),
+ keyOf({AnalyticsEventPlugin: null}),
+ keyOf({MobileSafariClickEventPlugin: null})
+];
+
+module.exports = DefaultEventPluginOrder;
+
+},{"./keyOf":93}],12:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule EnterLeaveEventPlugin
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var EventConstants = require("./EventConstants");
+var EventPropagators = require("./EventPropagators");
+var SyntheticMouseEvent = require("./SyntheticMouseEvent");
+
+var ReactMount = require("./ReactMount");
+var keyOf = require("./keyOf");
+
+var topLevelTypes = EventConstants.topLevelTypes;
+var getFirstReactDOM = ReactMount.getFirstReactDOM;
+
+var eventTypes = {
+ mouseEnter: {registrationName: keyOf({onMouseEnter: null})},
+ mouseLeave: {registrationName: keyOf({onMouseLeave: null})}
+};
+
+var extractedEvents = [null, null];
+
+var EnterLeaveEventPlugin = {
+
+ eventTypes: eventTypes,
+
+ /**
+ * For almost every interaction we care about, there will be both a top-level
+ * `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that
+ * we do not extract duplicate events. However, moving the mouse into the
+ * browser from outside will not fire a `mouseout` event. In this case, we use
+ * the `mouseover` top-level event.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {*} An accumulation of synthetic events.
+ * @see {EventPluginHub.extractEvents}
+ */
+ extractEvents: function(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent) {
+ if (topLevelType === topLevelTypes.topMouseOver &&
+ (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
+ return null;
+ }
+ if (topLevelType !== topLevelTypes.topMouseOut &&
+ topLevelType !== topLevelTypes.topMouseOver) {
+ // Must not be a mouse in or mouse out - ignoring.
+ return null;
+ }
+
+ var from, to;
+ if (topLevelType === topLevelTypes.topMouseOut) {
+ from = topLevelTarget;
+ to =
+ getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement) ||
+ window;
+ } else {
+ from = window;
+ to = topLevelTarget;
+ }
+
+ if (from === to) {
+ // Nothing pertains to our managed components.
+ return null;
+ }
+
+ var fromID = from ? ReactMount.getID(from) : '';
+ var toID = to ? ReactMount.getID(to) : '';
+
+ var leave = SyntheticMouseEvent.getPooled(
+ eventTypes.mouseLeave,
+ fromID,
+ nativeEvent
+ );
+ var enter = SyntheticMouseEvent.getPooled(
+ eventTypes.mouseEnter,
+ toID,
+ nativeEvent
+ );
+
+ EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
+
+ extractedEvents[0] = leave;
+ extractedEvents[1] = enter;
+
+ return extractedEvents;
+ }
+
+};
+
+module.exports = EnterLeaveEventPlugin;
+
+},{"./EventConstants":13,"./EventPropagators":18,"./ReactMount":45,"./SyntheticMouseEvent":63,"./keyOf":93}],13:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule EventConstants
+ */
+
+"use strict";
+
+var keyMirror = require("./keyMirror");
+
+var PropagationPhases = keyMirror({bubbled: null, captured: null});
+
+/**
+ * Types of raw signals from the browser caught at the top level.
+ */
+var topLevelTypes = keyMirror({
+ topBlur: null,
+ topChange: null,
+ topClick: null,
+ topCopy: null,
+ topCut: null,
+ topDOMCharacterDataModified: null,
+ topDoubleClick: null,
+ topDrag: null,
+ topDragEnd: null,
+ topDragEnter: null,
+ topDragExit: null,
+ topDragLeave: null,
+ topDragOver: null,
+ topDragStart: null,
+ topDrop: null,
+ topFocus: null,
+ topInput: null,
+ topKeyDown: null,
+ topKeyPress: null,
+ topKeyUp: null,
+ topMouseDown: null,
+ topMouseMove: null,
+ topMouseOut: null,
+ topMouseOver: null,
+ topMouseUp: null,
+ topPaste: null,
+ topScroll: null,
+ topSelectionChange: null,
+ topSubmit: null,
+ topTouchCancel: null,
+ topTouchEnd: null,
+ topTouchMove: null,
+ topTouchStart: null,
+ topWheel: null
+});
+
+var EventConstants = {
+ topLevelTypes: topLevelTypes,
+ PropagationPhases: PropagationPhases
+};
+
+module.exports = EventConstants;
+
+},{"./keyMirror":92}],14:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule EventListener
+ */
+
+/**
+ * Upstream version of event listener. Does not take into account specific
+ * nature of platform.
+ */
+var EventListener = {
+ /**
+ * Listens to bubbled events on a DOM node.
+ *
+ * @param {Element} el DOM element to register listener on.
+ * @param {string} handlerBaseName 'click'/'mouseover'
+ * @param {Function!} cb Callback function
+ */
+ listen: function(el, handlerBaseName, cb) {
+ if (el.addEventListener) {
+ el.addEventListener(handlerBaseName, cb, false);
+ } else if (el.attachEvent) {
+ el.attachEvent('on' + handlerBaseName, cb);
+ }
+ },
+
+ /**
+ * Listens to captured events on a DOM node.
+ *
+ * @see `EventListener.listen` for params.
+ * @throws Exception if addEventListener is not supported.
+ */
+ capture: function(el, handlerBaseName, cb) {
+ if (!el.addEventListener) {
+ if (true) {
+ console.error(
+ 'You are attempting to use addEventlistener ' +
+ 'in a browser that does not support it support it.' +
+ 'This likely means that you will not receive events that ' +
+ 'your application relies on (such as scroll).');
+ }
+ return;
+ } else {
+ el.addEventListener(handlerBaseName, cb, true);
+ }
+ }
+};
+
+module.exports = EventListener;
+
+},{}],15:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule EventPluginHub
+ */
+
+"use strict";
+
+var CallbackRegistry = require("./CallbackRegistry");
+var EventPluginRegistry = require("./EventPluginRegistry");
+var EventPluginUtils = require("./EventPluginUtils");
+var EventPropagators = require("./EventPropagators");
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+
+var accumulate = require("./accumulate");
+var forEachAccumulated = require("./forEachAccumulated");
+var invariant = require("./invariant");
+
+/**
+ * Internal queue of events that have accumulated their dispatches and are
+ * waiting to have their dispatches executed.
+ */
+var eventQueue = null;
+
+/**
+ * Dispatches an event and releases it back into the pool, unless persistent.
+ *
+ * @param {?object} event Synthetic event to be dispatched.
+ * @private
+ */
+var executeDispatchesAndRelease = function(event) {
+ if (event) {
+ var executeDispatch = EventPluginUtils.executeDispatch;
+ // Plugins can provide custom behavior when dispatching events.
+ var PluginModule = EventPluginRegistry.getPluginModuleForEvent(event);
+ if (PluginModule && PluginModule.executeDispatch) {
+ executeDispatch = PluginModule.executeDispatch;
+ }
+ EventPluginUtils.executeDispatchesInOrder(event, executeDispatch);
+
+ if (!event.isPersistent()) {
+ event.constructor.release(event);
+ }
+ }
+};
+
+/**
+ * This is a unified interface for event plugins to be installed and configured.
+ *
+ * Event plugins can implement the following properties:
+ *
+ * `extractEvents` {function(string, DOMEventTarget, string, object): *}
+ * Required. When a top-level event is fired, this method is expected to
+ * extract synthetic events that will in turn be queued and dispatched.
+ *
+ * `eventTypes` {object}
+ * Optional, plugins that fire events must publish a mapping of registration
+ * names that are used to register listeners. Values of this mapping must
+ * be objects that contain `registrationName` or `phasedRegistrationNames`.
+ *
+ * `executeDispatch` {function(object, function, string)}
+ * Optional, allows plugins to override how an event gets dispatched. By
+ * default, the listener is simply invoked.
+ *
+ * Each plugin that is injected into `EventsPluginHub` is immediately operable.
+ *
+ * @public
+ */
+var EventPluginHub = {
+
+ /**
+ * Methods for injecting dependencies.
+ */
+ injection: {
+
+ /**
+ * @param {object} InjectedInstanceHandle
+ * @public
+ */
+ injectInstanceHandle: EventPropagators.injection.injectInstanceHandle,
+
+ /**
+ * @param {array} InjectedEventPluginOrder
+ * @public
+ */
+ injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,
+
+ /**
+ * @param {object} injectedNamesToPlugins Map from names to plugin modules.
+ */
+ injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName
+
+ },
+
+ registrationNames: EventPluginRegistry.registrationNames,
+
+ putListener: CallbackRegistry.putListener,
+
+ getListener: CallbackRegistry.getListener,
+
+ deleteListener: CallbackRegistry.deleteListener,
+
+ deleteAllListeners: CallbackRegistry.deleteAllListeners,
+
+ /**
+ * Allows registered plugins an opportunity to extract events from top-level
+ * native browser events.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {*} An accumulation of synthetic events.
+ * @internal
+ */
+ extractEvents: function(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent) {
+ var events;
+ var plugins = EventPluginRegistry.plugins;
+ for (var i = 0, l = plugins.length; i < l; i++) {
+ // Not every plugin in the ordering may be loaded at runtime.
+ var possiblePlugin = plugins[i];
+ if (possiblePlugin) {
+ var extractedEvents = possiblePlugin.extractEvents(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent
+ );
+ if (extractedEvents) {
+ events = accumulate(events, extractedEvents);
+ }
+ }
+ }
+ return events;
+ },
+
+ /**
+ * Enqueues a synthetic event that should be dispatched when
+ * `processEventQueue` is invoked.
+ *
+ * @param {*} events An accumulation of synthetic events.
+ * @internal
+ */
+ enqueueEvents: function(events) {
+ if (events) {
+ eventQueue = accumulate(eventQueue, events);
+ }
+ },
+
+ /**
+ * Dispatches all synthetic events on the event queue.
+ *
+ * @internal
+ */
+ processEventQueue: function() {
+ // Set `eventQueue` to null before processing it so that we can tell if more
+ // events get enqueued while processing.
+ var processingEventQueue = eventQueue;
+ eventQueue = null;
+ forEachAccumulated(processingEventQueue, executeDispatchesAndRelease);
+ invariant(
+ !eventQueue,
+ 'processEventQueue(): Additional events were enqueued while processing ' +
+ 'an event queue. Support for this has not yet been implemented.'
+ );
+ }
+
+};
+
+if (ExecutionEnvironment.canUseDOM) {
+ window.EventPluginHub = EventPluginHub;
+}
+
+module.exports = EventPluginHub;
+
+},{"./CallbackRegistry":4,"./EventPluginRegistry":16,"./EventPluginUtils":17,"./EventPropagators":18,"./ExecutionEnvironment":19,"./accumulate":70,"./forEachAccumulated":82,"./invariant":89}],16:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule EventPluginRegistry
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+/**
+ * Injectable ordering of event plugins.
+ */
+var EventPluginOrder = null;
+
+/**
+ * Injectable mapping from names to event plugin modules.
+ */
+var namesToPlugins = {};
+
+/**
+ * Recomputes the plugin list using the injected plugins and plugin ordering.
+ *
+ * @private
+ */
+function recomputePluginOrdering() {
+ if (!EventPluginOrder) {
+ // Wait until an `EventPluginOrder` is injected.
+ return;
+ }
+ for (var pluginName in namesToPlugins) {
+ var PluginModule = namesToPlugins[pluginName];
+ var pluginIndex = EventPluginOrder.indexOf(pluginName);
+ invariant(
+ pluginIndex > -1,
+ 'EventPluginRegistry: Cannot inject event plugins that do not exist in ' +
+ 'the plugin ordering, `%s`.',
+ pluginName
+ );
+ if (EventPluginRegistry.plugins[pluginIndex]) {
+ continue;
+ }
+ invariant(
+ PluginModule.extractEvents,
+ 'EventPluginRegistry: Event plugins must implement an `extractEvents` ' +
+ 'method, but `%s` does not.',
+ pluginName
+ );
+ EventPluginRegistry.plugins[pluginIndex] = PluginModule;
+ var publishedEvents = PluginModule.eventTypes;
+ for (var eventName in publishedEvents) {
+ invariant(
+ publishEventForPlugin(publishedEvents[eventName], PluginModule),
+ 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',
+ eventName,
+ pluginName
+ );
+ }
+ }
+}
+
+/**
+ * Publishes an event so that it can be dispatched by the supplied plugin.
+ *
+ * @param {object} dispatchConfig Dispatch configuration for the event.
+ * @param {object} PluginModule Plugin publishing the event.
+ * @return {boolean} True if the event was successfully published.
+ * @private
+ */
+function publishEventForPlugin(dispatchConfig, PluginModule) {
+ var phasedRegistrationNames = dispatchConfig.phasedRegistrationNames;
+ if (phasedRegistrationNames) {
+ for (var phaseName in phasedRegistrationNames) {
+ if (phasedRegistrationNames.hasOwnProperty(phaseName)) {
+ var phasedRegistrationName = phasedRegistrationNames[phaseName];
+ publishRegistrationName(phasedRegistrationName, PluginModule);
+ }
+ }
+ return true;
+ } else if (dispatchConfig.registrationName) {
+ publishRegistrationName(dispatchConfig.registrationName, PluginModule);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Publishes a registration name that is used to identify dispatched events and
+ * can be used with `EventPluginHub.putListener` to register listeners.
+ *
+ * @param {string} registrationName Registration name to add.
+ * @param {object} PluginModule Plugin publishing the event.
+ * @private
+ */
+function publishRegistrationName(registrationName, PluginModule) {
+ invariant(
+ !EventPluginRegistry.registrationNames[registrationName],
+ 'EventPluginHub: More than one plugin attempted to publish the same ' +
+ 'registration name, `%s`.',
+ registrationName
+ );
+ EventPluginRegistry.registrationNames[registrationName] = PluginModule;
+ EventPluginRegistry.registrationNamesKeys.push(registrationName);
+}
+
+/**
+ * Registers plugins so that they can extract and dispatch events.
+ *
+ * @see {EventPluginHub}
+ */
+var EventPluginRegistry = {
+
+ /**
+ * Ordered list of injected plugins.
+ */
+ plugins: [],
+
+ /**
+ * Mapping from registration names to plugin modules.
+ */
+ registrationNames: {},
+
+ /**
+ * The keys of `registrationNames`.
+ */
+ registrationNamesKeys: [],
+
+ /**
+ * Injects an ordering of plugins (by plugin name). This allows the ordering
+ * to be decoupled from injection of the actual plugins so that ordering is
+ * always deterministic regardless of packaging, on-the-fly injection, etc.
+ *
+ * @param {array} InjectedEventPluginOrder
+ * @internal
+ * @see {EventPluginHub.injection.injectEventPluginOrder}
+ */
+ injectEventPluginOrder: function(InjectedEventPluginOrder) {
+ invariant(
+ !EventPluginOrder,
+ 'EventPluginRegistry: Cannot inject event plugin ordering more than once.'
+ );
+ // Clone the ordering so it cannot be dynamically mutated.
+ EventPluginOrder = Array.prototype.slice.call(InjectedEventPluginOrder);
+ recomputePluginOrdering();
+ },
+
+ /**
+ * Injects plugins to be used by `EventPluginHub`. The plugin names must be
+ * in the ordering injected by `injectEventPluginOrder`.
+ *
+ * Plugins can be injected as part of page initialization or on-the-fly.
+ *
+ * @param {object} injectedNamesToPlugins Map from names to plugin modules.
+ * @internal
+ * @see {EventPluginHub.injection.injectEventPluginsByName}
+ */
+ injectEventPluginsByName: function(injectedNamesToPlugins) {
+ var isOrderingDirty = false;
+ for (var pluginName in injectedNamesToPlugins) {
+ if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
+ continue;
+ }
+ var PluginModule = injectedNamesToPlugins[pluginName];
+ if (namesToPlugins[pluginName] !== PluginModule) {
+ invariant(
+ !namesToPlugins[pluginName],
+ 'EventPluginRegistry: Cannot inject two different event plugins ' +
+ 'using the same name, `%s`.',
+ pluginName
+ );
+ namesToPlugins[pluginName] = PluginModule;
+ isOrderingDirty = true;
+ }
+ }
+ if (isOrderingDirty) {
+ recomputePluginOrdering();
+ }
+ },
+
+ /**
+ * Looks up the plugin for the supplied event.
+ *
+ * @param {object} event A synthetic event.
+ * @return {?object} The plugin that created the supplied event.
+ * @internal
+ */
+ getPluginModuleForEvent: function(event) {
+ var dispatchConfig = event.dispatchConfig;
+ if (dispatchConfig.registrationName) {
+ return EventPluginRegistry.registrationNames[
+ dispatchConfig.registrationName
+ ] || null;
+ }
+ for (var phase in dispatchConfig.phasedRegistrationNames) {
+ if (!dispatchConfig.phasedRegistrationNames.hasOwnProperty(phase)) {
+ continue;
+ }
+ var PluginModule = EventPluginRegistry.registrationNames[
+ dispatchConfig.phasedRegistrationNames[phase]
+ ];
+ if (PluginModule) {
+ return PluginModule;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Exposed for unit testing.
+ * @private
+ */
+ _resetEventPlugins: function() {
+ EventPluginOrder = null;
+ for (var pluginName in namesToPlugins) {
+ if (namesToPlugins.hasOwnProperty(pluginName)) {
+ delete namesToPlugins[pluginName];
+ }
+ }
+ EventPluginRegistry.plugins.length = 0;
+ var registrationNames = EventPluginRegistry.registrationNames;
+ for (var registrationName in registrationNames) {
+ if (registrationNames.hasOwnProperty(registrationName)) {
+ delete registrationNames[registrationName];
+ }
+ }
+ EventPluginRegistry.registrationNamesKeys.length = 0;
+ }
+
+};
+
+module.exports = EventPluginRegistry;
+
+},{"./invariant":89}],17:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule EventPluginUtils
+ */
+
+"use strict";
+
+var EventConstants = require("./EventConstants");
+
+var invariant = require("./invariant");
+
+var topLevelTypes = EventConstants.topLevelTypes;
+
+function isEndish(topLevelType) {
+ return topLevelType === topLevelTypes.topMouseUp ||
+ topLevelType === topLevelTypes.topTouchEnd ||
+ topLevelType === topLevelTypes.topTouchCancel;
+}
+
+function isMoveish(topLevelType) {
+ return topLevelType === topLevelTypes.topMouseMove ||
+ topLevelType === topLevelTypes.topTouchMove;
+}
+function isStartish(topLevelType) {
+ return topLevelType === topLevelTypes.topMouseDown ||
+ topLevelType === topLevelTypes.topTouchStart;
+}
+
+var validateEventDispatches;
+if (true) {
+ validateEventDispatches = function(event) {
+ var dispatchListeners = event._dispatchListeners;
+ var dispatchIDs = event._dispatchIDs;
+
+ var listenersIsArr = Array.isArray(dispatchListeners);
+ var idsIsArr = Array.isArray(dispatchIDs);
+ var IDsLen = idsIsArr ? dispatchIDs.length : dispatchIDs ? 1 : 0;
+ var listenersLen = listenersIsArr ?
+ dispatchListeners.length :
+ dispatchListeners ? 1 : 0;
+
+ invariant(
+ idsIsArr === listenersIsArr && IDsLen === listenersLen,
+ 'EventPluginUtils: Invalid `event`.'
+ );
+ };
+}
+
+/**
+ * Invokes `cb(event, listener, id)`. Avoids using call if no scope is
+ * provided. The `(listener,id)` pair effectively forms the "dispatch" but are
+ * kept separate to conserve memory.
+ */
+function forEachEventDispatch(event, cb) {
+ var dispatchListeners = event._dispatchListeners;
+ var dispatchIDs = event._dispatchIDs;
+ if (true) {
+ validateEventDispatches(event);
+ }
+ if (Array.isArray(dispatchListeners)) {
+ for (var i = 0; i < dispatchListeners.length; i++) {
+ if (event.isPropagationStopped()) {
+ break;
+ }
+ // Listeners and IDs are two parallel arrays that are always in sync.
+ cb(event, dispatchListeners[i], dispatchIDs[i]);
+ }
+ } else if (dispatchListeners) {
+ cb(event, dispatchListeners, dispatchIDs);
+ }
+}
+
+/**
+ * Default implementation of PluginModule.executeDispatch().
+ * @param {SyntheticEvent} SyntheticEvent to handle
+ * @param {function} Application-level callback
+ * @param {string} domID DOM id to pass to the callback.
+ */
+function executeDispatch(event, listener, domID) {
+ listener(event, domID);
+}
+
+/**
+ * Standard/simple iteration through an event's collected dispatches.
+ */
+function executeDispatchesInOrder(event, executeDispatch) {
+ forEachEventDispatch(event, executeDispatch);
+ event._dispatchListeners = null;
+ event._dispatchIDs = null;
+}
+
+/**
+ * Standard/simple iteration through an event's collected dispatches, but stops
+ * at the first dispatch execution returning true, and returns that id.
+ *
+ * @return id of the first dispatch execution who's listener returns true, or
+ * null if no listener returned true.
+ */
+function executeDispatchesInOrderStopAtTrue(event) {
+ var dispatchListeners = event._dispatchListeners;
+ var dispatchIDs = event._dispatchIDs;
+ if (true) {
+ validateEventDispatches(event);
+ }
+ if (Array.isArray(dispatchListeners)) {
+ for (var i = 0; i < dispatchListeners.length; i++) {
+ if (event.isPropagationStopped()) {
+ break;
+ }
+ // Listeners and IDs are two parallel arrays that are always in sync.
+ if (dispatchListeners[i](event, dispatchIDs[i])) {
+ return dispatchIDs[i];
+ }
+ }
+ } else if (dispatchListeners) {
+ if (dispatchListeners(event, dispatchIDs)) {
+ return dispatchIDs;
+ }
+ }
+ return null;
+}
+
+/**
+ * Execution of a "direct" dispatch - there must be at most one dispatch
+ * accumulated on the event or it is considered an error. It doesn't really make
+ * sense for an event with multiple dispatches (bubbled) to keep track of the
+ * return values at each dispatch execution, but it does tend to make sense when
+ * dealing with "direct" dispatches.
+ *
+ * @return The return value of executing the single dispatch.
+ */
+function executeDirectDispatch(event) {
+ if (true) {
+ validateEventDispatches(event);
+ }
+ var dispatchListener = event._dispatchListeners;
+ var dispatchID = event._dispatchIDs;
+ invariant(
+ !Array.isArray(dispatchListener),
+ 'executeDirectDispatch(...): Invalid `event`.'
+ );
+ var res = dispatchListener ?
+ dispatchListener(event, dispatchID) :
+ null;
+ event._dispatchListeners = null;
+ event._dispatchIDs = null;
+ return res;
+}
+
+/**
+ * @param {SyntheticEvent} event
+ * @return {bool} True iff number of dispatches accumulated is greater than 0.
+ */
+function hasDispatches(event) {
+ return !!event._dispatchListeners;
+}
+
+/**
+ * General utilities that are useful in creating custom Event Plugins.
+ */
+var EventPluginUtils = {
+ isEndish: isEndish,
+ isMoveish: isMoveish,
+ isStartish: isStartish,
+ executeDispatchesInOrder: executeDispatchesInOrder,
+ executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
+ executeDirectDispatch: executeDirectDispatch,
+ hasDispatches: hasDispatches,
+ executeDispatch: executeDispatch
+};
+
+module.exports = EventPluginUtils;
+
+},{"./EventConstants":13,"./invariant":89}],18:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule EventPropagators
+ */
+
+"use strict";
+
+var CallbackRegistry = require("./CallbackRegistry");
+var EventConstants = require("./EventConstants");
+
+var accumulate = require("./accumulate");
+var forEachAccumulated = require("./forEachAccumulated");
+var getListener = CallbackRegistry.getListener;
+var PropagationPhases = EventConstants.PropagationPhases;
+
+/**
+ * Injected dependencies:
+ */
+
+/**
+ * - `InstanceHandle`: [required] Module that performs logical traversals of DOM
+ * hierarchy given ids of the logical DOM elements involved.
+ */
+var injection = {
+ InstanceHandle: null,
+ injectInstanceHandle: function(InjectedInstanceHandle) {
+ injection.InstanceHandle = InjectedInstanceHandle;
+ if (true) {
+ injection.validate();
+ }
+ },
+ validate: function() {
+ var invalid = !injection.InstanceHandle||
+ !injection.InstanceHandle.traverseTwoPhase ||
+ !injection.InstanceHandle.traverseEnterLeave;
+ if (invalid) {
+ throw new Error('InstanceHandle not injected before use!');
+ }
+ }
+};
+
+/**
+ * Some event types have a notion of different registration names for different
+ * "phases" of propagation. This finds listeners by a given phase.
+ */
+function listenerAtPhase(id, event, propagationPhase) {
+ var registrationName =
+ event.dispatchConfig.phasedRegistrationNames[propagationPhase];
+ return getListener(id, registrationName);
+}
+
+/**
+ * Tags a `SyntheticEvent` with dispatched listeners. Creating this function
+ * here, allows us to not have to bind or create functions for each event.
+ * Mutating the event's members allows us to not have to create a wrapping
+ * "dispatch" object that pairs the event with the listener.
+ */
+function accumulateDirectionalDispatches(domID, upwards, event) {
+ if (true) {
+ if (!domID) {
+ throw new Error('Dispatching id must not be null');
+ }
+ injection.validate();
+ }
+ var phase = upwards ? PropagationPhases.bubbled : PropagationPhases.captured;
+ var listener = listenerAtPhase(domID, event, phase);
+ if (listener) {
+ event._dispatchListeners = accumulate(event._dispatchListeners, listener);
+ event._dispatchIDs = accumulate(event._dispatchIDs, domID);
+ }
+}
+
+/**
+ * Collect dispatches (must be entirely collected before dispatching - see unit
+ * tests). Lazily allocate the array to conserve memory. We must loop through
+ * each event and perform the traversal for each one. We can not perform a
+ * single traversal for the entire collection of events because each event may
+ * have a different target.
+ */
+function accumulateTwoPhaseDispatchesSingle(event) {
+ if (event && event.dispatchConfig.phasedRegistrationNames) {
+ injection.InstanceHandle.traverseTwoPhase(
+ event.dispatchMarker,
+ accumulateDirectionalDispatches,
+ event
+ );
+ }
+}
+
+
+/**
+ * Accumulates without regard to direction, does not look for phased
+ * registration names. Same as `accumulateDirectDispatchesSingle` but without
+ * requiring that the `dispatchMarker` be the same as the dispatched ID.
+ */
+function accumulateDispatches(id, ignoredDirection, event) {
+ if (event && event.dispatchConfig.registrationName) {
+ var registrationName = event.dispatchConfig.registrationName;
+ var listener = getListener(id, registrationName);
+ if (listener) {
+ event._dispatchListeners = accumulate(event._dispatchListeners, listener);
+ event._dispatchIDs = accumulate(event._dispatchIDs, id);
+ }
+ }
+}
+
+/**
+ * Accumulates dispatches on an `SyntheticEvent`, but only for the
+ * `dispatchMarker`.
+ * @param {SyntheticEvent} event
+ */
+function accumulateDirectDispatchesSingle(event) {
+ if (event && event.dispatchConfig.registrationName) {
+ accumulateDispatches(event.dispatchMarker, null, event);
+ }
+}
+
+function accumulateTwoPhaseDispatches(events) {
+ if (true) {
+ injection.validate();
+ }
+ forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
+}
+
+function accumulateEnterLeaveDispatches(leave, enter, fromID, toID) {
+ if (true) {
+ injection.validate();
+ }
+ injection.InstanceHandle.traverseEnterLeave(
+ fromID,
+ toID,
+ accumulateDispatches,
+ leave,
+ enter
+ );
+}
+
+
+function accumulateDirectDispatches(events) {
+ if (true) {
+ injection.validate();
+ }
+ forEachAccumulated(events, accumulateDirectDispatchesSingle);
+}
+
+
+
+/**
+ * A small set of propagation patterns, each of which will accept a small amount
+ * of information, and generate a set of "dispatch ready event objects" - which
+ * are sets of events that have already been annotated with a set of dispatched
+ * listener functions/ids. The API is designed this way to discourage these
+ * propagation strategies from actually executing the dispatches, since we
+ * always want to collect the entire set of dispatches before executing event a
+ * single one.
+ *
+ * @constructor EventPropagators
+ */
+var EventPropagators = {
+ accumulateTwoPhaseDispatches: accumulateTwoPhaseDispatches,
+ accumulateDirectDispatches: accumulateDirectDispatches,
+ accumulateEnterLeaveDispatches: accumulateEnterLeaveDispatches,
+ injection: injection
+};
+
+module.exports = EventPropagators;
+
+},{"./CallbackRegistry":4,"./EventConstants":13,"./accumulate":70,"./forEachAccumulated":82}],19:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ExecutionEnvironment
+ */
+
+/*jslint evil: true */
+
+"use strict";
+
+var canUseDOM = typeof window !== 'undefined';
+
+/**
+ * Simple, lightweight module assisting with the detection and context of
+ * Worker. Helps avoid circular dependencies and allows code to reason about
+ * whether or not they are in a Worker, even if they never include the main
+ * `ReactWorker` dependency.
+ */
+var ExecutionEnvironment = {
+
+ canUseDOM: canUseDOM,
+
+ canUseWorkers: typeof Worker !== 'undefined',
+
+ isInWorker: !canUseDOM // For now, this is true - might change in the future.
+
+};
+
+module.exports = ExecutionEnvironment;
+
+},{}],20:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule MobileSafariClickEventPlugin
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var EventConstants = require("./EventConstants");
+
+var emptyFunction = require("./emptyFunction");
+
+var topLevelTypes = EventConstants.topLevelTypes;
+
+/**
+ * Mobile Safari does not fire properly bubble click events on non-interactive
+ * elements, which means delegated click listeners do not fire. The workaround
+ * for this bug involves attaching an empty click listener on the target node.
+ *
+ * This particular plugin works around the bug by attaching an empty click
+ * listener on `touchstart` (which does fire on every element).
+ */
+var MobileSafariClickEventPlugin = {
+
+ eventTypes: null,
+
+ /**
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {*} An accumulation of synthetic events.
+ * @see {EventPluginHub.extractEvents}
+ */
+ extractEvents: function(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent) {
+ if (topLevelType === topLevelTypes.topTouchStart) {
+ var target = nativeEvent.target;
+ if (target && !target.onclick) {
+ target.onclick = emptyFunction;
+ }
+ }
+ }
+
+};
+
+module.exports = MobileSafariClickEventPlugin;
+
+},{"./EventConstants":13,"./emptyFunction":77}],21:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule PooledClass
+ */
+
+"use strict";
+
+/**
+ * Static poolers. Several custom versions for each potential number of
+ * arguments. A completely generic pooler is easy to implement, but would
+ * require accessing the `arguments` object. In each of these, `this` refers to
+ * the Class itself, not an instance. If any others are needed, simply add them
+ * here, or in their own files.
+ */
+var oneArgumentPooler = function(copyFieldsFrom) {
+ var Klass = this;
+ if (Klass.instancePool.length) {
+ var instance = Klass.instancePool.pop();
+ Klass.call(instance, copyFieldsFrom);
+ return instance;
+ } else {
+ return new Klass(copyFieldsFrom);
+ }
+};
+
+var twoArgumentPooler = function(a1, a2) {
+ var Klass = this;
+ if (Klass.instancePool.length) {
+ var instance = Klass.instancePool.pop();
+ Klass.call(instance, a1, a2);
+ return instance;
+ } else {
+ return new Klass(a1, a2);
+ }
+};
+
+var threeArgumentPooler = function(a1, a2, a3) {
+ var Klass = this;
+ if (Klass.instancePool.length) {
+ var instance = Klass.instancePool.pop();
+ Klass.call(instance, a1, a2, a3);
+ return instance;
+ } else {
+ return new Klass(a1, a2, a3);
+ }
+};
+
+var fiveArgumentPooler = function(a1, a2, a3, a4, a5) {
+ var Klass = this;
+ if (Klass.instancePool.length) {
+ var instance = Klass.instancePool.pop();
+ Klass.call(instance, a1, a2, a3, a4, a5);
+ return instance;
+ } else {
+ return new Klass(a1, a2, a3, a4, a5);
+ }
+};
+
+var standardReleaser = function(instance) {
+ var Klass = this;
+ if (instance.destructor) {
+ instance.destructor();
+ }
+ if (Klass.instancePool.length < Klass.poolSize) {
+ Klass.instancePool.push(instance);
+ }
+};
+
+var DEFAULT_POOL_SIZE = 10;
+var DEFAULT_POOLER = oneArgumentPooler;
+
+/**
+ * Augments `CopyConstructor` to be a poolable class, augmenting only the class
+ * itself (statically) not adding any prototypical fields. Any CopyConstructor
+ * you give this may have a `poolSize` property, and will look for a
+ * prototypical `destructor` on instances (optional).
+ *
+ * @param {Function} CopyConstructor Constructor that can be used to reset.
+ * @param {Function} pooler Customizable pooler.
+ */
+var addPoolingTo = function(CopyConstructor, pooler) {
+ var NewKlass = CopyConstructor;
+ NewKlass.instancePool = [];
+ NewKlass.getPooled = pooler || DEFAULT_POOLER;
+ if (!NewKlass.poolSize) {
+ NewKlass.poolSize = DEFAULT_POOL_SIZE;
+ }
+ NewKlass.release = standardReleaser;
+ return NewKlass;
+};
+
+var PooledClass = {
+ addPoolingTo: addPoolingTo,
+ oneArgumentPooler: oneArgumentPooler,
+ twoArgumentPooler: twoArgumentPooler,
+ threeArgumentPooler: threeArgumentPooler,
+ fiveArgumentPooler: fiveArgumentPooler
+};
+
+module.exports = PooledClass;
+
+},{}],22:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule React
+ */
+
+"use strict";
+
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactComponent = require("./ReactComponent");
+var ReactDOM = require("./ReactDOM");
+var ReactMount = require("./ReactMount");
+var ReactPerf = require("./ReactPerf");
+var ReactPropTypes = require("./ReactPropTypes");
+var ReactServerRendering = require("./ReactServerRendering");
+
+var ReactDefaultInjection = require("./ReactDefaultInjection");
+
+ReactDefaultInjection.inject();
+
+var React = {
+ DOM: ReactDOM,
+ PropTypes: ReactPropTypes,
+ initializeTouchEvents: function(shouldUseTouch) {
+ ReactMount.useTouchEvents = shouldUseTouch;
+ },
+ createClass: ReactCompositeComponent.createClass,
+ constructAndRenderComponent: ReactMount.constructAndRenderComponent,
+ constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
+ renderComponent: ReactPerf.measure(
+ 'React',
+ 'renderComponent',
+ ReactMount.renderComponent
+ ),
+ renderComponentToString: ReactServerRendering.renderComponentToString,
+ unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
+ isValidClass: ReactCompositeComponent.isValidClass,
+ isValidComponent: ReactComponent.isValidComponent
+};
+
+// Version exists only in the open-source version of React, not in Facebook's
+// internal version.
+React.version = '0.5.0-alpha';
+
+module.exports = React;
+
+},{"./ReactComponent":23,"./ReactCompositeComponent":26,"./ReactDOM":28,"./ReactDefaultInjection":37,"./ReactMount":45,"./ReactPerf":51,"./ReactPropTypes":53,"./ReactServerRendering":55}],23:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactComponent
+ */
+
+"use strict";
+
+var ReactComponentEnvironment = require("./ReactComponentEnvironment");
+var ReactCurrentOwner = require("./ReactCurrentOwner");
+var ReactOwner = require("./ReactOwner");
+var ReactUpdates = require("./ReactUpdates");
+
+var invariant = require("./invariant");
+var keyMirror = require("./keyMirror");
+var merge = require("./merge");
+
+/**
+ * Prop key that references a component's owner.
+ * @private
+ */
+var OWNER = '{owner}';
+
+/**
+ * Props key that determines if a component's key was already validated.
+ * @private
+ */
+var IS_KEY_VALIDATED = '{is.key.validated}';
+
+/**
+ * Every React component is in one of these life cycles.
+ */
+var ComponentLifeCycle = keyMirror({
+ /**
+ * Mounted components have a DOM node representation and are capable of
+ * receiving new props.
+ */
+ MOUNTED: null,
+ /**
+ * Unmounted components are inactive and cannot receive new props.
+ */
+ UNMOUNTED: null
+});
+
+/**
+ * Warn if there's no key explicitly set on dynamic arrays of children.
+ * This allows us to keep track of children between updates.
+ */
+
+var ownerHasWarned = {};
+
+/**
+ * Warn if the component doesn't have an explicit key assigned to it.
+ * This component is in an array. The array could grow and shrink or be
+ * reordered. All children, that hasn't already been validated, are required to
+ * have a "key" property assigned to it.
+ *
+ * @internal
+ * @param {ReactComponent} component Component that requires a key.
+ */
+function validateExplicitKey(component) {
+ if (component[IS_KEY_VALIDATED] || component.props.key != null) {
+ return;
+ }
+ component[IS_KEY_VALIDATED] = true;
+
+ // We can't provide friendly warnings for top level components.
+ if (!ReactCurrentOwner.current) {
+ return;
+ }
+
+ // Name of the component whose render method tried to pass children.
+ var currentName = ReactCurrentOwner.current.constructor.displayName;
+ if (ownerHasWarned.hasOwnProperty(currentName)) {
+ return;
+ }
+ ownerHasWarned[currentName] = true;
+
+ var message = 'Each child in an array should have a unique "key" prop. ' +
+ 'Check the render method of ' + currentName + '.';
+ if (!component.isOwnedBy(ReactCurrentOwner.current)) {
+ // Name of the component that originally created this child.
+ var childOwnerName =
+ component.props[OWNER] && component.props[OWNER].constructor.displayName;
+
+ // Usually the current owner is the offender, but if it accepts
+ // children as a property, it may be the creator of the child that's
+ // responsible for assigning it a key.
+ message += ' It was passed a child from ' + childOwnerName + '.';
+ }
+
+ console.warn(message);
+}
+
+/**
+ * Ensure that every component either is passed in a static location or, if
+ * if it's passed in an array, has an explicit key property defined.
+ *
+ * @internal
+ * @param {*} component Statically passed child of any type.
+ * @return {boolean}
+ */
+function validateChildKeys(component) {
+ if (Array.isArray(component)) {
+ for (var i = 0; i < component.length; i++) {
+ var child = component[i];
+ if (ReactComponent.isValidComponent(child)) {
+ validateExplicitKey(child);
+ }
+ }
+ } else if (ReactComponent.isValidComponent(component)) {
+ // This component was passed in a valid location.
+ component[IS_KEY_VALIDATED] = true;
+ }
+}
+
+/**
+ * Components are the basic units of composition in React.
+ *
+ * Every component accepts a set of keyed input parameters known as "props" that
+ * are initialized by the constructor. Once a component is mounted, the props
+ * can be mutated using `setProps` or `replaceProps`.
+ *
+ * Every component is capable of the following operations:
+ *
+ * `mountComponent`
+ * Initializes the component, renders markup, and registers event listeners.
+ *
+ * `receiveProps`
+ * Updates the rendered DOM nodes given a new set of props.
+ *
+ * `unmountComponent`
+ * Releases any resources allocated by this component.
+ *
+ * Components can also be "owned" by other components. Being owned by another
+ * component means being constructed by that component. This is different from
+ * being the child of a component, which means having a DOM representation that
+ * is a child of the DOM representation of that component.
+ *
+ * @class ReactComponent
+ */
+var ReactComponent = {
+
+ /**
+ * @param {?object} object
+ * @return {boolean} True if `object` is a valid component.
+ * @final
+ */
+ isValidComponent: function(object) {
+ return !!(
+ object &&
+ typeof object.mountComponentIntoNode === 'function' &&
+ typeof object.receiveProps === 'function'
+ );
+ },
+
+ /**
+ * Generate a key string that identifies a component within a set.
+ *
+ * @param {*} component A component that could contain a manual key.
+ * @param {number} index Index that is used if a manual key is not provided.
+ * @return {string}
+ * @internal
+ */
+ getKey: function(component, index) {
+ if (component && component.props && component.props.key != null) {
+ // Explicit key
+ return '{' + component.props.key + '}';
+ }
+ // Implicit key determined by the index in the set
+ return '[' + index + ']';
+ },
+
+ /**
+ * @internal
+ */
+ LifeCycle: ComponentLifeCycle,
+
+ /**
+ * Injected module that provides ability to mutate individual properties.
+ * Injected into the base class because many different subclasses need access
+ * to this.
+ *
+ * @internal
+ */
+ DOMIDOperations: ReactComponentEnvironment.DOMIDOperations,
+
+ /**
+ * Optionally injectable environment dependent cleanup hook. (server vs.
+ * browser etc). Example: A browser system caches DOM nodes based on component
+ * ID and must remove that cache entry when this instance is unmounted.
+ *
+ * @private
+ */
+ unmountIDFromEnvironment: ReactComponentEnvironment.unmountIDFromEnvironment,
+
+ /**
+ * The "image" of a component tree, is the platform specific (typically
+ * serialized) data that represents a tree of lower level UI building blocks.
+ * On the web, this "image" is HTML markup which describes a construction of
+ * low level `div` and `span` nodes. Other platforms may have different
+ * encoding of this "image". This must be injected.
+ *
+ * @private
+ */
+ mountImageIntoNode: ReactComponentEnvironment.mountImageIntoNode,
+
+ /**
+ * React references `ReactReconcileTransaction` using this property in order
+ * to allow dependency injection.
+ *
+ * @internal
+ */
+ ReactReconcileTransaction:
+ ReactComponentEnvironment.ReactReconcileTransaction,
+
+ /**
+ * Base functionality for every ReactComponent constructor. Mixed into the
+ * `ReactComponent` prototype, but exposed statically for easy access.
+ *
+ * @lends {ReactComponent.prototype}
+ */
+ Mixin: merge(ReactComponentEnvironment.Mixin, {
+
+ /**
+ * Checks whether or not this component is mounted.
+ *
+ * @return {boolean} True if mounted, false otherwise.
+ * @final
+ * @protected
+ */
+ isMounted: function() {
+ return this._lifeCycleState === ComponentLifeCycle.MOUNTED;
+ },
+
+ /**
+ * Sets a subset of the props.
+ *
+ * @param {object} partialProps Subset of the next props.
+ * @param {?function} callback Called after props are updated.
+ * @final
+ * @public
+ */
+ setProps: function(partialProps, callback) {
+ // Merge with `_pendingProps` if it exists, otherwise with existing props.
+ this.replaceProps(
+ merge(this._pendingProps || this.props, partialProps),
+ callback
+ );
+ },
+
+ /**
+ * Replaces all of the props.
+ *
+ * @param {object} props New props.
+ * @param {?function} callback Called after props are updated.
+ * @final
+ * @public
+ */
+ replaceProps: function(props, callback) {
+ invariant(
+ !this.props[OWNER],
+ 'replaceProps(...): You called `setProps` or `replaceProps` on a ' +
+ 'component with an owner. This is an anti-pattern since props will ' +
+ 'get reactively updated when rendered. Instead, change the owner\'s ' +
+ '`render` method to pass the correct value as props to the component ' +
+ 'where it is created.'
+ );
+ invariant(
+ this.isMounted(),
+ 'replaceProps(...): Can only update a mounted component.'
+ );
+ this._pendingProps = props;
+ ReactUpdates.enqueueUpdate(this, callback);
+ },
+
+ /**
+ * Base constructor for all React component.
+ *
+ * Subclasses that override this method should make sure to invoke
+ * `ReactComponent.Mixin.construct.call(this, ...)`.
+ *
+ * @param {?object} initialProps
+ * @param {*} children
+ * @internal
+ */
+ construct: function(initialProps, children) {
+ this.props = initialProps || {};
+ // Record the component responsible for creating this component.
+ this.props[OWNER] = ReactCurrentOwner.current;
+ // All components start unmounted.
+ this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
+
+ this._pendingProps = null;
+ this._pendingCallbacks = null;
+
+ // Children can be more than one argument
+ var childrenLength = arguments.length - 1;
+ if (childrenLength === 1) {
+ if (true) {
+ validateChildKeys(children);
+ }
+ this.props.children = children;
+ } else if (childrenLength > 1) {
+ var childArray = Array(childrenLength);
+ for (var i = 0; i < childrenLength; i++) {
+ if (true) {
+ validateChildKeys(arguments[i + 1]);
+ }
+ childArray[i] = arguments[i + 1];
+ }
+ this.props.children = childArray;
+ }
+ },
+
+ /**
+ * Initializes the component, renders markup, and registers event listeners.
+ *
+ * NOTE: This does not insert any nodes into the DOM.
+ *
+ * Subclasses that override this method should make sure to invoke
+ * `ReactComponent.Mixin.mountComponent.call(this, ...)`.
+ *
+ * @param {string} rootID DOM ID of the root node.
+ * @param {ReactReconcileTransaction} transaction
+ * @return {?string} Rendered markup to be inserted into the DOM.
+ * @internal
+ */
+ mountComponent: function(rootID, transaction) {
+ invariant(
+ !this.isMounted(),
+ 'mountComponent(%s, ...): Can only mount an unmounted component.',
+ rootID
+ );
+ var props = this.props;
+ if (props.ref != null) {
+ ReactOwner.addComponentAsRefTo(this, props.ref, props[OWNER]);
+ }
+ this._rootNodeID = rootID;
+ this._lifeCycleState = ComponentLifeCycle.MOUNTED;
+ // Effectively: return '';
+ },
+
+ /**
+ * Releases any resources allocated by `mountComponent`.
+ *
+ * NOTE: This does not remove any nodes from the DOM.
+ *
+ * Subclasses that override this method should make sure to invoke
+ * `ReactComponent.Mixin.unmountComponent.call(this)`.
+ *
+ * @internal
+ */
+ unmountComponent: function() {
+ invariant(
+ this.isMounted(),
+ 'unmountComponent(): Can only unmount a mounted component.'
+ );
+ var props = this.props;
+ if (props.ref != null) {
+ ReactOwner.removeComponentAsRefFrom(this, props.ref, props[OWNER]);
+ }
+ ReactComponent.unmountIDFromEnvironment(this._rootNodeID);
+ this._rootNodeID = null;
+ this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
+ },
+
+ /**
+ * Updates the rendered DOM nodes given a new set of props.
+ *
+ * Subclasses that override this method should make sure to invoke
+ * `ReactComponent.Mixin.receiveProps.call(this, ...)`.
+ *
+ * @param {object} nextProps Next set of properties.
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ */
+ receiveProps: function(nextProps, transaction) {
+ invariant(
+ this.isMounted(),
+ 'receiveProps(...): Can only update a mounted component.'
+ );
+ this._pendingProps = nextProps;
+ this._performUpdateIfNecessary(transaction);
+ },
+
+ /**
+ * Call `_performUpdateIfNecessary` within a new transaction.
+ *
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ */
+ performUpdateIfNecessary: function() {
+ var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
+ transaction.perform(this._performUpdateIfNecessary, this, transaction);
+ ReactComponent.ReactReconcileTransaction.release(transaction);
+ },
+
+ /**
+ * If `_pendingProps` is set, update the component.
+ *
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ */
+ _performUpdateIfNecessary: function(transaction) {
+ if (this._pendingProps == null) {
+ return;
+ }
+ var prevProps = this.props;
+ this.props = this._pendingProps;
+ this._pendingProps = null;
+ this.updateComponent(transaction, prevProps);
+ },
+
+ /**
+ * Updates the component's currently mounted representation.
+ *
+ * @param {ReactReconcileTransaction} transaction
+ * @param {object} prevProps
+ * @internal
+ */
+ updateComponent: function(transaction, prevProps) {
+ var props = this.props;
+ // If either the owner or a `ref` has changed, make sure the newest owner
+ // has stored a reference to `this`, and the previous owner (if different)
+ // has forgotten the reference to `this`.
+ if (props[OWNER] !== prevProps[OWNER] || props.ref !== prevProps.ref) {
+ if (prevProps.ref != null) {
+ ReactOwner.removeComponentAsRefFrom(
+ this, prevProps.ref, prevProps[OWNER]
+ );
+ }
+ // Correct, even if the owner is the same, and only the ref has changed.
+ if (props.ref != null) {
+ ReactOwner.addComponentAsRefTo(this, props.ref, props[OWNER]);
+ }
+ }
+ },
+
+ /**
+ * Mounts this component and inserts it into the DOM.
+ *
+ * @param {string} rootID DOM ID of the root node.
+ * @param {DOMElement} container DOM element to mount into.
+ * @param {boolean} shouldReuseMarkup If true, do not insert markup
+ * @final
+ * @internal
+ * @see {ReactMount.renderComponent}
+ */
+ mountComponentIntoNode: function(rootID, container, shouldReuseMarkup) {
+ var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
+ transaction.perform(
+ this._mountComponentIntoNode,
+ this,
+ rootID,
+ container,
+ transaction,
+ shouldReuseMarkup
+ );
+ ReactComponent.ReactReconcileTransaction.release(transaction);
+ },
+
+ /**
+ * @param {string} rootID DOM ID of the root node.
+ * @param {DOMElement} container DOM element to mount into.
+ * @param {ReactReconcileTransaction} transaction
+ * @param {boolean} shouldReuseMarkup If true, do not insert markup
+ * @final
+ * @private
+ */
+ _mountComponentIntoNode: function(
+ rootID,
+ container,
+ transaction,
+ shouldReuseMarkup) {
+ var markup = this.mountComponent(rootID, transaction);
+ ReactComponent.mountImageIntoNode(markup, container, shouldReuseMarkup);
+ },
+
+ /**
+ * Checks if this component is owned by the supplied `owner` component.
+ *
+ * @param {ReactComponent} owner Component to check.
+ * @return {boolean} True if `owners` owns this component.
+ * @final
+ * @internal
+ */
+ isOwnedBy: function(owner) {
+ return this.props[OWNER] === owner;
+ },
+
+ /**
+ * Gets another component, that shares the same owner as this one, by ref.
+ *
+ * @param {string} ref of a sibling Component.
+ * @return {?ReactComponent} the actual sibling Component.
+ * @final
+ * @internal
+ */
+ getSiblingByRef: function(ref) {
+ var owner = this.props[OWNER];
+ if (!owner || !owner.refs) {
+ return null;
+ }
+ return owner.refs[ref];
+ }
+ })
+};
+
+module.exports = ReactComponent;
+
+},{"./ReactComponentEnvironment":25,"./ReactCurrentOwner":27,"./ReactOwner":50,"./ReactUpdates":57,"./invariant":89,"./keyMirror":92,"./merge":95}],24:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactComponentBrowserEnvironment
+ */
+
+/*jslint evil: true */
+
+"use strict";
+
+var ReactDOMIDOperations = require("./ReactDOMIDOperations");
+var ReactMarkupChecksum = require("./ReactMarkupChecksum");
+var ReactMount = require("./ReactMount");
+var ReactReconcileTransaction = require("./ReactReconcileTransaction");
+
+var getReactRootElementInContainer = require("./getReactRootElementInContainer");
+var invariant = require("./invariant");
+var mutateHTMLNodeWithMarkup = require("./mutateHTMLNodeWithMarkup");
+
+
+var ELEMENT_NODE_TYPE = 1;
+var DOC_NODE_TYPE = 9;
+
+
+/**
+ * Abstracts away all functionality of `ReactComponent` requires knowledge of
+ * the browser context.
+ */
+var ReactComponentBrowserEnvironment = {
+ /**
+ * Mixed into every component instance.
+ */
+ Mixin: {
+ /**
+ * Returns the DOM node rendered by this component.
+ *
+ * @return {DOMElement} The root node of this component.
+ * @final
+ * @protected
+ */
+ getDOMNode: function() {
+ invariant(
+ this.isMounted(),
+ 'getDOMNode(): A component must be mounted to have a DOM node.'
+ );
+ return ReactMount.getNode(this._rootNodeID);
+ }
+ },
+
+ ReactReconcileTransaction: ReactReconcileTransaction,
+
+ DOMIDOperations: ReactDOMIDOperations,
+
+ /**
+ * If a particular environment requires that some resources be cleaned up,
+ * specify this in the injected Mixin. In the DOM, we would likely want to
+ * purge any cached node ID lookups.
+ *
+ * @private
+ */
+ unmountIDFromEnvironment: function(rootNodeID) {
+ ReactMount.purgeID(rootNodeID);
+ },
+
+ /**
+ * @param {string} markup Markup string to place into the DOM Element.
+ * @param {DOMElement} container DOM Element to insert markup into.
+ * @param {boolean} shouldReuseMarkup Should reuse the existing markup in the
+ * container if possible.
+ */
+ mountImageIntoNode: function(markup, container, shouldReuseMarkup) {
+ invariant(
+ container && (
+ container.nodeType === ELEMENT_NODE_TYPE ||
+ container.nodeType === DOC_NODE_TYPE && ReactMount.allowFullPageRender
+ ),
+ 'mountComponentIntoNode(...): Target container is not valid.'
+ );
+ if (shouldReuseMarkup) {
+ if (ReactMarkupChecksum.canReuseMarkup(
+ markup,
+ getReactRootElementInContainer(container))) {
+ return;
+ } else {
+ if (true) {
+ console.warn(
+ 'React attempted to use reuse markup in a container but the ' +
+ 'checksum was invalid. This generally means that you are using ' +
+ 'server rendering and the markup generated on the server was ' +
+ 'not what the client was expecting. React injected new markup ' +
+ 'to compensate which works but you have lost many of the ' +
+ 'benefits of server rendering. Instead, figure out why the ' +
+ 'markup being generated is different on the client or server.'
+ );
+ }
+ }
+ }
+
+ // You can't naively set the innerHTML of the entire document. You need
+ // to mutate documentElement which requires doing some crazy tricks. See
+ // mutateHTMLNodeWithMarkup()
+ if (container.nodeType === DOC_NODE_TYPE) {
+ mutateHTMLNodeWithMarkup(container.documentElement, markup);
+ return;
+ }
+
+ // Asynchronously inject markup by ensuring that the container is not in
+ // the document when settings its `innerHTML`.
+ var parent = container.parentNode;
+ if (parent) {
+ var next = container.nextSibling;
+ parent.removeChild(container);
+ container.innerHTML = markup;
+ if (next) {
+ parent.insertBefore(container, next);
+ } else {
+ parent.appendChild(container);
+ }
+ } else {
+ container.innerHTML = markup;
+ }
+ }
+};
+
+module.exports = ReactComponentBrowserEnvironment;
+
+},{"./ReactDOMIDOperations":31,"./ReactMarkupChecksum":44,"./ReactMount":45,"./ReactReconcileTransaction":54,"./getReactRootElementInContainer":86,"./invariant":89,"./mutateHTMLNodeWithMarkup":99}],25:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactComponentEnvironment
+ */
+
+var ReactComponentBrowserEnvironment =
+ require("./ReactComponentBrowserEnvironment");
+
+var ReactComponentEnvironment = ReactComponentBrowserEnvironment;
+
+module.exports = ReactComponentEnvironment;
+
+},{"./ReactComponentBrowserEnvironment":24}],26:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactCompositeComponent
+ */
+
+"use strict";
+
+var ReactComponent = require("./ReactComponent");
+var ReactCurrentOwner = require("./ReactCurrentOwner");
+var ReactOwner = require("./ReactOwner");
+var ReactPerf = require("./ReactPerf");
+var ReactPropTransferer = require("./ReactPropTransferer");
+var ReactUpdates = require("./ReactUpdates");
+
+var invariant = require("./invariant");
+var keyMirror = require("./keyMirror");
+var merge = require("./merge");
+var mixInto = require("./mixInto");
+var objMap = require("./objMap");
+
+/**
+ * Policies that describe methods in `ReactCompositeComponentInterface`.
+ */
+var SpecPolicy = keyMirror({
+ /**
+ * These methods may be defined only once by the class specification or mixin.
+ */
+ DEFINE_ONCE: null,
+ /**
+ * These methods may be defined by both the class specification and mixins.
+ * Subsequent definitions will be chained. These methods must return void.
+ */
+ DEFINE_MANY: null,
+ /**
+ * These methods are overriding the base ReactCompositeComponent class.
+ */
+ OVERRIDE_BASE: null,
+ /**
+ * These methods are similar to DEFINE_MANY, except we assume they return
+ * objects. We try to merge the keys of the return values of all the mixed in
+ * functions. If there is a key conflict we throw.
+ */
+ DEFINE_MANY_MERGED: null
+});
+
+/**
+ * Composite components are higher-level components that compose other composite
+ * or native components.
+ *
+ * To create a new type of `ReactCompositeComponent`, pass a specification of
+ * your new class to `React.createClass`. The only requirement of your class
+ * specification is that you implement a `render` method.
+ *
+ * var MyComponent = React.createClass({
+ * render: function() {
+ * return <div>Hello World</div>;
+ * }
+ * });
+ *
+ * The class specification supports a specific protocol of methods that have
+ * special meaning (e.g. `render`). See `ReactCompositeComponentInterface` for
+ * more the comprehensive protocol. Any other properties and methods in the
+ * class specification will available on the prototype.
+ *
+ * @interface ReactCompositeComponentInterface
+ * @internal
+ */
+var ReactCompositeComponentInterface = {
+
+ /**
+ * An array of Mixin objects to include when defining your component.
+ *
+ * @type {array}
+ * @optional
+ */
+ mixins: SpecPolicy.DEFINE_MANY,
+
+ /**
+ * Definition of prop types for this component.
+ *
+ * @type {object}
+ * @optional
+ */
+ propTypes: SpecPolicy.DEFINE_ONCE,
+
+
+
+ // ==== Definition methods ====
+
+ /**
+ * Invoked when the component is mounted. Values in the mapping will be set on
+ * `this.props` if that prop is not specified (i.e. using an `in` check).
+ *
+ * This method is invoked before `getInitialState` and therefore cannot rely
+ * on `this.state` or use `this.setState`.
+ *
+ * @return {object}
+ * @optional
+ */
+ getDefaultProps: SpecPolicy.DEFINE_ONCE,
+
+ /**
+ * Invoked once before the component is mounted. The return value will be used
+ * as the initial value of `this.state`.
+ *
+ * getInitialState: function() {
+ * return {
+ * isOn: false,
+ * fooBaz: new BazFoo()
+ * }
+ * }
+ *
+ * @return {object}
+ * @optional
+ */
+ getInitialState: SpecPolicy.DEFINE_MANY_MERGED,
+
+ /**
+ * Uses props from `this.props` and state from `this.state` to render the
+ * structure of the component.
+ *
+ * No guarantees are made about when or how often this method is invoked, so
+ * it must not have side effects.
+ *
+ * render: function() {
+ * var name = this.props.name;
+ * return <div>Hello, {name}!</div>;
+ * }
+ *
+ * @return {ReactComponent}
+ * @nosideeffects
+ * @required
+ */
+ render: SpecPolicy.DEFINE_ONCE,
+
+
+
+ // ==== Delegate methods ====
+
+ /**
+ * Invoked when the component is initially created and about to be mounted.
+ * This may have side effects, but any external subscriptions or data created
+ * by this method must be cleaned up in `componentWillUnmount`.
+ *
+ * @optional
+ */
+ componentWillMount: SpecPolicy.DEFINE_MANY,
+
+ /**
+ * Invoked when the component has been mounted and has a DOM representation.
+ * However, there is no guarantee that the DOM node is in the document.
+ *
+ * Use this as an opportunity to operate on the DOM when the component has
+ * been mounted (initialized and rendered) for the first time.
+ *
+ * @param {DOMElement} rootNode DOM element representing the component.
+ * @optional
+ */
+ componentDidMount: SpecPolicy.DEFINE_MANY,
+
+ /**
+ * Invoked before the component receives new props.
+ *
+ * Use this as an opportunity to react to a prop transition by updating the
+ * state using `this.setState`. Current props are accessed via `this.props`.
+ *
+ * componentWillReceiveProps: function(nextProps) {
+ * this.setState({
+ * likesIncreasing: nextProps.likeCount > this.props.likeCount
+ * });
+ * }
+ *
+ * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
+ * transition may cause a state change, but the opposite is not true. If you
+ * need it, you are probably looking for `componentWillUpdate`.
+ *
+ * @param {object} nextProps
+ * @optional
+ */
+ componentWillReceiveProps: SpecPolicy.DEFINE_MANY,
+
+ /**
+ * Invoked while deciding if the component should be updated as a result of
+ * receiving new props and state.
+ *
+ * Use this as an opportunity to `return false` when you're certain that the
+ * transition to the new props and state will not require a component update.
+ *
+ * shouldComponentUpdate: function(nextProps, nextState) {
+ * return !equal(nextProps, this.props) || !equal(nextState, this.state);
+ * }
+ *
+ * @param {object} nextProps
+ * @param {?object} nextState
+ * @return {boolean} True if the component should update.
+ * @optional
+ */
+ shouldComponentUpdate: SpecPolicy.DEFINE_ONCE,
+
+ /**
+ * Invoked when the component is about to update due to a transition from
+ * `this.props` and `this.state` to `nextProps` and `nextState`.
+ *
+ * Use this as an opportunity to perform preparation before an update occurs.
+ *
+ * NOTE: You **cannot** use `this.setState()` in this method.
+ *
+ * @param {object} nextProps
+ * @param {?object} nextState
+ * @param {ReactReconcileTransaction} transaction
+ * @optional
+ */
+ componentWillUpdate: SpecPolicy.DEFINE_MANY,
+
+ /**
+ * Invoked when the component's DOM representation has been updated.
+ *
+ * Use this as an opportunity to operate on the DOM when the component has
+ * been updated.
+ *
+ * @param {object} prevProps
+ * @param {?object} prevState
+ * @param {DOMElement} rootNode DOM element representing the component.
+ * @optional
+ */
+ componentDidUpdate: SpecPolicy.DEFINE_MANY,
+
+ /**
+ * Invoked when the component is about to be removed from its parent and have
+ * its DOM representation destroyed.
+ *
+ * Use this as an opportunity to deallocate any external resources.
+ *
+ * NOTE: There is no `componentDidUnmount` since your component will have been
+ * destroyed by that point.
+ *
+ * @optional
+ */
+ componentWillUnmount: SpecPolicy.DEFINE_MANY,
+
+
+
+ // ==== Advanced methods ====
+
+ /**
+ * Updates the component's currently mounted DOM representation.
+ *
+ * By default, this implements React's rendering and reconciliation algorithm.
+ * Sophisticated clients may wish to override this.
+ *
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ * @overridable
+ */
+ updateComponent: SpecPolicy.OVERRIDE_BASE
+
+};
+
+/**
+ * Mapping from class specification keys to special processing functions.
+ *
+ * Although these are declared in the specification when defining classes
+ * using `React.createClass`, they will not be on the component's prototype.
+ */
+var RESERVED_SPEC_KEYS = {
+ displayName: function(Constructor, displayName) {
+ Constructor.displayName = displayName;
+ },
+ mixins: function(Constructor, mixins) {
+ if (mixins) {
+ for (var i = 0; i < mixins.length; i++) {
+ mixSpecIntoComponent(Constructor, mixins[i]);
+ }
+ }
+ },
+ propTypes: function(Constructor, propTypes) {
+ Constructor.propTypes = propTypes;
+ }
+};
+
+function validateMethodOverride(proto, name) {
+ var specPolicy = ReactCompositeComponentInterface[name];
+
+ // Disallow overriding of base class methods unless explicitly allowed.
+ if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
+ invariant(
+ specPolicy === SpecPolicy.OVERRIDE_BASE,
+ 'ReactCompositeComponentInterface: You are attempting to override ' +
+ '`%s` from your class specification. Ensure that your method names ' +
+ 'do not overlap with React methods.',
+ name
+ );
+ }
+
+ // Disallow defining methods more than once unless explicitly allowed.
+ if (proto.hasOwnProperty(name)) {
+ invariant(
+ specPolicy === SpecPolicy.DEFINE_MANY ||
+ specPolicy === SpecPolicy.DEFINE_MANY_MERGED,
+ 'ReactCompositeComponentInterface: You are attempting to define ' +
+ '`%s` on your component more than once. This conflict may be due ' +
+ 'to a mixin.',
+ name
+ );
+ }
+}
+
+
+function validateLifeCycleOnReplaceState(instance) {
+ var compositeLifeCycleState = instance._compositeLifeCycleState;
+ invariant(
+ instance.isMounted() ||
+ compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
+ 'replaceState(...): Can only update a mounted or mounting component.'
+ );
+ invariant(
+ compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
+ compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
+ 'replaceState(...): Cannot update while unmounting component or during ' +
+ 'an existing state transition (such as within `render`).'
+ );
+}
+
+/**
+ * Custom version of `mixInto` which handles policy validation and reserved
+ * specification keys when building `ReactCompositeComponent` classses.
+ */
+function mixSpecIntoComponent(Constructor, spec) {
+ var proto = Constructor.prototype;
+ for (var name in spec) {
+ var property = spec[name];
+ if (!spec.hasOwnProperty(name) || !property) {
+ continue;
+ }
+ validateMethodOverride(proto, name);
+
+ if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
+ RESERVED_SPEC_KEYS[name](Constructor, property);
+ } else {
+ // Setup methods on prototype:
+ // The following member methods should not be automatically bound:
+ // 1. Expected ReactCompositeComponent methods (in the "interface").
+ // 2. Overridden methods (that were mixed in).
+ var isCompositeComponentMethod = name in ReactCompositeComponentInterface;
+ var isInherited = name in proto;
+ var markedDontBind = property.__reactDontBind;
+ var isFunction = typeof property === 'function';
+ var shouldAutoBind =
+ isFunction &&
+ !isCompositeComponentMethod &&
+ !isInherited &&
+ !markedDontBind;
+
+ if (shouldAutoBind) {
+ if (!proto.__reactAutoBindMap) {
+ proto.__reactAutoBindMap = {};
+ }
+ proto.__reactAutoBindMap[name] = property;
+ proto[name] = property;
+ } else {
+ if (isInherited) {
+ // For methods which are defined more than once, call the existing
+ // methods before calling the new property.
+ if (ReactCompositeComponentInterface[name] ===
+ SpecPolicy.DEFINE_MANY_MERGED) {
+ proto[name] = createMergedResultFunction(proto[name], property);
+ } else {
+ proto[name] = createChainedFunction(proto[name], property);
+ }
+ } else {
+ proto[name] = property;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Merge two objects, but throw if both contain the same key.
+ *
+ * @param {object} one The first object, which is mutated.
+ * @param {object} two The second object
+ * @return {object} one after it has been mutated to contain everything in two.
+ */
+function mergeObjectsWithNoDuplicateKeys(one, two) {
+ invariant(
+ one && two && typeof one === 'object' && typeof two === 'object',
+ 'mergeObjectsWithNoDuplicateKeys(): Cannot merge non-objects'
+ );
+
+ objMap(two, function(value, key) {
+ invariant(
+ one[key] === undefined,
+ 'mergeObjectsWithNoDuplicateKeys(): ' +
+ 'Tried to merge two objects with the same key: %s',
+ key
+ );
+ one[key] = value;
+ });
+ return one;
+}
+
+/**
+ * Creates a function that invokes two functions and merges their return values.
+ *
+ * @param {function} one Function to invoke first.
+ * @param {function} two Function to invoke second.
+ * @return {function} Function that invokes the two argument functions.
+ * @private
+ */
+function createMergedResultFunction(one, two) {
+ return function mergedResult() {
+ return mergeObjectsWithNoDuplicateKeys(
+ one.apply(this, arguments),
+ two.apply(this, arguments)
+ );
+ };
+}
+
+/**
+ * Creates a function that invokes two functions and ignores their return vales.
+ *
+ * @param {function} one Function to invoke first.
+ * @param {function} two Function to invoke second.
+ * @return {function} Function that invokes the two argument functions.
+ * @private
+ */
+function createChainedFunction(one, two) {
+ return function chainedFunction() {
+ one.apply(this, arguments);
+ two.apply(this, arguments);
+ };
+}
+
+/**
+ * `ReactCompositeComponent` maintains an auxiliary life cycle state in
+ * `this._compositeLifeCycleState` (which can be null).
+ *
+ * This is different from the life cycle state maintained by `ReactComponent` in
+ * `this._lifeCycleState`. The following diagram shows how the states overlap in
+ * time. There are times when the CompositeLifeCycle is null - at those times it
+ * is only meaningful to look at ComponentLifeCycle alone.
+ *
+ * Top Row: ReactComponent.ComponentLifeCycle
+ * Low Row: ReactComponent.CompositeLifeCycle
+ *
+ * +-------+------------------------------------------------------+--------+
+ * | UN | MOUNTED | UN |
+ * |MOUNTED| | MOUNTED|
+ * +-------+------------------------------------------------------+--------+
+ * | ^--------+ +------+ +------+ +------+ +--------^ |
+ * | | | | | | | | | | | |
+ * | 0--|MOUNTING|-0-|RECEIV|-0-|RECEIV|-0-|RECEIV|-0-| UN |--->0 |
+ * | | | |PROPS | | PROPS| | STATE| |MOUNTING| |
+ * | | | | | | | | | | | |
+ * | | | | | | | | | | | |
+ * | +--------+ +------+ +------+ +------+ +--------+ |
+ * | | | |
+ * +-------+------------------------------------------------------+--------+
+ */
+var CompositeLifeCycle = keyMirror({
+ /**
+ * Components in the process of being mounted respond to state changes
+ * differently.
+ */
+ MOUNTING: null,
+ /**
+ * Components in the process of being unmounted are guarded against state
+ * changes.
+ */
+ UNMOUNTING: null,
+ /**
+ * Components that are mounted and receiving new props respond to state
+ * changes differently.
+ */
+ RECEIVING_PROPS: null,
+ /**
+ * Components that are mounted and receiving new state are guarded against
+ * additional state changes.
+ */
+ RECEIVING_STATE: null
+});
+
+/**
+ * @lends {ReactCompositeComponent.prototype}
+ */
+var ReactCompositeComponentMixin = {
+
+ /**
+ * Base constructor for all composite component.
+ *
+ * @param {?object} initialProps
+ * @param {*} children
+ * @final
+ * @internal
+ */
+ construct: function(initialProps, children) {
+ // Children can be either an array or more than one argument
+ ReactComponent.Mixin.construct.apply(this, arguments);
+ this.state = null;
+ this._pendingState = null;
+ this._compositeLifeCycleState = null;
+ },
+
+ /**
+ * Checks whether or not this composite component is mounted.
+ * @return {boolean} True if mounted, false otherwise.
+ * @protected
+ * @final
+ */
+ isMounted: function() {
+ return ReactComponent.Mixin.isMounted.call(this) &&
+ this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING;
+ },
+
+ /**
+ * Initializes the component, renders markup, and registers event listeners.
+ *
+ * @param {string} rootID DOM ID of the root node.
+ * @param {ReactReconcileTransaction} transaction
+ * @return {?string} Rendered markup to be inserted into the DOM.
+ * @final
+ * @internal
+ */
+ mountComponent: ReactPerf.measure(
+ 'ReactCompositeComponent',
+ 'mountComponent',
+ function(rootID, transaction) {
+ ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
+ this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
+
+ this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
+ this._processProps(this.props);
+
+ if (this.__reactAutoBindMap) {
+ this._bindAutoBindMethods();
+ }
+
+ this.state = this.getInitialState ? this.getInitialState() : null;
+ this._pendingState = null;
+ this._pendingForceUpdate = false;
+
+ if (this.componentWillMount) {
+ this.componentWillMount();
+ // When mounting, calls to `setState` by `componentWillMount` will set
+ // `this._pendingState` without triggering a re-render.
+ if (this._pendingState) {
+ this.state = this._pendingState;
+ this._pendingState = null;
+ }
+ }
+
+ this._renderedComponent = this._renderValidatedComponent();
+
+ // Done with mounting, `setState` will now trigger UI changes.
+ this._compositeLifeCycleState = null;
+ var markup = this._renderedComponent.mountComponent(rootID, transaction);
+ if (this.componentDidMount) {
+ transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
+ }
+ return markup;
+ }
+ ),
+
+ /**
+ * Releases any resources allocated by `mountComponent`.
+ *
+ * @final
+ * @internal
+ */
+ unmountComponent: function() {
+ this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;
+ if (this.componentWillUnmount) {
+ this.componentWillUnmount();
+ }
+ this._compositeLifeCycleState = null;
+
+ this._defaultProps = null;
+
+ ReactComponent.Mixin.unmountComponent.call(this);
+ this._renderedComponent.unmountComponent();
+ this._renderedComponent = null;
+
+ if (this.refs) {
+ this.refs = null;
+ }
+
+ // Some existing components rely on this.props even after they've been
+ // destroyed (in event handlers).
+ // TODO: this.props = null;
+ // TODO: this.state = null;
+ },
+
+ /**
+ * Sets a subset of the state. Always use this or `replaceState` to mutate
+ * state. You should treat `this.state` as immutable.
+ *
+ * There is no guarantee that `this.state` will be immediately updated, so
+ * accessing `this.state` after calling this method may return the old value.
+ *
+ * There is no guarantee that calls to `setState` will run synchronously,
+ * as they may eventually be batched together. You can provide an optional
+ * callback that will be executed when the call to setState is actually
+ * completed.
+ *
+ * @param {object} partialState Next partial state to be merged with state.
+ * @param {?function} callback Called after state is updated.
+ * @final
+ * @protected
+ */
+ setState: function(partialState, callback) {
+ // Merge with `_pendingState` if it exists, otherwise with existing state.
+ this.replaceState(
+ merge(this._pendingState || this.state, partialState),
+ callback
+ );
+ },
+
+ /**
+ * Replaces all of the state. Always use this or `setState` to mutate state.
+ * You should treat `this.state` as immutable.
+ *
+ * There is no guarantee that `this.state` will be immediately updated, so
+ * accessing `this.state` after calling this method may return the old value.
+ *
+ * @param {object} completeState Next state.
+ * @param {?function} callback Called after state is updated.
+ * @final
+ * @protected
+ */
+ replaceState: function(completeState, callback) {
+ validateLifeCycleOnReplaceState(this);
+ this._pendingState = completeState;
+ ReactUpdates.enqueueUpdate(this, callback);
+ },
+
+ /**
+ * Processes props by setting default values for unspecified props and
+ * asserting that the props are valid.
+ *
+ * @param {object} props
+ * @private
+ */
+ _processProps: function(props) {
+ var propName;
+ var defaultProps = this._defaultProps;
+ for (propName in defaultProps) {
+ if (!(propName in props)) {
+ props[propName] = defaultProps[propName];
+ }
+ }
+ var propTypes = this.constructor.propTypes;
+ if (propTypes) {
+ var componentName = this.constructor.displayName;
+ for (propName in propTypes) {
+ var checkProp = propTypes[propName];
+ if (checkProp) {
+ checkProp(props, propName, componentName);
+ }
+ }
+ }
+ },
+
+ performUpdateIfNecessary: function() {
+ var compositeLifeCycleState = this._compositeLifeCycleState;
+ // Do not trigger a state transition if we are in the middle of mounting or
+ // receiving props because both of those will already be doing this.
+ if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
+ compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
+ return;
+ }
+ ReactComponent.Mixin.performUpdateIfNecessary.call(this);
+ },
+
+ /**
+ * If any of `_pendingProps`, `_pendingState`, or `_pendingForceUpdate` is
+ * set, update the component.
+ *
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ */
+ _performUpdateIfNecessary: function(transaction) {
+ if (this._pendingProps == null &&
+ this._pendingState == null &&
+ !this._pendingForceUpdate) {
+ return;
+ }
+
+ var nextProps = this.props;
+ if (this._pendingProps != null) {
+ nextProps = this._pendingProps;
+ this._processProps(nextProps);
+ this._pendingProps = null;
+
+ this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
+ if (this.componentWillReceiveProps) {
+ this.componentWillReceiveProps(nextProps, transaction);
+ }
+ }
+
+ this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
+
+ var nextState = this._pendingState || this.state;
+ this._pendingState = null;
+
+ if (this._pendingForceUpdate ||
+ !this.shouldComponentUpdate ||
+ this.shouldComponentUpdate(nextProps, nextState)) {
+ this._pendingForceUpdate = false;
+ // Will set `this.props` and `this.state`.
+ this._performComponentUpdate(nextProps, nextState, transaction);
+ } else {
+ // If it's determined that a component should not update, we still want
+ // to set props and state.
+ this.props = nextProps;
+ this.state = nextState;
+ }
+
+ this._compositeLifeCycleState = null;
+ },
+
+ /**
+ * Merges new props and state, notifies delegate methods of update and
+ * performs update.
+ *
+ * @param {object} nextProps Next object to set as properties.
+ * @param {?object} nextState Next object to set as state.
+ * @param {ReactReconcileTransaction} transaction
+ * @private
+ */
+ _performComponentUpdate: function(nextProps, nextState, transaction) {
+ var prevProps = this.props;
+ var prevState = this.state;
+
+ if (this.componentWillUpdate) {
+ this.componentWillUpdate(nextProps, nextState, transaction);
+ }
+
+ this.props = nextProps;
+ this.state = nextState;
+
+ this.updateComponent(transaction, prevProps, prevState);
+
+ if (this.componentDidUpdate) {
+ transaction.getReactOnDOMReady().enqueue(
+ this,
+ this.componentDidUpdate.bind(this, prevProps, prevState)
+ );
+ }
+ },
+
+ /**
+ * Updates the component's currently mounted DOM representation.
+ *
+ * By default, this implements React's rendering and reconciliation algorithm.
+ * Sophisticated clients may wish to override this.
+ *
+ * @param {ReactReconcileTransaction} transaction
+ * @param {object} prevProps
+ * @param {?object} prevState
+ * @internal
+ * @overridable
+ */
+ updateComponent: ReactPerf.measure(
+ 'ReactCompositeComponent',
+ 'updateComponent',
+ function(transaction, prevProps, prevState) {
+ ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
+ var currentComponent = this._renderedComponent;
+ var nextComponent = this._renderValidatedComponent();
+ if (currentComponent.constructor === nextComponent.constructor) {
+ currentComponent.receiveProps(nextComponent.props, transaction);
+ } else {
+ // These two IDs are actually the same! But nothing should rely on that.
+ var thisID = this._rootNodeID;
+ var currentComponentID = currentComponent._rootNodeID;
+ currentComponent.unmountComponent();
+ var nextMarkup = nextComponent.mountComponent(thisID, transaction);
+ ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
+ currentComponentID,
+ nextMarkup
+ );
+ this._renderedComponent = nextComponent;
+ }
+ }
+ ),
+
+ /**
+ * Forces an update. This should only be invoked when it is known with
+ * certainty that we are **not** in a DOM transaction.
+ *
+ * You may want to call this when you know that some deeper aspect of the
+ * component's state has changed but `setState` was not called.
+ *
+ * This will not invoke `shouldUpdateComponent`, but it will invoke
+ * `componentWillUpdate` and `componentDidUpdate`.
+ *
+ * @param {?function} callback Called after update is complete.
+ * @final
+ * @protected
+ */
+ forceUpdate: function(callback) {
+ var compositeLifeCycleState = this._compositeLifeCycleState;
+ invariant(
+ this.isMounted() ||
+ compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
+ 'forceUpdate(...): Can only force an update on mounted or mounting ' +
+ 'components.'
+ );
+ invariant(
+ compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
+ compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
+ 'forceUpdate(...): Cannot force an update while unmounting component ' +
+ 'or during an existing state transition (such as within `render`).'
+ );
+ this._pendingForceUpdate = true;
+ ReactUpdates.enqueueUpdate(this, callback);
+ },
+
+ /**
+ * @private
+ */
+ _renderValidatedComponent: function() {
+ var renderedComponent;
+ ReactCurrentOwner.current = this;
+ try {
+ renderedComponent = this.render();
+ } catch (error) {
+ // IE8 requires `catch` in order to use `finally`.
+ throw error;
+ } finally {
+ ReactCurrentOwner.current = null;
+ }
+ invariant(
+ ReactComponent.isValidComponent(renderedComponent),
+ '%s.render(): A valid ReactComponent must be returned.',
+ this.constructor.displayName || 'ReactCompositeComponent'
+ );
+ return renderedComponent;
+ },
+
+ /**
+ * @private
+ */
+ _bindAutoBindMethods: function() {
+ for (var autoBindKey in this.__reactAutoBindMap) {
+ if (!this.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
+ continue;
+ }
+ var method = this.__reactAutoBindMap[autoBindKey];
+ this[autoBindKey] = this._bindAutoBindMethod(method);
+ }
+ },
+
+ /**
+ * Binds a method to the component.
+ *
+ * @param {function} method Method to be bound.
+ * @private
+ */
+ _bindAutoBindMethod: function(method) {
+ var component = this;
+ var boundMethod = function() {
+ return method.apply(component, arguments);
+ };
+ if (true) {
+ var componentName = component.constructor.displayName;
+ var _bind = boundMethod.bind;
+ boundMethod.bind = function(newThis) {
+ // User is trying to bind() an autobound method; we effectively will
+ // ignore the value of "this" that the user is trying to use, so
+ // let's warn.
+ if (newThis !== component && newThis !== null) {
+ console.warn(
+ 'bind(): React component methods may only be bound to the ' +
+ 'component instance. See ' + componentName
+ );
+ } else if (arguments.length === 1) {
+ console.warn(
+ 'bind(): You are binding a component method to the component. ' +
+ 'React does this for you automatically in a high-performance ' +
+ 'way, so you can safely remove this call. See ' + componentName
+ );
+ return boundMethod;
+ }
+ return _bind.apply(boundMethod, arguments);
+ };
+ }
+ return boundMethod;
+ }
+};
+
+var ReactCompositeComponentBase = function() {};
+mixInto(ReactCompositeComponentBase, ReactComponent.Mixin);
+mixInto(ReactCompositeComponentBase, ReactOwner.Mixin);
+mixInto(ReactCompositeComponentBase, ReactPropTransferer.Mixin);
+mixInto(ReactCompositeComponentBase, ReactCompositeComponentMixin);
+
+/**
+ * Module for creating composite components.
+ *
+ * @class ReactCompositeComponent
+ * @extends ReactComponent
+ * @extends ReactOwner
+ * @extends ReactPropTransferer
+ */
+var ReactCompositeComponent = {
+
+ LifeCycle: CompositeLifeCycle,
+
+ Base: ReactCompositeComponentBase,
+
+ /**
+ * Creates a composite component class given a class specification.
+ *
+ * @param {object} spec Class specification (which must define `render`).
+ * @return {function} Component constructor function.
+ * @public
+ */
+ createClass: function(spec) {
+ var Constructor = function() {};
+ Constructor.prototype = new ReactCompositeComponentBase();
+ Constructor.prototype.constructor = Constructor;
+ mixSpecIntoComponent(Constructor, spec);
+ invariant(
+ Constructor.prototype.render,
+ 'createClass(...): Class specification must implement a `render` method.'
+ );
+ // Reduce time spent doing lookups by setting these on the prototype.
+ for (var methodName in ReactCompositeComponentInterface) {
+ if (!Constructor.prototype[methodName]) {
+ Constructor.prototype[methodName] = null;
+ }
+ }
+
+ var ConvenienceConstructor = function(props, children) {
+ var instance = new Constructor();
+ instance.construct.apply(instance, arguments);
+ return instance;
+ };
+ ConvenienceConstructor.componentConstructor = Constructor;
+ ConvenienceConstructor.originalSpec = spec;
+ return ConvenienceConstructor;
+ },
+
+ /**
+ * Checks if a value is a valid component constructor.
+ *
+ * @param {*}
+ * @return {boolean}
+ * @public
+ */
+ isValidClass: function(componentClass) {
+ return componentClass instanceof Function &&
+ 'componentConstructor' in componentClass &&
+ componentClass.componentConstructor instanceof Function;
+ }
+};
+
+module.exports = ReactCompositeComponent;
+
+},{"./ReactComponent":23,"./ReactCurrentOwner":27,"./ReactOwner":50,"./ReactPerf":51,"./ReactPropTransferer":52,"./ReactUpdates":57,"./invariant":89,"./keyMirror":92,"./merge":95,"./mixInto":98,"./objMap":100}],27:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactCurrentOwner
+ */
+
+"use strict";
+
+/**
+ * Keeps track of the current owner.
+ *
+ * The current owner is the component who should own any components that are
+ * currently being constructed.
+ *
+ * The depth indicate how many composite components are above this render level.
+ */
+var ReactCurrentOwner = {
+
+ /**
+ * @internal
+ * @type {ReactComponent}
+ */
+ current: null
+
+};
+
+module.exports = ReactCurrentOwner;
+
+},{}],28:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOM
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var ReactNativeComponent = require("./ReactNativeComponent");
+
+var mergeInto = require("./mergeInto");
+var objMapKeyVal = require("./objMapKeyVal");
+
+/**
+ * Creates a new React class that is idempotent and capable of containing other
+ * React components. It accepts event listeners and DOM properties that are
+ * valid according to `DOMProperty`.
+ *
+ * - Event listeners: `onClick`, `onMouseDown`, etc.
+ * - DOM properties: `className`, `name`, `title`, etc.
+ *
+ * The `style` property functions differently from the DOM API. It accepts an
+ * object mapping of style properties to values.
+ *
+ * @param {string} tag Tag name (e.g. `div`).
+ * @param {boolean} omitClose True if the close tag should be omitted.
+ * @private
+ */
+function createDOMComponentClass(tag, omitClose) {
+ var Constructor = function() {};
+ Constructor.prototype = new ReactNativeComponent(tag, omitClose);
+ Constructor.prototype.constructor = Constructor;
+
+ var ConvenienceConstructor = function(props, children) {
+ var instance = new Constructor();
+ instance.construct.apply(instance, arguments);
+ return instance;
+ };
+ ConvenienceConstructor.componentConstructor = Constructor;
+ return ConvenienceConstructor;
+}
+
+/**
+ * Creates a mapping from supported HTML tags to `ReactNativeComponent` classes.
+ * This is also accessible via `React.DOM`.
+ *
+ * @public
+ */
+var ReactDOM = objMapKeyVal({
+ a: false,
+ abbr: false,
+ address: false,
+ area: false,
+ article: false,
+ aside: false,
+ audio: false,
+ b: false,
+ base: false,
+ bdi: false,
+ bdo: false,
+ big: false,
+ blockquote: false,
+ body: false,
+ br: true,
+ button: false,
+ canvas: false,
+ caption: false,
+ cite: false,
+ code: false,
+ col: true,
+ colgroup: false,
+ data: false,
+ datalist: false,
+ dd: false,
+ del: false,
+ details: false,
+ dfn: false,
+ div: false,
+ dl: false,
+ dt: false,
+ em: false,
+ embed: true,
+ fieldset: false,
+ figcaption: false,
+ figure: false,
+ footer: false,
+ form: false, // NOTE: Injected, see `ReactDOMForm`.
+ h1: false,
+ h2: false,
+ h3: false,
+ h4: false,
+ h5: false,
+ h6: false,
+ head: false,
+ header: false,
+ hr: true,
+ html: false,
+ i: false,
+ iframe: false,
+ img: true,
+ input: true,
+ ins: false,
+ kbd: false,
+ keygen: true,
+ label: false,
+ legend: false,
+ li: false,
+ link: false,
+ main: false,
+ map: false,
+ mark: false,
+ menu: false,
+ menuitem: false, // NOTE: Close tag should be omitted, but causes problems.
+ meta: true,
+ meter: false,
+ nav: false,
+ noscript: false,
+ object: false,
+ ol: false,
+ optgroup: false,
+ option: false,
+ output: false,
+ p: false,
+ param: true,
+ pre: false,
+ progress: false,
+ q: false,
+ rp: false,
+ rt: false,
+ ruby: false,
+ s: false,
+ samp: false,
+ script: false,
+ section: false,
+ select: false,
+ small: false,
+ source: false,
+ span: false,
+ strong: false,
+ style: false,
+ sub: false,
+ summary: false,
+ sup: false,
+ table: false,
+ tbody: false,
+ td: false,
+ textarea: false, // NOTE: Injected, see `ReactDOMTextarea`.
+ tfoot: false,
+ th: false,
+ thead: false,
+ time: false,
+ title: false,
+ tr: false,
+ track: true,
+ u: false,
+ ul: false,
+ 'var': false,
+ video: false,
+ wbr: false,
+
+ // SVG
+ circle: false,
+ g: false,
+ line: false,
+ path: false,
+ polyline: false,
+ rect: false,
+ svg: false,
+ text: false
+}, createDOMComponentClass);
+
+var injection = {
+ injectComponentClasses: function(componentClasses) {
+ mergeInto(ReactDOM, componentClasses);
+ }
+};
+
+ReactDOM.injection = injection;
+
+module.exports = ReactDOM;
+
+},{"./ReactNativeComponent":48,"./mergeInto":97,"./objMapKeyVal":101}],29:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMButton
+ */
+
+"use strict";
+
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactDOM = require("./ReactDOM");
+
+var keyMirror = require("./keyMirror");
+
+// Store a reference to the <button> `ReactNativeComponent`.
+var button = ReactDOM.button;
+
+var mouseListenerNames = keyMirror({
+ onClick: true,
+ onDoubleClick: true,
+ onMouseDown: true,
+ onMouseMove: true,
+ onMouseUp: true,
+ onClickCapture: true,
+ onDoubleClickCapture: true,
+ onMouseDownCapture: true,
+ onMouseMoveCapture: true,
+ onMouseUpCapture: true
+});
+
+/**
+ * Implements a <button> native component that does not receive mouse events
+ * when `disabled` is set.
+ */
+var ReactDOMButton = ReactCompositeComponent.createClass({
+
+ render: function() {
+ var props = {};
+
+ // Copy the props; except the mouse listeners if we're disabled
+ for (var key in this.props) {
+ if (this.props.hasOwnProperty(key) &&
+ (!this.props.disabled || !mouseListenerNames[key])) {
+ props[key] = this.props[key];
+ }
+ }
+
+ return button(props, this.props.children);
+ }
+
+});
+
+module.exports = ReactDOMButton;
+
+},{"./ReactCompositeComponent":26,"./ReactDOM":28,"./keyMirror":92}],30:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMForm
+ */
+
+"use strict";
+
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactDOM = require("./ReactDOM");
+var ReactEventEmitter = require("./ReactEventEmitter");
+var EventConstants = require("./EventConstants");
+
+// Store a reference to the <form> `ReactNativeComponent`.
+var form = ReactDOM.form;
+
+/**
+ * Since onSubmit doesn't bubble OR capture on the top level in IE8, we need
+ * to capture it on the <form> element itself. There are lots of hacks we could
+ * do to accomplish this, but the most reliable is to make <form> a
+ * composite component and use `componentDidMount` to attach the event handlers.
+ */
+var ReactDOMForm = ReactCompositeComponent.createClass({
+ render: function() {
+ // TODO: Instead of using `ReactDOM` directly, we should use JSX. However,
+ // `jshint` fails to parse JSX so in order for linting to work in the open
+ // source repo, we need to just use `ReactDOM.form`.
+ return this.transferPropsTo(form(null, this.props.children));
+ },
+
+ componentDidMount: function(node) {
+ ReactEventEmitter.trapBubbledEvent(
+ EventConstants.topLevelTypes.topSubmit,
+ 'submit',
+ node
+ );
+ }
+});
+
+module.exports = ReactDOMForm;
+
+},{"./EventConstants":13,"./ReactCompositeComponent":26,"./ReactDOM":28,"./ReactEventEmitter":39}],31:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMIDOperations
+ * @typechecks static-only
+ */
+
+/*jslint evil: true */
+
+"use strict";
+
+var CSSPropertyOperations = require("./CSSPropertyOperations");
+var DOMChildrenOperations = require("./DOMChildrenOperations");
+var DOMPropertyOperations = require("./DOMPropertyOperations");
+var ReactMount = require("./ReactMount");
+
+var getTextContentAccessor = require("./getTextContentAccessor");
+var invariant = require("./invariant");
+
+/**
+ * Errors for properties that should not be updated with `updatePropertyById()`.
+ *
+ * @type {object}
+ * @private
+ */
+var INVALID_PROPERTY_ERRORS = {
+ dangerouslySetInnerHTML:
+ '`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.',
+ style: '`style` must be set using `updateStylesByID()`.'
+};
+
+/**
+ * The DOM property to use when setting text content.
+ *
+ * @type {string}
+ * @private
+ */
+var textContentAccessor = getTextContentAccessor() || 'NA';
+
+/**
+ * Operations used to process updates to DOM nodes. This is made injectable via
+ * `ReactComponent.DOMIDOperations`.
+ */
+var ReactDOMIDOperations = {
+
+ /**
+ * Updates a DOM node with new property values. This should only be used to
+ * update DOM properties in `DOMProperty`.
+ *
+ * @param {string} id ID of the node to update.
+ * @param {string} name A valid property name, see `DOMProperty`.
+ * @param {*} value New value of the property.
+ * @internal
+ */
+ updatePropertyByID: function(id, name, value) {
+ var node = ReactMount.getNode(id);
+ invariant(
+ !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
+ 'updatePropertyByID(...): %s',
+ INVALID_PROPERTY_ERRORS[name]
+ );
+
+ // If we're updating to null or undefined, we should remove the property
+ // from the DOM node instead of inadvertantly setting to a string. This
+ // brings us in line with the same behavior we have on initial render.
+ if (value != null) {
+ DOMPropertyOperations.setValueForProperty(node, name, value);
+ } else {
+ DOMPropertyOperations.deleteValueForProperty(node, name);
+ }
+ },
+
+ /**
+ * Updates a DOM node to remove a property. This should only be used to remove
+ * DOM properties in `DOMProperty`.
+ *
+ * @param {string} id ID of the node to update.
+ * @param {string} name A property name to remove, see `DOMProperty`.
+ * @internal
+ */
+ deletePropertyByID: function(id, name, value) {
+ var node = ReactMount.getNode(id);
+ invariant(
+ !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
+ 'updatePropertyByID(...): %s',
+ INVALID_PROPERTY_ERRORS[name]
+ );
+ DOMPropertyOperations.deleteValueForProperty(node, name, value);
+ },
+
+ /**
+ * This should almost never be used instead of `updatePropertyByID()` due to
+ * the extra object allocation required by the API. That said, this is useful
+ * for batching up several operations across worker thread boundaries.
+ *
+ * @param {string} id ID of the node to update.
+ * @param {object} properties A mapping of valid property names.
+ * @internal
+ * @see {ReactDOMIDOperations.updatePropertyByID}
+ */
+ updatePropertiesByID: function(id, properties) {
+ for (var name in properties) {
+ if (!properties.hasOwnProperty(name)) {
+ continue;
+ }
+ ReactDOMIDOperations.updatePropertiesByID(id, name, properties[name]);
+ }
+ },
+
+ /**
+ * Updates a DOM node with new style values. If a value is specified as '',
+ * the corresponding style property will be unset.
+ *
+ * @param {string} id ID of the node to update.
+ * @param {object} styles Mapping from styles to values.
+ * @internal
+ */
+ updateStylesByID: function(id, styles) {
+ var node = ReactMount.getNode(id);
+ CSSPropertyOperations.setValueForStyles(node, styles);
+ },
+
+ /**
+ * Updates a DOM node's innerHTML set by `props.dangerouslySetInnerHTML`.
+ *
+ * @param {string} id ID of the node to update.
+ * @param {object} html An HTML object with the `__html` property.
+ * @internal
+ */
+ updateInnerHTMLByID: function(id, html) {
+ var node = ReactMount.getNode(id);
+ // HACK: IE8- normalize whitespace in innerHTML, removing leading spaces.
+ // @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html
+ node.innerHTML = (html && html.__html || '').replace(/^ /g, '&nbsp;');
+ },
+
+ /**
+ * Updates a DOM node's text content set by `props.content`.
+ *
+ * @param {string} id ID of the node to update.
+ * @param {string} content Text content.
+ * @internal
+ */
+ updateTextContentByID: function(id, content) {
+ var node = ReactMount.getNode(id);
+ node[textContentAccessor] = content;
+ },
+
+ /**
+ * Replaces a DOM node that exists in the document with markup.
+ *
+ * @param {string} id ID of child to be replaced.
+ * @param {string} markup Dangerous markup to inject in place of child.
+ * @internal
+ * @see {Danger.dangerouslyReplaceNodeWithMarkup}
+ */
+ dangerouslyReplaceNodeWithMarkupByID: function(id, markup) {
+ var node = ReactMount.getNode(id);
+ DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup);
+ },
+
+ /**
+ * Updates a component's children by processing a series of updates.
+ *
+ * @param {array<object>} updates List of update configurations.
+ * @param {array<string>} markup List of markup strings.
+ * @internal
+ */
+ dangerouslyProcessChildrenUpdates: function(updates, markup) {
+ for (var i = 0; i < updates.length; i++) {
+ updates[i].parentNode = ReactMount.getNode(updates[i].parentID);
+ }
+ DOMChildrenOperations.processUpdates(updates, markup);
+ }
+
+};
+
+module.exports = ReactDOMIDOperations;
+
+},{"./CSSPropertyOperations":3,"./DOMChildrenOperations":6,"./DOMPropertyOperations":8,"./ReactMount":45,"./getTextContentAccessor":87,"./invariant":89}],32:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMInput
+ */
+
+"use strict";
+
+var DOMPropertyOperations = require("./DOMPropertyOperations");
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactDOM = require("./ReactDOM");
+var ReactMount = require("./ReactMount");
+
+var invariant = require("./invariant");
+var merge = require("./merge");
+
+// Store a reference to the <input> `ReactNativeComponent`.
+var input = ReactDOM.input;
+
+var instancesByReactID = {};
+
+/**
+ * Implements an <input> native component that allows setting these optional
+ * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
+ *
+ * If `checked` or `value` are not supplied (or null/undefined), user actions
+ * that affect the checked state or value will trigger updates to the element.
+ *
+ * If they are supplied (and not null/undefined), the rendered element will not
+ * trigger updates to the element. Instead, the props must change in order for
+ * the rendered element to be updated.
+ *
+ * The rendered element will be initialized as unchecked (or `defaultChecked`)
+ * with an empty value (or `defaultValue`).
+ *
+ * @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
+ */
+var ReactDOMInput = ReactCompositeComponent.createClass({
+
+ getInitialState: function() {
+ var defaultValue = this.props.defaultValue;
+ return {
+ checked: this.props.defaultChecked || false,
+ value: defaultValue != null && defaultValue !== false ? defaultValue : ''
+ };
+ },
+
+ shouldComponentUpdate: function() {
+ // Defer any updates to this component during the `onChange` handler.
+ return !this._isChanging;
+ },
+
+ render: function() {
+ // Clone `this.props` so we don't mutate the input.
+ var props = merge(this.props);
+
+ props.defaultChecked = null;
+ props.defaultValue = null;
+ props.checked =
+ this.props.checked != null ? this.props.checked : this.state.checked;
+
+ props.value = this.props.value != null && this.props.value !== false
+ ? '' + this.props.value
+ : this.state.value;
+
+ props.onChange = this._handleChange;
+
+ return input(props, this.props.children);
+ },
+
+ componentDidMount: function(rootNode) {
+ var id = ReactMount.getID(rootNode);
+ instancesByReactID[id] = this;
+ },
+
+ componentWillUnmount: function() {
+ var rootNode = this.getDOMNode();
+ var id = ReactMount.getID(rootNode);
+ delete instancesByReactID[id];
+ },
+
+ componentDidUpdate: function(prevProps, prevState, rootNode) {
+ if (this.props.checked != null) {
+ DOMPropertyOperations.setValueForProperty(
+ rootNode,
+ 'checked',
+ this.props.checked || false
+ );
+ }
+ if (this.props.value != null) {
+ // Cast `this.props.value` to a string so falsey values that cast to
+ // truthy strings are not ignored.
+ DOMPropertyOperations.setValueForProperty(
+ rootNode,
+ 'value',
+ this.props.value !== false ? '' + this.props.value : ''
+ );
+ }
+ },
+
+ _handleChange: function(event) {
+ var returnValue;
+ if (this.props.onChange) {
+ this._isChanging = true;
+ returnValue = this.props.onChange(event);
+ this._isChanging = false;
+ }
+ this.setState({
+ checked: event.target.checked,
+ value: event.target.value
+ });
+
+ var name = this.props.name;
+ if (this.props.type === 'radio' && name != null) {
+ var rootNode = this.getDOMNode();
+ // If `rootNode.form` was non-null, then we could try `form.elements`,
+ // but that sometimes behaves strangely in IE8. We could also try using
+ // `form.getElementsByName`, but that will only return direct children
+ // and won't include inputs that use the HTML5 `form=` attribute. Since
+ // the input might not even be in a form, let's just use the global
+ // `getElementsByName` to ensure we don't miss anything.
+ var group = document.getElementsByName(name);
+ for (var i = 0, groupLen = group.length; i < groupLen; i++) {
+ var otherNode = group[i];
+ if (otherNode === rootNode ||
+ otherNode.nodeName !== 'INPUT' || otherNode.type !== 'radio' ||
+ otherNode.form !== rootNode.form) {
+ continue;
+ }
+ var otherID = ReactMount.getID(otherNode);
+ invariant(
+ otherID,
+ 'ReactDOMInput: Mixing React and non-React radio inputs with the ' +
+ 'same `name` is not supported.'
+ );
+ var otherInstance = instancesByReactID[otherID];
+ invariant(
+ otherInstance,
+ 'ReactDOMInput: Unknown radio button ID %s.',
+ otherID
+ );
+ // In some cases, this will actually change the `checked` state value.
+ // In other cases, there's no change but this forces a reconcile upon
+ // which componentDidUpdate will reset the DOM property to whatever it
+ // should be.
+ otherInstance.setState({
+ checked: false
+ });
+ }
+ }
+
+ return returnValue;
+ }
+
+});
+
+module.exports = ReactDOMInput;
+
+},{"./DOMPropertyOperations":8,"./ReactCompositeComponent":26,"./ReactDOM":28,"./ReactMount":45,"./invariant":89,"./merge":95}],33:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMOption
+ */
+
+"use strict";
+
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactDOM = require("./ReactDOM");
+
+// Store a reference to the <option> `ReactNativeComponent`.
+var option = ReactDOM.option;
+
+/**
+ * Implements an <option> native component that warns when `selected` is set.
+ */
+var ReactDOMOption = ReactCompositeComponent.createClass({
+
+ componentWillMount: function() {
+ // TODO (yungsters): Remove support for `selected` in <option>.
+ if (this.props.selected != null) {
+ if (true) {
+ console.warn(
+ 'Use the `defaultValue` or `value` props on <select> instead of ' +
+ 'setting `selected` on <option>.'
+ );
+ }
+ }
+ },
+
+ render: function() {
+ return option(this.props, this.props.children);
+ }
+
+});
+
+module.exports = ReactDOMOption;
+
+},{"./ReactCompositeComponent":26,"./ReactDOM":28}],34:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMSelect
+ */
+
+"use strict";
+
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactDOM = require("./ReactDOM");
+
+var invariant = require("./invariant");
+var merge = require("./merge");
+
+// Store a reference to the <select> `ReactNativeComponent`.
+var select = ReactDOM.select;
+
+/**
+ * Validation function for `value` and `defaultValue`.
+ * @private
+ */
+function selectValueType(props, propName, componentName) {
+ if (props[propName] == null) {
+ return;
+ }
+ if (props.multiple) {
+ invariant(
+ Array.isArray(props[propName]),
+ 'The `%s` prop supplied to <select> must be an array if `multiple` is ' +
+ 'true.',
+ propName
+ );
+ } else {
+ invariant(
+ !Array.isArray(props[propName]),
+ 'The `%s` prop supplied to <select> must be a scalar value if ' +
+ '`multiple` is false.',
+ propName
+ );
+ }
+}
+
+/**
+ * If `value` is supplied, updates <option> elements on mount and update.
+ * @private
+ */
+function updateOptions() {
+ /*jshint validthis:true */
+ var value = this.props.value != null ? this.props.value : this.state.value;
+ var options = this.getDOMNode().options;
+ var selectedValue = '' + value;
+
+ for (var i = 0, l = options.length; i < l; i++) {
+ var selected = this.props.multiple ?
+ selectedValue.indexOf(options[i].value) >= 0 :
+ selected = options[i].value === selectedValue;
+
+ if (selected !== options[i].selected) {
+ options[i].selected = selected;
+ }
+ }
+}
+
+/**
+ * Implements a <select> native component that allows optionally setting the
+ * props `value` and `defaultValue`. If `multiple` is false, the prop must be a
+ * string. If `multiple` is true, the prop must be an array of strings.
+ *
+ * If `value` is not supplied (or null/undefined), user actions that change the
+ * selected option will trigger updates to the rendered options.
+ *
+ * If it is supplied (and not null/undefined), the rendered options will not
+ * update in response to user actions. Instead, the `value` prop must change in
+ * order for the rendered options to update.
+ *
+ * If `defaultValue` is provided, any options with the supplied values will be
+ * selected.
+ */
+var ReactDOMSelect = ReactCompositeComponent.createClass({
+
+ propTypes: {
+ defaultValue: selectValueType,
+ value: selectValueType
+ },
+
+ getInitialState: function() {
+ return {value: this.props.defaultValue || (this.props.multiple ? [] : '')};
+ },
+
+ componentWillReceiveProps: function(nextProps) {
+ if (!this.props.multiple && nextProps.multiple) {
+ this.setState({value: [this.state.value]});
+ } else if (this.props.multiple && !nextProps.multiple) {
+ this.setState({value: this.state.value[0]});
+ }
+ },
+
+ shouldComponentUpdate: function() {
+ // Defer any updates to this component during the `onChange` handler.
+ return !this._isChanging;
+ },
+
+ render: function() {
+ // Clone `this.props` so we don't mutate the input.
+ var props = merge(this.props);
+
+ props.onChange = this._handleChange;
+ props.value = null;
+
+ return select(props, this.props.children);
+ },
+
+ componentDidMount: updateOptions,
+
+ componentDidUpdate: updateOptions,
+
+ _handleChange: function(event) {
+ var returnValue;
+ if (this.props.onChange) {
+ this._isChanging = true;
+ returnValue = this.props.onChange(event);
+ this._isChanging = false;
+ }
+
+ var selectedValue;
+ if (this.props.multiple) {
+ selectedValue = [];
+ var options = event.target.options;
+ for (var i = 0, l = options.length; i < l; i++) {
+ if (options[i].selected) {
+ selectedValue.push(options[i].value);
+ }
+ }
+ } else {
+ selectedValue = event.target.value;
+ }
+
+ this.setState({value: selectedValue});
+ return returnValue;
+ }
+
+});
+
+module.exports = ReactDOMSelect;
+
+},{"./ReactCompositeComponent":26,"./ReactDOM":28,"./invariant":89,"./merge":95}],35:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMTextarea
+ */
+
+"use strict";
+
+var DOMPropertyOperations = require("./DOMPropertyOperations");
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactDOM = require("./ReactDOM");
+
+var invariant = require("./invariant");
+var merge = require("./merge");
+
+// Store a reference to the <textarea> `ReactNativeComponent`.
+var textarea = ReactDOM.textarea;
+
+// For quickly matching children type, to test if can be treated as content.
+var CONTENT_TYPES = {'string': true, 'number': true};
+
+/**
+ * Implements a <textarea> native component that allows setting `value`, and
+ * `defaultValue`. This differs from the traditional DOM API because value is
+ * usually set as PCDATA children.
+ *
+ * If `value` is not supplied (or null/undefined), user actions that affect the
+ * value will trigger updates to the element.
+ *
+ * If `value` is supplied (and not null/undefined), the rendered element will
+ * not trigger updates to the element. Instead, the `value` prop must change in
+ * order for the rendered element to be updated.
+ *
+ * The rendered element will be initialized with an empty value, the prop
+ * `defaultValue` if specified, or the children content (deprecated).
+ */
+var ReactDOMTextarea = ReactCompositeComponent.createClass({
+
+ getInitialState: function() {
+ var defaultValue = this.props.defaultValue;
+ // TODO (yungsters): Remove support for children content in <textarea>.
+ var children = this.props.children;
+ if (children != null) {
+ if (true) {
+ console.warn(
+ 'Use the `defaultValue` or `value` props instead of setting ' +
+ 'children on <textarea>.'
+ );
+ }
+ invariant(
+ defaultValue == null,
+ 'If you supply `defaultValue` on a <textarea>, do not pass children.'
+ );
+ if (Array.isArray(children)) {
+ invariant(
+ children.length <= 1,
+ '<textarea> can only have at most one child.'
+ );
+ children = children[0];
+ }
+ invariant(
+ CONTENT_TYPES[typeof children],
+ 'If you specify children to <textarea>, it must be a single string ' +
+ 'or number., not an array or object.'
+ );
+ defaultValue = '' + children;
+ }
+ if (defaultValue == null) {
+ defaultValue = '';
+ }
+ return {
+ // We save the initial value so that `ReactNativeComponent` doesn't update
+ // `textContent` (unnecessary since we update value).
+ initialValue: this.props.value != null ? this.props.value : defaultValue,
+ value: defaultValue
+ };
+ },
+
+ shouldComponentUpdate: function() {
+ // Defer any updates to this component during the `onChange` handler.
+ return !this._isChanging;
+ },
+
+ render: function() {
+ // Clone `this.props` so we don't mutate the input.
+ var props = merge(this.props);
+
+ invariant(
+ props.dangerouslySetInnerHTML == null,
+ '`dangerouslySetInnerHTML` does not make sense on <textarea>.'
+ );
+
+ props.defaultValue = null;
+ props.value =
+ this.props.value != null ? this.props.value : this.state.value;
+ props.onChange = this._handleChange;
+
+ // Always set children to the same thing. In IE9, the selection range will
+ // get reset if `textContent` is mutated.
+ return textarea(props, this.state.initialValue);
+ },
+
+ componentDidUpdate: function(prevProps, prevState, rootNode) {
+ if (this.props.value != null) {
+ DOMPropertyOperations.setValueForProperty(
+ rootNode,
+ 'value',
+ this.props.value !== false ? '' + this.props.value : ''
+ );
+ }
+ },
+
+ _handleChange: function(event) {
+ var returnValue;
+ if (this.props.onChange) {
+ this._isChanging = true;
+ returnValue = this.props.onChange(event);
+ this._isChanging = false;
+ }
+ this.setState({value: event.target.value});
+ return returnValue;
+ }
+
+});
+
+module.exports = ReactDOMTextarea;
+
+},{"./DOMPropertyOperations":8,"./ReactCompositeComponent":26,"./ReactDOM":28,"./invariant":89,"./merge":95}],36:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDefaultBatchingStrategy
+ */
+
+"use strict";
+
+var ReactUpdates = require("./ReactUpdates");
+var Transaction = require("./Transaction");
+
+var emptyFunction = require("./emptyFunction");
+var mixInto = require("./mixInto");
+
+var RESET_BATCHED_UPDATES = {
+ initialize: emptyFunction,
+ close: function() {
+ ReactDefaultBatchingStrategy.isBatchingUpdates = false;
+ }
+};
+
+var FLUSH_BATCHED_UPDATES = {
+ initialize: emptyFunction,
+ close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
+};
+
+var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
+
+function ReactDefaultBatchingStrategyTransaction() {
+ this.reinitializeTransaction();
+}
+
+mixInto(ReactDefaultBatchingStrategyTransaction, Transaction.Mixin);
+mixInto(ReactDefaultBatchingStrategyTransaction, {
+ getTransactionWrappers: function() {
+ return TRANSACTION_WRAPPERS;
+ }
+});
+
+var transaction = new ReactDefaultBatchingStrategyTransaction();
+
+var ReactDefaultBatchingStrategy = {
+ isBatchingUpdates: false,
+
+ /**
+ * Call the provided function in a context within which calls to `setState`
+ * and friends are batched such that components aren't updated unnecessarily.
+ */
+ batchedUpdates: function(callback, param) {
+ var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
+
+ ReactDefaultBatchingStrategy.isBatchingUpdates = true;
+
+ // The code is written this way to avoid extra allocations
+ if (alreadyBatchingUpdates) {
+ callback(param);
+ } else {
+ transaction.perform(callback, null, param);
+ }
+ }
+};
+
+module.exports = ReactDefaultBatchingStrategy;
+
+},{"./ReactUpdates":57,"./Transaction":68,"./emptyFunction":77,"./mixInto":98}],37:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDefaultInjection
+ */
+
+"use strict";
+
+var ReactDOM = require("./ReactDOM");
+var ReactDOMButton = require("./ReactDOMButton");
+var ReactDOMForm = require("./ReactDOMForm");
+var ReactDOMInput = require("./ReactDOMInput");
+var ReactDOMOption = require("./ReactDOMOption");
+var ReactDOMSelect = require("./ReactDOMSelect");
+var ReactDOMTextarea = require("./ReactDOMTextarea");
+var ReactEventEmitter = require("./ReactEventEmitter");
+var ReactEventTopLevelCallback = require("./ReactEventTopLevelCallback");
+var ReactPerf = require("./ReactPerf");
+
+var DefaultDOMPropertyConfig = require("./DefaultDOMPropertyConfig");
+var DOMProperty = require("./DOMProperty");
+
+var DefaultEventPluginOrder = require("./DefaultEventPluginOrder");
+var EnterLeaveEventPlugin = require("./EnterLeaveEventPlugin");
+var ChangeEventPlugin = require("./ChangeEventPlugin");
+var EventPluginHub = require("./EventPluginHub");
+var ReactInstanceHandles = require("./ReactInstanceHandles");
+var SimpleEventPlugin = require("./SimpleEventPlugin");
+var MobileSafariClickEventPlugin = require("./MobileSafariClickEventPlugin");
+
+var ReactDefaultBatchingStrategy = require("./ReactDefaultBatchingStrategy");
+var ReactUpdates = require("./ReactUpdates");
+
+function inject() {
+ ReactEventEmitter.TopLevelCallbackCreator = ReactEventTopLevelCallback;
+ /**
+ * Inject module for resolving DOM hierarchy and plugin ordering.
+ */
+ EventPluginHub.injection.injectEventPluginOrder(DefaultEventPluginOrder);
+ EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
+
+ /**
+ * Some important event plugins included by default (without having to require
+ * them).
+ */
+ EventPluginHub.injection.injectEventPluginsByName({
+ 'SimpleEventPlugin': SimpleEventPlugin,
+ 'EnterLeaveEventPlugin': EnterLeaveEventPlugin,
+ 'ChangeEventPlugin': ChangeEventPlugin,
+ 'MobileSafariClickEventPlugin': MobileSafariClickEventPlugin
+ });
+
+ ReactDOM.injection.injectComponentClasses({
+ button: ReactDOMButton,
+ form: ReactDOMForm,
+ input: ReactDOMInput,
+ option: ReactDOMOption,
+ select: ReactDOMSelect,
+ textarea: ReactDOMTextarea
+ });
+
+ DOMProperty.injection.injectDOMPropertyConfig(DefaultDOMPropertyConfig);
+
+ if (true) {
+ ReactPerf.injection.injectMeasure(require("./ReactDefaultPerf").measure);
+ }
+
+ ReactUpdates.injection.injectBatchingStrategy(
+ ReactDefaultBatchingStrategy
+ );
+}
+
+module.exports = {
+ inject: inject
+};
+
+},{"./ChangeEventPlugin":5,"./DOMProperty":7,"./DefaultDOMPropertyConfig":10,"./DefaultEventPluginOrder":11,"./EnterLeaveEventPlugin":12,"./EventPluginHub":15,"./MobileSafariClickEventPlugin":20,"./ReactDOM":28,"./ReactDOMButton":29,"./ReactDOMForm":30,"./ReactDOMInput":32,"./ReactDOMOption":33,"./ReactDOMSelect":34,"./ReactDOMTextarea":35,"./ReactDefaultBatchingStrategy":36,"./ReactDefaultPerf":38,"./ReactEventEmitter":39,"./ReactEventTopLevelCallback":41,"./ReactInstanceHandles":43,"./ReactPerf":51,"./ReactUpdates":57,"./SimpleEventPlugin":58}],38:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDefaultPerf
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var performanceNow = require("./performanceNow");
+
+var ReactDefaultPerf = {};
+
+if (true) {
+ ReactDefaultPerf = {
+ /**
+ * Gets the stored information for a given object's function.
+ *
+ * @param {string} objName
+ * @param {string} fnName
+ * @return {?object}
+ */
+ getInfo: function(objName, fnName) {
+ if (!this.info[objName] || !this.info[objName][fnName]) {
+ return null;
+ }
+ return this.info[objName][fnName];
+ },
+
+ /**
+ * Gets the logs pertaining to a given object's function.
+ *
+ * @param {string} objName
+ * @param {string} fnName
+ * @return {?array<object>}
+ */
+ getLogs: function(objName, fnName) {
+ if (!this.getInfo(objName, fnName)) {
+ return null;
+ }
+ return this.logs.filter(function(log) {
+ return log.objName === objName && log.fnName === fnName;
+ });
+ },
+
+ /**
+ * Runs through the logs and builds an array of arrays, where each array
+ * walks through the mounting/updating of each component underneath.
+ *
+ * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
+ * @return {array<array>}
+ */
+ getRawRenderHistory: function(rootID) {
+ var history = [];
+ /**
+ * Since logs are added after the method returns, the logs are in a sense
+ * upside-down: the inner-most elements from mounting/updating are logged
+ * first, and the last addition to the log is the top renderComponent.
+ * Therefore, we flip the logs upside down for ease of processing, and
+ * reverse the history array at the end so the earliest event has index 0.
+ */
+ var logs = this.logs.filter(function(log) {
+ return log.reactID.indexOf(rootID) === 0;
+ }).reverse();
+
+ var subHistory = [];
+ logs.forEach(function(log, i) {
+ if (i && log.reactID === rootID && logs[i - 1].reactID !== rootID) {
+ subHistory.length && history.push(subHistory);
+ subHistory = [];
+ }
+ subHistory.push(log);
+ });
+ if (subHistory.length) {
+ history.push(subHistory);
+ }
+ return history.reverse();
+ },
+
+ /**
+ * Runs through the logs and builds an array of strings, where each string
+ * is a multiline formatted way of walking through the mounting/updating
+ * underneath.
+ *
+ * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
+ * @return {array<string>}
+ */
+ getRenderHistory: function(rootID) {
+ var history = this.getRawRenderHistory(rootID);
+
+ return history.map(function(subHistory) {
+ var headerString = (
+ 'log# Component (execution time) [bloat from logging]\n' +
+ '================================================================\n'
+ );
+ return headerString + subHistory.map(function(log) {
+ // Add two spaces for every layer in the reactID.
+ var indents = '\t' + Array(log.reactID.split('.[').length).join(' ');
+ var delta = _microTime(log.timing.delta);
+ var bloat = _microTime(log.timing.timeToLog);
+
+ return log.index + indents + log.name + ' (' + delta + 'ms)' +
+ ' [' + bloat + 'ms]';
+ }).join('\n');
+ });
+ },
+
+ /**
+ * Print the render history from `getRenderHistory` using console.log.
+ * This is currently the best way to display perf data from
+ * any React component; working on that.
+ *
+ * @param {string} rootID The reactID of the root node, e.g. '.r[2cpyq]'
+ * @param {number} index
+ */
+ printRenderHistory: function(rootID, index) {
+ var history = this.getRenderHistory(rootID);
+ if (!history[index]) {
+ console.warn(
+ 'Index', index, 'isn\'t available! ' +
+ 'The render history is', history.length, 'long.'
+ );
+ return;
+ }
+ console.log(
+ 'Loading render history #' + (index + 1) +
+ ' of ' + history.length + ':\n' + history[index]
+ );
+ },
+
+ /**
+ * Prints the heatmap legend to console, showing how the colors correspond
+ * with render times. This relies on console.log styles.
+ */
+ printHeatmapLegend: function() {
+ if (!this.options.heatmap.enabled) {
+ return;
+ }
+ var max = this.info.React
+ && this.info.React.renderComponent
+ && this.info.React.renderComponent.max;
+ if (max) {
+ var logStr = 'Heatmap: ';
+ for (var ii = 0; ii <= 10 * max; ii += max) {
+ logStr += '%c ' + (Math.round(ii) / 10) + 'ms ';
+ }
+ console.log(
+ logStr,
+ 'background-color: hsla(100, 100%, 50%, 0.6);',
+ 'background-color: hsla( 90, 100%, 50%, 0.6);',
+ 'background-color: hsla( 80, 100%, 50%, 0.6);',
+ 'background-color: hsla( 70, 100%, 50%, 0.6);',
+ 'background-color: hsla( 60, 100%, 50%, 0.6);',
+ 'background-color: hsla( 50, 100%, 50%, 0.6);',
+ 'background-color: hsla( 40, 100%, 50%, 0.6);',
+ 'background-color: hsla( 30, 100%, 50%, 0.6);',
+ 'background-color: hsla( 20, 100%, 50%, 0.6);',
+ 'background-color: hsla( 10, 100%, 50%, 0.6);',
+ 'background-color: hsla( 0, 100%, 50%, 0.6);'
+ );
+ }
+ },
+
+ /**
+ * Measure a given function with logging information, and calls a callback
+ * if there is one.
+ *
+ * @param {string} objName
+ * @param {string} fnName
+ * @param {function} func
+ * @return {function}
+ */
+ measure: function(objName, fnName, func) {
+ var info = _getNewInfo(objName, fnName);
+
+ var fnArgs = _getFnArguments(func);
+
+ return function() {
+ var timeBeforeFn = performanceNow();
+ var fnReturn = func.apply(this, arguments);
+ var timeAfterFn = performanceNow();
+
+ /**
+ * Hold onto arguments in a readable way: args[1] -> args.component.
+ * args is also passed to the callback, so if you want to save an
+ * argument in the log, do so in the callback.
+ */
+ var args = {};
+ for (var i = 0; i < arguments.length; i++) {
+ args[fnArgs[i]] = arguments[i];
+ }
+
+ var log = {
+ index: ReactDefaultPerf.logs.length,
+ fnName: fnName,
+ objName: objName,
+ timing: {
+ before: timeBeforeFn,
+ after: timeAfterFn,
+ delta: timeAfterFn - timeBeforeFn
+ }
+ };
+
+ ReactDefaultPerf.logs.push(log);
+
+ /**
+ * The callback gets:
+ * - this (the component)
+ * - the original method's arguments
+ * - what the method returned
+ * - the log object, and
+ * - the wrapped method's info object.
+ */
+ var callback = _getCallback(objName, fnName);
+ callback && callback(this, args, fnReturn, log, info);
+
+ log.timing.timeToLog = performanceNow() - timeAfterFn;
+
+ return fnReturn;
+ };
+ },
+
+ /**
+ * Holds information on wrapped objects/methods.
+ * For instance, ReactDefaultPerf.info.React.renderComponent
+ */
+ info: {},
+
+ /**
+ * Holds all of the logs. Filter this to pull desired information.
+ */
+ logs: [],
+
+ /**
+ * Toggle settings for ReactDefaultPerf
+ */
+ options: {
+ /**
+ * The heatmap sets the background color of the React containers
+ * according to how much total time has been spent rendering them.
+ * The most temporally expensive component is set as pure red,
+ * and the others are colored from green to red as a fraction
+ * of that max component time.
+ */
+ heatmap: {
+ enabled: true
+ }
+ }
+ };
+
+ /**
+ * Gets a info area for a given object's function, adding a new one if
+ * necessary.
+ *
+ * @param {string} objName
+ * @param {string} fnName
+ * @return {object}
+ */
+ var _getNewInfo = function(objName, fnName) {
+ var info = ReactDefaultPerf.getInfo(objName, fnName);
+ if (info) {
+ return info;
+ }
+ ReactDefaultPerf.info[objName] = ReactDefaultPerf.info[objName] || {};
+
+ return ReactDefaultPerf.info[objName][fnName] = {
+ getLogs: function() {
+ return ReactDefaultPerf.getLogs(objName, fnName);
+ }
+ };
+ };
+
+ /**
+ * Gets a list of the argument names from a function's definition.
+ * This is useful for storing arguments by their names within wrapFn().
+ *
+ * @param {function} fn
+ * @return {array<string>}
+ */
+ var _getFnArguments = function(fn) {
+ var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
+ var fnStr = fn.toString().replace(STRIP_COMMENTS, '');
+ fnStr = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')'));
+ return fnStr.match(/([^\s,]+)/g);
+ };
+
+ /**
+ * Store common callbacks within ReactDefaultPerf.
+ *
+ * @param {string} objName
+ * @param {string} fnName
+ * @return {?function}
+ */
+ var _getCallback = function(objName, fnName) {
+ switch (objName + '.' + fnName) {
+ case 'React.renderComponent':
+ return _renderComponentCallback;
+ case 'ReactNativeComponent.mountComponent':
+ case 'ReactNativeComponent.updateComponent':
+ return _nativeComponentCallback;
+ case 'ReactCompositeComponent.mountComponent':
+ case 'ReactCompositeComponent.updateComponent':
+ return _compositeComponentCallback;
+ default:
+ return null;
+ }
+ };
+
+ /**
+ * Callback function for React.renderComponent
+ *
+ * @param {object} component
+ * @param {object} args
+ * @param {?object} fnReturn
+ * @param {object} log
+ * @param {object} info
+ */
+ var _renderComponentCallback =
+ function(component, args, fnReturn, log, info) {
+ log.name = args.nextComponent.constructor.displayName || '[unknown]';
+ log.reactID = fnReturn._rootNodeID || null;
+
+ if (ReactDefaultPerf.options.heatmap.enabled) {
+ var container = args.container;
+ if (!container.loggedByReactDefaultPerf) {
+ container.loggedByReactDefaultPerf = true;
+ info.components = info.components || [];
+ info.components.push(container);
+ }
+
+ container.count = container.count || 0;
+ container.count += log.timing.delta;
+ info.max = info.max || 0;
+ if (container.count > info.max) {
+ info.max = container.count;
+ info.components.forEach(function(component) {
+ _setHue(component, 100 - 100 * component.count / info.max);
+ });
+ } else {
+ _setHue(container, 100 - 100 * container.count / info.max);
+ }
+ }
+ };
+
+ /**
+ * Callback function for ReactNativeComponent
+ *
+ * @param {object} component
+ * @param {object} args
+ * @param {?object} fnReturn
+ * @param {object} log
+ * @param {object} info
+ */
+ var _nativeComponentCallback =
+ function(component, args, fnReturn, log, info) {
+ log.name = component.tagName || '[unknown]';
+ log.reactID = component._rootNodeID;
+ };
+
+ /**
+ * Callback function for ReactCompositeComponent
+ *
+ * @param {object} component
+ * @param {object} args
+ * @param {?object} fnReturn
+ * @param {object} log
+ * @param {object} info
+ */
+ var _compositeComponentCallback =
+ function(component, args, fnReturn, log, info) {
+ log.name = component.constructor.displayName || '[unknown]';
+ log.reactID = component._rootNodeID;
+ };
+
+ /**
+ * Using the hsl() background-color attribute, colors an element.
+ *
+ * @param {DOMElement} el
+ * @param {number} hue [0 for red, 120 for green, 240 for blue]
+ */
+ var _setHue = function(el, hue) {
+ el.style.backgroundColor = 'hsla(' + hue + ', 100%, 50%, 0.6)';
+ };
+
+ /**
+ * Round to the thousandth place.
+ * @param {number} time
+ * @return {number}
+ */
+ var _microTime = function(time) {
+ return Math.round(time * 1000) / 1000;
+ };
+}
+
+module.exports = ReactDefaultPerf;
+
+},{"./performanceNow":102}],39:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactEventEmitter
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var EventConstants = require("./EventConstants");
+var EventListener = require("./EventListener");
+var EventPluginHub = require("./EventPluginHub");
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+var ReactEventEmitterMixin = require("./ReactEventEmitterMixin");
+var ViewportMetrics = require("./ViewportMetrics");
+
+var invariant = require("./invariant");
+var isEventSupported = require("./isEventSupported");
+var merge = require("./merge");
+
+/**
+ * Summary of `ReactEventEmitter` event handling:
+ *
+ * - Top-level delegation is used to trap native browser events. We normalize
+ * and de-duplicate events to account for browser quirks.
+ *
+ * - Forward these native events (with the associated top-level type used to
+ * trap it) to `EventPluginHub`, which in turn will ask plugins if they want
+ * to extract any synthetic events.
+ *
+ * - The `EventPluginHub` will then process each event by annotating them with
+ * "dispatches", a sequence of listeners and IDs that care about that event.
+ *
+ * - The `EventPluginHub` then dispatches the events.
+ *
+ * Overview of React and the event system:
+ *
+ * .
+ * +------------+ .
+ * | DOM | .
+ * +------------+ . +-----------+
+ * + . +--------+|SimpleEvent|
+ * | . | |Plugin |
+ * +-----|------+ . v +-----------+
+ * | | | . +--------------+ +------------+
+ * | +-----------.--->|EventPluginHub| | Event |
+ * | | . | | +-----------+ | Propagators|
+ * | ReactEvent | . | | |TapEvent | |------------|
+ * | Emitter | . | |<---+|Plugin | |other plugin|
+ * | | . | | +-----------+ | utilities |
+ * | +-----------.---------+ | +------------+
+ * | | | . +----|---------+
+ * +-----|------+ . | ^ +-----------+
+ * | . | | |Enter/Leave|
+ * + . | +-------+|Plugin |
+ * +-------------+ . v +-----------+
+ * | application | . +----------+
+ * |-------------| . | callback |
+ * | | . | registry |
+ * | | . +----------+
+ * +-------------+ .
+ * .
+ * React Core . General Purpose Event Plugin System
+ */
+
+/**
+ * Traps top-level events by using event bubbling.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {string} handlerBaseName Event name (e.g. "click").
+ * @param {DOMEventTarget} element Element on which to attach listener.
+ * @internal
+ */
+function trapBubbledEvent(topLevelType, handlerBaseName, element) {
+ EventListener.listen(
+ element,
+ handlerBaseName,
+ ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
+ topLevelType
+ )
+ );
+}
+
+/**
+ * Traps a top-level event by using event capturing.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {string} handlerBaseName Event name (e.g. "click").
+ * @param {DOMEventTarget} element Element on which to attach listener.
+ * @internal
+ */
+function trapCapturedEvent(topLevelType, handlerBaseName, element) {
+ EventListener.capture(
+ element,
+ handlerBaseName,
+ ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
+ topLevelType
+ )
+ );
+}
+
+/**
+ * Listens to window scroll and resize events. We cache scroll values so that
+ * application code can access them without triggering reflows.
+ *
+ * NOTE: Scroll events do not bubble.
+ *
+ * @private
+ * @see http://www.quirksmode.org/dom/events/scroll.html
+ */
+function registerScrollValueMonitoring() {
+ var refresh = ViewportMetrics.refreshScrollValues;
+ EventListener.listen(window, 'scroll', refresh);
+ EventListener.listen(window, 'resize', refresh);
+}
+
+/**
+ * `ReactEventEmitter` is used to attach top-level event listeners. For example:
+ *
+ * ReactEventEmitter.putListener('myID', 'onClick', myFunction);
+ *
+ * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
+ *
+ * @internal
+ */
+var ReactEventEmitter = merge(ReactEventEmitterMixin, {
+
+ /**
+ * React references `ReactEventTopLevelCallback` using this property in order
+ * to allow dependency injection.
+ */
+ TopLevelCallbackCreator: null,
+
+ /**
+ * Ensures that top-level event delegation listeners are installed.
+ *
+ * There are issues with listening to both touch events and mouse events on
+ * the top-level, so we make the caller choose which one to listen to. (If
+ * there's a touch top-level listeners, anchors don't receive clicks for some
+ * reason, and only in some cases).
+ *
+ * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
+ * @param {DOMDocument} contentDocument DOM document to listen on
+ */
+ ensureListening: function(touchNotMouse, contentDocument) {
+ invariant(
+ ExecutionEnvironment.canUseDOM,
+ 'ensureListening(...): Cannot toggle event listening in a Worker ' +
+ 'thread. This is likely a bug in the framework. Please report ' +
+ 'immediately.'
+ );
+ invariant(
+ ReactEventEmitter.TopLevelCallbackCreator,
+ 'ensureListening(...): Cannot be called without a top level callback ' +
+ 'creator being injected.'
+ );
+ // Call out to base implementation.
+ ReactEventEmitterMixin.ensureListening.call(
+ ReactEventEmitter,
+ {
+ touchNotMouse: touchNotMouse,
+ contentDocument: contentDocument
+ }
+ );
+ },
+
+ /**
+ * Sets whether or not any created callbacks should be enabled.
+ *
+ * @param {boolean} enabled True if callbacks should be enabled.
+ */
+ setEnabled: function(enabled) {
+ invariant(
+ ExecutionEnvironment.canUseDOM,
+ 'setEnabled(...): Cannot toggle event listening in a Worker thread. ' +
+ 'This is likely a bug in the framework. Please report immediately.'
+ );
+ if (ReactEventEmitter.TopLevelCallbackCreator) {
+ ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
+ }
+ },
+
+ /**
+ * @return {boolean} True if callbacks are enabled.
+ */
+ isEnabled: function() {
+ return !!(
+ ReactEventEmitter.TopLevelCallbackCreator &&
+ ReactEventEmitter.TopLevelCallbackCreator.isEnabled()
+ );
+ },
+
+ /**
+ * We listen for bubbled touch events on the document object.
+ *
+ * Firefox v8.01 (and possibly others) exhibited strange behavior when
+ * mounting `onmousemove` events at some node that was not the document
+ * element. The symptoms were that if your mouse is not moving over something
+ * contained within that mount point (for example on the background) the
+ * top-level listeners for `onmousemove` won't be called. However, if you
+ * register the `mousemove` on the document object, then it will of course
+ * catch all `mousemove`s. This along with iOS quirks, justifies restricting
+ * top-level listeners to the document object only, at least for these
+ * movement types of events and possibly all events.
+ *
+ * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
+ *
+ * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
+ * they bubble to document.
+ *
+ * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
+ * @param {DOMDocument} contentDocument Document which owns the container
+ * @private
+ * @see http://www.quirksmode.org/dom/events/keys.html.
+ */
+ listenAtTopLevel: function(touchNotMouse, contentDocument) {
+ invariant(
+ !contentDocument._isListening,
+ 'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
+ );
+ var topLevelTypes = EventConstants.topLevelTypes;
+ var mountAt = contentDocument;
+
+ registerScrollValueMonitoring();
+ trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
+ trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
+ trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
+ trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
+ trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
+ trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
+ trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
+ if (touchNotMouse) {
+ trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
+ trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
+ trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
+ trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
+ }
+ trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
+ trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
+ trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
+ trapBubbledEvent(topLevelTypes.topInput, 'input', mountAt);
+ trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
+ trapBubbledEvent(
+ topLevelTypes.topSelectionChange,
+ 'selectionchange',
+ mountAt
+ );
+ trapBubbledEvent(
+ topLevelTypes.topDOMCharacterDataModified,
+ 'DOMCharacterDataModified',
+ mountAt
+ );
+
+ if (isEventSupported('drag')) {
+ trapBubbledEvent(topLevelTypes.topDrag, 'drag', mountAt);
+ trapBubbledEvent(topLevelTypes.topDragEnd, 'dragend', mountAt);
+ trapBubbledEvent(topLevelTypes.topDragEnter, 'dragenter', mountAt);
+ trapBubbledEvent(topLevelTypes.topDragExit, 'dragexit', mountAt);
+ trapBubbledEvent(topLevelTypes.topDragLeave, 'dragleave', mountAt);
+ trapBubbledEvent(topLevelTypes.topDragOver, 'dragover', mountAt);
+ trapBubbledEvent(topLevelTypes.topDragStart, 'dragstart', mountAt);
+ trapBubbledEvent(topLevelTypes.topDrop, 'drop', mountAt);
+ }
+
+ if (isEventSupported('wheel')) {
+ trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
+ } else if (isEventSupported('mousewheel')) {
+ trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
+ } else {
+ // Firefox needs to capture a different mouse scroll event.
+ // @see http://www.quirksmode.org/dom/events/tests/scroll.html
+ trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt);
+ }
+
+ // IE<9 does not support capturing so just trap the bubbled event there.
+ if (isEventSupported('scroll', true)) {
+ trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
+ } else {
+ trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
+ }
+
+ if (isEventSupported('focus', true)) {
+ trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
+ trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
+ } else if (isEventSupported('focusin')) {
+ // IE has `focusin` and `focusout` events which bubble.
+ // @see
+ // http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
+ trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
+ trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
+ }
+
+ if (isEventSupported('copy')) {
+ trapBubbledEvent(topLevelTypes.topCopy, 'copy', mountAt);
+ trapBubbledEvent(topLevelTypes.topCut, 'cut', mountAt);
+ trapBubbledEvent(topLevelTypes.topPaste, 'paste', mountAt);
+ }
+ },
+
+ registrationNames: EventPluginHub.registrationNames,
+
+ putListener: EventPluginHub.putListener,
+
+ getListener: EventPluginHub.getListener,
+
+ deleteListener: EventPluginHub.deleteListener,
+
+ deleteAllListeners: EventPluginHub.deleteAllListeners,
+
+ trapBubbledEvent: trapBubbledEvent,
+
+ trapCapturedEvent: trapCapturedEvent
+
+});
+
+
+module.exports = ReactEventEmitter;
+
+},{"./EventConstants":13,"./EventListener":14,"./EventPluginHub":15,"./ExecutionEnvironment":19,"./ReactEventEmitterMixin":40,"./ViewportMetrics":69,"./invariant":89,"./isEventSupported":90,"./merge":95}],40:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactEventEmitterMixin
+ */
+
+"use strict";
+
+var EventPluginHub = require("./EventPluginHub");
+var ReactUpdates = require("./ReactUpdates");
+
+function runEventQueueInBatch(events) {
+ EventPluginHub.enqueueEvents(events);
+ EventPluginHub.processEventQueue();
+}
+
+var ReactEventEmitterMixin = {
+ /**
+ * Whether or not `ensureListening` has been invoked.
+ * @type {boolean}
+ * @private
+ */
+ _isListening: false,
+
+ /**
+ * Function, must be implemented. Listens to events on the top level of the
+ * application.
+ *
+ * @abstract
+ *
+ * listenAtTopLevel: null,
+ */
+
+ /**
+ * Ensures that top-level event delegation listeners are installed.
+ *
+ * There are issues with listening to both touch events and mouse events on
+ * the top-level, so we make the caller choose which one to listen to. (If
+ * there's a touch top-level listeners, anchors don't receive clicks for some
+ * reason, and only in some cases).
+ *
+ * @param {*} config Configuration passed through to `listenAtTopLevel`.
+ */
+ ensureListening: function(config) {
+ if (!config.contentDocument._reactIsListening) {
+ this.listenAtTopLevel(config.touchNotMouse, config.contentDocument);
+ config.contentDocument._reactIsListening = true;
+ }
+ },
+
+ /**
+ * Streams a fired top-level event to `EventPluginHub` where plugins have the
+ * opportunity to create `ReactEvent`s to be dispatched.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {object} topLevelTarget The listening component root node.
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
+ * @param {object} nativeEvent Native environment event.
+ */
+ handleTopLevel: function(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent) {
+ var events = EventPluginHub.extractEvents(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent
+ );
+
+ // Event queue being processed in the same cycle allows `preventDefault`.
+ ReactUpdates.batchedUpdates(runEventQueueInBatch, events);
+ }
+};
+
+module.exports = ReactEventEmitterMixin;
+
+},{"./EventPluginHub":15,"./ReactUpdates":57}],41:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactEventTopLevelCallback
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var ReactEventEmitter = require("./ReactEventEmitter");
+var ReactMount = require("./ReactMount");
+
+var getEventTarget = require("./getEventTarget");
+
+/**
+ * @type {boolean}
+ * @private
+ */
+var _topLevelListenersEnabled = true;
+
+/**
+ * Top-level callback creator used to implement event handling using delegation.
+ * This is used via dependency injection.
+ */
+var ReactEventTopLevelCallback = {
+
+ /**
+ * Sets whether or not any created callbacks should be enabled.
+ *
+ * @param {boolean} enabled True if callbacks should be enabled.
+ */
+ setEnabled: function(enabled) {
+ _topLevelListenersEnabled = !!enabled;
+ },
+
+ /**
+ * @return {boolean} True if callbacks are enabled.
+ */
+ isEnabled: function() {
+ return _topLevelListenersEnabled;
+ },
+
+ /**
+ * Creates a callback for the supplied `topLevelType` that could be added as
+ * a listener to the document. The callback computes a `topLevelTarget` which
+ * should be the root node of a mounted React component where the listener
+ * is attached.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @return {function} Callback for handling top-level events.
+ */
+ createTopLevelCallback: function(topLevelType) {
+ return function(nativeEvent) {
+ if (!_topLevelListenersEnabled) {
+ return;
+ }
+ // TODO: Remove when synthetic events are ready, this is for IE<9.
+ if (nativeEvent.srcElement &&
+ nativeEvent.srcElement !== nativeEvent.target) {
+ nativeEvent.target = nativeEvent.srcElement;
+ }
+ var topLevelTarget = ReactMount.getFirstReactDOM(
+ getEventTarget(nativeEvent)
+ ) || window;
+ var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
+ ReactEventEmitter.handleTopLevel(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent
+ );
+ };
+ }
+
+};
+
+module.exports = ReactEventTopLevelCallback;
+
+},{"./ReactEventEmitter":39,"./ReactMount":45,"./getEventTarget":84}],42:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactInputSelection
+ */
+
+"use strict";
+
+var getTextContentAccessor = require("./getTextContentAccessor");
+
+// It is not safe to read the document.activeElement property in IE if there's
+// nothing focused.
+function getActiveElement() {
+ try {
+ return document.activeElement;
+ } catch (e) {
+ }
+}
+
+function isInDocument(node) {
+ return document.documentElement.contains(node);
+}
+
+/**
+ * @ReactInputSelection: React input selection module. Based on Selection.js,
+ * but modified to be suitable for react and has a couple of bug fixes (doesn't
+ * assume buttons have range selections allowed).
+ * Input selection module for React.
+ */
+var ReactInputSelection = {
+
+ hasSelectionCapabilities: function(elem) {
+ return elem && (
+ (elem.nodeName === 'INPUT' && elem.type === 'text') ||
+ elem.nodeName === 'TEXTAREA' ||
+ elem.contentEditable === 'true'
+ );
+ },
+
+ getSelectionInformation: function() {
+ var focusedElem = getActiveElement();
+ return {
+ focusedElem: focusedElem,
+ selectionRange:
+ ReactInputSelection.hasSelectionCapabilities(focusedElem) ?
+ ReactInputSelection.getSelection(focusedElem) :
+ null
+ };
+ },
+
+ /**
+ * @restoreSelection: If any selection information was potentially lost,
+ * restore it. This is useful when performing operations that could remove dom
+ * nodes and place them back in, resulting in focus being lost.
+ */
+ restoreSelection: function(priorSelectionInformation) {
+ var curFocusedElem = getActiveElement();
+ var priorFocusedElem = priorSelectionInformation.focusedElem;
+ var priorSelectionRange = priorSelectionInformation.selectionRange;
+ if (curFocusedElem !== priorFocusedElem &&
+ isInDocument(priorFocusedElem)) {
+ if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
+ ReactInputSelection.setSelection(
+ priorFocusedElem,
+ priorSelectionRange
+ );
+ }
+ priorFocusedElem.focus();
+ }
+ },
+
+ /**
+ * @getSelection: Gets the selection bounds of a focused textarea, input or
+ * contentEditable node.
+ * -@input: Look up selection bounds of this input
+ * -@return {start: selectionStart, end: selectionEnd}
+ */
+ getSelection: function(input) {
+ var range;
+ if (input.contentEditable === 'true' && window.getSelection) {
+ var selection = window.getSelection();
+ if (selection.rangeCount > 0) {
+ range = selection.getRangeAt(0);
+ var commonAncestor = range.commonAncestorContainer;
+ if (commonAncestor && commonAncestor.nodeType === 3) {
+ commonAncestor = commonAncestor.parentNode;
+ }
+ if (commonAncestor === input) {
+ return {start: range.startOffset, end: range.endOffset};
+ }
+ }
+ return {start: 0, end: 0};
+ }
+
+ if (!document.selection) {
+ // Mozilla, Safari, etc.
+ return {start: input.selectionStart, end: input.selectionEnd};
+ }
+
+ range = document.selection.createRange();
+ if (range.parentElement() !== input) {
+ // There can only be one selection per document in IE, so if the
+ // containing element of the document's selection isn't our text field,
+ // our text field must have no selection.
+ return {start: 0, end: 0};
+ }
+
+ var value = input.value || input[getTextContentAccessor()];
+ var length = value.length;
+
+ if (input.nodeName === 'INPUT') {
+ return {
+ start: -range.moveStart('character', -length),
+ end: -range.moveEnd('character', -length)
+ };
+ } else {
+ var range2 = range.duplicate();
+ range2.moveToElementText(input);
+ range2.setEndPoint('StartToEnd', range);
+ var end = length - range2.text.length;
+ range2.setEndPoint('StartToStart', range);
+ return {
+ start: length - range2.text.length,
+ end: end
+ };
+ }
+ },
+
+ /**
+ * @setSelection: Sets the selection bounds of a textarea or input and focuses
+ * the input.
+ * -@input Set selection bounds of this input or textarea
+ * -@rangeObj Object of same form that is returned from get*
+ */
+ setSelection: function(input, rangeObj) {
+ var range;
+ var start = rangeObj.start;
+ var end = rangeObj.end;
+ if (typeof end === 'undefined') {
+ end = start;
+ }
+ if (document.selection) {
+ // IE is inconsistent about character offsets when it comes to carriage
+ // returns, so we need to manually take them into account
+ if (input.tagName === 'TEXTAREA') {
+ var cr_before =
+ (input.value.slice(0, start).match(/\r/g) || []).length;
+ var cr_inside =
+ (input.value.slice(start, end).match(/\r/g) || []).length;
+ start -= cr_before;
+ end -= cr_before + cr_inside;
+ }
+ range = input.createTextRange();
+ range.collapse(true);
+ range.moveStart('character', start);
+ range.moveEnd('character', end - start);
+ range.select();
+ } else {
+ if (input.contentEditable === 'true') {
+ if (input.childNodes.length === 1) {
+ range = document.createRange();
+ range.setStart(input.childNodes[0], start);
+ range.setEnd(input.childNodes[0], end);
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+ } else {
+ input.selectionStart = start;
+ input.selectionEnd = Math.min(end, input.value.length);
+ input.focus();
+ }
+ }
+ }
+
+};
+
+module.exports = ReactInputSelection;
+
+},{"./getTextContentAccessor":87}],43:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactInstanceHandles
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+var SEPARATOR = '.';
+var SEPARATOR_LENGTH = SEPARATOR.length;
+
+/**
+ * Maximum depth of traversals before we consider the possibility of a bad ID.
+ */
+var MAX_TREE_DEPTH = 100;
+
+/**
+ * Size of the reactRoot ID space. We generate random numbers for React root
+ * IDs and if there's a collision the events and DOM update system will
+ * get confused. If we assume 100 React components per page, and a user
+ * loads 1 page per minute 24/7 for 50 years, with a mount point space of
+ * 9,999,999 the likelihood of never having a collision is 99.997%.
+ */
+var GLOBAL_MOUNT_POINT_MAX = 9999999;
+
+/**
+ * Creates a DOM ID prefix to use when mounting React components.
+ *
+ * @param {number} index A unique integer
+ * @return {string} React root ID.
+ * @internal
+ */
+function getReactRootIDString(index) {
+ return SEPARATOR + 'r[' + index.toString(36) + ']';
+}
+
+/**
+ * Checks if a character in the supplied ID is a separator or the end.
+ *
+ * @param {string} id A React DOM ID.
+ * @param {number} index Index of the character to check.
+ * @return {boolean} True if the character is a separator or end of the ID.
+ * @private
+ */
+function isBoundary(id, index) {
+ return id.charAt(index) === SEPARATOR || index === id.length;
+}
+
+/**
+ * Checks if the supplied string is a valid React DOM ID.
+ *
+ * @param {string} id A React DOM ID, maybe.
+ * @return {boolean} True if the string is a valid React DOM ID.
+ * @private
+ */
+function isValidID(id) {
+ return id === '' || (
+ id.charAt(0) === SEPARATOR && id.charAt(id.length - 1) !== SEPARATOR
+ );
+}
+
+/**
+ * Checks if the first ID is an ancestor of or equal to the second ID.
+ *
+ * @param {string} ancestorID
+ * @param {string} descendantID
+ * @return {boolean} True if `ancestorID` is an ancestor of `descendantID`.
+ * @internal
+ */
+function isAncestorIDOf(ancestorID, descendantID) {
+ return (
+ descendantID.indexOf(ancestorID) === 0 &&
+ isBoundary(descendantID, ancestorID.length)
+ );
+}
+
+/**
+ * Gets the parent ID of the supplied React DOM ID, `id`.
+ *
+ * @param {string} id ID of a component.
+ * @return {string} ID of the parent, or an empty string.
+ * @private
+ */
+function getParentID(id) {
+ return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : '';
+}
+
+/**
+ * Gets the next DOM ID on the tree path from the supplied `ancestorID` to the
+ * supplied `destinationID`. If they are equal, the ID is returned.
+ *
+ * @param {string} ancestorID ID of an ancestor node of `destinationID`.
+ * @param {string} destinationID ID of the destination node.
+ * @return {string} Next ID on the path from `ancestorID` to `destinationID`.
+ * @private
+ */
+function getNextDescendantID(ancestorID, destinationID) {
+ invariant(
+ isValidID(ancestorID) && isValidID(destinationID),
+ 'getNextDescendantID(%s, %s): Received an invalid React DOM ID.',
+ ancestorID,
+ destinationID
+ );
+ invariant(
+ isAncestorIDOf(ancestorID, destinationID),
+ 'getNextDescendantID(...): React has made an invalid assumption about ' +
+ 'the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',
+ ancestorID,
+ destinationID
+ );
+ if (ancestorID === destinationID) {
+ return ancestorID;
+ }
+ // Skip over the ancestor and the immediate separator. Traverse until we hit
+ // another separator or we reach the end of `destinationID`.
+ var start = ancestorID.length + SEPARATOR_LENGTH;
+ for (var i = start; i < destinationID.length; i++) {
+ if (isBoundary(destinationID, i)) {
+ break;
+ }
+ }
+ return destinationID.substr(0, i);
+}
+
+/**
+ * Gets the nearest common ancestor ID of two IDs.
+ *
+ * Using this ID scheme, the nearest common ancestor ID is the longest common
+ * prefix of the two IDs that immediately preceded a "marker" in both strings.
+ *
+ * @param {string} oneID
+ * @param {string} twoID
+ * @return {string} Nearest common ancestor ID, or the empty string if none.
+ * @private
+ */
+function getFirstCommonAncestorID(oneID, twoID) {
+ var minLength = Math.min(oneID.length, twoID.length);
+ if (minLength === 0) {
+ return '';
+ }
+ var lastCommonMarkerIndex = 0;
+ // Use `<=` to traverse until the "EOL" of the shorter string.
+ for (var i = 0; i <= minLength; i++) {
+ if (isBoundary(oneID, i) && isBoundary(twoID, i)) {
+ lastCommonMarkerIndex = i;
+ } else if (oneID.charAt(i) !== twoID.charAt(i)) {
+ break;
+ }
+ }
+ var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);
+ invariant(
+ isValidID(longestCommonID),
+ 'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s',
+ oneID,
+ twoID,
+ longestCommonID
+ );
+ return longestCommonID;
+}
+
+/**
+ * Traverses the parent path between two IDs (either up or down). The IDs must
+ * not be the same, and there must exist a parent path between them.
+ *
+ * @param {?string} start ID at which to start traversal.
+ * @param {?string} stop ID at which to end traversal.
+ * @param {function} cb Callback to invoke each ID with.
+ * @param {?boolean} skipFirst Whether or not to skip the first node.
+ * @param {?boolean} skipLast Whether or not to skip the last node.
+ * @private
+ */
+function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
+ start = start || '';
+ stop = stop || '';
+ invariant(
+ start !== stop,
+ 'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',
+ start
+ );
+ var traverseUp = isAncestorIDOf(stop, start);
+ invariant(
+ traverseUp || isAncestorIDOf(start, stop),
+ 'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +
+ 'not have a parent path.',
+ start,
+ stop
+ );
+ // Traverse from `start` to `stop` one depth at a time.
+ var depth = 0;
+ var traverse = traverseUp ? getParentID : getNextDescendantID;
+ for (var id = start; /* until break */; id = traverse(id, stop)) {
+ if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {
+ cb(id, traverseUp, arg);
+ }
+ if (id === stop) {
+ // Only break //after// visiting `stop`.
+ break;
+ }
+ invariant(
+ depth++ < MAX_TREE_DEPTH,
+ 'traverseParentPath(%s, %s, ...): Detected an infinite loop while ' +
+ 'traversing the React DOM ID tree. This may be due to malformed IDs: %s',
+ start, stop
+ );
+ }
+}
+
+/**
+ * Manages the IDs assigned to DOM representations of React components. This
+ * uses a specific scheme in order to traverse the DOM efficiently (e.g. in
+ * order to simulate events).
+ *
+ * @internal
+ */
+var ReactInstanceHandles = {
+
+ createReactRootID: function() {
+ return getReactRootIDString(
+ Math.ceil(Math.random() * GLOBAL_MOUNT_POINT_MAX)
+ );
+ },
+
+ /**
+ * Constructs a React ID by joining a root ID with a name.
+ *
+ * @param {string} rootID Root ID of a parent component.
+ * @param {string} name A component's name (as flattened children).
+ * @return {string} A React ID.
+ * @internal
+ */
+ createReactID: function(rootID, name) {
+ return rootID + SEPARATOR + name;
+ },
+
+ /**
+ * Gets the DOM ID of the React component that is the root of the tree that
+ * contains the React component with the supplied DOM ID.
+ *
+ * @param {string} id DOM ID of a React component.
+ * @return {?string} DOM ID of the React component that is the root.
+ * @internal
+ */
+ getReactRootIDFromNodeID: function(id) {
+ var regexResult = /\.r\[[^\]]+\]/.exec(id);
+ return regexResult && regexResult[0];
+ },
+
+ /**
+ * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
+ * should would receive a `mouseEnter` or `mouseLeave` event.
+ *
+ * NOTE: Does not invoke the callback on the nearest common ancestor because
+ * nothing "entered" or "left" that element.
+ *
+ * @param {string} leaveID ID being left.
+ * @param {string} enterID ID being entered.
+ * @param {function} cb Callback to invoke on each entered/left ID.
+ * @param {*} upArg Argument to invoke the callback with on left IDs.
+ * @param {*} downArg Argument to invoke the callback with on entered IDs.
+ * @internal
+ */
+ traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) {
+ var ancestorID = getFirstCommonAncestorID(leaveID, enterID);
+ if (ancestorID !== leaveID) {
+ traverseParentPath(leaveID, ancestorID, cb, upArg, false, true);
+ }
+ if (ancestorID !== enterID) {
+ traverseParentPath(ancestorID, enterID, cb, downArg, true, false);
+ }
+ },
+
+ /**
+ * Simulates the traversal of a two-phase, capture/bubble event dispatch.
+ *
+ * NOTE: This traversal happens on IDs without touching the DOM.
+ *
+ * @param {string} targetID ID of the target node.
+ * @param {function} cb Callback to invoke.
+ * @param {*} arg Argument to invoke the callback with.
+ * @internal
+ */
+ traverseTwoPhase: function(targetID, cb, arg) {
+ if (targetID) {
+ traverseParentPath('', targetID, cb, arg, true, false);
+ traverseParentPath(targetID, '', cb, arg, false, true);
+ }
+ },
+
+ /**
+ * Exposed for unit testing.
+ * @private
+ */
+ _getFirstCommonAncestorID: getFirstCommonAncestorID,
+
+ /**
+ * Exposed for unit testing.
+ * @private
+ */
+ _getNextDescendantID: getNextDescendantID,
+
+ isAncestorIDOf: isAncestorIDOf,
+
+ SEPARATOR: SEPARATOR
+
+};
+
+module.exports = ReactInstanceHandles;
+
+},{"./invariant":89}],44:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactMarkupChecksum
+ */
+
+"use strict";
+
+var adler32 = require("./adler32");
+
+var ReactMarkupChecksum = {
+ CHECKSUM_ATTR_NAME: 'data-react-checksum',
+
+ /**
+ * @param {string} markup Markup string
+ * @return {string} Markup string with checksum attribute attached
+ */
+ addChecksumToMarkup: function(markup) {
+ var checksum = adler32(markup);
+ return markup.replace(
+ '>',
+ ' ' + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="' + checksum + '">'
+ );
+ },
+
+ /**
+ * @param {string} markup to use
+ * @param {DOMElement} element root React element
+ * @returns {boolean} whether or not the markup is the same
+ */
+ canReuseMarkup: function(markup, element) {
+ var existingChecksum = element.getAttribute(
+ ReactMarkupChecksum.CHECKSUM_ATTR_NAME
+ );
+ existingChecksum = existingChecksum && parseInt(existingChecksum, 10);
+ var markupChecksum = adler32(markup);
+ return markupChecksum === existingChecksum;
+ }
+};
+
+module.exports = ReactMarkupChecksum;
+
+},{"./adler32":71}],45:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactMount
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+var getReactRootElementInContainer = require("./getReactRootElementInContainer");
+var ReactEventEmitter = require("./ReactEventEmitter");
+var ReactInstanceHandles = require("./ReactInstanceHandles");
+
+var SEPARATOR = ReactInstanceHandles.SEPARATOR;
+
+var ATTR_NAME = 'data-reactid';
+var nodeCache = {};
+
+var ELEMENT_NODE_TYPE = 1;
+var DOC_NODE_TYPE = 9;
+
+var $ = require("./$");
+
+/** Mapping from reactRootID to React component instance. */
+var instancesByReactRootID = {};
+
+/** Mapping from reactRootID to `container` nodes. */
+var containersByReactRootID = {};
+
+if (true) {
+ /** __DEV__-only mapping from reactRootID to root elements. */
+ var rootElementsByReactRootID = {};
+}
+
+/**
+ * @param {DOMElement} container DOM element that may contain a React component.
+ * @return {?string} A "reactRoot" ID, if a React component is rendered.
+ */
+function getReactRootID(container) {
+ var rootElement = getReactRootElementInContainer(container);
+ return rootElement && ReactMount.getID(rootElement);
+}
+
+/**
+ * Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form
+ * element can return its control whose name or ID equals ATTR_NAME. All
+ * DOM nodes support `getAttributeNode` but this can also get called on
+ * other objects so just return '' if we're given something other than a
+ * DOM node (such as window).
+ *
+ * @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.
+ * @return {string} ID of the supplied `domNode`.
+ */
+function getID(node) {
+ var id = internalGetID(node);
+ if (id) {
+ if (nodeCache.hasOwnProperty(id)) {
+ var cached = nodeCache[id];
+ if (cached !== node) {
+ invariant(
+ !isValid(cached, id),
+ 'ReactMount: Two valid but unequal nodes with the same `%s`: %s',
+ ATTR_NAME, id
+ );
+
+ nodeCache[id] = node;
+ }
+ } else {
+ nodeCache[id] = node;
+ }
+ }
+
+ return id;
+}
+
+function internalGetID(node) {
+ // If node is something like a window, document, or text node, none of
+ // which support attributes or a .getAttribute method, gracefully return
+ // the empty string, as if the attribute were missing.
+ return node && node.getAttribute && node.getAttribute(ATTR_NAME) || '';
+}
+
+/**
+ * Sets the React-specific ID of the given node.
+ *
+ * @param {DOMElement} node The DOM node whose ID will be set.
+ * @param {string} id The value of the ID attribute.
+ */
+function setID(node, id) {
+ var oldID = internalGetID(node);
+ if (oldID !== id) {
+ delete nodeCache[oldID];
+ }
+ node.setAttribute(ATTR_NAME, id);
+ nodeCache[id] = node;
+}
+
+/**
+ * Finds the node with the supplied React-generated DOM ID.
+ *
+ * @param {string} id A React-generated DOM ID.
+ * @return {DOMElement} DOM node with the suppled `id`.
+ * @internal
+ */
+function getNode(id) {
+ if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
+ nodeCache[id] = ReactMount.findReactNodeByID(id);
+ }
+ return nodeCache[id];
+}
+
+/**
+ * A node is "valid" if it is contained by a currently mounted container.
+ *
+ * This means that the node does not have to be contained by a document in
+ * order to be considered valid.
+ *
+ * @param {?DOMElement} node The candidate DOM node.
+ * @param {string} id The expected ID of the node.
+ * @return {boolean} Whether the node is contained by a mounted container.
+ */
+function isValid(node, id) {
+ if (node) {
+ invariant(
+ internalGetID(node) === id,
+ 'ReactMount: Unexpected modification of `%s`',
+ ATTR_NAME
+ );
+
+ var container = ReactMount.findReactContainerForID(id);
+ if (container && contains(container, node)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function contains(ancestor, descendant) {
+ if (ancestor.contains) {
+ // Supported natively in virtually all browsers, but not in jsdom.
+ return ancestor.contains(descendant);
+ }
+
+ if (descendant === ancestor) {
+ return true;
+ }
+
+ if (descendant.nodeType === 3) {
+ // If descendant is a text node, start from descendant.parentNode
+ // instead, so that we can assume all ancestors worth considering are
+ // element nodes with nodeType === 1.
+ descendant = descendant.parentNode;
+ }
+
+ while (descendant && descendant.nodeType === 1) {
+ if (descendant === ancestor) {
+ return true;
+ }
+ descendant = descendant.parentNode;
+ }
+
+ return false;
+}
+
+/**
+ * Causes the cache to forget about one React-specific ID.
+ *
+ * @param {string} id The ID to forget.
+ */
+function purgeID(id) {
+ delete nodeCache[id];
+}
+
+/**
+ * Mounting is the process of initializing a React component by creatings its
+ * representative DOM elements and inserting them into a supplied `container`.
+ * Any prior content inside `container` is destroyed in the process.
+ *
+ * ReactMount.renderComponent(component, $('container'));
+ *
+ * <div id="container"> <-- Supplied `container`.
+ * <div data-reactid=".r[3]"> <-- Rendered reactRoot of React
+ * // ... component.
+ * </div>
+ * </div>
+ *
+ * Inside of `container`, the first element rendered is the "reactRoot".
+ */
+var ReactMount = {
+ /**
+ * Safety guard to prevent accidentally rendering over the entire HTML tree.
+ */
+ allowFullPageRender: false,
+
+ /** Time spent generating markup. */
+ totalInstantiationTime: 0,
+
+ /** Time spent inserting markup into the DOM. */
+ totalInjectionTime: 0,
+
+ /** Whether support for touch events should be initialized. */
+ useTouchEvents: false,
+
+ /** Exposed for debugging purposes **/
+ _instancesByReactRootID: instancesByReactRootID,
+
+ /**
+ * This is a hook provided to support rendering React components while
+ * ensuring that the apparent scroll position of its `container` does not
+ * change.
+ *
+ * @param {DOMElement} container The `container` being rendered into.
+ * @param {function} renderCallback This must be called once to do the render.
+ */
+ scrollMonitor: function(container, renderCallback) {
+ renderCallback();
+ },
+
+ /**
+ * Ensures that the top-level event delegation listener is set up. This will
+ * be invoked some time before the first time any React component is rendered.
+ * @param {DOMElement} container container we're rendering into
+ *
+ * @private
+ */
+ prepareEnvironmentForDOM: function(container) {
+ invariant(
+ container && (
+ container.nodeType === ELEMENT_NODE_TYPE ||
+ container.nodeType === DOC_NODE_TYPE
+ ),
+ 'prepareEnvironmentForDOM(...): Target container is not a DOM element.'
+ );
+ var doc = container.nodeType === ELEMENT_NODE_TYPE ?
+ container.ownerDocument :
+ container;
+ ReactEventEmitter.ensureListening(ReactMount.useTouchEvents, doc);
+ },
+
+ /**
+ * Take a component that's already mounted into the DOM and replace its props
+ * @param {ReactComponent} prevComponent component instance already in the DOM
+ * @param {ReactComponent} nextComponent component instance to render
+ * @param {DOMElement} container container to render into
+ * @param {?function} callback function triggered on completion
+ */
+ _updateRootComponent: function(
+ prevComponent,
+ nextComponent,
+ container,
+ callback) {
+ var nextProps = nextComponent.props;
+ ReactMount.scrollMonitor(container, function() {
+ prevComponent.replaceProps(nextProps, callback);
+ });
+
+ if (true) {
+ // Record the root element in case it later gets transplanted.
+ rootElementsByReactRootID[getReactRootID(container)] =
+ getReactRootElementInContainer(container);
+ }
+
+ return prevComponent;
+ },
+
+ /**
+ * Register a component into the instance map and start the events system.
+ * @param {ReactComponent} nextComponent component instance to render
+ * @param {DOMElement} container container to render into
+ * @return {string} reactRoot ID prefix
+ */
+ _registerComponent: function(nextComponent, container) {
+ ReactMount.prepareEnvironmentForDOM(container);
+
+ var reactRootID = ReactMount.registerContainer(container);
+ instancesByReactRootID[reactRootID] = nextComponent;
+ return reactRootID;
+ },
+
+ /**
+ * Render a new component into the DOM.
+ * @param {ReactComponent} nextComponent component instance to render
+ * @param {DOMElement} container container to render into
+ * @param {boolean} shouldReuseMarkup if we should skip the markup insertion
+ * @return {ReactComponent} nextComponent
+ */
+ _renderNewRootComponent: function(
+ nextComponent,
+ container,
+ shouldReuseMarkup) {
+ var reactRootID = ReactMount._registerComponent(nextComponent, container);
+ nextComponent.mountComponentIntoNode(
+ reactRootID,
+ container,
+ shouldReuseMarkup
+ );
+
+ if (true) {
+ // Record the root element in case it later gets transplanted.
+ rootElementsByReactRootID[reactRootID] =
+ getReactRootElementInContainer(container);
+ }
+
+ return nextComponent;
+ },
+
+ /**
+ * Renders a React component into the DOM in the supplied `container`.
+ *
+ * If the React component was previously rendered into `container`, this will
+ * perform an update on it and only mutate the DOM as necessary to reflect the
+ * latest React component.
+ *
+ * @param {ReactComponent} nextComponent Component instance to render.
+ * @param {DOMElement} container DOM element to render into.
+ * @param {?function} callback function triggered on completion
+ * @return {ReactComponent} Component instance rendered in `container`.
+ */
+ renderComponent: function(nextComponent, container, callback) {
+ var registeredComponent = instancesByReactRootID[getReactRootID(container)];
+
+ if (registeredComponent) {
+ if (registeredComponent.constructor === nextComponent.constructor) {
+ return ReactMount._updateRootComponent(
+ registeredComponent,
+ nextComponent,
+ container,
+ callback
+ );
+ } else {
+ ReactMount.unmountAndReleaseReactRootNode(container);
+ }
+ }
+
+ var reactRootElement = getReactRootElementInContainer(container);
+ var containerHasReactMarkup =
+ reactRootElement && ReactMount.isRenderedByReact(reactRootElement);
+
+ var shouldReuseMarkup = containerHasReactMarkup && !registeredComponent;
+
+ var component = ReactMount._renderNewRootComponent(
+ nextComponent,
+ container,
+ shouldReuseMarkup
+ );
+ callback && callback();
+ return component;
+ },
+
+ /**
+ * Constructs a component instance of `constructor` with `initialProps` and
+ * renders it into the supplied `container`.
+ *
+ * @param {function} constructor React component constructor.
+ * @param {?object} props Initial props of the component instance.
+ * @param {DOMElement} container DOM element to render into.
+ * @return {ReactComponent} Component instance rendered in `container`.
+ */
+ constructAndRenderComponent: function(constructor, props, container) {
+ return ReactMount.renderComponent(constructor(props), container);
+ },
+
+ /**
+ * Constructs a component instance of `constructor` with `initialProps` and
+ * renders it into a container node identified by supplied `id`.
+ *
+ * @param {function} componentConstructor React component constructor
+ * @param {?object} props Initial props of the component instance.
+ * @param {string} id ID of the DOM element to render into.
+ * @return {ReactComponent} Component instance rendered in the container node.
+ */
+ constructAndRenderComponentByID: function(constructor, props, id) {
+ return ReactMount.constructAndRenderComponent(constructor, props, $(id));
+ },
+
+ /**
+ * Registers a container node into which React components will be rendered.
+ * This also creates the "reatRoot" ID that will be assigned to the element
+ * rendered within.
+ *
+ * @param {DOMElement} container DOM element to register as a container.
+ * @return {string} The "reactRoot" ID of elements rendered within.
+ */
+ registerContainer: function(container) {
+ var reactRootID = getReactRootID(container);
+ if (reactRootID) {
+ // If one exists, make sure it is a valid "reactRoot" ID.
+ reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID);
+ }
+ if (!reactRootID) {
+ // No valid "reactRoot" ID found, create one.
+ reactRootID = ReactInstanceHandles.createReactRootID();
+ }
+ containersByReactRootID[reactRootID] = container;
+ return reactRootID;
+ },
+
+ /**
+ * Unmounts and destroys the React component rendered in the `container`.
+ *
+ * @param {DOMElement} container DOM element containing a React component.
+ * @return {boolean} True if a component was found in and unmounted from
+ * `container`
+ */
+ unmountAndReleaseReactRootNode: function(container) {
+ var reactRootID = getReactRootID(container);
+ var component = instancesByReactRootID[reactRootID];
+ if (!component) {
+ return false;
+ }
+ ReactMount.unmountComponentFromNode(component, container);
+ delete instancesByReactRootID[reactRootID];
+ delete containersByReactRootID[reactRootID];
+ if (true) {
+ delete rootElementsByReactRootID[reactRootID];
+ }
+ return true;
+ },
+
+ /**
+ * Unmounts a component and removes it from the DOM.
+ *
+ * @param {ReactComponent} instance React component instance.
+ * @param {DOMElement} container DOM element to unmount from.
+ * @final
+ * @internal
+ * @see {ReactMount.unmountAndReleaseReactRootNode}
+ */
+ unmountComponentFromNode: function(instance, container) {
+ instance.unmountComponent();
+
+ // http://jsperf.com/emptying-a-node
+ while (container.lastChild) {
+ container.removeChild(container.lastChild);
+ }
+ },
+
+ /**
+ * Finds the container DOM element that contains React component to which the
+ * supplied DOM `id` belongs.
+ *
+ * @param {string} id The ID of an element rendered by a React component.
+ * @return {?DOMElement} DOM element that contains the `id`.
+ */
+ findReactContainerForID: function(id) {
+ var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id);
+ var container = containersByReactRootID[reactRootID];
+
+ if (true) {
+ var rootElement = rootElementsByReactRootID[reactRootID];
+ if (rootElement && rootElement.parentNode !== container) {
+ invariant(
+ // Call internalGetID here because getID calls isValid which calls
+ // findReactContainerForID (this function).
+ internalGetID(rootElement) === reactRootID,
+ 'ReactMount: Root element ID differed from reactRootID.'
+ );
+
+ var containerChild = container.firstChild;
+ if (containerChild &&
+ reactRootID === internalGetID(containerChild)) {
+ // If the container has a new child with the same ID as the old
+ // root element, then rootElementsByReactRootID[reactRootID] is
+ // just stale and needs to be updated. The case that deserves a
+ // warning is when the container is empty.
+ rootElementsByReactRootID[reactRootID] = containerChild;
+ } else {
+ console.warn(
+ 'ReactMount: Root element has been removed from its original ' +
+ 'container. New container:', rootElement.parentNode
+ );
+ }
+ }
+ }
+
+ return container;
+ },
+
+ /**
+ * Finds an element rendered by React with the supplied ID.
+ *
+ * @param {string} id ID of a DOM node in the React component.
+ * @return {DOMElement} Root DOM node of the React component.
+ */
+ findReactNodeByID: function(id) {
+ var reactRoot = ReactMount.findReactContainerForID(id);
+ return ReactMount.findComponentRoot(reactRoot, id);
+ },
+
+ /**
+ * True if the supplied `node` is rendered by React.
+ *
+ * @param {*} node DOM Element to check.
+ * @return {boolean} True if the DOM Element appears to be rendered by React.
+ * @internal
+ */
+ isRenderedByReact: function(node) {
+ if (node.nodeType !== 1) {
+ // Not a DOMElement, therefore not a React component
+ return false;
+ }
+ var id = ReactMount.getID(node);
+ return id ? id.charAt(0) === SEPARATOR : false;
+ },
+
+ /**
+ * Traverses up the ancestors of the supplied node to find a node that is a
+ * DOM representation of a React component.
+ *
+ * @param {*} node
+ * @return {?DOMEventTarget}
+ * @internal
+ */
+ getFirstReactDOM: function(node) {
+ var current = node;
+ while (current && current.parentNode !== current) {
+ if (ReactMount.isRenderedByReact(current)) {
+ return current;
+ }
+ current = current.parentNode;
+ }
+ return null;
+ },
+
+ /**
+ * Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
+ * Exploits the ID naming scheme to perform the search quickly.
+ *
+ * @param {DOMEventTarget} ancestorNode Search from this root.
+ * @pararm {string} id ID of the DOM representation of the component.
+ * @return {DOMEventTarget} DOM node with the supplied `id`.
+ * @internal
+ */
+ findComponentRoot: function(ancestorNode, id) {
+ var firstChildren = [ancestorNode.firstChild];
+ var childIndex = 0;
+
+ while (childIndex < firstChildren.length) {
+ var child = firstChildren[childIndex++];
+ while (child) {
+ var childID = ReactMount.getID(child);
+ if (childID) {
+ if (id === childID) {
+ return child;
+ } else if (ReactInstanceHandles.isAncestorIDOf(childID, id)) {
+ // If we find a child whose ID is an ancestor of the given ID,
+ // then we can be sure that we only want to search the subtree
+ // rooted at this child, so we can throw out the rest of the
+ // search state.
+ firstChildren.length = childIndex = 0;
+ firstChildren.push(child.firstChild);
+ break;
+ } else {
+ // TODO This should not be necessary if the ID hierarchy is
+ // correct, but is occasionally necessary if the DOM has been
+ // modified in unexpected ways.
+ firstChildren.push(child.firstChild);
+ }
+ } else {
+ // If this child had no ID, then there's a chance that it was
+ // injected automatically by the browser, as when a `<table>`
+ // element sprouts an extra `<tbody>` child as a side effect of
+ // `.innerHTML` parsing. Optimistically continue down this
+ // branch, but not before examining the other siblings.
+ firstChildren.push(child.firstChild);
+ }
+ child = child.nextSibling;
+ }
+ }
+
+ if (true) {
+ console.error(
+ 'Error while invoking `findComponentRoot` with the following ' +
+ 'ancestor node:',
+ ancestorNode
+ );
+ }
+ invariant(
+ false,
+ 'findComponentRoot(..., %s): Unable to find element. This probably ' +
+ 'means the DOM was unexpectedly mutated (e.g. by the browser).',
+ id,
+ ReactMount.getID(ancestorNode)
+ );
+ },
+
+
+ /**
+ * React ID utilities.
+ */
+
+ ATTR_NAME: ATTR_NAME,
+
+ getID: getID,
+
+ setID: setID,
+
+ getNode: getNode,
+
+ purgeID: purgeID,
+
+ injection: {}
+};
+
+module.exports = ReactMount;
+
+},{"./$":1,"./ReactEventEmitter":39,"./ReactInstanceHandles":43,"./getReactRootElementInContainer":86,"./invariant":89}],46:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactMultiChild
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var ReactComponent = require("./ReactComponent");
+var ReactMultiChildUpdateTypes = require("./ReactMultiChildUpdateTypes");
+
+/**
+ * Given a `curChild` and `newChild`, determines if `curChild` should be
+ * updated as opposed to being destroyed or replaced.
+ *
+ * @param {?ReactComponent} curChild
+ * @param {?ReactComponent} newChild
+ * @return {boolean} True if `curChild` should be updated with `newChild`.
+ * @protected
+ */
+function shouldUpdateChild(curChild, newChild) {
+ return curChild && newChild && curChild.constructor === newChild.constructor;
+}
+
+/**
+ * Updating children of a component may trigger recursive updates. The depth is
+ * used to batch recursive updates to render markup more efficiently.
+ *
+ * @type {number}
+ * @private
+ */
+var updateDepth = 0;
+
+/**
+ * Queue of update configuration objects.
+ *
+ * Each object has a `type` property that is in `ReactMultiChildUpdateTypes`.
+ *
+ * @type {array<object>}
+ * @private
+ */
+var updateQueue = [];
+
+/**
+ * Queue of markup to be rendered.
+ *
+ * @type {array<string>}
+ * @private
+ */
+var markupQueue = [];
+
+/**
+ * Enqueues markup to be rendered and inserted at a supplied index.
+ *
+ * @param {string} parentID ID of the parent component.
+ * @param {string} markup Markup that renders into an element.
+ * @param {number} toIndex Destination index.
+ * @private
+ */
+function enqueueMarkup(parentID, markup, toIndex) {
+ // NOTE: Null values reduce hidden classes.
+ updateQueue.push({
+ parentID: parentID,
+ parentNode: null,
+ type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
+ markupIndex: markupQueue.push(markup) - 1,
+ fromIndex: null,
+ textContent: null,
+ toIndex: toIndex
+ });
+}
+
+/**
+ * Enqueues moving an existing element to another index.
+ *
+ * @param {string} parentID ID of the parent component.
+ * @param {number} fromIndex Source index of the existing element.
+ * @param {number} toIndex Destination index of the element.
+ * @private
+ */
+function enqueueMove(parentID, fromIndex, toIndex) {
+ // NOTE: Null values reduce hidden classes.
+ updateQueue.push({
+ parentID: parentID,
+ parentNode: null,
+ type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
+ markupIndex: null,
+ textContent: null,
+ fromIndex: fromIndex,
+ toIndex: toIndex
+ });
+}
+
+/**
+ * Enqueues removing an element at an index.
+ *
+ * @param {string} parentID ID of the parent component.
+ * @param {number} fromIndex Index of the element to remove.
+ * @private
+ */
+function enqueueRemove(parentID, fromIndex) {
+ // NOTE: Null values reduce hidden classes.
+ updateQueue.push({
+ parentID: parentID,
+ parentNode: null,
+ type: ReactMultiChildUpdateTypes.REMOVE_NODE,
+ markupIndex: null,
+ textContent: null,
+ fromIndex: fromIndex,
+ toIndex: null
+ });
+}
+
+/**
+ * Enqueues setting the text content.
+ *
+ * @param {string} parentID ID of the parent component.
+ * @param {string} textContent Text content to set.
+ * @private
+ */
+function enqueueTextContent(parentID, textContent) {
+ // NOTE: Null values reduce hidden classes.
+ updateQueue.push({
+ parentID: parentID,
+ parentNode: null,
+ type: ReactMultiChildUpdateTypes.TEXT_CONTENT,
+ markupIndex: null,
+ textContent: textContent,
+ fromIndex: null,
+ toIndex: null
+ });
+}
+
+/**
+ * Processes any enqueued updates.
+ *
+ * @private
+ */
+function processQueue() {
+ if (updateQueue.length) {
+ ReactComponent.DOMIDOperations.dangerouslyProcessChildrenUpdates(
+ updateQueue,
+ markupQueue
+ );
+ clearQueue();
+ }
+}
+
+/**
+ * Clears any enqueued updates.
+ *
+ * @private
+ */
+function clearQueue() {
+ updateQueue.length = 0;
+ markupQueue.length = 0;
+}
+
+/**
+ * ReactMultiChild are capable of reconciling multiple children.
+ *
+ * @class ReactMultiChild
+ * @internal
+ */
+var ReactMultiChild = {
+
+ /**
+ * Provides common functionality for components that must reconcile multiple
+ * children. This is used by `ReactNativeComponent` to mount, update, and
+ * unmount child components.
+ *
+ * @lends {ReactMultiChild.prototype}
+ */
+ Mixin: {
+
+ /**
+ * Generates a "mount image" for each of the supplied children. In the case
+ * of `ReactNativeComponent`, a mount image is a string of markup.
+ *
+ * @param {?object} children As returned by `flattenChildren`.
+ * @return {array} An array of mounted representations.
+ * @internal
+ */
+ mountChildren: function(children, transaction) {
+ var mountImages = [];
+ var index = 0;
+ for (var name in children) {
+ var child = children[name];
+ if (children.hasOwnProperty(name) && child) {
+ // Inlined for performance, see `ReactInstanceHandles.createReactID`.
+ var rootID = this._rootNodeID + '.' + name;
+ var mountImage = child.mountComponent(rootID, transaction);
+ child._mountImage = mountImage;
+ child._mountIndex = index;
+ mountImages.push(mountImage);
+ index++;
+ }
+ }
+ this._renderedChildren = children;
+ return mountImages;
+ },
+
+ /**
+ * Replaces any rendered children with a text content string.
+ *
+ * @param {string} nextContent String of content.
+ * @internal
+ */
+ updateTextContent: function(nextContent) {
+ updateDepth++;
+ try {
+ var prevChildren = this._renderedChildren;
+ // Remove any rendered children.
+ for (var name in prevChildren) {
+ if (prevChildren.hasOwnProperty(name) &&
+ prevChildren[name]) {
+ this._unmountChildByName(prevChildren[name], name);
+ }
+ }
+ // Set new text content.
+ this.setTextContent(nextContent);
+ } catch (error) {
+ updateDepth--;
+ updateDepth || clearQueue();
+ throw error;
+ }
+ updateDepth--;
+ updateDepth || processQueue();
+ },
+
+ /**
+ * Updates the rendered children with new children.
+ *
+ * @param {?object} nextChildren As returned by `flattenChildren`.
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ */
+ updateChildren: function(nextChildren, transaction) {
+ updateDepth++;
+ try {
+ this._updateChildren(nextChildren, transaction);
+ } catch (error) {
+ updateDepth--;
+ updateDepth || clearQueue();
+ throw error;
+ }
+ updateDepth--;
+ updateDepth || processQueue();
+ },
+
+ /**
+ * Improve performance by isolating this hot code path from the try/catch
+ * block in `updateChildren`.
+ *
+ * @param {?object} nextChildren As returned by `flattenChildren`.
+ * @param {ReactReconcileTransaction} transaction
+ * @final
+ * @protected
+ */
+ _updateChildren: function(nextChildren, transaction) {
+ var prevChildren = this._renderedChildren;
+ if (!nextChildren && !prevChildren) {
+ return;
+ }
+ var name;
+ // `nextIndex` will increment for each child in `nextChildren`, but
+ // `lastIndex` will be the last index visited in `prevChildren`.
+ var lastIndex = 0;
+ var nextIndex = 0;
+ for (name in nextChildren) {
+ if (!nextChildren.hasOwnProperty(name)) {
+ continue;
+ }
+ var prevChild = prevChildren && prevChildren[name];
+ var nextChild = nextChildren[name];
+ if (shouldUpdateChild(prevChild, nextChild)) {
+ this.moveChild(prevChild, nextIndex, lastIndex);
+ lastIndex = Math.max(prevChild._mountIndex, lastIndex);
+ prevChild.receiveProps(nextChild.props, transaction);
+ prevChild._mountIndex = nextIndex;
+ } else {
+ if (prevChild) {
+ // Update `lastIndex` before `_mountIndex` gets unset by unmounting.
+ lastIndex = Math.max(prevChild._mountIndex, lastIndex);
+ this._unmountChildByName(prevChild, name);
+ }
+ if (nextChild) {
+ this._mountChildByNameAtIndex(
+ nextChild, name, nextIndex, transaction
+ );
+ }
+ }
+ if (nextChild) {
+ nextIndex++;
+ }
+ }
+ // Remove children that are no longer present.
+ for (name in prevChildren) {
+ if (prevChildren.hasOwnProperty(name) &&
+ prevChildren[name] &&
+ !(nextChildren && nextChildren[name])) {
+ this._unmountChildByName(prevChildren[name], name);
+ }
+ }
+ },
+
+ /**
+ * Unmounts all rendered children. This should be used to clean up children
+ * when this component is unmounted.
+ *
+ * @internal
+ */
+ unmountChildren: function() {
+ var renderedChildren = this._renderedChildren;
+ for (var name in renderedChildren) {
+ var renderedChild = renderedChildren[name];
+ if (renderedChild && renderedChild.unmountComponent) {
+ renderedChild.unmountComponent();
+ }
+ }
+ this._renderedChildren = null;
+ },
+
+ /**
+ * Moves a child component to the supplied index.
+ *
+ * @param {ReactComponent} child Component to move.
+ * @param {number} toIndex Destination index of the element.
+ * @param {number} lastIndex Last index visited of the siblings of `child`.
+ * @protected
+ */
+ moveChild: function(child, toIndex, lastIndex) {
+ // If the index of `child` is less than `lastIndex`, then it needs to
+ // be moved. Otherwise, we do not need to move it because a child will be
+ // inserted or moved before `child`.
+ if (child._mountIndex < lastIndex) {
+ enqueueMove(this._rootNodeID, child._mountIndex, toIndex);
+ }
+ },
+
+ /**
+ * Creates a child component.
+ *
+ * @param {ReactComponent} child Component to create.
+ * @protected
+ */
+ createChild: function(child) {
+ enqueueMarkup(this._rootNodeID, child._mountImage, child._mountIndex);
+ },
+
+ /**
+ * Removes a child component.
+ *
+ * @param {ReactComponent} child Child to remove.
+ * @protected
+ */
+ removeChild: function(child) {
+ enqueueRemove(this._rootNodeID, child._mountIndex);
+ },
+
+ /**
+ * Sets this text content string.
+ *
+ * @param {string} textContent Text content to set.
+ * @protected
+ */
+ setTextContent: function(textContent) {
+ enqueueTextContent(this._rootNodeID, textContent);
+ },
+
+ /**
+ * Mounts a child with the supplied name.
+ *
+ * NOTE: This is part of `updateChildren` and is here for readability.
+ *
+ * @param {ReactComponent} child Component to mount.
+ * @param {string} name Name of the child.
+ * @param {number} index Index at which to insert the child.
+ * @param {ReactReconcileTransaction} transaction
+ * @private
+ */
+ _mountChildByNameAtIndex: function(child, name, index, transaction) {
+ // Inlined for performance, see `ReactInstanceHandles.createReactID`.
+ var rootID = this._rootNodeID + '.' + name;
+ var mountImage = child.mountComponent(rootID, transaction);
+ child._mountImage = mountImage;
+ child._mountIndex = index;
+ this.createChild(child);
+ this._renderedChildren = this._renderedChildren || {};
+ this._renderedChildren[name] = child;
+ },
+
+ /**
+ * Unmounts a rendered child by name.
+ *
+ * NOTE: This is part of `updateChildren` and is here for readability.
+ *
+ * @param {ReactComponent} child Component to unmount.
+ * @param {string} name Name of the child in `this._renderedChildren`.
+ * @private
+ */
+ _unmountChildByName: function(child, name) {
+ if (ReactComponent.isValidComponent(child)) {
+ this.removeChild(child);
+ child._mountImage = null;
+ child._mountIndex = null;
+ child.unmountComponent();
+ delete this._renderedChildren[name];
+ }
+ }
+
+ }
+
+};
+
+module.exports = ReactMultiChild;
+
+},{"./ReactComponent":23,"./ReactMultiChildUpdateTypes":47}],47:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactMultiChildUpdateTypes
+ */
+
+var keyMirror = require("./keyMirror");
+
+/**
+ * When a component's children are updated, a series of update configuration
+ * objects are created in order to batch and serialize the required changes.
+ *
+ * Enumerates all the possible types of update configurations.
+ *
+ * @internal
+ */
+var ReactMultiChildUpdateTypes = keyMirror({
+ INSERT_MARKUP: null,
+ MOVE_EXISTING: null,
+ REMOVE_NODE: null,
+ TEXT_CONTENT: null
+});
+
+module.exports = ReactMultiChildUpdateTypes;
+
+},{"./keyMirror":92}],48:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactNativeComponent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var CSSPropertyOperations = require("./CSSPropertyOperations");
+var DOMProperty = require("./DOMProperty");
+var DOMPropertyOperations = require("./DOMPropertyOperations");
+var ReactComponent = require("./ReactComponent");
+var ReactEventEmitter = require("./ReactEventEmitter");
+var ReactMultiChild = require("./ReactMultiChild");
+var ReactMount = require("./ReactMount");
+var ReactPerf = require("./ReactPerf");
+
+var escapeTextForBrowser = require("./escapeTextForBrowser");
+var flattenChildren = require("./flattenChildren");
+var invariant = require("./invariant");
+var keyOf = require("./keyOf");
+var merge = require("./merge");
+var mixInto = require("./mixInto");
+
+var putListener = ReactEventEmitter.putListener;
+var deleteListener = ReactEventEmitter.deleteListener;
+var registrationNames = ReactEventEmitter.registrationNames;
+
+// For quickly matching children type, to test if can be treated as content.
+var CONTENT_TYPES = {'string': true, 'number': true};
+
+var DANGEROUSLY_SET_INNER_HTML = keyOf({dangerouslySetInnerHTML: null});
+var STYLE = keyOf({style: null});
+
+/**
+ * @param {?object} props
+ */
+function assertValidProps(props) {
+ if (!props) {
+ return;
+ }
+ // Note the use of `==` which checks for null or undefined.
+ invariant(
+ props.children == null || props.dangerouslySetInnerHTML == null,
+ 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.'
+ );
+ invariant(
+ props.style == null || typeof props.style === 'object',
+ 'The `style` prop expects a mapping from style properties to values, ' +
+ 'not a string.'
+ );
+}
+
+/**
+ * @constructor ReactNativeComponent
+ * @extends ReactComponent
+ * @extends ReactMultiChild
+ */
+function ReactNativeComponent(tag, omitClose) {
+ this._tagOpen = '<' + tag;
+ this._tagClose = omitClose ? '' : '</' + tag + '>';
+ this.tagName = tag.toUpperCase();
+}
+
+ReactNativeComponent.Mixin = {
+
+ /**
+ * Generates root tag markup then recurses. This method has side effects and
+ * is not idempotent.
+ *
+ * @internal
+ * @param {string} rootID The root DOM ID for this node.
+ * @param {ReactReconcileTransaction} transaction
+ * @return {string} The computed markup.
+ */
+ mountComponent: ReactPerf.measure(
+ 'ReactNativeComponent',
+ 'mountComponent',
+ function(rootID, transaction) {
+ ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
+ assertValidProps(this.props);
+ return (
+ this._createOpenTagMarkup() +
+ this._createContentMarkup(transaction) +
+ this._tagClose
+ );
+ }
+ ),
+
+ /**
+ * Creates markup for the open tag and all attributes.
+ *
+ * This method has side effects because events get registered.
+ *
+ * Iterating over object properties is faster than iterating over arrays.
+ * @see http://jsperf.com/obj-vs-arr-iteration
+ *
+ * @private
+ * @return {string} Markup of opening tag.
+ */
+ _createOpenTagMarkup: function() {
+ var props = this.props;
+ var ret = this._tagOpen;
+
+ for (var propKey in props) {
+ if (!props.hasOwnProperty(propKey)) {
+ continue;
+ }
+ var propValue = props[propKey];
+ if (propValue == null) {
+ continue;
+ }
+ if (registrationNames[propKey]) {
+ putListener(this._rootNodeID, propKey, propValue);
+ } else {
+ if (propKey === STYLE) {
+ if (propValue) {
+ propValue = props.style = merge(props.style);
+ }
+ propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
+ }
+ var markup =
+ DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
+ if (markup) {
+ ret += ' ' + markup;
+ }
+ }
+ }
+
+ var escapedID = escapeTextForBrowser(this._rootNodeID);
+ return ret + ' ' + ReactMount.ATTR_NAME + '="' + escapedID + '">';
+ },
+
+ /**
+ * Creates markup for the content between the tags.
+ *
+ * @private
+ * @param {ReactReconcileTransaction} transaction
+ * @return {string} Content markup.
+ */
+ _createContentMarkup: function(transaction) {
+ // Intentional use of != to avoid catching zero/false.
+ var innerHTML = this.props.dangerouslySetInnerHTML;
+ if (innerHTML != null) {
+ if (innerHTML.__html != null) {
+ return innerHTML.__html;
+ }
+ } else {
+ var contentToUse =
+ CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
+ var childrenToUse = contentToUse != null ? null : this.props.children;
+ if (contentToUse != null) {
+ return escapeTextForBrowser(contentToUse);
+ } else if (childrenToUse != null) {
+ var mountImages = this.mountChildren(
+ flattenChildren(childrenToUse),
+ transaction
+ );
+ return mountImages.join('');
+ }
+ }
+ return '';
+ },
+
+ receiveProps: function(nextProps, transaction) {
+ assertValidProps(nextProps);
+ ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
+ },
+
+ /**
+ * Updates a native DOM component after it has already been allocated and
+ * attached to the DOM. Reconciles the root DOM node, then recurses.
+ *
+ * @param {ReactReconcileTransaction} transaction
+ * @param {object} prevProps
+ * @internal
+ * @overridable
+ */
+ updateComponent: ReactPerf.measure(
+ 'ReactNativeComponent',
+ 'updateComponent',
+ function(transaction, prevProps) {
+ ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
+ this._updateDOMProperties(prevProps);
+ this._updateDOMChildren(prevProps, transaction);
+ }
+ ),
+
+ /**
+ * Reconciles the properties by detecting differences in property values and
+ * updating the DOM as necessary. This function is probably the single most
+ * critical path for performance optimization.
+ *
+ * TODO: Benchmark whether checking for changed values in memory actually
+ * improves performance (especially statically positioned elements).
+ * TODO: Benchmark the effects of putting this at the top since 99% of props
+ * do not change for a given reconciliation.
+ * TODO: Benchmark areas that can be improved with caching.
+ *
+ * @private
+ * @param {object} lastProps
+ */
+ _updateDOMProperties: function(lastProps) {
+ var nextProps = this.props;
+ var propKey;
+ var styleName;
+ var styleUpdates;
+ for (propKey in lastProps) {
+ if (nextProps.hasOwnProperty(propKey) ||
+ !lastProps.hasOwnProperty(propKey)) {
+ continue;
+ }
+ if (propKey === STYLE) {
+ var lastStyle = lastProps[propKey];
+ for (styleName in lastStyle) {
+ if (lastStyle.hasOwnProperty(styleName)) {
+ styleUpdates = styleUpdates || {};
+ styleUpdates[styleName] = '';
+ }
+ }
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
+ // http://jsperf.com/emptying-speed
+ ReactComponent.DOMIDOperations.updateTextContentByID(
+ this._rootNodeID,
+ ''
+ );
+ } else if (registrationNames[propKey]) {
+ deleteListener(this._rootNodeID, propKey);
+ } else {
+ ReactComponent.DOMIDOperations.deletePropertyByID(
+ this._rootNodeID,
+ propKey
+ );
+ }
+ }
+ for (propKey in nextProps) {
+ var nextProp = nextProps[propKey];
+ var lastProp = lastProps[propKey];
+ if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
+ continue;
+ }
+ if (propKey === STYLE) {
+ if (nextProp) {
+ nextProp = nextProps.style = merge(nextProp);
+ }
+ if (lastProp) {
+ // Unset styles on `lastProp` but not on `nextProp`.
+ for (styleName in lastProp) {
+ if (lastProp.hasOwnProperty(styleName) &&
+ !nextProp.hasOwnProperty(styleName)) {
+ styleUpdates = styleUpdates || {};
+ styleUpdates[styleName] = '';
+ }
+ }
+ // Update styles that changed since `lastProp`.
+ for (styleName in nextProp) {
+ if (nextProp.hasOwnProperty(styleName) &&
+ lastProp[styleName] !== nextProp[styleName]) {
+ styleUpdates = styleUpdates || {};
+ styleUpdates[styleName] = nextProp[styleName];
+ }
+ }
+ } else {
+ // Relies on `updateStylesByID` not mutating `styleUpdates`.
+ styleUpdates = nextProp;
+ }
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
+ var lastHtml = lastProp && lastProp.__html;
+ var nextHtml = nextProp && nextProp.__html;
+ if (lastHtml !== nextHtml) {
+ ReactComponent.DOMIDOperations.updateInnerHTMLByID(
+ this._rootNodeID,
+ nextProp
+ );
+ }
+ } else if (registrationNames[propKey]) {
+ putListener(this._rootNodeID, propKey, nextProp);
+ } else if (
+ DOMProperty.isStandardName[propKey] ||
+ DOMProperty.isCustomAttribute(propKey)) {
+ ReactComponent.DOMIDOperations.updatePropertyByID(
+ this._rootNodeID,
+ propKey,
+ nextProp
+ );
+ }
+ }
+ if (styleUpdates) {
+ ReactComponent.DOMIDOperations.updateStylesByID(
+ this._rootNodeID,
+ styleUpdates
+ );
+ }
+ },
+
+ /**
+ * Reconciles the children with the various properties that affect the
+ * children content.
+ *
+ * @param {object} lastProps
+ * @param {ReactReconcileTransaction} transaction
+ */
+ _updateDOMChildren: function(lastProps, transaction) {
+ var nextProps = this.props;
+
+ var lastUsedContent =
+ CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
+ var contentToUse =
+ CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
+
+ // Note the use of `!=` which checks for null or undefined.
+
+ var lastUsedChildren =
+ lastUsedContent != null ? null : lastProps.children;
+ var childrenToUse = contentToUse != null ? null : nextProps.children;
+
+ if (contentToUse != null) {
+ var childrenRemoved = lastUsedChildren != null && childrenToUse == null;
+ if (childrenRemoved) {
+ this.updateChildren(null, transaction);
+ }
+ if (lastUsedContent !== contentToUse) {
+ this.updateTextContent('' + contentToUse);
+ }
+ } else {
+ var contentRemoved = lastUsedContent != null && contentToUse == null;
+ if (contentRemoved) {
+ this.updateTextContent('');
+ }
+ this.updateChildren(flattenChildren(nextProps.children), transaction);
+ }
+ },
+
+ /**
+ * Destroys all event registrations for this instance. Does not remove from
+ * the DOM. That must be done by the parent.
+ *
+ * @internal
+ */
+ unmountComponent: function() {
+ ReactEventEmitter.deleteAllListeners(this._rootNodeID);
+ ReactComponent.Mixin.unmountComponent.call(this);
+ this.unmountChildren();
+ }
+
+};
+
+mixInto(ReactNativeComponent, ReactComponent.Mixin);
+mixInto(ReactNativeComponent, ReactNativeComponent.Mixin);
+mixInto(ReactNativeComponent, ReactMultiChild.Mixin);
+
+module.exports = ReactNativeComponent;
+
+},{"./CSSPropertyOperations":3,"./DOMProperty":7,"./DOMPropertyOperations":8,"./ReactComponent":23,"./ReactEventEmitter":39,"./ReactMount":45,"./ReactMultiChild":46,"./ReactPerf":51,"./escapeTextForBrowser":78,"./flattenChildren":81,"./invariant":89,"./keyOf":93,"./merge":95,"./mixInto":98}],49:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactOnDOMReady
+ */
+
+"use strict";
+
+var PooledClass = require("./PooledClass");
+
+var mixInto = require("./mixInto");
+
+/**
+ * A specialized pseudo-event module to help keep track of components waiting to
+ * be notified when their DOM representations are available for use.
+ *
+ * This implements `PooledClass`, so you should never need to instantiate this.
+ * Instead, use `ReactOnDOMReady.getPooled()`.
+ *
+ * @param {?array<function>} initialCollection
+ * @class ReactOnDOMReady
+ * @implements PooledClass
+ * @internal
+ */
+function ReactOnDOMReady(initialCollection) {
+ this._queue = initialCollection || null;
+}
+
+mixInto(ReactOnDOMReady, {
+
+ /**
+ * Enqueues a callback to be invoked when `notifyAll` is invoked. This is used
+ * to enqueue calls to `componentDidMount` and `componentDidUpdate`.
+ *
+ * @param {ReactComponent} component Component being rendered.
+ * @param {function(DOMElement)} callback Invoked when `notifyAll` is invoked.
+ * @internal
+ */
+ enqueue: function(component, callback) {
+ this._queue = this._queue || [];
+ this._queue.push({component: component, callback: callback});
+ },
+
+ /**
+ * Invokes all enqueued callbacks and clears the queue. This is invoked after
+ * the DOM representation of a component has been created or updated.
+ *
+ * @internal
+ */
+ notifyAll: function() {
+ var queue = this._queue;
+ if (queue) {
+ this._queue = null;
+ for (var i = 0, l = queue.length; i < l; i++) {
+ var component = queue[i].component;
+ var callback = queue[i].callback;
+ callback.call(component, component.getDOMNode());
+ }
+ queue.length = 0;
+ }
+ },
+
+ /**
+ * Resets the internal queue.
+ *
+ * @internal
+ */
+ reset: function() {
+ this._queue = null;
+ },
+
+ /**
+ * `PooledClass` looks for this.
+ */
+ destructor: function() {
+ this.reset();
+ }
+
+});
+
+PooledClass.addPoolingTo(ReactOnDOMReady);
+
+module.exports = ReactOnDOMReady;
+
+},{"./PooledClass":21,"./mixInto":98}],50:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactOwner
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+/**
+ * ReactOwners are capable of storing references to owned components.
+ *
+ * All components are capable of //being// referenced by owner components, but
+ * only ReactOwner components are capable of //referencing// owned components.
+ * The named reference is known as a "ref".
+ *
+ * Refs are available when mounted and updated during reconciliation.
+ *
+ * var MyComponent = React.createClass({
+ * render: function() {
+ * return (
+ * <div onClick={this.handleClick}>
+ * <CustomComponent ref="custom" />
+ * </div>
+ * );
+ * },
+ * handleClick: function() {
+ * this.refs.custom.handleClick();
+ * },
+ * componentDidMount: function() {
+ * this.refs.custom.initialize();
+ * }
+ * });
+ *
+ * Refs should rarely be used. When refs are used, they should only be done to
+ * control data that is not handled by React's data flow.
+ *
+ * @class ReactOwner
+ */
+var ReactOwner = {
+
+ /**
+ * @param {?object} object
+ * @return {boolean} True if `object` is a valid owner.
+ * @final
+ */
+ isValidOwner: function(object) {
+ return !!(
+ object &&
+ typeof object.attachRef === 'function' &&
+ typeof object.detachRef === 'function'
+ );
+ },
+
+ /**
+ * Adds a component by ref to an owner component.
+ *
+ * @param {ReactComponent} component Component to reference.
+ * @param {string} ref Name by which to refer to the component.
+ * @param {ReactOwner} owner Component on which to record the ref.
+ * @final
+ * @internal
+ */
+ addComponentAsRefTo: function(component, ref, owner) {
+ invariant(
+ ReactOwner.isValidOwner(owner),
+ 'addComponentAsRefTo(...): Only a ReactOwner can have refs.'
+ );
+ owner.attachRef(ref, component);
+ },
+
+ /**
+ * Removes a component by ref from an owner component.
+ *
+ * @param {ReactComponent} component Component to dereference.
+ * @param {string} ref Name of the ref to remove.
+ * @param {ReactOwner} owner Component on which the ref is recorded.
+ * @final
+ * @internal
+ */
+ removeComponentAsRefFrom: function(component, ref, owner) {
+ invariant(
+ ReactOwner.isValidOwner(owner),
+ 'removeComponentAsRefFrom(...): Only a ReactOwner can have refs.'
+ );
+ // Check that `component` is still the current ref because we do not want to
+ // detach the ref if another component stole it.
+ if (owner.refs[ref] === component) {
+ owner.detachRef(ref);
+ }
+ },
+
+ /**
+ * A ReactComponent must mix this in to have refs.
+ *
+ * @lends {ReactOwner.prototype}
+ */
+ Mixin: {
+
+ /**
+ * Lazily allocates the refs object and stores `component` as `ref`.
+ *
+ * @param {string} ref Reference name.
+ * @param {component} component Component to store as `ref`.
+ * @final
+ * @private
+ */
+ attachRef: function(ref, component) {
+ invariant(
+ component.isOwnedBy(this),
+ 'attachRef(%s, ...): Only a component\'s owner can store a ref to it.',
+ ref
+ );
+ var refs = this.refs || (this.refs = {});
+ refs[ref] = component;
+ },
+
+ /**
+ * Detaches a reference name.
+ *
+ * @param {string} ref Name to dereference.
+ * @final
+ * @private
+ */
+ detachRef: function(ref) {
+ delete this.refs[ref];
+ }
+
+ }
+
+};
+
+module.exports = ReactOwner;
+
+},{"./invariant":89}],51:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactPerf
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var ReactPerf = {
+ /**
+ * Boolean to enable/disable measurement. Set to false by default to prevent
+ * accidental logging and perf loss.
+ */
+ enableMeasure: false,
+
+ /**
+ * Holds onto the measure function in use. By default, don't measure
+ * anything, but we'll override this if we inject a measure function.
+ */
+ storedMeasure: _noMeasure,
+
+ /**
+ * Use this to wrap methods you want to measure.
+ *
+ * @param {string} objName
+ * @param {string} fnName
+ * @param {function} func
+ * @return {function}
+ */
+ measure: function(objName, fnName, func) {
+ if (true) {
+ if (this.enableMeasure) {
+ var measuredFunc = null;
+ return function() {
+ if (!measuredFunc) {
+ measuredFunc = ReactPerf.storedMeasure(objName, fnName, func);
+ }
+ return measuredFunc.apply(this, arguments);
+ };
+ }
+ }
+ return func;
+ },
+
+ injection: {
+ /**
+ * @param {function} measure
+ */
+ injectMeasure: function(measure) {
+ ReactPerf.storedMeasure = measure;
+ }
+ }
+};
+
+if (true) {
+ var ExecutionEnvironment = require("./ExecutionEnvironment");
+ var URL = ExecutionEnvironment.canUseDOM ? window.location.href : '';
+ ReactPerf.enableMeasure = ReactPerf.enableMeasure ||
+ !!URL.match(/[?&]react_perf\b/);
+}
+
+/**
+ * Simply passes through the measured function, without measuring it.
+ *
+ * @param {string} objName
+ * @param {string} fnName
+ * @param {function} func
+ * @return {function}
+ */
+function _noMeasure(objName, fnName, func) {
+ return func;
+}
+
+module.exports = ReactPerf;
+
+},{"./ExecutionEnvironment":19}],52:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactPropTransferer
+ */
+
+"use strict";
+
+var emptyFunction = require("./emptyFunction");
+var joinClasses = require("./joinClasses");
+var merge = require("./merge");
+
+/**
+ * Creates a transfer strategy that will merge prop values using the supplied
+ * `mergeStrategy`. If a prop was previously unset, this just sets it.
+ *
+ * @param {function} mergeStrategy
+ * @return {function}
+ */
+function createTransferStrategy(mergeStrategy) {
+ return function(props, key, value) {
+ if (!props.hasOwnProperty(key)) {
+ props[key] = value;
+ } else {
+ props[key] = mergeStrategy(props[key], value);
+ }
+ };
+}
+
+/**
+ * Transfer strategies dictate how props are transferred by `transferPropsTo`.
+ */
+var TransferStrategies = {
+ /**
+ * Never transfer `children`.
+ */
+ children: emptyFunction,
+ /**
+ * Transfer the `className` prop by merging them.
+ */
+ className: createTransferStrategy(joinClasses),
+ /**
+ * Never transfer the `ref` prop.
+ */
+ ref: emptyFunction,
+ /**
+ * Transfer the `style` prop (which is an object) by merging them.
+ */
+ style: createTransferStrategy(merge)
+};
+
+/**
+ * ReactPropTransferer are capable of transferring props to another component
+ * using a `transferPropsTo` method.
+ *
+ * @class ReactPropTransferer
+ */
+var ReactPropTransferer = {
+
+ TransferStrategies: TransferStrategies,
+
+ /**
+ * @lends {ReactPropTransferer.prototype}
+ */
+ Mixin: {
+
+ /**
+ * Transfer props from this component to a target component.
+ *
+ * Props that do not have an explicit transfer strategy will be transferred
+ * only if the target component does not already have the prop set.
+ *
+ * This is usually used to pass down props to a returned root component.
+ *
+ * @param {ReactComponent} component Component receiving the properties.
+ * @return {ReactComponent} The supplied `component`.
+ * @final
+ * @protected
+ */
+ transferPropsTo: function(component) {
+ var props = {};
+ for (var thatKey in component.props) {
+ if (component.props.hasOwnProperty(thatKey)) {
+ props[thatKey] = component.props[thatKey];
+ }
+ }
+ for (var thisKey in this.props) {
+ if (!this.props.hasOwnProperty(thisKey)) {
+ continue;
+ }
+ var transferStrategy = TransferStrategies[thisKey];
+ if (transferStrategy) {
+ transferStrategy(props, thisKey, this.props[thisKey]);
+ } else if (!props.hasOwnProperty(thisKey)) {
+ props[thisKey] = this.props[thisKey];
+ }
+ }
+ component.props = props;
+ return component;
+ }
+
+ }
+
+};
+
+module.exports = ReactPropTransferer;
+
+},{"./emptyFunction":77,"./joinClasses":91,"./merge":95}],53:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactPropTypes
+ */
+
+"use strict";
+
+var createObjectFrom = require("./createObjectFrom");
+var invariant = require("./invariant");
+
+/**
+ * Collection of methods that allow declaration and validation of props that are
+ * supplied to React components. Example usage:
+ *
+ * var Props = require('ReactPropTypes');
+ * var MyArticle = React.createClass({
+ * propTypes: {
+ * // An optional string prop named "description".
+ * description: Props.string,
+ *
+ * // A required enum prop named "category".
+ * category: Props.oneOf(['News','Photos']).isRequired,
+ *
+ * // A prop named "dialog" that requires an instance of Dialog.
+ * dialog: Props.instanceOf(Dialog).isRequired
+ * },
+ * render: function() { ... }
+ * });
+ *
+ * A more formal specification of how these methods are used:
+ *
+ * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...)
+ * decl := ReactPropTypes.{type}(.isRequired)?
+ *
+ * Each and every declaration produces a function with the same signature. This
+ * allows the creation of custom validation functions. For example:
+ *
+ * var Props = require('ReactPropTypes');
+ * var MyLink = React.createClass({
+ * propTypes: {
+ * // An optional string or URI prop named "href".
+ * href: function(props, propName, componentName) {
+ * var propValue = props[propName];
+ * invariant(
+ * propValue == null ||
+ * typeof propValue === 'string' ||
+ * propValue instanceof URI,
+ * 'Invalid `%s` supplied to `%s`, expected string or URI.',
+ * propName,
+ * componentName
+ * );
+ * }
+ * },
+ * render: function() { ... }
+ * });
+ *
+ * @internal
+ */
+var Props = {
+
+ array: createPrimitiveTypeChecker('array'),
+ bool: createPrimitiveTypeChecker('boolean'),
+ func: createPrimitiveTypeChecker('function'),
+ number: createPrimitiveTypeChecker('number'),
+ object: createPrimitiveTypeChecker('object'),
+ string: createPrimitiveTypeChecker('string'),
+
+ oneOf: createEnumTypeChecker,
+
+ instanceOf: createInstanceTypeChecker
+
+};
+
+var ANONYMOUS = '<<anonymous>>';
+
+function createPrimitiveTypeChecker(expectedType) {
+ function validatePrimitiveType(propValue, propName, componentName) {
+ var propType = typeof propValue;
+ if (propType === 'object' && Array.isArray(propValue)) {
+ propType = 'array';
+ }
+ invariant(
+ propType === expectedType,
+ 'Invalid prop `%s` of type `%s` supplied to `%s`, expected `%s`.',
+ propName,
+ propType,
+ componentName,
+ expectedType
+ );
+ }
+ return createChainableTypeChecker(validatePrimitiveType);
+}
+
+function createEnumTypeChecker(expectedValues) {
+ var expectedEnum = createObjectFrom(expectedValues);
+ function validateEnumType(propValue, propName, componentName) {
+ invariant(
+ expectedEnum[propValue],
+ 'Invalid prop `%s` supplied to `%s`, expected one of %s.',
+ propName,
+ componentName,
+ JSON.stringify(Object.keys(expectedEnum))
+ );
+ }
+ return createChainableTypeChecker(validateEnumType);
+}
+
+function createInstanceTypeChecker(expectedClass) {
+ function validateInstanceType(propValue, propName, componentName) {
+ invariant(
+ propValue instanceof expectedClass,
+ 'Invalid prop `%s` supplied to `%s`, expected instance of `%s`.',
+ propName,
+ componentName,
+ expectedClass.name || ANONYMOUS
+ );
+ }
+ return createChainableTypeChecker(validateInstanceType);
+}
+
+function createChainableTypeChecker(validate) {
+ function createTypeChecker(isRequired) {
+ function checkType(props, propName, componentName) {
+ var propValue = props[propName];
+ if (propValue != null) {
+ // Only validate if there is a value to check.
+ validate(propValue, propName, componentName || ANONYMOUS);
+ } else {
+ invariant(
+ !isRequired,
+ 'Required prop `%s` was not specified in `%s`.',
+ propName,
+ componentName || ANONYMOUS
+ );
+ }
+ }
+ if (!isRequired) {
+ checkType.isRequired = createTypeChecker(true);
+ }
+ return checkType;
+ }
+ return createTypeChecker(false);
+}
+
+module.exports = Props;
+
+},{"./createObjectFrom":75,"./invariant":89}],54:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactReconcileTransaction
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+var PooledClass = require("./PooledClass");
+var ReactEventEmitter = require("./ReactEventEmitter");
+var ReactInputSelection = require("./ReactInputSelection");
+var ReactOnDOMReady = require("./ReactOnDOMReady");
+var Transaction = require("./Transaction");
+
+var mixInto = require("./mixInto");
+
+/**
+ * Ensures that, when possible, the selection range (currently selected text
+ * input) is not disturbed by performing the transaction.
+ */
+var SELECTION_RESTORATION = {
+ /**
+ * @return {Selection} Selection information.
+ */
+ initialize: ReactInputSelection.getSelectionInformation,
+ /**
+ * @param {Selection} sel Selection information returned from `initialize`.
+ */
+ close: ReactInputSelection.restoreSelection
+};
+
+/**
+ * Suppresses events (blur/focus) that could be inadvertently dispatched due to
+ * high level DOM manipulations (like temporarily removing a text input from the
+ * DOM).
+ */
+var EVENT_SUPPRESSION = {
+ /**
+ * @return {boolean} The enabled status of `ReactEventEmitter` before the
+ * reconciliation.
+ */
+ initialize: function() {
+ var currentlyEnabled = ReactEventEmitter.isEnabled();
+ ReactEventEmitter.setEnabled(false);
+ return currentlyEnabled;
+ },
+
+ /**
+ * @param {boolean} previouslyEnabled Enabled status of `ReactEventEmitter`
+ * before the reconciliation occured. `close` restores the previous value.
+ */
+ close: function(previouslyEnabled) {
+ ReactEventEmitter.setEnabled(previouslyEnabled);
+ }
+};
+
+/**
+ * Provides a `ReactOnDOMReady` queue for collecting `onDOMReady` callbacks
+ * during the performing of the transaction.
+ */
+var ON_DOM_READY_QUEUEING = {
+ /**
+ * Initializes the internal `onDOMReady` queue.
+ */
+ initialize: function() {
+ this.reactOnDOMReady.reset();
+ },
+
+ /**
+ * After DOM is flushed, invoke all registered `onDOMReady` callbacks.
+ */
+ close: function() {
+ this.reactOnDOMReady.notifyAll();
+ }
+};
+
+/**
+ * Executed within the scope of the `Transaction` instance. Consider these as
+ * being member methods, but with an implied ordering while being isolated from
+ * each other.
+ */
+var TRANSACTION_WRAPPERS = [
+ SELECTION_RESTORATION,
+ EVENT_SUPPRESSION,
+ ON_DOM_READY_QUEUEING
+];
+
+/**
+ * Currently:
+ * - The order that these are listed in the transaction is critical:
+ * - Suppresses events.
+ * - Restores selection range.
+ *
+ * Future:
+ * - Restore document/overflow scroll positions that were unintentionally
+ * modified via DOM insertions above the top viewport boundary.
+ * - Implement/integrate with customized constraint based layout system and keep
+ * track of which dimensions must be remeasured.
+ *
+ * @class ReactReconcileTransaction
+ */
+function ReactReconcileTransaction() {
+ this.reinitializeTransaction();
+ this.reactOnDOMReady = ReactOnDOMReady.getPooled(null);
+}
+
+var Mixin = {
+ /**
+ * @see Transaction
+ * @abstract
+ * @final
+ * @return {array<object>} List of operation wrap proceedures.
+ * TODO: convert to array<TransactionWrapper>
+ */
+ getTransactionWrappers: function() {
+ if (ExecutionEnvironment.canUseDOM) {
+ return TRANSACTION_WRAPPERS;
+ } else {
+ return [];
+ }
+ },
+
+ /**
+ * @return {object} The queue to collect `onDOMReady` callbacks with.
+ * TODO: convert to ReactOnDOMReady
+ */
+ getReactOnDOMReady: function() {
+ return this.reactOnDOMReady;
+ },
+
+ /**
+ * `PooledClass` looks for this, and will invoke this before allowing this
+ * instance to be resused.
+ */
+ destructor: function() {
+ ReactOnDOMReady.release(this.reactOnDOMReady);
+ this.reactOnDOMReady = null;
+ }
+};
+
+
+mixInto(ReactReconcileTransaction, Transaction.Mixin);
+mixInto(ReactReconcileTransaction, Mixin);
+
+PooledClass.addPoolingTo(ReactReconcileTransaction);
+
+module.exports = ReactReconcileTransaction;
+
+},{"./ExecutionEnvironment":19,"./PooledClass":21,"./ReactEventEmitter":39,"./ReactInputSelection":42,"./ReactOnDOMReady":49,"./Transaction":68,"./mixInto":98}],55:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @typechecks static-only
+ * @providesModule ReactServerRendering
+ */
+"use strict";
+
+var ReactMarkupChecksum = require("./ReactMarkupChecksum");
+var ReactReconcileTransaction = require("./ReactReconcileTransaction");
+var ReactInstanceHandles = require("./ReactInstanceHandles");
+
+/**
+ * @param {object} component
+ * @param {function} callback
+ */
+function renderComponentToString(component, callback) {
+ // We use a callback API to keep the API async in case in the future we ever
+ // need it, but in reality this is a synchronous operation.
+ var id = ReactInstanceHandles.createReactRootID();
+ var transaction = ReactReconcileTransaction.getPooled();
+ transaction.reinitializeTransaction();
+ try {
+ transaction.perform(function() {
+ var markup = component.mountComponent(id, transaction);
+ markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
+ callback(markup);
+ }, null);
+ } finally {
+ ReactReconcileTransaction.release(transaction);
+ }
+}
+
+module.exports = {
+ renderComponentToString: renderComponentToString
+};
+
+},{"./ReactInstanceHandles":43,"./ReactMarkupChecksum":44,"./ReactReconcileTransaction":54}],56:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactTextComponent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var ReactComponent = require("./ReactComponent");
+var ReactMount = require("./ReactMount");
+
+var escapeTextForBrowser = require("./escapeTextForBrowser");
+var mixInto = require("./mixInto");
+
+/**
+ * Text nodes violate a couple assumptions that React makes about components:
+ *
+ * - When mounting text into the DOM, adjacent text nodes are merged.
+ * - Text nodes cannot be assigned a React root ID.
+ *
+ * This component is used to wrap strings in elements so that they can undergo
+ * the same reconciliation that is applied to elements.
+ *
+ * TODO: Investigate representing React components in the DOM with text nodes.
+ *
+ * @class ReactTextComponent
+ * @extends ReactComponent
+ * @internal
+ */
+var ReactTextComponent = function(initialText) {
+ this.construct({text: initialText});
+};
+
+mixInto(ReactTextComponent, ReactComponent.Mixin);
+mixInto(ReactTextComponent, {
+
+ /**
+ * Creates the markup for this text node. This node is not intended to have
+ * any features besides containing text content.
+ *
+ * @param {string} rootID DOM ID of the root node.
+ * @return {string} Markup for this text node.
+ * @internal
+ */
+ mountComponent: function(rootID) {
+ ReactComponent.Mixin.mountComponent.call(this, rootID);
+ return (
+ '<span ' + ReactMount.ATTR_NAME + '="' + rootID + '">' +
+ escapeTextForBrowser(this.props.text) +
+ '</span>'
+ );
+ },
+
+ /**
+ * Updates this component by updating the text content.
+ *
+ * @param {object} nextProps Contains the next text content.
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ */
+ receiveProps: function(nextProps, transaction) {
+ if (nextProps.text !== this.props.text) {
+ this.props.text = nextProps.text;
+ ReactComponent.DOMIDOperations.updateTextContentByID(
+ this._rootNodeID,
+ nextProps.text
+ );
+ }
+ }
+
+});
+
+module.exports = ReactTextComponent;
+
+},{"./ReactComponent":23,"./ReactMount":45,"./escapeTextForBrowser":78,"./mixInto":98}],57:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactUpdates
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+var dirtyComponents = [];
+
+var batchingStrategy = null;
+
+function ensureBatchingStrategy() {
+ invariant(batchingStrategy, 'ReactUpdates: must inject a batching strategy');
+}
+
+function batchedUpdates(callback, param) {
+ ensureBatchingStrategy();
+ batchingStrategy.batchedUpdates(callback, param);
+}
+
+function runBatchedUpdates() {
+ // TODO: Sort components by depth such that parent components update first
+ for (var i = 0; i < dirtyComponents.length; i++) {
+ // If a component is unmounted before pending changes apply, ignore them
+ // TODO: Queue unmounts in the same list to avoid this happening at all
+ var component = dirtyComponents[i];
+ if (component.isMounted()) {
+ // If performUpdateIfNecessary happens to enqueue any new updates, we
+ // shouldn't execute the callbacks until the next render happens, so
+ // stash the callbacks first
+ var callbacks = component._pendingCallbacks;
+ component._pendingCallbacks = null;
+ component.performUpdateIfNecessary();
+ if (callbacks) {
+ for (var j = 0; j < callbacks.length; j++) {
+ callbacks[j].call(component);
+ }
+ }
+ }
+ }
+}
+
+function clearDirtyComponents() {
+ dirtyComponents.length = 0;
+}
+
+function flushBatchedUpdates() {
+ // Run these in separate functions so the JIT can optimize
+ try {
+ runBatchedUpdates();
+ } catch (e) {
+ // IE 8 requires catch to use finally.
+ throw e;
+ } finally {
+ clearDirtyComponents();
+ }
+}
+
+/**
+ * Mark a component as needing a rerender, adding an optional callback to a
+ * list of functions which will be executed once the rerender occurs.
+ */
+function enqueueUpdate(component, callback) {
+ invariant(
+ !callback || typeof callback === "function",
+ 'enqueueUpdate(...): You called `setProps`, `replaceProps`, ' +
+ '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
+ 'isn\'t callable.'
+ );
+ ensureBatchingStrategy();
+
+ if (!batchingStrategy.isBatchingUpdates) {
+ component.performUpdateIfNecessary();
+ callback && callback();
+ return;
+ }
+
+ dirtyComponents.push(component);
+
+ if (callback) {
+ if (component._pendingCallbacks) {
+ component._pendingCallbacks.push(callback);
+ } else {
+ component._pendingCallbacks = [callback];
+ }
+ }
+}
+
+var ReactUpdatesInjection = {
+ injectBatchingStrategy: function(_batchingStrategy) {
+ invariant(
+ _batchingStrategy,
+ 'ReactUpdates: must provide a batching strategy'
+ );
+ invariant(
+ typeof _batchingStrategy.batchedUpdates === 'function',
+ 'ReactUpdates: must provide a batchedUpdates() function'
+ );
+ invariant(
+ typeof _batchingStrategy.isBatchingUpdates === 'boolean',
+ 'ReactUpdates: must provide an isBatchingUpdates boolean attribute'
+ );
+ batchingStrategy = _batchingStrategy;
+ }
+};
+
+var ReactUpdates = {
+ batchedUpdates: batchedUpdates,
+ enqueueUpdate: enqueueUpdate,
+ flushBatchedUpdates: flushBatchedUpdates,
+ injection: ReactUpdatesInjection
+};
+
+module.exports = ReactUpdates;
+
+},{"./invariant":89}],58:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SimpleEventPlugin
+ */
+
+"use strict";
+
+var EventConstants = require("./EventConstants");
+var EventPropagators = require("./EventPropagators");
+var SyntheticClipboardEvent = require("./SyntheticClipboardEvent");
+var SyntheticEvent = require("./SyntheticEvent");
+var SyntheticFocusEvent = require("./SyntheticFocusEvent");
+var SyntheticKeyboardEvent = require("./SyntheticKeyboardEvent");
+var SyntheticMouseEvent = require("./SyntheticMouseEvent");
+var SyntheticMutationEvent = require("./SyntheticMutationEvent");
+var SyntheticTouchEvent = require("./SyntheticTouchEvent");
+var SyntheticUIEvent = require("./SyntheticUIEvent");
+var SyntheticWheelEvent = require("./SyntheticWheelEvent");
+
+var invariant = require("./invariant");
+var keyOf = require("./keyOf");
+
+var topLevelTypes = EventConstants.topLevelTypes;
+
+var eventTypes = {
+ blur: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onBlur: true}),
+ captured: keyOf({onBlurCapture: true})
+ }
+ },
+ click: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onClick: true}),
+ captured: keyOf({onClickCapture: true})
+ }
+ },
+ copy: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onCopy: true}),
+ captured: keyOf({onCopyCapture: true})
+ }
+ },
+ cut: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onCut: true}),
+ captured: keyOf({onCutCapture: true})
+ }
+ },
+ doubleClick: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDoubleClick: true}),
+ captured: keyOf({onDoubleClickCapture: true})
+ }
+ },
+ drag: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDrag: true}),
+ captured: keyOf({onDragCapture: true})
+ }
+ },
+ dragEnd: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDragEnd: true}),
+ captured: keyOf({onDragEndCapture: true})
+ }
+ },
+ dragEnter: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDragEnter: true}),
+ captured: keyOf({onDragEnterCapture: true})
+ }
+ },
+ dragExit: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDragExit: true}),
+ captured: keyOf({onDragExitCapture: true})
+ }
+ },
+ dragLeave: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDragLeave: true}),
+ captured: keyOf({onDragLeaveCapture: true})
+ }
+ },
+ dragOver: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDragOver: true}),
+ captured: keyOf({onDragOverCapture: true})
+ }
+ },
+ dragStart: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDragStart: true}),
+ captured: keyOf({onDragStartCapture: true})
+ }
+ },
+ drop: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDrop: true}),
+ captured: keyOf({onDropCapture: true})
+ }
+ },
+ DOMCharacterDataModified: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onDOMCharacterDataModified: true}),
+ captured: keyOf({onDOMCharacterDataModifiedCapture: true})
+ }
+ },
+ focus: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onFocus: true}),
+ captured: keyOf({onFocusCapture: true})
+ }
+ },
+ input: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onInput: true}),
+ captured: keyOf({onInputCapture: true})
+ }
+ },
+ keyDown: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onKeyDown: true}),
+ captured: keyOf({onKeyDownCapture: true})
+ }
+ },
+ keyPress: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onKeyPress: true}),
+ captured: keyOf({onKeyPressCapture: true})
+ }
+ },
+ keyUp: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onKeyUp: true}),
+ captured: keyOf({onKeyUpCapture: true})
+ }
+ },
+ // Note: We do not allow listening to mouseOver events. Instead, use the
+ // onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`.
+ mouseDown: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onMouseDown: true}),
+ captured: keyOf({onMouseDownCapture: true})
+ }
+ },
+ mouseMove: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onMouseMove: true}),
+ captured: keyOf({onMouseMoveCapture: true})
+ }
+ },
+ mouseUp: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onMouseUp: true}),
+ captured: keyOf({onMouseUpCapture: true})
+ }
+ },
+ paste: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onPaste: true}),
+ captured: keyOf({onPasteCapture: true})
+ }
+ },
+ scroll: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onScroll: true}),
+ captured: keyOf({onScrollCapture: true})
+ }
+ },
+ submit: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onSubmit: true}),
+ captured: keyOf({onSubmitCapture: true})
+ }
+ },
+ touchCancel: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onTouchCancel: true}),
+ captured: keyOf({onTouchCancelCapture: true})
+ }
+ },
+ touchEnd: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onTouchEnd: true}),
+ captured: keyOf({onTouchEndCapture: true})
+ }
+ },
+ touchMove: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onTouchMove: true}),
+ captured: keyOf({onTouchMoveCapture: true})
+ }
+ },
+ touchStart: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onTouchStart: true}),
+ captured: keyOf({onTouchStartCapture: true})
+ }
+ },
+ wheel: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onWheel: true}),
+ captured: keyOf({onWheelCapture: true})
+ }
+ }
+};
+
+var topLevelEventsToDispatchConfig = {
+ topBlur: eventTypes.blur,
+ topClick: eventTypes.click,
+ topCopy: eventTypes.copy,
+ topCut: eventTypes.cut,
+ topDoubleClick: eventTypes.doubleClick,
+ topDOMCharacterDataModified: eventTypes.DOMCharacterDataModified,
+ topDrag: eventTypes.drag,
+ topDragEnd: eventTypes.dragEnd,
+ topDragEnter: eventTypes.dragEnter,
+ topDragExit: eventTypes.dragExit,
+ topDragLeave: eventTypes.dragLeave,
+ topDragOver: eventTypes.dragOver,
+ topDragStart: eventTypes.dragStart,
+ topDrop: eventTypes.drop,
+ topFocus: eventTypes.focus,
+ topInput: eventTypes.input,
+ topKeyDown: eventTypes.keyDown,
+ topKeyPress: eventTypes.keyPress,
+ topKeyUp: eventTypes.keyUp,
+ topMouseDown: eventTypes.mouseDown,
+ topMouseMove: eventTypes.mouseMove,
+ topMouseUp: eventTypes.mouseUp,
+ topPaste: eventTypes.paste,
+ topScroll: eventTypes.scroll,
+ topSubmit: eventTypes.submit,
+ topTouchCancel: eventTypes.touchCancel,
+ topTouchEnd: eventTypes.touchEnd,
+ topTouchMove: eventTypes.touchMove,
+ topTouchStart: eventTypes.touchStart,
+ topWheel: eventTypes.wheel
+};
+
+var SimpleEventPlugin = {
+
+ eventTypes: eventTypes,
+
+ /**
+ * Same as the default implementation, except cancels the event when return
+ * value is false.
+ *
+ * @param {object} Event to be dispatched.
+ * @param {function} Application-level callback.
+ * @param {string} domID DOM ID to pass to the callback.
+ */
+ executeDispatch: function(event, listener, domID) {
+ var returnValue = listener(event, domID);
+ if (returnValue === false) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ },
+
+ /**
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {*} An accumulation of synthetic events.
+ * @see {EventPluginHub.extractEvents}
+ */
+ extractEvents: function(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent) {
+ var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
+ if (!dispatchConfig) {
+ return null;
+ }
+ var EventConstructor;
+ switch(topLevelType) {
+ case topLevelTypes.topInput:
+ case topLevelTypes.topSubmit:
+ // HTML Events
+ // @see http://www.w3.org/TR/html5/index.html#events-0
+ EventConstructor = SyntheticEvent;
+ break;
+ case topLevelTypes.topKeyDown:
+ case topLevelTypes.topKeyPress:
+ case topLevelTypes.topKeyUp:
+ EventConstructor = SyntheticKeyboardEvent;
+ break;
+ case topLevelTypes.topBlur:
+ case topLevelTypes.topFocus:
+ EventConstructor = SyntheticFocusEvent;
+ break;
+ case topLevelTypes.topClick:
+ case topLevelTypes.topDoubleClick:
+ case topLevelTypes.topDrag:
+ case topLevelTypes.topDragEnd:
+ case topLevelTypes.topDragEnter:
+ case topLevelTypes.topDragExit:
+ case topLevelTypes.topDragLeave:
+ case topLevelTypes.topDragOver:
+ case topLevelTypes.topDragStart:
+ case topLevelTypes.topDrop:
+ case topLevelTypes.topMouseDown:
+ case topLevelTypes.topMouseMove:
+ case topLevelTypes.topMouseUp:
+ EventConstructor = SyntheticMouseEvent;
+ break;
+ case topLevelTypes.topDOMCharacterDataModified:
+ EventConstructor = SyntheticMutationEvent;
+ break;
+ case topLevelTypes.topTouchCancel:
+ case topLevelTypes.topTouchEnd:
+ case topLevelTypes.topTouchMove:
+ case topLevelTypes.topTouchStart:
+ EventConstructor = SyntheticTouchEvent;
+ break;
+ case topLevelTypes.topScroll:
+ EventConstructor = SyntheticUIEvent;
+ break;
+ case topLevelTypes.topWheel:
+ EventConstructor = SyntheticWheelEvent;
+ break;
+ case topLevelTypes.topCopy:
+ case topLevelTypes.topCut:
+ case topLevelTypes.topPaste:
+ EventConstructor = SyntheticClipboardEvent;
+ break;
+ }
+ invariant(
+ EventConstructor,
+ 'SimpleEventPlugin: Unhandled event type, `%s`.',
+ topLevelType
+ );
+ var event = EventConstructor.getPooled(
+ dispatchConfig,
+ topLevelTargetID,
+ nativeEvent
+ );
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+ return event;
+ }
+
+};
+
+module.exports = SimpleEventPlugin;
+
+},{"./EventConstants":13,"./EventPropagators":18,"./SyntheticClipboardEvent":59,"./SyntheticEvent":60,"./SyntheticFocusEvent":61,"./SyntheticKeyboardEvent":62,"./SyntheticMouseEvent":63,"./SyntheticMutationEvent":64,"./SyntheticTouchEvent":65,"./SyntheticUIEvent":66,"./SyntheticWheelEvent":67,"./invariant":89,"./keyOf":93}],59:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticClipboardEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var SyntheticEvent = require("./SyntheticEvent");
+
+/**
+ * @interface Event
+ * @see http://www.w3.org/TR/clipboard-apis/
+ */
+var ClipboardEventInterface = {
+ clipboardData: null
+};
+
+/**
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ * @extends {SyntheticUIEvent}
+ */
+function SyntheticClipboardEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent);
+}
+
+SyntheticEvent.augmentClass(SyntheticClipboardEvent, ClipboardEventInterface);
+
+module.exports = SyntheticClipboardEvent;
+
+
+},{"./SyntheticEvent":60}],60:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var PooledClass = require("./PooledClass");
+
+var emptyFunction = require("./emptyFunction");
+var getEventTarget = require("./getEventTarget");
+var merge = require("./merge");
+var mergeInto = require("./mergeInto");
+
+/**
+ * @interface Event
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var EventInterface = {
+ type: null,
+ target: getEventTarget,
+ currentTarget: null,
+ eventPhase: null,
+ bubbles: null,
+ cancelable: null,
+ timeStamp: function(event) {
+ return event.timeStamp || Date.now();
+ },
+ defaultPrevented: null,
+ isTrusted: null
+};
+
+/**
+ * Synthetic events are dispatched by event plugins, typically in response to a
+ * top-level event delegation handler.
+ *
+ * These systems should generally use pooling to reduce the frequency of garbage
+ * collection. The system should check `isPersistent` to determine whether the
+ * event should be released into the pool after being dispatched. Users that
+ * need a persisted event should invoke `persist`.
+ *
+ * Synthetic events (and subclasses) implement the DOM Level 3 Events API by
+ * normalizing browser quirks. Subclasses do not necessarily have to implement a
+ * DOM interface; custom application-specific events can also subclass this.
+ *
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ */
+function SyntheticEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ this.dispatchConfig = dispatchConfig;
+ this.dispatchMarker = dispatchMarker;
+ this.nativeEvent = nativeEvent;
+
+ var Interface = this.constructor.Interface;
+ for (var propName in Interface) {
+ if (!Interface.hasOwnProperty(propName)) {
+ continue;
+ }
+ var normalize = Interface[propName];
+ if (normalize) {
+ this[propName] = normalize(nativeEvent);
+ } else {
+ this[propName] = nativeEvent[propName];
+ }
+ }
+
+ if (nativeEvent.defaultPrevented || nativeEvent.returnValue === false) {
+ this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
+ } else {
+ this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
+ }
+ this.isPropagationStopped = emptyFunction.thatReturnsFalse;
+}
+
+mergeInto(SyntheticEvent.prototype, {
+
+ preventDefault: function() {
+ this.defaultPrevented = true;
+ var event = this.nativeEvent;
+ event.preventDefault ? event.preventDefault() : event.returnValue = false;
+ this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
+ },
+
+ stopPropagation: function() {
+ var event = this.nativeEvent;
+ event.stopPropagation ? event.stopPropagation() : event.cancelBubble = true;
+ this.isPropagationStopped = emptyFunction.thatReturnsTrue;
+ },
+
+ /**
+ * We release all dispatched `SyntheticEvent`s after each event loop, adding
+ * them back into the pool. This allows a way to hold onto a reference that
+ * won't be added back into the pool.
+ */
+ persist: function() {
+ this.isPersistent = emptyFunction.thatReturnsTrue;
+ },
+
+ /**
+ * Checks if this event should be released back into the pool.
+ *
+ * @return {boolean} True if this should not be released, false otherwise.
+ */
+ isPersistent: emptyFunction.thatReturnsFalse,
+
+ /**
+ * `PooledClass` looks for `destructor` on each instance it releases.
+ */
+ destructor: function() {
+ var Interface = this.constructor.Interface;
+ for (var propName in Interface) {
+ this[propName] = null;
+ }
+ this.dispatchConfig = null;
+ this.dispatchMarker = null;
+ this.nativeEvent = null;
+ }
+
+});
+
+SyntheticEvent.Interface = EventInterface;
+
+/**
+ * Helper to reduce boilerplate when creating subclasses.
+ *
+ * @param {function} Class
+ * @param {?object} Interface
+ */
+SyntheticEvent.augmentClass = function(Class, Interface) {
+ var Super = this;
+
+ var prototype = Object.create(Super.prototype);
+ mergeInto(prototype, Class.prototype);
+ Class.prototype = prototype;
+ Class.prototype.constructor = Class;
+
+ Class.Interface = merge(Super.Interface, Interface);
+ Class.augmentClass = Super.augmentClass;
+
+ PooledClass.addPoolingTo(Class, PooledClass.threeArgumentPooler);
+};
+
+PooledClass.addPoolingTo(SyntheticEvent, PooledClass.threeArgumentPooler);
+
+module.exports = SyntheticEvent;
+
+},{"./PooledClass":21,"./emptyFunction":77,"./getEventTarget":84,"./merge":95,"./mergeInto":97}],61:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticFocusEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var SyntheticUIEvent = require("./SyntheticUIEvent");
+
+/**
+ * @interface FocusEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var FocusEventInterface = {
+ relatedTarget: null
+};
+
+/**
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ * @extends {SyntheticUIEvent}
+ */
+function SyntheticFocusEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent);
+}
+
+SyntheticUIEvent.augmentClass(SyntheticFocusEvent, FocusEventInterface);
+
+module.exports = SyntheticFocusEvent;
+
+},{"./SyntheticUIEvent":66}],62:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticKeyboardEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var SyntheticUIEvent = require("./SyntheticUIEvent");
+
+/**
+ * @interface KeyboardEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var KeyboardEventInterface = {
+ 'char': null,
+ key: null,
+ location: null,
+ ctrlKey: null,
+ shiftKey: null,
+ altKey: null,
+ metaKey: null,
+ repeat: null,
+ locale: null,
+ // Legacy Interface
+ charCode: null,
+ keyCode: null,
+ which: null
+};
+
+/**
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ * @extends {SyntheticUIEvent}
+ */
+function SyntheticKeyboardEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent);
+}
+
+SyntheticUIEvent.augmentClass(SyntheticKeyboardEvent, KeyboardEventInterface);
+
+module.exports = SyntheticKeyboardEvent;
+
+},{"./SyntheticUIEvent":66}],63:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticMouseEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var SyntheticUIEvent = require("./SyntheticUIEvent");
+var ViewportMetrics = require("./ViewportMetrics");
+
+/**
+ * @interface MouseEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var MouseEventInterface = {
+ screenX: null,
+ screenY: null,
+ clientX: null,
+ clientY: null,
+ ctrlKey: null,
+ shiftKey: null,
+ altKey: null,
+ metaKey: null,
+ button: function(event) {
+ // Webkit, Firefox, IE9+
+ // which: 1 2 3
+ // button: 0 1 2 (standard)
+ var button = event.button;
+ if ('which' in event) {
+ return button;
+ }
+ // IE<9
+ // which: undefined
+ // button: 0 0 0
+ // button: 1 4 2 (onmouseup)
+ return button === 2 ? 2 : button === 4 ? 1 : 0;
+ },
+ buttons: null,
+ relatedTarget: function(event) {
+ return event.relatedTarget || (
+ event.fromElement === event.srcElement ?
+ event.toElement :
+ event.fromElement
+ );
+ },
+ // "Proprietary" Interface.
+ pageX: function(event) {
+ return 'pageX' in event ?
+ event.pageX :
+ event.clientX + ViewportMetrics.currentScrollLeft;
+ },
+ pageY: function(event) {
+ return 'pageY' in event ?
+ event.pageY :
+ event.clientY + ViewportMetrics.currentScrollTop;
+ }
+};
+
+/**
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ * @extends {SyntheticUIEvent}
+ */
+function SyntheticMouseEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent);
+}
+
+SyntheticUIEvent.augmentClass(SyntheticMouseEvent, MouseEventInterface);
+
+module.exports = SyntheticMouseEvent;
+
+},{"./SyntheticUIEvent":66,"./ViewportMetrics":69}],64:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticMutationEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var SyntheticEvent = require("./SyntheticEvent");
+
+/**
+ * @interface MutationEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var MutationEventInterface = {
+ relatedNode: null,
+ prevValue: null,
+ newValue: null,
+ attrName: null,
+ attrChange: null
+};
+
+/**
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ * @extends {SyntheticEvent}
+ */
+function SyntheticMutationEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent);
+}
+
+SyntheticEvent.augmentClass(SyntheticMutationEvent, MutationEventInterface);
+
+module.exports = SyntheticMutationEvent;
+
+},{"./SyntheticEvent":60}],65:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticTouchEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var SyntheticUIEvent = require("./SyntheticUIEvent");
+
+/**
+ * @interface TouchEvent
+ * @see http://www.w3.org/TR/touch-events/
+ */
+var TouchEventInterface = {
+ touches: null,
+ targetTouches: null,
+ changedTouches: null,
+ altKey: null,
+ metaKey: null,
+ ctrlKey: null,
+ shiftKey: null
+};
+
+/**
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ * @extends {SyntheticUIEvent}
+ */
+function SyntheticTouchEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ SyntheticUIEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent);
+}
+
+SyntheticUIEvent.augmentClass(SyntheticTouchEvent, TouchEventInterface);
+
+module.exports = SyntheticTouchEvent;
+
+},{"./SyntheticUIEvent":66}],66:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticUIEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var SyntheticEvent = require("./SyntheticEvent");
+
+/**
+ * @interface UIEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var UIEventInterface = {
+ view: null,
+ detail: null
+};
+
+/**
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ * @extends {SyntheticEvent}
+ */
+function SyntheticUIEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent);
+}
+
+SyntheticEvent.augmentClass(SyntheticUIEvent, UIEventInterface);
+
+module.exports = SyntheticUIEvent;
+
+},{"./SyntheticEvent":60}],67:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule SyntheticWheelEvent
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var SyntheticMouseEvent = require("./SyntheticMouseEvent");
+
+/**
+ * @interface WheelEvent
+ * @see http://www.w3.org/TR/DOM-Level-3-Events/
+ */
+var WheelEventInterface = {
+ deltaX: function(event) {
+ // NOTE: IE<9 does not support x-axis delta.
+ return (
+ 'deltaX' in event ? event.deltaX :
+ // Fallback to `wheelDeltaX` for Webkit and normalize (right is positive).
+ 'wheelDeltaX' in event ? -event.wheelDeltaX : 0
+ );
+ },
+ deltaY: function(event) {
+ return (
+ // Normalize (up is positive).
+ 'deltaY' in event ? -event.deltaY :
+ // Fallback to `wheelDeltaY` for Webkit.
+ 'wheelDeltaY' in event ? event.wheelDeltaY :
+ // Fallback to `wheelDelta` for IE<9.
+ 'wheelDelta' in event ? event.wheelData : 0
+ );
+ },
+ deltaZ: null,
+ deltaMode: null
+};
+
+/**
+ * @param {object} dispatchConfig Configuration used to dispatch this event.
+ * @param {string} dispatchMarker Marker identifying the event target.
+ * @param {object} nativeEvent Native browser event.
+ * @extends {SyntheticMouseEvent}
+ */
+function SyntheticWheelEvent(dispatchConfig, dispatchMarker, nativeEvent) {
+ SyntheticMouseEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent);
+}
+
+SyntheticMouseEvent.augmentClass(SyntheticWheelEvent, WheelEventInterface);
+
+module.exports = SyntheticWheelEvent;
+
+},{"./SyntheticMouseEvent":63}],68:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule Transaction
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+/**
+ * `Transaction` creates a black box that is able to wrap any method such that
+ * certain invariants are maintained before and after the method is invoked
+ * (Even if an exception is thrown while invoking the wrapped method). Whoever
+ * instantiates a transaction can provide enforcers of the invariants at
+ * creation time. The `Transaction` class itself will supply one additional
+ * automatic invariant for you - the invariant that any transaction instance
+ * should not be ran while it is already being ran. You would typically create a
+ * single instance of a `Transaction` for reuse multiple times, that potentially
+ * is used to wrap several different methods. Wrappers are extremely simple -
+ * they only require implementing two methods.
+ *
+ * <pre>
+ * wrappers (injected at creation time)
+ * + +
+ * | |
+ * +-----------------|--------|--------------+
+ * | v | |
+ * | +---------------+ | |
+ * | +--| wrapper1 |---|----+ |
+ * | | +---------------+ v | |
+ * | | +-------------+ | |
+ * | | +----| wrapper2 |--------+ |
+ * | | | +-------------+ | | |
+ * | | | | | |
+ * | v v v v | wrapper
+ * | +---+ +---+ +---------+ +---+ +---+ | invariants
+ * perform(anyMethod) | | | | | | | | | | | | maintained
+ * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
+ * | | | | | | | | | | | |
+ * | | | | | | | | | | | |
+ * | | | | | | | | | | | |
+ * | +---+ +---+ +---------+ +---+ +---+ |
+ * | initialize close |
+ * +-----------------------------------------+
+ * </pre>
+ *
+ * Bonus:
+ * - Reports timing metrics by method name and wrapper index.
+ *
+ * Use cases:
+ * - Preserving the input selection ranges before/after reconciliation.
+ * Restoring selection even in the event of an unexpected error.
+ * - Deactivating events while rearranging the DOM, preventing blurs/focuses,
+ * while guaranteeing that afterwards, the event system is reactivated.
+ * - Flushing a queue of collected DOM mutations to the main UI thread after a
+ * reconciliation takes place in a worker thread.
+ * - Invoking any collected `componentDidRender` callbacks after rendering new
+ * content.
+ * - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
+ * to preserve the `scrollTop` (an automatic scroll aware DOM).
+ * - (Future use case): Layout calculations before and after DOM upates.
+ *
+ * Transactional plugin API:
+ * - A module that has an `initialize` method that returns any precomputation.
+ * - and a `close` method that accepts the precomputation. `close` is invoked
+ * when the wrapped process is completed, or has failed.
+ *
+ * @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
+ * that implement `initialize` and `close`.
+ * @return {Transaction} Single transaction for reuse in thread.
+ *
+ * @class Transaction
+ */
+var Mixin = {
+ /**
+ * Sets up this instance so that it is prepared for collecting metrics. Does
+ * so such that this setup method may be used on an instance that is already
+ * initialized, in a way that does not consume additional memory upon reuse.
+ * That can be useful if you decide to make your subclass of this mixin a
+ * "PooledClass".
+ */
+ reinitializeTransaction: function() {
+ this.transactionWrappers = this.getTransactionWrappers();
+ if (!this.wrapperInitData) {
+ this.wrapperInitData = [];
+ } else {
+ this.wrapperInitData.length = 0;
+ }
+ if (!this.timingMetrics) {
+ this.timingMetrics = {};
+ }
+ this.timingMetrics.methodInvocationTime = 0;
+ if (!this.timingMetrics.wrapperInitTimes) {
+ this.timingMetrics.wrapperInitTimes = [];
+ } else {
+ this.timingMetrics.wrapperInitTimes.length = 0;
+ }
+ if (!this.timingMetrics.wrapperCloseTimes) {
+ this.timingMetrics.wrapperCloseTimes = [];
+ } else {
+ this.timingMetrics.wrapperCloseTimes.length = 0;
+ }
+ this._isInTransaction = false;
+ },
+
+ _isInTransaction: false,
+
+ /**
+ * @abstract
+ * @return {Array<TransactionWrapper>} Array of transaction wrappers.
+ */
+ getTransactionWrappers: null,
+
+ isInTransaction: function() {
+ return !!this._isInTransaction;
+ },
+
+ /**
+ * Executes the function within a safety window. Use this for the top level
+ * methods that result in large amounts of computation/mutations that would
+ * need to be safety checked.
+ *
+ * @param {function} method Member of scope to call.
+ * @param {Object} scope Scope to invoke from.
+ * @param {Object?=} args... Arguments to pass to the method (optional).
+ * Helps prevent need to bind in many cases.
+ * @return Return value from `method`.
+ */
+ perform: function(method, scope, a, b, c, d, e, f) {
+ invariant(
+ !this.isInTransaction(),
+ 'Transaction.perform(...): Cannot initialize a transaction when there ' +
+ 'is already an outstanding transaction.'
+ );
+ var memberStart = Date.now();
+ var errorToThrow = null;
+ var ret;
+ try {
+ this.initializeAll();
+ ret = method.call(scope, a, b, c, d, e, f);
+ } catch (error) {
+ // IE8 requires `catch` in order to use `finally`.
+ errorToThrow = error;
+ } finally {
+ var memberEnd = Date.now();
+ this.methodInvocationTime += (memberEnd - memberStart);
+ try {
+ this.closeAll();
+ } catch (closeError) {
+ // If `method` throws, prefer to show that stack trace over any thrown
+ // by invoking `closeAll`.
+ errorToThrow = errorToThrow || closeError;
+ }
+ }
+ if (errorToThrow) {
+ throw errorToThrow;
+ }
+ return ret;
+ },
+
+ initializeAll: function() {
+ this._isInTransaction = true;
+ var transactionWrappers = this.transactionWrappers;
+ var wrapperInitTimes = this.timingMetrics.wrapperInitTimes;
+ var errorToThrow = null;
+ for (var i = 0; i < transactionWrappers.length; i++) {
+ var initStart = Date.now();
+ var wrapper = transactionWrappers[i];
+ try {
+ this.wrapperInitData[i] = wrapper.initialize ?
+ wrapper.initialize.call(this) :
+ null;
+ } catch (initError) {
+ // Prefer to show the stack trace of the first error.
+ errorToThrow = errorToThrow || initError;
+ this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
+ } finally {
+ var curInitTime = wrapperInitTimes[i];
+ var initEnd = Date.now();
+ wrapperInitTimes[i] = (curInitTime || 0) + (initEnd - initStart);
+ }
+ }
+ if (errorToThrow) {
+ throw errorToThrow;
+ }
+ },
+
+ /**
+ * Invokes each of `this.transactionWrappers.close[i]` functions, passing into
+ * them the respective return values of `this.transactionWrappers.init[i]`
+ * (`close`rs that correspond to initializers that failed will not be
+ * invoked).
+ */
+ closeAll: function() {
+ invariant(
+ this.isInTransaction(),
+ 'Transaction.closeAll(): Cannot close transaction when none are open.'
+ );
+ var transactionWrappers = this.transactionWrappers;
+ var wrapperCloseTimes = this.timingMetrics.wrapperCloseTimes;
+ var errorToThrow = null;
+ for (var i = 0; i < transactionWrappers.length; i++) {
+ var wrapper = transactionWrappers[i];
+ var closeStart = Date.now();
+ var initData = this.wrapperInitData[i];
+ try {
+ if (initData !== Transaction.OBSERVED_ERROR) {
+ wrapper.close && wrapper.close.call(this, initData);
+ }
+ } catch (closeError) {
+ // Prefer to show the stack trace of the first error.
+ errorToThrow = errorToThrow || closeError;
+ } finally {
+ var closeEnd = Date.now();
+ var curCloseTime = wrapperCloseTimes[i];
+ wrapperCloseTimes[i] = (curCloseTime || 0) + (closeEnd - closeStart);
+ }
+ }
+ this.wrapperInitData.length = 0;
+ this._isInTransaction = false;
+ if (errorToThrow) {
+ throw errorToThrow;
+ }
+ }
+};
+
+var Transaction = {
+
+ Mixin: Mixin,
+
+ /**
+ * Token to look for to determine if an error occured.
+ */
+ OBSERVED_ERROR: {}
+
+};
+
+module.exports = Transaction;
+
+},{"./invariant":89}],69:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ViewportMetrics
+ */
+
+"use strict";
+
+var ViewportMetrics = {
+
+ currentScrollLeft: 0,
+
+ currentScrollTop: 0,
+
+ refreshScrollValues: function() {
+ ViewportMetrics.currentScrollLeft =
+ document.body.scrollLeft + document.documentElement.scrollLeft;
+ ViewportMetrics.currentScrollTop =
+ document.body.scrollTop + document.documentElement.scrollTop;
+ }
+
+};
+
+module.exports = ViewportMetrics;
+
+},{}],70:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule accumulate
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+/**
+ * Accumulates items that must not be null or undefined.
+ *
+ * This is used to conserve memory by avoiding array allocations.
+ *
+ * @return {*|array<*>} An accumulation of items.
+ */
+function accumulate(current, next) {
+ invariant(
+ next != null,
+ 'accumulate(...): Accumulated items must be not be null or undefined.'
+ );
+ if (current == null) {
+ return next;
+ } else {
+ // Both are not empty. Warning: Never call x.concat(y) when you are not
+ // certain that x is an Array (x could be a string with concat method).
+ var currentIsArray = Array.isArray(current);
+ var nextIsArray = Array.isArray(next);
+ if (currentIsArray) {
+ return current.concat(next);
+ } else {
+ if (nextIsArray) {
+ return [current].concat(next);
+ } else {
+ return [current, next];
+ }
+ }
+ }
+}
+
+module.exports = accumulate;
+
+},{"./invariant":89}],71:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule adler32
+ */
+
+"use strict";
+
+var MOD = 65521;
+
+// This is a clean-room implementation of adler32 designed for detecting
+// if markup is not what we expect it to be. It does not need to be
+// cryptographically strong, only reasonable good at detecting if markup
+// generated on the server is different than that on the client.
+function adler32(data) {
+ var a = 1;
+ var b = 0;
+ for (var i = 0; i < data.length; i++) {
+ a = (a + data.charCodeAt(i)) % MOD;
+ b = (b + a) % MOD;
+ }
+ return a | (b << 16);
+}
+
+module.exports = adler32;
+
+},{}],72:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule copyProperties
+ */
+
+/**
+ * Copy properties from one or more objects (up to 5) into the first object.
+ * This is a shallow copy. It mutates the first object and also returns it.
+ *
+ * NOTE: `arguments` has a very significant performance penalty, which is why
+ * we don't support unlimited arguments.
+ */
+function copyProperties(obj, a, b, c, d, e, f) {
+ obj = obj || {};
+
+ if (true) {
+ if (f) {
+ throw new Error('Too many arguments passed to copyProperties');
+ }
+ }
+
+ var args = [a, b, c, d, e];
+ var ii = 0, v;
+ while (args[ii]) {
+ v = args[ii++];
+ for (var k in v) {
+ obj[k] = v[k];
+ }
+
+ // IE ignores toString in object iteration.. See:
+ // webreflection.blogspot.com/2007/07/quick-fix-internet-explorer-and.html
+ if (v.hasOwnProperty && v.hasOwnProperty('toString') &&
+ (typeof v.toString != 'undefined') && (obj.toString !== v.toString)) {
+ obj.toString = v.toString;
+ }
+ }
+
+ return obj;
+}
+
+module.exports = copyProperties;
+
+},{}],73:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule createArrayFrom
+ * @typechecks
+ */
+
+/**
+ * NOTE: if you are a previous user of this function, it has been considered
+ * unsafe because it's inconsistent across browsers for some inputs.
+ * Instead use `Array.isArray()`.
+ *
+ * Perform a heuristic test to determine if an object is "array-like".
+ *
+ * A monk asked Joshu, a Zen master, "Has a dog Buddha nature?"
+ * Joshu replied: "Mu."
+ *
+ * This function determines if its argument has "array nature": it returns
+ * true if the argument is an actual array, an `arguments' object, or an
+ * HTMLCollection (e.g. node.childNodes or node.getElementsByTagName()).
+ *
+ * @param {*} obj
+ * @return {boolean}
+ */
+function hasArrayNature(obj) {
+ return (
+ // not null/false
+ !!obj &&
+ // arrays are objects, NodeLists are functions in Safari
+ (typeof obj == 'object' || typeof obj == 'function') &&
+ // quacks like an array
+ ('length' in obj) &&
+ // not window
+ !('setInterval' in obj) &&
+ // no DOM node should be considered an array-like
+ // a 'select' element has 'length' and 'item' properties on IE8
+ (typeof obj.nodeType != 'number') &&
+ (
+ // a real array
+ (// HTMLCollection/NodeList
+ (Array.isArray(obj) ||
+ // arguments
+ ('callee' in obj) || 'item' in obj))
+ )
+ );
+}
+
+/**
+ * Ensure that the argument is an array by wrapping it in an array if it is not.
+ * Creates a copy of the argument if it is already an array.
+ *
+ * This is mostly useful idiomatically:
+ *
+ * var createArrayFrom = require('createArrayFrom');
+ *
+ * function takesOneOrMoreThings(things) {
+ * things = createArrayFrom(things);
+ * ...
+ * }
+ *
+ * This allows you to treat `things' as an array, but accept scalars in the API.
+ *
+ * This is also good for converting certain pseudo-arrays, like `arguments` or
+ * HTMLCollections, into arrays.
+ *
+ * @param {*} obj
+ * @return {array}
+ */
+function createArrayFrom(obj) {
+ if (!hasArrayNature(obj)) {
+ return [obj];
+ }
+ if (obj.item) {
+ // IE does not support Array#slice on HTMLCollections
+ var l = obj.length, ret = new Array(l);
+ while (l--) { ret[l] = obj[l]; }
+ return ret;
+ }
+ return Array.prototype.slice.call(obj);
+}
+
+module.exports = createArrayFrom;
+
+},{}],74:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule createNodesFromMarkup
+ * @typechecks
+ */
+
+/*jslint evil: true, sub: true */
+
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+
+var createArrayFrom = require("./createArrayFrom");
+var getMarkupWrap = require("./getMarkupWrap");
+var invariant = require("./invariant");
+
+/**
+ * Dummy container used to render all markup.
+ */
+var dummyNode =
+ ExecutionEnvironment.canUseDOM ? document.createElement('div') : null;
+
+/**
+ * Pattern used by `getNodeName`.
+ */
+var nodeNamePattern = /^\s*<(\w+)/;
+
+/**
+ * Extracts the `nodeName` of the first element in a string of markup.
+ *
+ * @param {string} markup String of markup.
+ * @return {?string} Node name of the supplied markup.
+ */
+function getNodeName(markup) {
+ var nodeNameMatch = markup.match(nodeNamePattern);
+ return nodeNameMatch && nodeNameMatch[1].toLowerCase();
+}
+
+/**
+ * Creates an array containing the nodes rendered from the supplied markup. The
+ * optionally supplied `handleScript` function will be invoked once for each
+ * <script> element that is rendered. If no `handleScript` function is supplied,
+ * an exception is thrown if any <script> elements are rendered.
+ *
+ * @param {string} markup A string of valid HTML markup.
+ * @param {?function} handleScript Invoked once for each rendered <script>.
+ * @return {array<DOMElement|DOMTextNode>} An array of rendered nodes.
+ */
+function createNodesFromMarkup(markup, handleScript) {
+ var node = dummyNode;
+ invariant(!!dummyNode, 'createNodesFromMarkup dummy not initialized');
+ var nodeName = getNodeName(markup);
+
+ var wrap = nodeName && getMarkupWrap(nodeName);
+ if (wrap) {
+ node.innerHTML = wrap[1] + markup + wrap[2];
+
+ var wrapDepth = wrap[0];
+ while (wrapDepth--) {
+ node = node.lastChild;
+ }
+ } else {
+ node.innerHTML = markup;
+ }
+
+ var scripts = node.getElementsByTagName('script');
+ if (scripts.length) {
+ invariant(
+ handleScript,
+ 'createNodesFromMarkup(...): Unexpected <script> element rendered.'
+ );
+ createArrayFrom(scripts).forEach(handleScript);
+ }
+
+ var nodes = createArrayFrom(node.childNodes);
+ while (node.lastChild) {
+ node.removeChild(node.lastChild);
+ }
+ return nodes;
+}
+
+module.exports = createNodesFromMarkup;
+
+},{"./ExecutionEnvironment":19,"./createArrayFrom":73,"./getMarkupWrap":85,"./invariant":89}],75:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule createObjectFrom
+ */
+
+/**
+ * Construct an object from an array of keys
+ * and optionally specified value or list of values.
+ *
+ * >>> createObjectFrom(['a','b','c']);
+ * {a: true, b: true, c: true}
+ *
+ * >>> createObjectFrom(['a','b','c'], false);
+ * {a: false, b: false, c: false}
+ *
+ * >>> createObjectFrom(['a','b','c'], 'monkey');
+ * {c:'monkey', b:'monkey' c:'monkey'}
+ *
+ * >>> createObjectFrom(['a','b','c'], [1,2,3]);
+ * {a: 1, b: 2, c: 3}
+ *
+ * >>> createObjectFrom(['women', 'men'], [true, false]);
+ * {women: true, men: false}
+ *
+ * @param Array list of keys
+ * @param mixed optional value or value array. defaults true.
+ * @returns object
+ */
+function createObjectFrom(keys, values /* = true */) {
+ if (true) {
+ if (!Array.isArray(keys)) {
+ throw new TypeError('Must pass an array of keys.');
+ }
+ }
+
+ var object = {};
+ var isArray = Array.isArray(values);
+ if (typeof values == 'undefined') {
+ values = true;
+ }
+
+ for (var ii = keys.length; ii--;) {
+ object[keys[ii]] = isArray ? values[ii] : values;
+ }
+ return object;
+}
+
+module.exports = createObjectFrom;
+
+},{}],76:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule dangerousStyleValue
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var CSSProperty = require("./CSSProperty");
+
+/**
+ * Convert a value into the proper css writable value. The `styleName` name
+ * name should be logical (no hyphens), as specified
+ * in `CSSProperty.isUnitlessNumber`.
+ *
+ * @param {string} styleName CSS property name such as `topMargin`.
+ * @param {*} value CSS property value such as `10px`.
+ * @return {string} Normalized style value with dimensions applied.
+ */
+function dangerousStyleValue(styleName, value) {
+ // Note that we've removed escapeTextForBrowser() calls here since the
+ // whole string will be escaped when the attribute is injected into
+ // the markup. If you provide unsafe user data here they can inject
+ // arbitrary CSS which may be problematic (I couldn't repro this):
+ // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
+ // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
+ // This is not an XSS hole but instead a potential CSS injection issue
+ // which has lead to a greater discussion about how we're going to
+ // trust URLs moving forward. See #2115901
+
+ var isEmpty = value == null || typeof value === 'boolean' || value === '';
+ if (isEmpty) {
+ return '';
+ }
+
+ var isNonNumeric = isNaN(value);
+ if (isNonNumeric || value === 0 || CSSProperty.isUnitlessNumber[styleName]) {
+ return '' + value; // cast to string
+ }
+
+ return value + 'px';
+}
+
+module.exports = dangerousStyleValue;
+
+},{"./CSSProperty":2}],77:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule emptyFunction
+ */
+
+var copyProperties = require("./copyProperties");
+
+function makeEmptyFunction(arg) {
+ return function() {
+ return arg;
+ };
+}
+
+/**
+ * This function accepts and discards inputs; it has no side effects. This is
+ * primarily useful idiomatically for overridable function endpoints which
+ * always need to be callable, since JS lacks a null-call idiom ala Cocoa.
+ */
+function emptyFunction() {}
+
+copyProperties(emptyFunction, {
+ thatReturns: makeEmptyFunction,
+ thatReturnsFalse: makeEmptyFunction(false),
+ thatReturnsTrue: makeEmptyFunction(true),
+ thatReturnsNull: makeEmptyFunction(null),
+ thatReturnsThis: function() { return this; },
+ thatReturnsArgument: function(arg) { return arg; }
+});
+
+module.exports = emptyFunction;
+
+},{"./copyProperties":72}],78:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule escapeTextForBrowser
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+var ESCAPE_LOOKUP = {
+ "&": "&amp;",
+ ">": "&gt;",
+ "<": "&lt;",
+ "\"": "&quot;",
+ "'": "&#x27;",
+ "/": "&#x2f;"
+};
+
+function escaper(match) {
+ return ESCAPE_LOOKUP[match];
+}
+
+/**
+ * Escapes text to prevent scripting attacks.
+ *
+ * @param {number|string} text Text value to escape.
+ * @return {string} An escaped string.
+ */
+function escapeTextForBrowser(text) {
+ var type = typeof text;
+ invariant(
+ type !== 'object',
+ 'escapeTextForBrowser(...): Attempted to escape an object.'
+ );
+ if (text === '') {
+ return '';
+ } else {
+ if (type === 'string') {
+ return text.replace(/[&><"'\/]/g, escaper);
+ } else {
+ return (''+text).replace(/[&><"'\/]/g, escaper);
+ }
+ }
+}
+
+module.exports = escapeTextForBrowser;
+
+},{"./invariant":89}],79:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ex
+ * @typechecks
+ * @nostacktrace
+ */
+
+/**
+ * This function transforms error message with arguments into plain text error
+ * message, so that it can be passed to window.onerror without losing anything.
+ * It can then be transformed back by `erx()` function.
+ *
+ * Usage:
+ * throw new Error(ex('Error %s from %s', errorCode, userID));
+ *
+ * @param {string} errorMessage
+ */
+
+var ex = function(errorMessage/*, arg1, arg2, ...*/) {
+ var args = Array.prototype.slice.call(arguments).map(function(arg) {
+ return String(arg);
+ });
+ var expectedLength = errorMessage.split('%s').length - 1;
+
+ if (expectedLength !== args.length - 1) {
+ // something wrong with the formatting string
+ return ex('ex args number mismatch: %s', JSON.stringify(args));
+ }
+
+ return ex._prefix + JSON.stringify(args) + ex._suffix;
+};
+
+ex._prefix = '<![EX[';
+ex._suffix = ']]>';
+
+module.exports = ex;
+
+},{}],80:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule filterAttributes
+ * @typechecks static-only
+ */
+
+/*jslint evil: true */
+
+'use strict';
+
+/**
+ * Like filter(), but for a DOM nodes attributes. Returns an array of
+ * the filter DOMAttribute objects. Does some perf related this like
+ * caching attributes.length.
+ *
+ * @param {DOMElement} node Node whose attributes you want to filter
+ * @return {array} array of DOM attribute objects.
+ */
+function filterAttributes(node, func, context) {
+ var attributes = node.attributes;
+ var numAttributes = attributes.length;
+ var accumulator = [];
+ for (var i = 0; i < numAttributes; i++) {
+ var attr = attributes.item(i);
+ if (func.call(context, attr)) {
+ accumulator.push(attr);
+ }
+ }
+ return accumulator;
+}
+
+module.exports = filterAttributes;
+
+},{}],81:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule flattenChildren
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+var traverseAllChildren = require("./traverseAllChildren");
+
+/**
+ * @param {function} traverseContext Context passed through traversal.
+ * @param {?ReactComponent} child React child component.
+ * @param {!string} name String name of key path to child.
+ */
+function flattenSingleChildIntoContext(traverseContext, child, name) {
+ // We found a component instance.
+ var result = traverseContext;
+ invariant(
+ !result.hasOwnProperty(name),
+ 'flattenChildren(...): Encountered two children with the same key, `%s`. ' +
+ 'Children keys must be unique.',
+ name
+ );
+ result[name] = child;
+}
+
+/**
+ * Flattens children that are typically specified as `props.children`.
+ * @return {!object} flattened children keyed by name.
+ */
+function flattenChildren(children) {
+ if (children == null) {
+ return children;
+ }
+ var result = {};
+ traverseAllChildren(children, flattenSingleChildIntoContext, result);
+ return result;
+}
+
+module.exports = flattenChildren;
+
+},{"./invariant":89,"./traverseAllChildren":103}],82:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule forEachAccumulated
+ */
+
+"use strict";
+
+/**
+ * @param {array} an "accumulation" of items which is either an Array or
+ * a single item. Useful when paired with the `accumulate` module. This is a
+ * simple utility that allows us to reason about a collection of items, but
+ * handling the case when there is exactly one item (and we do not need to
+ * allocate an array).
+ */
+var forEachAccumulated = function(arr, cb, scope) {
+ if (Array.isArray(arr)) {
+ arr.forEach(cb, scope);
+ } else if (arr) {
+ cb.call(scope, arr);
+ }
+};
+
+module.exports = forEachAccumulated;
+
+},{}],83:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ge
+ */
+
+/**
+ * Find a node by ID. Optionally search a sub-tree outside of the document
+ *
+ * Use ge if you're not sure whether or not the element exists. You can test
+ * for existence yourself in your application code.
+ *
+ * If your application code depends on the existence of the element, use $
+ * instead, which will throw in DEV if the element doesn't exist.
+ */
+function ge(arg, root, tag) {
+ return typeof arg != 'string' ? arg :
+ !root ? document.getElementById(arg) :
+ _geFromSubtree(arg, root, tag);
+}
+
+function _geFromSubtree(id, root, tag) {
+ var elem, children, ii;
+
+ if (_getNodeID(root) == id) {
+ return root;
+ } else if (root.getElementsByTagName) {
+ // All Elements implement this, which does an iterative DFS, which is
+ // faster than recursion and doesn't run into stack depth issues.
+ children = root.getElementsByTagName(tag || '*');
+ for (ii = 0; ii < children.length; ii++) {
+ if (_getNodeID(children[ii]) == id) {
+ return children[ii];
+ }
+ }
+ } else {
+ // DocumentFragment does not implement getElementsByTagName, so
+ // recurse over its children. Its children must be Elements, so
+ // each child will use the getElementsByTagName case instead.
+ children = root.childNodes;
+ for (ii = 0; ii < children.length; ii++) {
+ elem = _geFromSubtree(id, children[ii]);
+ if (elem) {
+ return elem;
+ }
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Return the ID value for a given node. This allows us to avoid issues
+ * with forms that contain inputs with name="id".
+ *
+ * @return string (null if attribute not set)
+ */
+function _getNodeID(node) {
+ // #document and #document-fragment do not have getAttributeNode.
+ var id = node.getAttributeNode && node.getAttributeNode('id');
+ return id ? id.value : null;
+}
+
+module.exports = ge;
+
+},{}],84:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule getEventTarget
+ * @typechecks static-only
+ */
+
+"use strict";
+
+/**
+ * Gets the target node from a native browser event by accounting for
+ * inconsistencies in browser DOM APIs.
+ *
+ * @param {object} nativeEvent Native browser event.
+ * @return {DOMEventTarget} Target node.
+ */
+function getEventTarget(nativeEvent) {
+ var target = nativeEvent.target || nativeEvent.srcElement || window;
+ // Safari may fire events on text nodes (Node.TEXT_NODE is 3).
+ // @see http://www.quirksmode.org/js/events_properties.html
+ return target.nodeType === 3 ? target.parentNode : target;
+}
+
+module.exports = getEventTarget;
+
+},{}],85:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule getMarkupWrap
+ */
+
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+
+var invariant = require("./invariant");
+
+/**
+ * Dummy container used to detect which wraps are necessary.
+ */
+var dummyNode =
+ ExecutionEnvironment.canUseDOM ? document.createElement('div') : null;
+
+/**
+ * Some browsers cannot use `innerHTML` to render certain elements standalone,
+ * so we wrap them, render the wrapped nodes, then extract the desired node.
+ *
+ * In IE8, certain elements cannot render alone, so wrap all elements ('*').
+ */
+var shouldWrap = {};
+var markupWrap = {
+ 'area': [1, '<map>', '</map>'],
+ 'caption': [1, '<table>', '</table>'],
+ 'col': [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
+ 'colgroup': [1, '<table>', '</table>'],
+ 'legend': [1, '<fieldset>', '</fieldset>'],
+ 'optgroup': [1, '<select multiple="true">', '</select>'],
+ 'option': [1, '<select multiple="true">', '</select>'],
+ 'param': [1, '<object>', '</object>'],
+ 'tbody': [1, '<table>', '</table>'],
+ 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
+ 'tfoot': [1, '<table>', '</table>'],
+ 'th': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
+ 'thead': [1, '<table>', '</table>'],
+ 'tr': [2, '<table><tbody>', '</tbody></table>'],
+ '*': [1, '?<div>', '</div>']
+};
+
+/**
+ * Gets the markup wrap configuration for the supplied `nodeName`.
+ *
+ * NOTE: This lazily detects which wraps are necessary for the current browser.
+ *
+ * @param {string} nodeName Lowercase `nodeName`.
+ * @return {?array} Markup wrap configuration, if applicable.
+ */
+function getMarkupWrap(nodeName) {
+ invariant(!!dummyNode, 'Markup wrapping node not initialized');
+ if (!markupWrap.hasOwnProperty(nodeName)) {
+ nodeName = '*';
+ }
+ if (!shouldWrap.hasOwnProperty(nodeName)) {
+ if (nodeName === '*') {
+ dummyNode.innerHTML = '<link />';
+ } else {
+ dummyNode.innerHTML = '<' + nodeName + '></' + nodeName + '>';
+ }
+ shouldWrap[nodeName] = !dummyNode.firstChild;
+ }
+ return shouldWrap[nodeName] ? markupWrap[nodeName] : null;
+}
+
+
+module.exports = getMarkupWrap;
+
+},{"./ExecutionEnvironment":19,"./invariant":89}],86:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule getReactRootElementInContainer
+ */
+
+"use strict";
+
+/**
+ * @param {DOMElement} container DOM element that may contain a React component
+ * @return {?*} DOM element that may have the reactRoot ID, or null.
+ */
+function getReactRootElementInContainer(container) {
+ return container && container.firstChild;
+}
+
+module.exports = getReactRootElementInContainer;
+
+},{}],87:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule getTextContentAccessor
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+
+var contentKey = null;
+
+/**
+ * Gets the key used to access text content on a DOM node.
+ *
+ * @return {?string} Key used to access text content.
+ * @internal
+ */
+function getTextContentAccessor() {
+ if (!contentKey && ExecutionEnvironment.canUseDOM) {
+ contentKey = 'innerText' in document.createElement('div') ?
+ 'innerText' :
+ 'textContent';
+ }
+ return contentKey;
+}
+
+module.exports = getTextContentAccessor;
+
+},{"./ExecutionEnvironment":19}],88:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule hyphenate
+ * @typechecks
+ */
+
+var _uppercasePattern = /([A-Z])/g;
+
+/**
+ * Hyphenates a camelcased string, for example:
+ *
+ * > hyphenate('backgroundColor')
+ * < "background-color"
+ *
+ * @param {string} string
+ * @return {string}
+ */
+function hyphenate(string) {
+ return string.replace(_uppercasePattern, '-$1').toLowerCase();
+}
+
+module.exports = hyphenate;
+
+},{}],89:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule invariant
+ */
+
+/**
+ * Use invariant() to assert state which your program assumes to be true.
+ *
+ * Provide sprintf style format and arguments to provide information about
+ * what broke and what you were expecting.
+ *
+ * The invariant message will be stripped in production, but the invariant
+ * will remain to ensure logic does not differ in production.
+ */
+
+function invariant(condition) {
+ if (!condition) {
+ throw new Error('Invariant Violation');
+ }
+}
+
+module.exports = invariant;
+
+if (true) {
+ var invariantDev = function(condition, format, a, b, c, d, e, f) {
+ if (format === undefined) {
+ throw new Error('invariant requires an error message argument');
+ }
+
+ if (!condition) {
+ var args = [a, b, c, d, e, f];
+ var argIndex = 0;
+ throw new Error(
+ 'Invariant Violation: ' +
+ format.replace(/%s/g, function() { return args[argIndex++]; })
+ );
+ }
+ };
+
+ module.exports = invariantDev;
+}
+
+},{}],90:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule isEventSupported
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+
+var testNode, useHasFeature;
+if (ExecutionEnvironment.canUseDOM) {
+ testNode = document.createElement('div');
+ useHasFeature =
+ document.implementation &&
+ document.implementation.hasFeature &&
+ // `hasFeature` always returns true in Firefox 19+.
+ document.implementation.hasFeature('', '') !== true;
+}
+
+/**
+ * Checks if an event is supported in the current execution environment.
+ *
+ * NOTE: This will not work correctly for non-generic events such as `change`,
+ * `reset`, `load`, `error`, and `select`.
+ *
+ * Borrows from Modernizr.
+ *
+ * @param {string} eventNameSuffix Event name, e.g. "click".
+ * @param {?boolean} capture Check if the capture phase is supported.
+ * @return {boolean} True if the event is supported.
+ * @internal
+ * @license Modernizr 3.0.0pre (Custom Build) | MIT
+ */
+function isEventSupported(eventNameSuffix, capture) {
+ if (!testNode || (capture && !testNode.addEventListener)) {
+ return false;
+ }
+ var element = document.createElement('div');
+
+ var eventName = 'on' + eventNameSuffix;
+ var isSupported = eventName in element;
+
+ if (!isSupported) {
+ element.setAttribute(eventName, '');
+ isSupported = typeof element[eventName] === 'function';
+ if (typeof element[eventName] !== 'undefined') {
+ element[eventName] = undefined;
+ }
+ element.removeAttribute(eventName);
+ }
+
+ if (!isSupported && useHasFeature && eventNameSuffix === 'wheel') {
+ // This is the only way to test support for the `wheel` event in IE9+.
+ isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
+ }
+
+ element = null;
+ return isSupported;
+}
+
+module.exports = isEventSupported;
+
+},{"./ExecutionEnvironment":19}],91:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule joinClasses
+ * @typechecks static-only
+ */
+
+"use strict";
+
+/**
+ * Combines multiple className strings into one.
+ * http://jsperf.com/joinclasses-args-vs-array
+ *
+ * @param {...?string} classes
+ * @return {string}
+ */
+function joinClasses(className/*, ... */) {
+ if (!className) {
+ className = '';
+ }
+ var nextClass;
+ var argLength = arguments.length;
+ if (argLength > 1) {
+ for (var ii = 1; ii < argLength; ii++) {
+ nextClass = arguments[ii];
+ nextClass && (className += ' ' + nextClass);
+ }
+ }
+ return className;
+}
+
+module.exports = joinClasses;
+
+},{}],92:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule keyMirror
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+
+/**
+ * Constructs an enumeration with keys equal to their value.
+ *
+ * For example:
+ *
+ * var COLORS = keyMirror({blue: null, red: null});
+ * var myColor = COLORS.blue;
+ * var isColorValid = !!COLORS[myColor];
+ *
+ * The last line could not be performed if the values of the generated enum were
+ * not equal to their keys.
+ *
+ * Input: {key1: val1, key2: val2}
+ * Output: {key1: key1, key2: key2}
+ *
+ * @param {object} obj
+ * @return {object}
+ */
+var keyMirror = function(obj) {
+ var ret = {};
+ var key;
+ invariant(
+ obj instanceof Object && !Array.isArray(obj),
+ 'keyMirror(...): Argument must be an object.'
+ );
+ for (key in obj) {
+ if (!obj.hasOwnProperty(key)) {
+ continue;
+ }
+ ret[key] = key;
+ }
+ return ret;
+};
+
+module.exports = keyMirror;
+
+},{"./invariant":89}],93:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule keyOf
+ */
+
+/**
+ * Allows extraction of a minified key. Let's the build system minify keys
+ * without loosing the ability to dynamically use key strings as values
+ * themselves. Pass in an object with a single key/val pair and it will return
+ * you the string key of that single record. Suppose you want to grab the
+ * value for a key 'className' inside of an object. Key/val minification may
+ * have aliased that key to be 'xa12'. keyOf({className: null}) will return
+ * 'xa12' in that case. Resolve keys you want to use once at startup time, then
+ * reuse those resolutions.
+ */
+var keyOf = function(oneKeyObj) {
+ var key;
+ for (key in oneKeyObj) {
+ if (!oneKeyObj.hasOwnProperty(key)) {
+ continue;
+ }
+ return key;
+ }
+ return null;
+};
+
+
+module.exports = keyOf;
+
+},{}],94:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule memoizeStringOnly
+ * @typechecks static-only
+ */
+
+"use strict";
+
+/**
+ * Memoizes the return value of a function that accepts one string argument.
+ *
+ * @param {function} callback
+ * @return {function}
+ */
+function memoizeStringOnly(callback) {
+ var cache = {};
+ return function(string) {
+ if (cache.hasOwnProperty(string)) {
+ return cache[string];
+ } else {
+ return cache[string] = callback.call(this, string);
+ }
+ };
+}
+
+module.exports = memoizeStringOnly;
+
+},{}],95:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule merge
+ */
+
+"use strict";
+
+var mergeInto = require("./mergeInto");
+
+/**
+ * Shallow merges two structures into a return value, without mutating either.
+ *
+ * @param {?object} one Optional object with properties to merge from.
+ * @param {?object} two Optional object with properties to merge from.
+ * @return {object} The shallow extension of one by two.
+ */
+var merge = function(one, two) {
+ var result = {};
+ mergeInto(result, one);
+ mergeInto(result, two);
+ return result;
+};
+
+module.exports = merge;
+
+},{"./mergeInto":97}],96:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule mergeHelpers
+ *
+ * requiresPolyfills: Array.isArray
+ */
+
+"use strict";
+
+var invariant = require("./invariant");
+var keyMirror = require("./keyMirror");
+
+/**
+ * Maximum number of levels to traverse. Will catch circular structures.
+ * @const
+ */
+var MAX_MERGE_DEPTH = 36;
+
+/**
+ * We won't worry about edge cases like new String('x') or new Boolean(true).
+ * Functions are considered terminals, and arrays are not.
+ * @param {*} o The item/object/value to test.
+ * @return {boolean} true iff the argument is a terminal.
+ */
+var isTerminal = function(o) {
+ return typeof o !== 'object' || o === null;
+};
+
+var mergeHelpers = {
+
+ MAX_MERGE_DEPTH: MAX_MERGE_DEPTH,
+
+ isTerminal: isTerminal,
+
+ /**
+ * Converts null/undefined values into empty object.
+ *
+ * @param {?Object=} arg Argument to be normalized (nullable optional)
+ * @return {!Object}
+ */
+ normalizeMergeArg: function(arg) {
+ return arg === undefined || arg === null ? {} : arg;
+ },
+
+ /**
+ * If merging Arrays, a merge strategy *must* be supplied. If not, it is
+ * likely the caller's fault. If this function is ever called with anything
+ * but `one` and `two` being `Array`s, it is the fault of the merge utilities.
+ *
+ * @param {*} one Array to merge into.
+ * @param {*} two Array to merge from.
+ */
+ checkMergeArrayArgs: function(one, two) {
+ invariant(
+ Array.isArray(one) && Array.isArray(two),
+ 'Critical assumptions about the merge functions have been violated. ' +
+ 'This is the fault of the merge functions themselves, not necessarily ' +
+ 'the callers.'
+ );
+ },
+
+ /**
+ * @param {*} one Object to merge into.
+ * @param {*} two Object to merge from.
+ */
+ checkMergeObjectArgs: function(one, two) {
+ mergeHelpers.checkMergeObjectArg(one);
+ mergeHelpers.checkMergeObjectArg(two);
+ },
+
+ /**
+ * @param {*} arg
+ */
+ checkMergeObjectArg: function(arg) {
+ invariant(
+ !isTerminal(arg) && !Array.isArray(arg),
+ 'Critical assumptions about the merge functions have been violated. ' +
+ 'This is the fault of the merge functions themselves, not necessarily ' +
+ 'the callers.'
+ );
+ },
+
+ /**
+ * Checks that a merge was not given a circular object or an object that had
+ * too great of depth.
+ *
+ * @param {number} Level of recursion to validate against maximum.
+ */
+ checkMergeLevel: function(level) {
+ invariant(
+ level < MAX_MERGE_DEPTH,
+ 'Maximum deep merge depth exceeded. You may be attempting to merge ' +
+ 'circular structures in an unsupported way.'
+ );
+ },
+
+ /**
+ * Checks that the supplied merge strategy is valid.
+ *
+ * @param {string} Array merge strategy.
+ */
+ checkArrayStrategy: function(strategy) {
+ invariant(
+ strategy === undefined || strategy in mergeHelpers.ArrayStrategies,
+ 'You must provide an array strategy to deep merge functions to ' +
+ 'instruct the deep merge how to resolve merging two arrays.'
+ );
+ },
+
+ /**
+ * Set of possible behaviors of merge algorithms when encountering two Arrays
+ * that must be merged together.
+ * - `clobber`: The left `Array` is ignored.
+ * - `indexByIndex`: The result is achieved by recursively deep merging at
+ * each index. (not yet supported.)
+ */
+ ArrayStrategies: keyMirror({
+ Clobber: true,
+ IndexByIndex: true
+ })
+
+};
+
+module.exports = mergeHelpers;
+
+},{"./invariant":89,"./keyMirror":92}],97:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule mergeInto
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var mergeHelpers = require("./mergeHelpers");
+
+var checkMergeObjectArg = mergeHelpers.checkMergeObjectArg;
+
+/**
+ * Shallow merges two structures by mutating the first parameter.
+ *
+ * @param {object} one Object to be merged into.
+ * @param {?object} two Optional object with properties to merge from.
+ */
+function mergeInto(one, two) {
+ checkMergeObjectArg(one);
+ if (two != null) {
+ checkMergeObjectArg(two);
+ for (var key in two) {
+ if (!two.hasOwnProperty(key)) {
+ continue;
+ }
+ one[key] = two[key];
+ }
+ }
+}
+
+module.exports = mergeInto;
+
+},{"./mergeHelpers":96}],98:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule mixInto
+ */
+
+"use strict";
+
+/**
+ * Simply copies properties to the prototype.
+ */
+var mixInto = function(constructor, methodBag) {
+ var methodName;
+ for (methodName in methodBag) {
+ if (!methodBag.hasOwnProperty(methodName)) {
+ continue;
+ }
+ constructor.prototype[methodName] = methodBag[methodName];
+ }
+};
+
+module.exports = mixInto;
+
+},{}],99:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule mutateHTMLNodeWithMarkup
+ * @typechecks static-only
+ */
+
+/*jslint evil: true */
+
+'use strict';
+
+var createNodesFromMarkup = require("./createNodesFromMarkup");
+var filterAttributes = require("./filterAttributes");
+var invariant = require("./invariant");
+
+/**
+ * You can't set the innerHTML of a document. Unless you have
+ * this function.
+ *
+ * @param {DOMElement} node with tagName == 'html'
+ * @param {string} markup markup string including <html>.
+ */
+function mutateHTMLNodeWithMarkup(node, markup) {
+ invariant(
+ node.tagName.toLowerCase() === 'html',
+ 'mutateHTMLNodeWithMarkup(): node must have tagName of "html", got %s',
+ node.tagName
+ );
+
+ markup = markup.trim();
+ invariant(
+ markup.toLowerCase().indexOf('<html') === 0,
+ 'mutateHTMLNodeWithMarkup(): markup must start with <html'
+ );
+
+ // First let's extract the various pieces of markup.
+ var htmlOpenTagEnd = markup.indexOf('>') + 1;
+ var htmlCloseTagStart = markup.lastIndexOf('<');
+ var htmlOpenTag = markup.substring(0, htmlOpenTagEnd);
+ var innerHTML = markup.substring(htmlOpenTagEnd, htmlCloseTagStart);
+
+ // Now for the fun stuff. Pass through both sets of attributes and
+ // bring them up-to-date. We get the new set by creating a markup
+ // fragment.
+ var shouldExtractAttributes = htmlOpenTag.indexOf(' ') > -1;
+ var attributeHolder = null;
+
+ if (shouldExtractAttributes) {
+ // We extract the attributes by creating a <span> and evaluating
+ // the node.
+ attributeHolder = createNodesFromMarkup(
+ htmlOpenTag.replace('html ', 'span ') + '</span>'
+ )[0];
+
+ // Add all attributes present in attributeHolder
+ var attributesToSet = filterAttributes(
+ attributeHolder,
+ function(attr) {
+ return node.getAttributeNS(attr.namespaceURI, attr.name) !== attr.value;
+ }
+ );
+ attributesToSet.forEach(function(attr) {
+ node.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
+ });
+ }
+
+ // Remove all attributes not present in attributeHolder
+ var attributesToRemove = filterAttributes(
+ node,
+ function(attr) {
+ // Remove all attributes if attributeHolder is null or if it does not have
+ // the desired attribute.
+ return !(
+ attributeHolder &&
+ attributeHolder.hasAttributeNS(attr.namespaceURI, attr.name)
+ );
+ }
+ );
+ attributesToRemove.forEach(function(attr) {
+ node.removeAttributeNS(attr.namespaceURI, attr.name);
+ });
+
+ // Finally, set the inner HTML. No tricks needed. Do this last to
+ // minimize likelihood of triggering reflows.
+ node.innerHTML = innerHTML;
+}
+
+module.exports = mutateHTMLNodeWithMarkup;
+
+},{"./createNodesFromMarkup":74,"./filterAttributes":80,"./invariant":89}],100:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule objMap
+ */
+
+"use strict";
+
+/**
+ * For each key/value pair, invokes callback func and constructs a resulting
+ * object which contains, for every key in obj, values that are the result of
+ * of invoking the function:
+ *
+ * func(value, key, iteration)
+ *
+ * @param {?object} obj Object to map keys over
+ * @param {function} func Invoked for each key/val pair.
+ * @param {?*} context
+ * @return {?object} Result of mapping or null if obj is falsey
+ */
+function objMap(obj, func, context) {
+ if (!obj) {
+ return null;
+ }
+ var i = 0;
+ var ret = {};
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ ret[key] = func.call(context, obj[key], key, i++);
+ }
+ }
+ return ret;
+}
+
+module.exports = objMap;
+
+},{}],101:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule objMapKeyVal
+ */
+
+"use strict";
+
+/**
+ * Behaves the same as `objMap` but invokes func with the key first, and value
+ * second. Use `objMap` unless you need this special case.
+ * Invokes func as:
+ *
+ * func(key, value, iteration)
+ *
+ * @param {?object} obj Object to map keys over
+ * @param {!function} func Invoked for each key/val pair.
+ * @param {?*} context
+ * @return {?object} Result of mapping or null if obj is falsey
+ */
+function objMapKeyVal(obj, func, context) {
+ if (!obj) {
+ return null;
+ }
+ var i = 0;
+ var ret = {};
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ ret[key] = func.call(context, key, obj[key], i++);
+ }
+ }
+ return ret;
+}
+
+module.exports = objMapKeyVal;
+
+},{}],102:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule performanceNow
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+
+/**
+ * Detect if we can use window.performance.now() and gracefully
+ * fallback to Date.now() if it doesn't exist.
+ * We need to support Firefox < 15 for now due to Facebook's webdriver
+ * infrastructure.
+ */
+var performance = null;
+
+if (ExecutionEnvironment.canUseDOM) {
+ performance = window.performance || window.webkitPerformance;
+}
+
+if (!performance || !performance.now) {
+ performance = Date;
+}
+
+var performanceNow = performance.now.bind(performance);
+
+module.exports = performanceNow;
+
+},{"./ExecutionEnvironment":19}],103:[function(require,module,exports){
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule traverseAllChildren
+ */
+
+"use strict";
+
+var ReactComponent = require("./ReactComponent");
+var ReactTextComponent = require("./ReactTextComponent");
+
+var invariant = require("./invariant");
+
+/**
+ * TODO: Test that:
+ * 1. `mapChildren` transforms strings and numbers into `ReactTextComponent`.
+ * 2. it('should fail when supplied duplicate key', function() {
+ * 3. That a single child and an array with one item have the same key pattern.
+ * });
+ */
+
+/**
+ * @param {?*} children Children tree container.
+ * @param {!string} nameSoFar Name of the key path so far.
+ * @param {!number} indexSoFar Number of children encountered until this point.
+ * @param {!function} callback Callback to invoke with each child found.
+ * @param {?*} traverseContext Used to pass information throughout the traversal
+ * process.
+ * @return {!number} The number of children in this subtree.
+ */
+var traverseAllChildrenImpl =
+ function(children, nameSoFar, indexSoFar, callback, traverseContext) {
+ var subtreeCount = 0; // Count of children found in the current subtree.
+ if (Array.isArray(children)) {
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var nextName = nameSoFar + ReactComponent.getKey(child, i);
+ var nextIndex = indexSoFar + subtreeCount;
+ subtreeCount += traverseAllChildrenImpl(
+ child,
+ nextName,
+ nextIndex,
+ callback,
+ traverseContext
+ );
+ }
+ } else {
+ var type = typeof children;
+ var isOnlyChild = nameSoFar === '';
+ // If it's the only child, treat the name as if it was wrapped in an array
+ // so that it's consistent if the number of children grows
+ var storageName = isOnlyChild ?
+ ReactComponent.getKey(children, 0):
+ nameSoFar;
+ if (children === null || children === undefined || type === 'boolean') {
+ // All of the above are perceived as null.
+ callback(traverseContext, null, storageName, indexSoFar);
+ subtreeCount = 1;
+ } else if (children.mountComponentIntoNode) {
+ callback(traverseContext, children, storageName, indexSoFar);
+ subtreeCount = 1;
+ } else {
+ if (type === 'object') {
+ invariant(
+ !children || children.nodeType !== 1,
+ 'traverseAllChildren(...): Encountered an invalid child; DOM ' +
+ 'elements are not valid children of React components.'
+ );
+ for (var key in children) {
+ if (children.hasOwnProperty(key)) {
+ subtreeCount += traverseAllChildrenImpl(
+ children[key],
+ nameSoFar + '{' + key + '}',
+ indexSoFar + subtreeCount,
+ callback,
+ traverseContext
+ );
+ }
+ }
+ } else if (type === 'string') {
+ var normalizedText = new ReactTextComponent(children);
+ callback(traverseContext, normalizedText, storageName, indexSoFar);
+ subtreeCount += 1;
+ } else if (type === 'number') {
+ var normalizedNumber = new ReactTextComponent('' + children);
+ callback(traverseContext, normalizedNumber, storageName, indexSoFar);
+ subtreeCount += 1;
+ }
+ }
+ }
+ return subtreeCount;
+ };
+
+/**
+ * Traverses children that are typically specified as `props.children`, but
+ * might also be specified through attributes:
+ *
+ * - `traverseAllChildren(this.props.children, ...)`
+ * - `traverseAllChildren(this.props.leftPanelChildren, ...)`
+ *
+ * The `traverseContext` is an optional argument that is passed through the
+ * entire traversal. It can be used to store accumulations or anything else that
+ * the callback might find relevant.
+ *
+ * @param {?*} children Children tree object.
+ * @param {!function} callback To invoke upon traversing each child.
+ * @param {?*} traverseContext Context for traversal.
+ */
+function traverseAllChildren(children, callback, traverseContext) {
+ if (children !== null && children !== undefined) {
+ traverseAllChildrenImpl(children, '', 0, callback, traverseContext);
+ }
+}
+
+module.exports = traverseAllChildren;
+
+},{"./ReactComponent":23,"./ReactTextComponent":56,"./invariant":89}]},{},[22])
+(22)
+});
+; \ No newline at end of file
diff --git a/frontend/delta/js/main.js b/frontend/delta/js/main.js
index 6f8b497..45b74e3 100644
--- a/frontend/delta/js/main.js
+++ b/frontend/delta/js/main.js
@@ -1,62 +1,65 @@
/*
Copyright 2008-2013 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
function _pm_logEvent(anEvent) {
// console.log("####", anEvent);
anEvent.preventDefault();
}
function handleGenericDeferredError(anError) {
var result;
if (anError instanceof MochiKit.Async.CancelledError) {
result = anError;
} else {
MochiKit.Logging.logError("## MainController - GENERIC ERROR" + "\n" + "==>> " + anError + " <<==\n" + anError.stack);
//console.log(anError);
result = new MochiKit.Async.CancelledError(anError);
}
return result;
}
+React.initializeTouchEvents(true);
Clipperz.PM.RunTime = {};
function run() {
var parameters = {};
Clipperz.PM.Strings.Languages.initSetup();
if ((window.location.search.indexOf('registration') != -1) || (window.location.hash.indexOf('registration') != -1)) {
parameters['shouldShowRegistrationForm'] = true;
} else {
parameters['shouldShowRegistrationForm'] = false;
}
+ Clipperz.PM.DataModel.devicePreferences = new Clipperz.PM.DataModel.DevicePreferences({});
+
Clipperz.PM.RunTime.mainController = new Clipperz.PM.UI.MainController();
Clipperz.PM.RunTime.mainController.run(parameters);
}
MochiKit.DOM.addLoadEvent(run);