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, ' '); + }, + + /** + * 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 = { + "&": "&", + ">": ">", + "<": "<", + "\"": """, + "'": "'", + "/": "/" +}; + +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); |