Repository: ramitos/react-tvml Branch: master Commit: 6c003d2715c4 Files: 16 Total size: 98.3 KB Directory structure: gitextract_asnex2vt/ ├── .gitignore ├── .tern-project ├── package.json ├── readme.md └── src/ ├── Component.js ├── EventEmitter.js ├── EventListener.js ├── EventPlugin.js ├── IDOperations.js ├── Mount.js ├── ReconcileTransaction.js ├── TextComponent.js ├── globals.js ├── instantiateReactComponent.js ├── menuBar.js └── react-tvml.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io ### Node ### # Logs logs *.log # Runtime data pids *.pid *.seed # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules ### Windows ### # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk ### Linux ### *~ # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* ### OSX ### .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### SublimeText ### # cache files for sublime text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache # workspace files are user-specific *.sublime-workspace # project files should be checked into the repository, unless a significant # proportion of contributors will probably not be using SublimeText # *.sublime-project # sftp configuration file sftp-config.json ### TextMate ### *.tmproj *.tmproject tmtags ### Vim ### [._]*.s[a-w][a-z] [._]s[a-w][a-z] *.un~ Session.vim .netrwhist *~ ================================================ FILE: .tern-project ================================================ { "libs": ["ecma6"], "plugins": { "doc_comment": true, "node": {} } } ================================================ FILE: package.json ================================================ { "name": "react-tvml", "version": "1.0.4", "license": "BSD", "description": "react bindings to Apple's TVJS & TVML", "author": "Sérgio Ramos ", "main": "src/react-tvml.js", "repository": { "type": "git", "url": "https://github.com/ramitos/react-tvml" }, "bugs": { "url": "https://github.com/ramitos/react-tvml/issues" }, "keywords": [ "react", "tvjs", "tvml", "tvos", "apple" ], "dependencies": { "fbjs": "0.7.0", "has-own-prop": "1.0.0", "lodash.clone": "^3.0.3", "react": "^0.14.6" }, "devDependencies": { "imports-loader": "0.6.5" } } ================================================ FILE: readme.md ================================================ # react-tvml **this is a very alpha release** React bindings to Apple's [TVJS and TVML](https://developer.apple.com/library/prerelease/tvos/navigation/) [![](http://g.recordit.co/qWrCpEb3MQ.gif)](https://cldup.com/u6sOUJLLE9.mp4) (it's not this slow, click on the gif to see a video) ## install ```bash $ npm install --save react-tvml ``` ## example [sprice/tvOS-hello-world-example](https://github.com/sprice/tvOS-hello-world-example) ## usage ```js var React = require('react'); var TVML = require('react-tvml'); var App = React.createClass({ render: function() { return ( Loading... ); } }); TVML.render(); ``` ## todo (PRs are welcome) * Most of the code is copied from the react dom renderer. A lot of it needs to be removed and cleaned according to TVML use case * push vs replace document * some events * A **lot** of polish * Validations: e.g. some components can only be children of some specific components * consistent code style and linting * tests ## license BSD ================================================ FILE: src/Component.js ================================================ 'use strict'; // TODO: validate children elements (or parents?). Ex: can only be a child of // - buttonLockup // - header // - lockup // - overlay // - row // - text // - title // TODO: validate CSS props. Only some are valid var __DEV__ = process.env.NODE_ENV !== 'production'; var CSSPropertyOperations = require('react/lib/CSSPropertyOperations'); var DOMProperty = require('react/lib/DOMProperty'); var DOMPropertyOperations = require('react/lib/DOMPropertyOperations'); // var EventConstants = require('react/lib/EventConstants'); var EventEmitter = require('./EventEmitter'); var IDOperations = require('./IDOperations'); var MultiChild = require('react/lib/ReactMultiChild'); var Perf = require('react/lib/ReactPerf'); var UpdateQueue = require('react/lib/ReactUpdateQueue'); var assign = require('react/lib/Object.assign'); var escapeTextContentForBrowser = require('react/lib/escapeTextContentForBrowser'); var invariant = require('fbjs/lib/invariant'); var isEventSupported = require('react/lib/isEventSupported'); var keyOf = require('fbjs/lib/keyOf'); var setInnerHTML = require('react/lib/setInnerHTML'); var setTextContent = require('react/lib/setTextContent'); var shallowEqual = require('fbjs/lib/shallowEqual'); var validateDOMNesting = require('react/lib/validateDOMNesting'); var warning = require('fbjs/lib/warning'); var hasOwnProperty = require('has-own-prop'); var Mount = require('./Mount'); var ELEMENT_NODE_TYPE = 1; // For quickly matching children type, to test if can be treated as content. var CONTENT_TYPES = { 'string': true, 'number': true }; var STYLE = keyOf({ 'style': null }); var COMPONENT_CLASSES = { menubar: require('./menuBar') }; function getDeclarationErrorAddendum(internalInstance) { if (internalInstance) { var owner = internalInstance._currentElement._owner || null; if (owner) { var name = owner.getName(); if (name) { return ' This DOM node was rendered by `' + name + '`.'; } } } return ''; } function legacyGetDOMNode() { if (__DEV__) { var component = this._reactInternalComponent; warning( false, 'ReactTVMLComponent: Do not access .getDOMNode() of a DOM node; ' + 'instead, use the node directly.%s', getDeclarationErrorAddendum(component) ); } return this; } function legacyIsMounted() { var component = this._reactInternalComponent; if (__DEV__) { warning( false, 'ReactTVMLComponent: Do not access .isMounted() of a DOM node.%s', getDeclarationErrorAddendum(component) ); } return !!component; } function legacySetStateEtc() { if (__DEV__) { var component = this._reactInternalComponent; warning( false, 'ReactTVMLComponent: Do not access .setState(), .replaceState(), or ' + '.forceUpdate() of a DOM node. This is a no-op.%s', getDeclarationErrorAddendum(component) ); } } function legacySetProps(partialProps, callback) { var component = this._reactInternalComponent; if (__DEV__) { warning( false, 'ReactTVMLComponent: Do not access .setProps() of a DOM node. ' + 'Instead, call ReactDOM.render again at the top level.%s', getDeclarationErrorAddendum(component) ); } if (!component) { return; } UpdateQueue.enqueueSetPropsInternal(component, partialProps); if (callback) { UpdateQueue.enqueueCallbackInternal(component, callback); } } function legacyReplaceProps(partialProps, callback) { var component = this._reactInternalComponent; if (__DEV__) { warning( false, 'ReactTVMLComponent: Do not access .replaceProps() of a DOM node. ' + 'Instead, call ReactDOM.render again at the top level.%s', getDeclarationErrorAddendum(component) ); } if (!component) { return; } UpdateQueue.enqueueReplacePropsInternal(component, partialProps); if (callback) { UpdateQueue.enqueueCallbackInternal(component, callback); } } var styleMutationWarning = {}; function checkAndWarnForMutatedStyle(style1, style2, component) { if (style1 == null || style2 == null) { return; } if (shallowEqual(style1, style2)) { return; } var componentName = component._tag; var owner = component._currentElement._owner; var ownerName; if (owner) { ownerName = owner.getName(); } var hash = ownerName + '|' + componentName; if (hasOwnProperty(styleMutationWarning, hash)) { return; } styleMutationWarning[hash] = true; warning( false, '`%s` was passed a style object that has previously been mutated. ' + 'Mutating `style` is deprecated. Consider cloning it beforehand. Check ' + 'the `render` %s. Previous style: %s. Mutated style: %s.', componentName, owner ? 'of `' + ownerName + '`' : 'using <' + componentName + '>', JSON.stringify(style1), JSON.stringify(style2) ); } /** * @param {object} component * @param {?object} props */ function assertValidProps(component, props) { if (!props) { return; } // Note the use of `==` which checks for null or undefined. if (__DEV__) { if (voidElementTags[component._tag]) { warning( props.children == null && props.dangerouslySetInnerHTML == null, '%s is a void element tag and must not have `children` or ' + 'use `props.dangerouslySetInnerHTML`.%s', component._tag, component._currentElement._owner ? ' Check the render method of ' + component._currentElement._owner.getName() + '.' : '' ); } } if (props.dangerouslySetInnerHTML != null) { invariant( props.children == null, 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.' ); invariant( typeof props.dangerouslySetInnerHTML === 'object' && '__html' in props.dangerouslySetInnerHTML, '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' + 'Please visit https://fb.me/react-invariant-dangerously-set-inner-html ' + 'for more information.' ); } if (__DEV__) { warning( props.innerHTML == null, 'Directly setting property `innerHTML` is not permitted. ' + 'For more information, lookup documentation on `dangerouslySetInnerHTML`.' ); warning( !props.contentEditable || props.children == null, 'A component is `contentEditable` and contains `children` managed by ' + 'React. It is now your responsibility to guarantee that none of ' + 'those nodes are unexpectedly modified or duplicated. This is ' + 'probably not intentional.' ); } invariant( props.style == null || typeof props.style === 'object', 'The `style` prop expects a mapping from style properties to values, ' + 'not a string. For example, style={{marginRight: spacing + \'em\'}} when ' + 'using JSX.%s', getDeclarationErrorAddendum(component) ); } function enqueuePutListener(id, registrationName, listener, transaction) { var container = Mount.findReactContainerForID(id); if (container) { var doc = container.nodeType === ELEMENT_NODE_TYPE ? container.ownerDocument : container; EventEmitter.listenTo(registrationName, doc); } transaction.getReactMountReady().enqueue(putListener, { id: id, registrationName: registrationName, listener: listener }); } function putListener(id, registrationName, listener, transaction) { // TODO check if tvml registers events the same way as listenTo // TODO I'm not sure we can register events in `document` and delegate EventEmitter.EventEmitter.listenTo(registrationName, Mount.findReactContainerForID(id)); transaction.getReactMountReady().enqueue(putListener, { id: id, registrationName: registrationName, listener: listener, }); // if (container) { // var doc = container.nodeType === ELEMENT_NODE_TYPE ? // container.ownerDocument : // container; // EventEmitter.listenTo(registrationName, doc); // } } function putListener() { var listenerToPut = this; EventEmitter.putListener( listenerToPut.id, listenerToPut.registrationName, listenerToPut.listener ); } // There are so many media events, it makes sense to just // maintain a list rather than create a `trapBubbledEvent` for each var mediaEvents = { // topAbort: 'abort', // topCanPlay: 'canplay', // topCanPlayThrough: 'canplaythrough', // topDurationChange: 'durationchange', // topEmptied: 'emptied', // topEncrypted: 'encrypted', // topEnded: 'ended', // topError: 'error', // topLoadedData: 'loadeddata', // topLoadedMetadata: 'loadedmetadata', // topLoadStart: 'loadstart', // topPause: 'pause', // topPlay: 'play', // topPlaying: 'playing', // topProgress: 'progress', // topRateChange: 'ratechange', // topSeeked: 'seeked', // topSeeking: 'seeking', // topStalled: 'stalled', // topSuspend: 'suspend', // topTimeUpdate: 'timeupdate', // topVolumeChange: 'volumechange', // topWaiting: 'waiting', }; function postUpdateSelectWrapper() { DOMSelect.postUpdateWrapper(this); } // For HTML, certain tags should omit their close tag. We keep a whitelist for // those special cased tags. var omittedCloseTags = { 'badge': true, 'decorationImage': true, 'fullscreenImg': true, 'heroImg': true, 'img': true, 'ratingBadge': true, 'asset': true, 'monogram': true }; var newlineEatingTags = { // 'listing': true, // 'pre': true, // 'textarea': true, }; // For HTML, certain tags cannot have children. This has the same purpose as // `omittedCloseTags` except that `menuitem` should still have its closing tag. var voidElementTags = assign({ }, omittedCloseTags); // We accept any tag to be rendered but since this gets injected into arbitrary // HTML, we want to make sure that it's a safe tag. // http://www.w3.org/TR/REC-xml/#NT-Name var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset var validatedTagCache = {}; function validateDangerousTag(tag) { if (!hasOwnProperty(validatedTagCache, tag)) { invariant(VALID_TAG_REGEX.test(tag), 'Invalid tag: %s', tag); validatedTagCache[tag] = true; } } function processChildContext(context, inst) { if (__DEV__) { // Pass down our tag name to child components for validation purposes context = assign({}, context); var info = context[validateDOMNesting.ancestorInfoContextKey]; context[validateDOMNesting.ancestorInfoContextKey] = validateDOMNesting.updatedAncestorInfo(info, inst._tag, inst); } return context; } function isCustomComponent(tagName, props) { return tagName.indexOf('-') >= 0 || props.is != null; } /** * 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. * * @constructor ReactTVMLComponent * @extends ReactMultiChild */ function ReactTVMLComponent(tag) { validateDangerousTag(tag); this._tag = tag.toLowerCase(); this._renderedChildren = null; this._previousStyle = null; this._previousStyleCopy = null; this._rootNodeID = null; this._wrapperState = null; this._topLevelWrapper = null; this._nodeWithLegacyProperties = null; } ReactTVMLComponent.displayName = 'ReactTVMLComponent'; ReactTVMLComponent.Mixin = { construct: function(element) { this._currentElement = element; }, /** * 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|ReactServerRenderingTransaction} transaction * @param {object} context * @return {string} The computed markup. */ mountComponent: function(rootID, transaction, context) { this._rootNodeID = rootID; var props = this._currentElement.props; if (hasOwnProperty(COMPONENT_CLASSES, this._tag)) { if (hasOwnProperty(COMPONENT_CLASSES[this._tag], 'getNativeProps')) { props = COMPONENT_CLASSES[this._tag].getNativeProps(this, props, context); } } assertValidProps(this, props); if (__DEV__) { if (context[validateDOMNesting.ancestorInfoContextKey]) { validateDOMNesting( this._tag, this, context[validateDOMNesting.ancestorInfoContextKey] ); } } var mountImage; // isn't useCreateElement always false? if (transaction.useCreateElement) { var ownerDocument = context[Mount.ownerDocumentContextKey]; var el = ownerDocument.createElement(this._currentElement.type); DOMPropertyOperations.setAttributeForID(el, this._rootNodeID); // Populate node cache Mount.getID(el); this._updateDOMProperties({}, props, transaction, el); this._createInitialChildren(transaction, props, context, el); mountImage = el; } else { var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props); var tagContent = this._createContentMarkup(transaction, props, context); if (!tagContent && omittedCloseTags[this._tag]) { mountImage = tagOpen + '/>'; } else { mountImage = tagOpen + '>' + tagContent + ''; } } return mountImage; }, /** * 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 * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction * @param {object} props * @return {string} Markup of opening tag. */ _createOpenTagMarkupAndPutListeners: function(transaction, props) { var ret = '<' + this._currentElement.type; for (var propKey in props) { if (!hasOwnProperty(props, propKey)) { continue; } var propValue = props[propKey]; if (propValue == null) { continue; } if (hasOwnProperty(EventEmitter.registrationNameModules, propKey)) { enqueuePutListener(this._rootNodeID, propKey, propValue, transaction); } else { if (propKey === STYLE) { if (propValue) { if (__DEV__) { // See `_updateDOMProperties`. style block this._previousStyle = propValue; } propValue = this._previousStyleCopy = assign({}, props.style); } propValue = CSSPropertyOperations.createMarkupForStyles(propValue); } var markup = null; if (this._tag != null && isCustomComponent(this._tag, props)) { markup = DOMPropertyOperations.createMarkupForCustomAttribute(propKey, propValue); } else { markup = DOMPropertyOperations.createMarkupForProperty(propKey, propValue); } if (markup) { ret += ' ' + markup; } } } // For static pages, no need to put React ID and checksum. Saves lots of // bytes. if (transaction.renderToStaticMarkup) { return ret; } var markupForID = DOMPropertyOperations.createMarkupForID(this._rootNodeID); return ret + ' ' + markupForID; }, /** * Creates markup for the content between the tags. * * @private * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction * @param {object} props * @param {object} context * @return {string} Content markup. */ _createContentMarkup: function(transaction, props, context) { var ret = ''; // Intentional use of != to avoid catching zero/false. var innerHTML = props.dangerouslySetInnerHTML; if (innerHTML != null) { if (innerHTML.__html != null) { ret = innerHTML.__html; } } else { var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null; var childrenToUse = contentToUse != null ? null : props.children; if (contentToUse != null) { // TODO: Validate that text is allowed as a child of this node ret = escapeTextContentForBrowser(contentToUse); } else if (childrenToUse != null) { var mountImages = this.mountChildren( childrenToUse, transaction, processChildContext(context, this) ); ret = mountImages.join(''); } } if (newlineEatingTags[this._tag] && ret.charAt(0) === '\n') { // text/html ignores the first character in these tags if it's a newline // Prefer to break application/xml over text/html (for now) by adding // a newline specifically to get eaten by the parser. (Alternately for // textareas, replacing "^\n" with "\r\n" doesn't get eaten, and the first // \r is normalized out by HTMLTextAreaElement#value.) // See: // See: // See: // See: Parsing of "textarea" "listing" and "pre" elements // from return '\n' + ret; } else { return ret; } }, _createInitialChildren: function(transaction, props, context, el) { // Intentional use of != to avoid catching zero/false. var innerHTML = props.dangerouslySetInnerHTML; if (innerHTML != null) { if (innerHTML.__html != null) { setInnerHTML(el, innerHTML.__html); } } else { var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null; var childrenToUse = contentToUse != null ? null : props.children; if (contentToUse != null) { // TODO: Validate that text is allowed as a child of this node setTextContent(el, contentToUse); } else if (childrenToUse != null) { var mountImages = this.mountChildren( childrenToUse, transaction, processChildContext(context, this) ); for (var i = 0; i < mountImages.length; i++) { el.appendChild(mountImages[i]); } } } }, /** * Receives a next element and updates the component. * * @internal * @param {ReactElement} nextElement * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction * @param {object} context */ receiveComponent: function(nextElement, transaction, context) { var prevElement = this._currentElement; this._currentElement = nextElement; this.updateComponent(transaction, prevElement, nextElement, context); }, /** * 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 {ReactElement} prevElement * @param {ReactElement} nextElement * @internal * @overridable */ updateComponent: function(transaction, prevElement, nextElement, context) { var lastProps = prevElement.props; var nextProps = this._currentElement.props; if (hasOwnProperty(COMPONENT_CLASSES, this._tag)) { if (hasOwnProperty(COMPONENT_CLASSES[this._tag], 'getNativeProps')) { lastProps = COMPONENT_CLASSES[this._tag].getNativeProps(this, lastProps); nextProps = COMPONENT_CLASSES[this._tag].getNativeProps(this, nextProps); } } assertValidProps(this, nextProps); this._updateDOMProperties(lastProps, nextProps, transaction, null); this._updateDOMChildren( lastProps, nextProps, transaction, processChildContext(context, this) ); }, /** * 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 * @param {object} nextProps * @param {ReactReconcileTransaction} transaction * @param {?DOMElement} node */ _updateDOMProperties: function(lastProps, nextProps, transaction, node) { var propKey; var styleName; var styleUpdates; for (propKey in lastProps) { if (hasOwnProperty(nextProps, propKey) || !hasOwnProperty(lastProps, propKey)) { continue; } if (propKey === STYLE) { var lastStyle = this._previousStyleCopy; for (styleName in lastStyle) { if (hasOwnProperty(lastStyle, styleName)) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = ''; } } this._previousStyleCopy = null; } else if (hasOwnProperty(EventEmitter.registrationNameModules, propKey)) { if (lastProps[propKey]) { // Only call deleteListener if there was a listener previously or // else willDeleteListener gets called when there wasn't actually a // listener (e.g., onClick={null}) EventEmitter.deleteListener(this._rootNodeID, propKey); } } else if ( DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) { if (!node) { node = Mount.getNode(this._rootNodeID); } DOMPropertyOperations.deleteValueForProperty(node, propKey); } } for (propKey in nextProps) { var nextProp = nextProps[propKey]; var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps[propKey]; if (!hasOwnProperty(nextProps, propKey) || nextProp === lastProp) { continue; } if (propKey === STYLE) { if (nextProp) { if (__DEV__) { checkAndWarnForMutatedStyle( this._previousStyleCopy, this._previousStyle, this ); this._previousStyle = nextProp; } nextProp = this._previousStyleCopy = assign({}, nextProp); } else { this._previousStyleCopy = null; } if (lastProp) { // Unset styles on `lastProp` but not on `nextProp`. for (styleName in lastProp) { if (hasOwnProperty(lastProp, styleName) && (!nextProp || !hasOwnProperty(nextProp, styleName))) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = ''; } } // Update styles that changed since `lastProp`. for (styleName in nextProp) { if (hasOwnProperty(nextProp, styleName) && lastProp[styleName] !== nextProp[styleName]) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = nextProp[styleName]; } } } else { // Relies on `updateStylesByID` not mutating `styleUpdates`. styleUpdates = nextProp; } } else if (hasOwnProperty(propKey)) { if (nextProp) { enqueuePutListener(this._rootNodeID, propKey, nextProp, transaction); } else if (lastProp) { EventEmitter.deleteListener(this._rootNodeID, propKey); } } else if (isCustomComponent(this._tag, nextProps)) { if (!node) { node = Mount.getNode(this._rootNodeID); } DOMPropertyOperations.setValueForAttribute( node, propKey, nextProp ); } else if ( DOMProperty.properties[propKey] || DOMProperty.isCustomAttribute(propKey)) { if (!node) { node = Mount.getNode(this._rootNodeID); } // 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 (nextProp != null) { DOMPropertyOperations.setValueForProperty(node, propKey, nextProp); } else { DOMPropertyOperations.deleteValueForProperty(node, propKey); } } } if (styleUpdates) { if (!node) { node = Mount.getNode(this._rootNodeID); } CSSPropertyOperations.setValueForStyles(node, styleUpdates); } }, /** * Reconciles the children with the various properties that affect the * children content. * * @param {object} lastProps * @param {object} nextProps * @param {ReactReconcileTransaction} transaction * @param {object} context */ _updateDOMChildren: function(lastProps, nextProps, transaction, context) { var lastContent = CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null; var nextContent = CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null; var lastHtml = lastProps.dangerouslySetInnerHTML && lastProps.dangerouslySetInnerHTML.__html; var nextHtml = nextProps.dangerouslySetInnerHTML && nextProps.dangerouslySetInnerHTML.__html; // Note the use of `!=` which checks for null or undefined. var lastChildren = lastContent != null ? null : lastProps.children; var nextChildren = nextContent != null ? null : nextProps.children; // If we're switching from children to content/html or vice versa, remove // the old content var lastHasContentOrHtml = lastContent != null || lastHtml != null; var nextHasContentOrHtml = nextContent != null || nextHtml != null; if (lastChildren != null && nextChildren == null) { this.updateChildren(null, transaction, context); } else if (lastHasContentOrHtml && !nextHasContentOrHtml) { this.updateTextContent(''); } if (nextContent != null) { if (lastContent !== nextContent) { this.updateTextContent('' + nextContent); } } else if (nextHtml != null) { if (lastHtml !== nextHtml) { this.updateMarkup('' + nextHtml); } } else if (nextChildren != null) { this.updateChildren(nextChildren, transaction, context); } }, /** * Destroys all event registrations for this instance. Does not remove from * the DOM. That must be done by the parent. * * @internal */ unmountComponent: function() { this.unmountChildren(); EventEmitter.deleteAllListeners(this._rootNodeID); IDOperations.unmountIDFromEnvironment(this._rootNodeID); this._rootNodeID = null; this._wrapperState = null; if (this._nodeWithLegacyProperties) { var node = this._nodeWithLegacyProperties; node._reactInternalComponent = null; this._nodeWithLegacyProperties = null; } }, getPublicInstance: function() { if (this._nodeWithLegacyProperties) { return this._nodeWithLegacyProperties; } var node = Mount.getNode(this._rootNodeID); node._reactInternalComponent = this; node.getDOMNode = legacyGetDOMNode; node.isMounted = legacyIsMounted; node.setState = legacySetStateEtc; node.replaceState = legacySetStateEtc; node.forceUpdate = legacySetStateEtc; node.setProps = legacySetProps; node.replaceProps = legacyReplaceProps; // updateComponent will update this property on subsequent renders node.props = this._currentElement.props; this._nodeWithLegacyProperties = node; } }; Perf.measureMethods(ReactTVMLComponent, 'ReactTVMLComponent', { mountComponent: 'mountComponent', updateComponent: 'updateComponent' }); assign( ReactTVMLComponent.prototype, ReactTVMLComponent.Mixin, MultiChild.Mixin ); module.exports = ReactTVMLComponent; ================================================ FILE: src/EventEmitter.js ================================================ // For components like lockup, listItemLockup you can expect select, play, highlight, holdselect. For Buttons only select and play events are fired. For text fields change event is sent. 'use strict'; var EventPluginHub = require('react/lib//EventPluginHub'); var EventPluginRegistry = require('react/lib//EventPluginRegistry'); var ReactEventEmitterMixin = require('react/lib//ReactEventEmitterMixin'); var EventListener = require('./EventListener'); var assign = require('react/lib/Object.assign'); var hasOwnProperty = require('has-own-prop'); /** * Summary of `EventEmitter` event handling: * * - Top-level delegation is used to trap most native browser events. This * may only occur in the main thread and is the responsibility of * EventListener, which is injected and can therefore support pluggable * event sources. This is the only work that occurs in the main thread. * * - We normalize and de-duplicate events to account for browser quirks. This * may be done in the worker thread. * * - 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 | . * +------------+ . * | . * v . * +------------+ . * | ReactEvent | . * | Listener | . * +------------+ . +-----------+ * | . +--------+|SimpleEvent| * | . | |Plugin | * +-----|------+ . v +-----------+ * | | | . +--------------+ +------------+ * | +-----------.--->|EventPluginHub| | Event | * | | . | | +-----------+ | Propagators| * | ReactEvent | . | | |TapEvent | |------------| * | Emitter | . | |<---+|Plugin | |other plugin| * | | . | | +-----------+ | utilities | * | +-----------.--->| | +------------+ * | | | . +--------------+ * +-----|------+ . ^ +-----------+ * | . | |Enter/Leave| * + . +-------+|Plugin | * +-------------+ . +-----------+ * | application | . * |-------------| . * | | . * | | . * +-------------+ . * . * React Core . General Purpose Event Plugin System */ var alreadyListeningTo = {}; var reactTopListenersCounter = 0; // For events like 'submit' which don't consistently bubble (which we trap at a // lower node than `document`), binding at `document` would cause duplicate // events so we don't include them here var topEventMapping = { topSelect: 'select', topPlay: 'play', topHighlight: 'highlight', topHoldselect: 'holdselect' }; /** * To ensure no conflicts with other potential React instances on the page */ var topListenersIDKey = '_reactListenersID' + String(Math.random()).slice(2); function getListeningForDocument(mountAt) { if (!hasOwnProperty(mountAt, topListenersIDKey)) { mountAt[topListenersIDKey] = reactTopListenersCounter + 1; alreadyListeningTo[mountAt[topListenersIDKey]] = {}; } return alreadyListeningTo[mountAt[topListenersIDKey]]; } var getDependencies = function(name) { return EventPluginRegistry.registrationNameDependencies[name]; }; /** * `EventEmitter` is used to attach top-level event listeners. For * example: * * EventEmitter.putListener('myID', 'onClick', myFunction); * * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'. * * @internal */ var EventEmitter = assign({}, ReactEventEmitterMixin, { /** * Sets whether or not any created callbacks should be enabled. * * @param {boolean} enabled True if callbacks should be enabled. */ setEnabled: function(enabled) { EventListener.setEnabled(enabled); }, /** * @return {boolean} True if callbacks are enabled. */ isEnabled: function() { return EventListener.isEnabled(); }, /** * We listen for bubbled touch events on the document object. * * * @param {string} registrationName Name of listener (e.g. `onSelect`). * @param {object} mountAt Document which owns the container */ listenTo: function(registrationName, mountAt) { var isListening = getListeningForDocument(mountAt); var dependencies = getDependencies(registrationName); dependencies.forEach(function(dependency) { if ((hasOwnProperty(isListening, dependency) && isListening[dependency])) { return; } if (hasOwnProperty(topEventMapping, dependency)) { EventEmitter.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt); } isListening[dependency] = true; }); }, trapBubbledEvent: function(topLevelType, handlerBaseName, handle) { return EventListener.trapBubbledEvent(topLevelType, handlerBaseName, handle); }, trapCapturedEvent: function(topLevelType, handlerBaseName, handle) { return EventListener.trapCapturedEvent(topLevelType, handlerBaseName, handle); }, eventNameDispatchConfigs: EventPluginHub.eventNameDispatchConfigs, registrationNameModules: EventPluginHub.registrationNameModules, putListener: EventPluginHub.putListener, getListener: EventPluginHub.getListener, deleteListener: EventPluginHub.deleteListener, deleteAllListeners: EventPluginHub.deleteAllListeners }); module.exports = EventEmitter; ================================================ FILE: src/EventListener.js ================================================ 'use strict'; var EventListener = require('fbjs/lib/EventListener'); var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); var PooledClass = require('react/lib/PooledClass'); var ReactInstanceHandles = require('react/lib/ReactInstanceHandles'); var ReactMount = require('./Mount'); var ReactUpdates = require('react/lib/ReactUpdates'); var assign = require('react/lib/Object.assign'); var getEventTarget = require('react/lib/getEventTarget'); var getUnboundedScrollPosition = require('fbjs/lib/getUnboundedScrollPosition'); var DOCUMENT_FRAGMENT_NODE_TYPE = 11; /** * Finds the parent React component of `node`. * * @param {*} node * @return {?DOMEventTarget} Parent container, or `null` if the specified node * is not nested. */ function findParent(node) { // TODO: It may be a good idea to cache this to prevent unnecessary DOM // traversal, but caching is difficult to do correctly without using a // mutation observer to listen for all DOM changes. var nodeID = ReactMount.getID(node); var rootID = ReactInstanceHandles.getReactRootIDFromNodeID(nodeID); var container = ReactMount.findReactContainerForID(rootID); var parent = ReactMount.getFirstReactDOM(container); return parent; } // Used to store ancestor hierarchy in top level callback function TopLevelCallbackBookKeeping(topLevelType, nativeEvent) { this.topLevelType = topLevelType; this.nativeEvent = nativeEvent; this.ancestors = []; } assign(TopLevelCallbackBookKeeping.prototype, { destructor: function () { this.topLevelType = null; this.nativeEvent = null; this.ancestors.length = 0; } }); PooledClass.addPoolingTo(TopLevelCallbackBookKeeping, PooledClass.twoArgumentPooler); function handleTopLevelImpl(bookKeeping) { // TODO: Re-enable event.path handling // // if (bookKeeping.nativeEvent.path && bookKeeping.nativeEvent.path.length > 1) { // // New browsers have a path attribute on native events // handleTopLevelWithPath(bookKeeping); // } else { // // Legacy browsers don't have a path attribute on native events // handleTopLevelWithoutPath(bookKeeping); // } void handleTopLevelWithPath; // temporarily unused handleTopLevelWithoutPath(bookKeeping); } // Legacy browsers don't have a path attribute on native events function handleTopLevelWithoutPath(bookKeeping) { var topLevelTarget = ReactMount.getFirstReactDOM(getEventTarget(bookKeeping.nativeEvent)) || window; // Loop through the hierarchy, in case there's any nested components. // It's important that we build the array of ancestors before calling any // event handlers, because event handlers can modify the DOM, leading to // inconsistencies with ReactMount's node cache. See #1105. var ancestor = topLevelTarget; while (ancestor) { bookKeeping.ancestors.push(ancestor); ancestor = findParent(ancestor); } for (var i = 0; i < bookKeeping.ancestors.length; i++) { topLevelTarget = bookKeeping.ancestors[i]; var topLevelTargetID = ReactMount.getID(topLevelTarget) || ''; ReactEventListener._handleTopLevel(bookKeeping.topLevelType, topLevelTarget, topLevelTargetID, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent)); } } // New browsers have a path attribute on native events function handleTopLevelWithPath(bookKeeping) { var path = bookKeeping.nativeEvent.path; var currentNativeTarget = path[0]; var eventsFired = 0; for (var i = 0; i < path.length; i++) { var currentPathElement = path[i]; if (currentPathElement.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE) { currentNativeTarget = path[i + 1]; } // TODO: slow var reactParent = ReactMount.getFirstReactDOM(currentPathElement); if (reactParent === currentPathElement) { var currentPathElementID = ReactMount.getID(currentPathElement); var newRootID = ReactInstanceHandles.getReactRootIDFromNodeID(currentPathElementID); bookKeeping.ancestors.push(currentPathElement); var topLevelTargetID = ReactMount.getID(currentPathElement) || ''; eventsFired++; ReactEventListener._handleTopLevel(bookKeeping.topLevelType, currentPathElement, topLevelTargetID, bookKeeping.nativeEvent, currentNativeTarget); // Jump to the root of this React render tree while (currentPathElementID !== newRootID) { i++; currentPathElement = path[i]; currentPathElementID = ReactMount.getID(currentPathElement); } } } if (eventsFired === 0) { ReactEventListener._handleTopLevel(bookKeeping.topLevelType, window, '', bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent)); } } function scrollValueMonitor(cb) { var scrollPosition = getUnboundedScrollPosition(window); cb(scrollPosition); } var ReactEventListener = { _enabled: true, _handleTopLevel: null, WINDOW_HANDLE: ExecutionEnvironment.canUseDOM ? window : null, setHandleTopLevel: function (handleTopLevel) { ReactEventListener._handleTopLevel = handleTopLevel; }, setEnabled: function (enabled) { ReactEventListener._enabled = !!enabled; }, isEnabled: function () { return ReactEventListener._enabled; }, /** * Traps top-level events by using event bubbling. * * @param {string} topLevelType Record from `EventConstants`. * @param {string} handlerBaseName Event name (e.g. "click"). * @param {object} handle Element on which to attach listener. * @return {?object} An object with a remove function which will forcefully * remove the listener. * @internal */ trapBubbledEvent: function (topLevelType, handlerBaseName, handle) { var element = handle; if (!element) { return null; } return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, 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 {object} handle Element on which to attach listener. * @return {?object} An object with a remove function which will forcefully * remove the listener. * @internal */ trapCapturedEvent: function (topLevelType, handlerBaseName, handle) { var element = handle; if (!element) { return null; } return EventListener.capture(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType)); }, dispatchEvent: function (topLevelType, nativeEvent) { if (!ReactEventListener._enabled) { return; } var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent); try { // Event queue being processed in the same cycle allows // `preventDefault`. ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping); } finally { TopLevelCallbackBookKeeping.release(bookKeeping); } } }; module.exports = ReactEventListener; ================================================ FILE: src/EventPlugin.js ================================================ 'use strict'; var EventPropagators = require('react/lib/EventPropagators'); var SyntheticEvent = require('react/lib/SyntheticEvent'); var keyOf = require('fbjs/lib/keyOf'); var eventTypes = { select: { phasedRegistrationNames: { bubbled: keyOf({ onSelect: true }), captured: keyOf({ onSelectCapture: true }) } }, play: { phasedRegistrationNames: { bubbled: keyOf({ onPlay: true }), captured: keyOf({ onPlayCapture: true }) } }, highlight: { phasedRegistrationNames: { bubbled: keyOf({ onHighlight: true }), captured: keyOf({ onHighlightCapture: true }) } }, holdSelect: { phasedRegistrationNames: { bubbled: keyOf({ onHoldSelect: true }), captured: keyOf({ onHoldSelectCapture: true }) } } }; var topLevelEventsToDispatchConfig = { topSelect: eventTypes.select, topPlay: eventTypes.play, topHighlight: eventTypes.highlight, topHoldSelect: eventTypes.holdSelect }; for (var type in topLevelEventsToDispatchConfig) { topLevelEventsToDispatchConfig[type].dependencies = [type]; } var SimpleEventPlugin = { 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, nativeEventTarget) { var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType]; if (!dispatchConfig) { return null; } var ev = SyntheticEvent.getPooled(dispatchConfig, topLevelTargetID, nativeEvent, nativeEventTarget); EventPropagators.accumulateTwoPhaseDispatches(ev); return ev; } }; module.exports = SimpleEventPlugin; ================================================ FILE: src/IDOperations.js ================================================ 'use strict'; var DOMChildrenOperations = require('react/lib/DOMChildrenOperations'); var DOMPropertyOperations = require('react/lib/DOMPropertyOperations'); var ReactPerf = require('react/lib/ReactPerf'); var Mount = require('./Mount'); var invariant = require('fbjs/lib/invariant'); /** * Operations used to process updates to DOM nodes. */ var ReactTVMLIDOperations = { /** * 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} */ replaceNodeWithMarkupByID: function(id, markup) { var node = Mount.getNode(id); DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup); }, /** * Updates a component's children by processing a series of updates. * * @param {array} updates List of update configurations. * @param {array} markup List of markup strings. * @internal */ processChildrenUpdates: function(updates, markup) { DOMChildrenOperations.processUpdates(updates.map(function(update) { update.parentNode = Mount.getNode(update.parentID); return update; }), markup); }, /** * 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) { Mount.purgeID(rootNodeID); } }; ReactPerf.measureMethods(ReactTVMLIDOperations, 'ReactTVMLIDOperations', { updatePropertyByID: 'updatePropertyByID', replaceNodeWithMarkupByID: 'replaceNodeWithMarkupByID', processChildrenUpdates: 'processChildrenUpdates' }); module.exports = ReactTVMLIDOperations; ================================================ FILE: src/Mount.js ================================================ 'use strict'; var __DEV__ = false; // process.env.NODE_ENV !== 'production'; var DOMProperty = require('react/lib/DOMProperty'); var ReactCurrentOwner = require('react/lib/ReactCurrentOwner'); var ReactDOMFeatureFlags = require('react/lib/ReactDOMFeatureFlags'); var ReactElement = require('react/lib/ReactElement'); var ReactEmptyComponentRegistry = require('react/lib/ReactEmptyComponentRegistry'); var ReactInstanceHandles = require('react/lib/ReactInstanceHandles'); var ReactInstanceMap = require('react/lib/ReactInstanceMap'); var ReactMarkupChecksum = require('react/lib/ReactMarkupChecksum'); var ReactPerf = require('react/lib/ReactPerf'); var ReactReconciler = require('react/lib/ReactReconciler'); var ReactUpdateQueue = require('react/lib/ReactUpdateQueue'); var ReactUpdates = require('react/lib/ReactUpdates'); var assign = require('react/lib/Object.assign'); var emptyObject = require('fbjs/lib/emptyObject'); var containsNode = require('fbjs/lib/containsNode'); var instantiateReactComponent = require('./instantiateReactComponent'); var invariant = require('fbjs/lib/invariant'); var setInnerHTML = require('react/lib/setInnerHTML'); var shouldUpdateReactComponent = require('react/lib/shouldUpdateReactComponent'); var validateDOMNesting = require('react/lib/validateDOMNesting'); var warning = require('fbjs/lib/warning'); var parser = new DOMParser(); var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME; var nodeCache = {}; var ELEMENT_NODE_TYPE = 1; var DOC_NODE_TYPE = 9; var DOCUMENT_FRAGMENT_NODE_TYPE = 11; var ownerDocumentContextKey = '__TVMLMount_ownerDocument$' + Math.random().toString(36).slice(2); /** Mapping from reactRootID to React component instance. */ var instancesByReactRootID = {}; /** Mapping from reactRootID to `container` nodes. */ var containersByReactRootID = {}; if (__DEV__) { /** __DEV__-only mapping from reactRootID to root elements. */ var rootElementsByReactRootID = {}; } // Used to store breadth-first search state in findComponentRoot. var findComponentRootReusableArray = []; /** * Finds the index of the first character * that's not common between the two given strings. * * @return {number} the index of the character where the strings diverge */ function firstDifferenceIndex(string1, string2) { var minLen = Math.min(string1.length, string2.length); for (var i = 0; i < minLen; i++) { if (string1.charAt(i) !== string2.charAt(i)) { return i; } } return string1.length === string2.length ? -1 : minLen; } /** * @param {DOMElement|DOMDocument} container DOM element that may contain * a React component * @return {?*} DOM element that may have the reactRoot ID, or null. */ function getReactRootElementInContainer(container) { if (!container) { return null; } return container.documentElement; } /** * @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 && TVMLMount.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), 'TVMLMount: 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] = TVMLMount.findReactNodeByID(id); } return nodeCache[id]; } /** * Finds the node with the supplied public React instance. * * @param {*} instance A public React instance. * @return {?DOMElement} DOM node with the suppled `id`. * @internal */ function getNodeFromInstance(instance) { var id = ReactInstanceMap.get(instance)._rootNodeID; if (ReactEmptyComponentRegistry.isNullComponentID(id)) { return null; } if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) { nodeCache[id] = TVMLMount.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, 'TVMLMount: Unexpected modification of `%s`', ATTR_NAME ); var container = TVMLMount.findReactContainerForID(id); if (container && containsNode(container, node)) { return true; } } 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]; } var deepestNodeSoFar = null; function findDeepestCachedAncestorImpl(ancestorID) { var ancestor = nodeCache[ancestorID]; if (ancestor && isValid(ancestor, ancestorID)) { deepestNodeSoFar = ancestor; } else { // This node isn't populated in the cache, so presumably none of its // descendants are. Break out of the loop. return false; } } /** * Return the deepest cached node whose ID is a prefix of `targetID`. */ function findDeepestCachedAncestor(targetID) { deepestNodeSoFar = null; ReactInstanceHandles.traverseAncestors( targetID, findDeepestCachedAncestorImpl ); var foundNode = deepestNodeSoFar; deepestNodeSoFar = null; return foundNode; } /** * Mounts this component and inserts it into the DOM. * * @param {ReactComponent} componentInstance The instance to mount. * @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 */ function mountComponentIntoNode( componentInstance, rootID, container, transaction, shouldReuseMarkup, context ) { if (ReactDOMFeatureFlags.useCreateElement) { context = assign({}, context); if (container.nodeType === DOC_NODE_TYPE) { context[ownerDocumentContextKey] = container; } else { context[ownerDocumentContextKey] = container.ownerDocument; } } if (__DEV__) { if (context === emptyObject) { context = {}; } var tag = container.nodeName.toLowerCase(); context[validateDOMNesting.ancestorInfoContextKey] = validateDOMNesting.updatedAncestorInfo(null, tag, null); } var markup = ReactReconciler.mountComponent( componentInstance, rootID, transaction, context ); componentInstance._renderedComponent._topLevelWrapper = componentInstance; TVMLMount._mountImageIntoNode( markup, container, shouldReuseMarkup, transaction ); } /** * Batched mount. * * @param {ReactComponent} componentInstance The instance to mount. * @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 */ function batchedMountComponentIntoNode( componentInstance, rootID, container, shouldReuseMarkup, context ) { var transaction = ReactUpdates.ReactReconcileTransaction.getPooled( /* forceHTML */ shouldReuseMarkup ); transaction.perform( mountComponentIntoNode, null, componentInstance, rootID, container, transaction, shouldReuseMarkup, context ); ReactUpdates.ReactReconcileTransaction.release(transaction); } /** * 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 {TVMLMount.unmountComponentAtNode} */ function unmountComponentFromNode(instance, container) { ReactReconciler.unponent(instance); if (container.nodeType === DOC_NODE_TYPE) { container = container.documentElement; } // http://jsperf.com/emptying-a-node while (container.lastChild) { container.removeChild(container.lastChild); } } /** * True if the supplied DOM node has a direct React-rendered child that is * not a React root element. Useful for warning in `render`, * `unmountComponentAtNode`, etc. * * @param {?DOMElement} node The candidate DOM node. * @return {boolean} True if the DOM element contains a direct child that was * rendered by React but is not a root element. * @internal */ function hasNonRootReactChild(node) { var reactRootID = getReactRootID(node); return reactRootID ? reactRootID !== ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false; } /** * Returns the first (deepest) ancestor of a node which is rendered by this copy * of React. */ function findFirstReactDOMImpl(node) { // This node might be from another React instance, so we make sure not to // examine the node cache here for (; node && node.parentNode !== node; node = node.parentNode) { var nodeID = internalGetID(node); if (!nodeID) { continue; } var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(nodeID); // If containersByReactRootID contains the container we find by crawling up // the tree, we know that this instance of React rendered the node. // nb. isValid's strategy (with containsNode) does not work because render // trees may be nested and we don't want a false positive in that case. var current = node; var lastID; do { lastID = internalGetID(current); current = current.parentNode; if (current == null) { // The passed-in node has been detached from the container it was // originally rendered into. return null; } } while (lastID !== reactRootID); var expected = containersByReactRootID[reactRootID]; if (( expected.nodeType === DOC_NODE_TYPE && current.nodeType === ELEMENT_NODE_TYPE && current.ownerDocument === expected )) { return node; } if (current === expected) { return node; } } return null; } /** * Temporary (?) hack so that we can store all top-level pending updates on * composites instead of having to worry about different types of components * here. */ var TopLevelWrapper = function() {}; TopLevelWrapper.isReactClass = {}; if (__DEV__) { TopLevelWrapper.displayName = 'TopLevelWrapper'; } TopLevelWrapper.prototype.render = function() { // this.props is actually a ReactElement return this.props; }; /** * Mounting is the process of initializing a React component by creating its * representative DOM elements and inserting them into a supplied `container`. * Any prior content inside `container` is destroyed in the process. * * TVMLMount.render( * component, * document.getElementById('container') * ); * *
<-- Supplied `container`. *
<-- Rendered reactRoot of React * // ... component. *
*
* * Inside of `container`, the first element rendered is the "reactRoot". */ var TVMLMount = { /** Exposed for debugging purposes **/ _instancesByReactRootID: instancesByReactRootID, generateEmptyContainer: function () { return parser.parseFromString('', 'text/xml'); }, /** * Take a component that's already mounted into the DOM and replace its props * @param {ReactComponent} prevComponent component instance already in the DOM * @param {ReactElement} nextElement component instance to render * @param {DOMElement} container container to render into * @param {?function} callback function triggered on completion */ _updateRootComponent: function( prevComponent, nextElement, container, callback) { TVMLMount.scrollMonitor(container, function() { ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement); if (callback) { ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); } }); if (__DEV__) { // 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 starts scroll value * monitoring * @param {ReactComponent} nextComponent component instance to render * @param {DOMElement} container container to render into * @return {string} reactRoot ID prefix */ _registerComponent: function(nextComponent, container) { invariant( container && ( container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE ), '_registerComponent(...): Target container is not a DOM element.' ); var reactRootID = TVMLMount.registerContainer(container); instancesByReactRootID[reactRootID] = nextComponent; return reactRootID; }, /** * Render a new component into the DOM. * @param {ReactElement} nextElement element to render * @param {DOMElement} container container to render into * @param {boolean} shouldReuseMarkup if we should skip the markup insertion * @return {ReactComponent} nextComponent */ _renderNewRootComponent: function( nextElement, container, shouldReuseMarkup, context ) { // Various parts of our code (such as ReactCompositeComponent's // _renderValidatedComponent) assume that calls to render aren't nested; // verify that that's the case. warning( ReactCurrentOwner.current == null, '_renderNewRootComponent(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from ' + 'render is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent' ); var componentInstance = instantiateReactComponent(nextElement, null); var reactRootID = TVMLMount._registerComponent( componentInstance, container ); // The initial render is synchronous but any updates that happen during // rendering, in componentWillMount or componentDidMount, will be batched // according to the current batching strategy. ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, componentInstance, reactRootID, container, shouldReuseMarkup, context ); if (__DEV__) { // Record the root element in case it later gets transplanted. rootElementsByReactRootID[reactRootID] = getReactRootElementInContainer(container); } return componentInstance; }, /** * 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} parentComponent The conceptual parent of this render tree. * @param {ReactElement} nextElement Component element to render. * @param {DOMElement} container DOM element to render into. * @param {?function} callback function triggered on completion * @return {ReactComponent} Component instance rendered in `container`. */ renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) { invariant( parentComponent != null && parentComponent._reactInternalInstance != null, 'parentComponent must be a valid React Component' ); return TVMLMount._renderSubtreeIntoContainer( parentComponent, nextElement, container, callback ); }, _renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) { invariant( ReactElement.isValidElement(nextElement), 'ReactDOM.render(): Invalid component element.%s', ( typeof nextElement === 'string' ? ' Instead of passing an element string, make sure to instantiate ' + 'it by passing it to React.createElement.' : typeof nextElement === 'function' ? ' Instead of passing a component class, make sure to instantiate ' + 'it by passing it to React.createElement.' : // Check if it quacks like an element nextElement != null && nextElement.props !== undefined ? ' This may be caused by unintentionally loading two independent ' + 'copies of React.' : '' ) ); warning( !container || !container.tagName || container.tagName.toUpperCase() !== 'BODY', 'render(): Rendering components directly into document.body is ' + 'discouraged, since its children are often manipulated by third-party ' + 'scripts and browser extensions. This may lead to subtle ' + 'reconciliation issues. Try rendering into a container element created ' + 'for your app.' ); var nextWrappedElement = new ReactElement( TopLevelWrapper, null, null, null, null, null, nextElement ); var prevComponent = instancesByReactRootID[getReactRootID(container)]; if (prevComponent) { var prevWrappedElement = prevComponent._currentElement; var prevElement = prevWrappedElement.props; if (shouldUpdateReactComponent(prevElement, nextElement)) { return TVMLMount._updateRootComponent( prevComponent, nextWrappedElement, container, callback )._renderedComponent.getPublicInstance(); } else { TVMLMount.unmountComponentAtNode(container); } } var reactRootElement = getReactRootElementInContainer(container); var containerHasReactMarkup = reactRootElement && !!internalGetID(reactRootElement); var containerHasNonRootReactChild = hasNonRootReactChild(container); if (__DEV__) { warning( !containerHasNonRootReactChild, 'render(...): Replacing React-rendered children with a new root ' + 'component. If you intended to update the children of this node, ' + 'you should instead have the existing children update their state ' + 'and render the new components instead of calling ReactDOM.render.' ); if (!containerHasReactMarkup || reactRootElement.nextSibling) { var rootElementSibling = reactRootElement; while (rootElementSibling) { if (internalGetID(rootElementSibling)) { warning( false, 'render(): Target node has markup rendered by React, but there ' + 'are unrelated nodes as well. This is most commonly caused by ' + 'white-space inserted around server-rendered markup.' ); break; } rootElementSibling = rootElementSibling.nextSibling; } } } var shouldReuseMarkup = containerHasReactMarkup && !prevComponent && !containerHasNonRootReactChild; var component = TVMLMount._renderNewRootComponent( nextWrappedElement, container, shouldReuseMarkup, parentComponent != null ? parentComponent._reactInternalInstance._processChildContext( parentComponent._reactInternalInstance._context ) : emptyObject )._renderedComponent.getPublicInstance(); if (callback) { callback.call(component); } return component; }, /** * 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 {ReactElement} nextElement Component element to render. * @param {DOMElement} container DOM element to render into. * @param {?function} callback function triggered on completion * @return {ReactComponent} Component instance rendered in `container`. */ render: function(nextElement, callback) { var container = TVMLMount.generateEmptyContainer(); var component = TVMLMount._renderSubtreeIntoContainer(null, nextElement, container, callback); navigationDocument.pushDocument(container); return component; }, /** * Registers a container node into which React components will be rendered. * This also creates the "reactRoot" 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` */ unmountComponentAtNode: function(container) { // Various parts of our code (such as ReactCompositeComponent's // _renderValidatedComponent) assume that calls to render aren't nested; // verify that that's the case. (Strictly speaking, unmounting won't cause a // render but we still don't expect to be in a render call here.) warning( ReactCurrentOwner.current == null, 'unmountComponentAtNode(): Render methods should be a pure function ' + 'of props and state; triggering nested component updates from render ' + 'is not allowed. If necessary, trigger nested updates in ' + 'componentDidUpdate. Check the render method of %s.', ReactCurrentOwner.current && ReactCurrentOwner.current.getName() || 'ReactCompositeComponent' ); invariant( container && ( container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE ), 'unmountComponentAtNode(...): Target container is not a DOM element.' ); var reactRootID = getReactRootID(container); var component = instancesByReactRootID[reactRootID]; if (!component) { // Check if the node being unmounted was rendered by React, but isn't a // root node. var containerHasNonRootReactChild = hasNonRootReactChild(container); // Check if the container itself is a React root node. var containerID = internalGetID(container); var isContainerReactRoot = containerID && containerID === ReactInstanceHandles.getReactRootIDFromNodeID(containerID); if (__DEV__) { warning( !containerHasNonRootReactChild, 'unmountComponentAtNode(): The node you\'re attempting to unmount ' + 'was rendered by React and is not a top-level container. %s', ( isContainerReactRoot ? 'You may have accidentally passed in a React root node instead ' + 'of its container.' : 'Instead, have the parent component update its state and ' + 'rerender in order to remove this component.' ) ); } return false; } ReactUpdates.batchedUpdates( unmountComponentFromNode, component, container ); delete instancesByReactRootID[reactRootID]; delete containersByReactRootID[reactRootID]; if (__DEV__) { delete rootElementsByReactRootID[reactRootID]; } return true; }, /** * 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 (__DEV__) { var rootElement = rootElementsByReactRootID[reactRootID]; if (rootElement && rootElement.parentNode !== container) { warning( // Call internalGetID here because getID calls isValid which calls // findReactContainerForID (this function). internalGetID(rootElement) === reactRootID, 'TVMLMount: 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 { warning( false, 'TVMLMount: Root element has been removed from its original ' + 'container. New container: %s', 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 = TVMLMount.findReactContainerForID(id); return TVMLMount.findComponentRoot(reactRoot, id); }, /** * Traverses up the ancestors of the supplied node to find a node that is a * DOM representation of a React component rendered by this copy of React. * * @param {*} node * @return {?DOMEventTarget} * @internal */ getFirstReactDOM: function(node) { return findFirstReactDOMImpl(node); }, /** * Finds a node with the supplied `targetID` inside of the supplied * `ancestorNode`. Exploits the ID naming scheme to perform the search * quickly. * * @param {DOMEventTarget} ancestorNode Search from this root. * @pararm {string} targetID ID of the DOM representation of the component. * @return {DOMEventTarget} DOM node with the supplied `targetID`. * @internal */ findComponentRoot: function(ancestorNode, targetID) { var firstChildren = findComponentRootReusableArray; var childIndex = 0; var deepestAncestor = findDeepestCachedAncestor(targetID) || ancestorNode; if (__DEV__) { // This will throw on the next line; give an early warning warning( deepestAncestor != null, 'React can\'t find the root component node for data-reactid value ' + '`%s`. If you\'re seeing this message, it probably means that ' + 'you\'ve loaded two copies of React on the page. At this time, only ' + 'a single copy of React can be loaded at a time.', targetID ); } firstChildren[0] = deepestAncestor.firstChild; firstChildren.length = 1; while (childIndex < firstChildren.length) { var child = firstChildren[childIndex++]; var targetChild; while (child) { var childID = TVMLMount.getID(child); if (childID) { // Even if we find the node we're looking for, we finish looping // through its siblings to ensure they're cached so that we don't have // to revisit this node again. Otherwise, we make n^2 calls to getID // when visiting the many children of a single node in order. if (targetID === childID) { targetChild = child; } else if (ReactInstanceHandles.isAncestorIDOf(childID, targetID)) { // 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); } } else { // If this child had no ID, then there's a chance that it was // injected automatically by the browser, as when a `` // element sprouts an extra `` 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 (targetChild) { // Emptying firstChildren/findComponentRootReusableArray is // not necessary for correctness, but it helps the GC reclaim // any nodes that were left at the end of the search. firstChildren.length = 0; return targetChild; } } firstChildren.length = 0; invariant( false, 'findComponentRoot(..., %s): Unable to find element. This probably ' + 'means the DOM was unexpectedly mutated (e.g., by the browser), ' + 'usually due to forgetting a when using tables, nesting tags ' + 'like ,

, or , or using non-SVG elements in an ' + 'parent. ' + 'Try inspecting the child nodes of the element with React ID `%s`.', targetID, TVMLMount.getID(ancestorNode) ); }, _mountImageIntoNode: function( markup, container, shouldReuseMarkup, transaction ) { invariant( container && ( container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE ), 'mountComponentIntoNode(...): Target container is not valid.' ); if (shouldReuseMarkup) { var rootElement = getReactRootElementInContainer(container); if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) { return; } else { var checksum = rootElement.getAttribute( ReactMarkupChecksum.CHECKSUM_ATTR_NAME ); rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); var rootMarkup = rootElement.outerHTML; rootElement.setAttribute( ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum ); var normalizedMarkup = markup; if (__DEV__) { // because rootMarkup is retrieved from the DOM, various normalizations // will have occurred which will not be present in `markup`. Here, // insert markup into a

or