From aac4d62dc038bdbeabd22a22ff6db744520340e5 Mon Sep 17 00:00:00 2001 From: Rodolfo Carvalho Date: Wed, 20 Apr 2016 20:09:22 -0400 Subject: [PATCH 1/5] Did a rewrite to make it play nicely with eslint Did that by adopting community standards --- .eslintrc | 3 + example/webpack.config.js | 17 +- lib/add-class.js | 7 - lib/combobox.js | 616 ++++++++++++++++++++------------------ lib/main.js | 154 +++++----- lib/option.js | 80 ++--- lib/token.js | 63 ++-- package.json | 25 +- webpack.config.js | 23 +- 9 files changed, 536 insertions(+), 452 deletions(-) create mode 100644 .eslintrc delete mode 100644 lib/add-class.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..b0c0c8b --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "airbnb" +} diff --git a/example/webpack.config.js b/example/webpack.config.js index 519550d..e4aa810 100644 --- a/example/webpack.config.js +++ b/example/webpack.config.js @@ -1,16 +1,27 @@ module.exports = { entry: { - './example/bundle': './example/main.js' + './example-bundle': './example/main.js' }, output: { filename: "[name].js" }, debug: true, - devtool: '#inline-source-map', + devtool: 'inline-source-map', module: { loaders: [ - { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader?experimental'} + { + test: /\.js$/, + loader: 'babel', + exclude: /node_modules|example/, + query: { + presets: ['es2015', 'react'] + } + } ] + }, + + devServer: { + contentBase: './' } }; diff --git a/lib/add-class.js b/lib/add-class.js deleted file mode 100644 index ab40f7b..0000000 --- a/lib/add-class.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = addClass; - -function addClass(existing, added) { - if (!existing) return added; - if (existing.indexOf(added) > -1) return existing; - return existing + ' ' + added; -} diff --git a/lib/combobox.js b/lib/combobox.js index 919e19a..1e50e33 100644 --- a/lib/combobox.js +++ b/lib/combobox.js @@ -1,59 +1,52 @@ -var React = require('react'); -var guid = 0; -var k = function(){}; -var addClass = require('./add-class'); -var ComboboxOption = require('./option'); - -var div = React.createFactory('div'); -var span = React.createFactory('span'); -var input = React.createFactory('input'); - -module.exports = React.createClass({ - - propTypes: { - /** - * Called when the combobox receives user input, this is your chance to - * filter the data and rerender the options. - * - * Signature: - * - * ```js - * function(userInput){} - * ``` - */ - onInput: React.PropTypes.func, - - /** - * Called when the combobox receives a selection. You probably want to reset - * the options to the full list at this point. - * - * Signature: - * - * ```js - * function(selectedValue){} - * ``` - */ - onSelect: React.PropTypes.func, - - /** - * Shown when the combobox is empty. - */ - placeholder: React.PropTypes.string - }, +import React from 'react'; +import classnames from 'classnames'; +import ComboboxOption from './option'; - getDefaultProps: function() { - return { - autocomplete: 'both', - onInput: k, - onSelect: k, - value: null, - showListOnFocus: false - }; +let guid = 0; + +const K_BACKSPACE = 8; + +function emptyFunction() { + +} + +const KEYDOWN_MAPS = { + inputKeydownMap: { + [K_BACKSPACE]: 'removeLastToken', + 13: 'selectOnEnter', + 27: 'hideOnEscape', + 38: 'focusPrevious', + 40: 'focusNext', }, - getInitialState: function() { - return { - value: this.props.value, + optionKeydownMap: { + 13: 'selectOption', + 27: 'hideOnEscape', + 38: 'focusPrevious', + 40: 'focusNext', + }, +}; + +export default class ComboBox extends React.Component { + constructor(props, context) { + super(props, context); + this.focusOption = this.focusOption.bind(this); + this.handleButtonClick = this.handleButtonClick.bind(this); + this.handleInputBlur = this.handleInputBlur.bind(this); + this.handleInputChange = this.handleInputChange.bind(this); + this.handleInputClick = this.handleInputClick.bind(this); + this.handleInputFocus = this.handleInputFocus.bind(this); + this.handleInputKeyUp = this.handleInputKeyUp.bind(this); + this.handleKeydown = this.handleKeydown.bind(this); + this.handleOptionBlur = this.handleOptionBlur.bind(this); + this.handleOptionFocus = this.handleOptionFocus.bind(this); + this.handleOptionKeyDown = this.handleOptionKeyDown.bind(this); + this.handleOptionMouseEnter = this.handleOptionMouseEnter.bind(this); + this.hideList = this.hideList.bind(this); + this.selectOption = this.selectOption.bind(this); + + this.state = { + value: props.value, // the value displayed in the input inputValue: this.findInitialInputValue(), isOpen: false, @@ -62,48 +55,51 @@ module.exports = React.createClass({ // this prevents crazy jumpiness since we focus options on mouseenter usingKeyboard: false, activedescendant: null, - listId: 'ic-tokeninput-list-'+(++guid), + listId: `ic-tokeninput-list-${++guid}`, menu: { children: [], activedescendant: null, - isEmpty: true - } + isEmpty: true, + }, }; - }, + } - componentWillMount: function() { - this.setState({menu: this.makeMenu(this.props.children)}); - }, + componentWillMount() { + this.setState({ menu: this.makeMenu(this.props.children) }); + } - componentWillReceiveProps: function(newProps) { - this.setState({menu: this.makeMenu(newProps.children)}, function() { - if(newProps.children.length && (this.isOpen || document.activeElement === this.refs.input)) { - if(!this.state.menu.children.length) { - return + componentWillReceiveProps(newProps) { + this.setState({ menu: this.makeMenu(newProps.children) }, () => { + if (newProps.children.length && (this.isOpen || document.activeElement === this.refs.input)) { + if (!this.state.menu.children.length) { + return; } - this.setState({ - isOpen: true - }, function() { + this.setState({ isOpen: true }, () => { this.refs.list.scrollTop = 0; - }.bind(this)) + }); } else { this.hideList(); } + }); + } - }.bind(this)); - }, + getClassName() { + return classnames(this.props.className, 'ic-tokeninput', { + 'ic-tokeninput-is-open': this.state.isOpen, + }); + } /** * We don't create the components, the user supplies them, * so before rendering we attach handlers to facilitate communication from * the ComboboxOption to the Combobox. */ - makeMenu: function(children) { - var activedescendant; - var isEmpty = true; + makeMenu(children) { + let activedescendant; + let isEmpty = true; // Should this instead use React.addons.cloneWithProps or React.cloneElement? - var _children = React.Children.map(children, function(child, index) { + const _children = React.Children.map(children, (child, index) => { // console.log(child.type, ComboboxOption.type) if (child.type !== ComboboxOption) { // allow random elements to live in this list @@ -111,158 +107,144 @@ module.exports = React.createClass({ } isEmpty = false; // TODO: cloneWithProps and map instead of altering the children in-place - var props = child.props; - var newProps = {}; - if (this.state.value === child.props.value) { + const { props } = child; + const newProps = { + onBlur: this.handleOptionBlur, + onClick: this.selectOption.bind(this, child), + onFocus: this.handleOptionFocus, + onKeyDown: this.handleOptionKeyDown.bind(this, child), + onMouseEnter: this.handleOptionMouseEnter.bind(this, index), + isSelected: this.state.value === props.value, + }; + + if (this.state.value === props.value) { // need an ID for WAI-ARIA - newProps.id = props.id || 'ic-tokeninput-selected-'+(++guid); - newProps.isSelected = true + newProps.id = props.id || `ic-tokeninput-selected-${++guid}`; activedescendant = props.id; } - newProps.onBlur = this.handleOptionBlur; - newProps.onClick = this.selectOption.bind(this, child); - newProps.onFocus = this.handleOptionFocus; - newProps.onKeyDown = this.handleOptionKeyDown.bind(this, child); - newProps.onMouseEnter = this.handleOptionMouseEnter.bind(this, index); return React.cloneElement(child, newProps); - }.bind(this)); + }); return { children: _children, - activedescendant: activedescendant, - isEmpty: isEmpty + activedescendant, + isEmpty, }; - }, - - getClassName: function() { - var className = addClass(this.props.className, 'ic-tokeninput'); - if (this.state.isOpen) - className = addClass(className, 'ic-tokeninput-is-open'); - return className; - }, + } /** * When the user begins typing again we need to clear out any state that has * to do with an existing or potential selection. */ - clearSelectedState: function(cb) { + clearSelectedState(cb) { this.setState({ focusedIndex: null, inputValue: null, value: null, matchedAutocompleteOption: null, - activedescendant: null + activedescendant: null, }, cb); - }, + } - handleInputChange: function() { - var value = this.refs.input.value; - this.clearSelectedState(function() { + handleInputChange() { + const { value } = this.refs.input; + this.clearSelectedState(() => { this.props.onInput(value); - }.bind(this)); - }, + }); + } - handleInputFocus: function() { + handleInputFocus() { this.maybeShowList(); - }, + } - handleInputClick: function() { + handleInputClick() { this.maybeShowList(); - }, + } - maybeShowList: function(){ - if (this.props.showListOnFocus){ - this.showList() + maybeShowList() { + if (this.props.showListOnFocus) { + this.showList(); } - }, + } - handleInputBlur: function() { - var focusedAnOption = this.state.focusedIndex != null; - if (focusedAnOption) + handleInputBlur() { + const focusedAnOption = this.state.focusedIndex !== null + && this.state.focusedIndex !== undefined; + if (focusedAnOption) { return; + } this.maybeSelectAutocompletedOption(); this.hideList(); - }, + } - handleOptionBlur: function() { + handleOptionBlur() { // don't want to hide the list if we focused another option this.blurTimer = setTimeout(this.hideList, 0); - }, - + } - handleOptionFocus: function() { + handleOptionFocus() { // see `handleOptionBlur` clearTimeout(this.blurTimer); - }, + } - handleInputKeyUp: function(event) { + handleInputKeyUp(event) { if ( - this.state.menu.isEmpty || + this.state.menu.isEmpty // autocompleting while backspacing feels super weird, so let's not - event.keyCode === 8 /*backspace*/ || - !this.props.autocomplete.match(/both|inline/) + || event.keyCode === K_BACKSPACE + || !this.props.autocomplete.match(/both|inline/) ) return; - }, + } - handleButtonClick: function() { - this.state.isOpen ? this.hideList() : this.showList(); + handleButtonClick() { + if (this.state.isOpen) { + this.hideList(); + } else { + this.showList(); + } this.focusInput(); - }, + } - showList: function() { - if(!this.state.menu.children.length) { - return + showList() { + if (!this.state.menu.children.length) { + return; } - this.setState({isOpen: true}) - }, + this.setState({ isOpen: true }); + } - hideList: function() { + hideList() { this.setState({ isOpen: false, - focusedIndex: null + focusedIndex: null, }); - }, + } - hideOnEscape: function(event) { + hideOnEscape(event) { this.hideList(); this.focusInput(); event.preventDefault(); - }, + } - focusInput: function() { + focusInput() { this.refs.input.focus(); - }, + } - selectInput: function() { + selectInput() { this.refs.input.select(); - }, - - inputKeydownMap: { - 8: 'removeLastToken', - 13: 'selectOnEnter', - 27: 'hideOnEscape', - 38: 'focusPrevious', - 40: 'focusNext' - }, - - optionKeydownMap: { - 13: 'selectOption', - 27: 'hideOnEscape', - 38: 'focusPrevious', - 40: 'focusNext' - }, + } - handleKeydown: function(event) { - var handlerName = this.inputKeydownMap[event.keyCode]; - if (!handlerName) - return - this.setState({usingKeyboard: true}); - return this[handlerName].call(this,event); - }, + handleKeydown(event) { + const handlerName = KEYDOWN_MAPS.inputKeydownMap[event.keyCode]; + if (!handlerName) { + return null; + } + this.setState({ usingKeyboard: true }); + return this[handlerName].call(this, event); + } - handleOptionKeyDown: function(child, event) { - var handlerName = this.optionKeydownMap[event.keyCode]; + handleOptionKeyDown(child, event) { + const handlerName = KEYDOWN_MAPS.optionKeydownMap[event.keyCode]; if (!handlerName) { // if the user starts typing again while focused on an option, move focus // to the inpute, select so it wipes out any existing value @@ -270,174 +252,226 @@ module.exports = React.createClass({ return; } event.preventDefault(); - this.setState({usingKeyboard: true}); + this.setState({ usingKeyboard: true }); this[handlerName].call(this, child); - }, + } - handleOptionMouseEnter: function(index) { - if (this.state.usingKeyboard) - this.setState({usingKeyboard: false}); - else + handleOptionMouseEnter(index) { + if (this.state.usingKeyboard) { + this.setState({ usingKeyboard: false }); + } else { this.focusOptionAtIndex(index); - }, + } + } - selectOnEnter: function(event) { + selectOnEnter(event) { event.preventDefault(); - this.maybeSelectAutocompletedOption() - }, + this.maybeSelectAutocompletedOption(); + } - maybeSelectAutocompletedOption: function() { + maybeSelectAutocompletedOption() { if (!this.state.matchedAutocompleteOption) { - this.selectText() + this.selectText(); } else { - this.selectOption(this.state.matchedAutocompleteOption, {focus: false}); + this.selectOption(this.state.matchedAutocompleteOption, { focus: false }); } - }, + } - selectOption: function(child, options) { - options = options || {}; + selectOption(child, options = {}) { this.setState({ // value: child.props.value, // inputValue: getLabel(child), - matchedAutocompleteOption: null - }, function() { + matchedAutocompleteOption: null, + }, () => { this.props.onSelect(child.props.value, child); this.hideList(); this.clearSelectedState(); // added - if (options.focus !== false) + if (options.focus !== false) { this.selectInput(); - }.bind(this)); - this.refs.input.value = '' // added - }, + } + }); + this.refs.input.value = ''; // added + } - selectText: function() { - var value = this.refs.input.value; - if(!value) return; + selectText() { + const value = this.refs.input.value; + if (!value) { + return; + } this.props.onSelect(value); this.clearSelectedState(); - this.refs.input.value = '' // added - }, + this.refs.input.value = ''; // added + } - focusNext: function(event) { - if(event.preventDefault) event.preventDefault(); - if (this.state.menu.isEmpty) return; - var index = this.state.focusedIndex == null ? + focusNext(event) { + if (event.preventDefault) { + event.preventDefault(); + } + + if (this.state.menu.isEmpty) { + return; + } + + const index = this.state.focusedIndex === null ? 0 : this.state.focusedIndex + 1; this.focusOptionAtIndex(index); - }, + } - removeLastToken: function() { - if(this.props.onRemoveLast && !this.refs.input.value) { - this.props.onRemoveLast() + removeLastToken() { + if (this.props.onRemoveLast && !this.refs.input.value) { + this.props.onRemoveLast(); } - return true - }, + return true; + } - focusPrevious: function(event) { - if(event.preventDefault) event.preventDefault(); + focusPrevious(event) { + if (event.preventDefault) event.preventDefault(); if (this.state.menu.isEmpty) return; - var last = this.props.children.length - 1; - var index = this.state.focusedIndex == null ? + const last = this.props.children.length - 1; + const index = this.state.focusedIndex === null ? last : this.state.focusedIndex - 1; this.focusOptionAtIndex(index); - }, + } - focusSelectedOption: function() { - var selectedIndex; - React.Children.forEach(this.props.children, function(child, index) { - if (child.props.value === this.state.value) + focusSelectedOption() { + let selectedIndex; + React.Children.forEach(this.props.children, (child, index) => { + if (child.props.value === this.state.value) { selectedIndex = index; - }.bind(this)); + } + }); this.showList(); this.setState({ - focusedIndex: selectedIndex + focusedIndex: selectedIndex, }, this.focusOption); - }, + } - findInitialInputValue: function() { + findInitialInputValue() { // TODO: might not need this, we should know this in `makeMenu` - var inputValue; - React.Children.forEach(this.props.children, function(child) { - if (child.props.value === this.props.value) - inputValue = getLabel(child); - }.bind(this)); + let inputValue; + React.Children.forEach(this.props.children, (child) => { + if (child.props.value === this.props.value) { + inputValue = child.props.label || child.props.children; + } + }); return inputValue; - }, + } + + focusOptionAtIndex(index) { + let focusedIndex = index; + if (!this.state.isOpen && this.state.value) { + this.focusSelectedOption(); + return; + } - focusOptionAtIndex: function(index) { - if (!this.state.isOpen && this.state.value) - return this.focusSelectedOption(); this.showList(); - var length = this.props.children.length; - if (index === -1) - index = length - 1; - else if (index === length) - index = 0; - this.setState({ - focusedIndex: index - }, this.focusOption); - }, + const { length } = this.props.children; + + if (focusedIndex === -1) { + focusedIndex = length - 1; + } else if (index === length) { + focusedIndex = 0; + } + this.setState({ focusedIndex }, this.focusOption); + } - focusOption: function() { - var index = this.state.focusedIndex; + focusOption() { + const index = this.state.focusedIndex; this.refs.list.childNodes[index].focus(); - }, + } - render: function() { - var ariaLabel = this.props['aria-label'] || 'Start typing to search. ' + + render() { + const ariaLabel = this.props.ariaLabel || 'Start typing to search. ' + 'Press the down arrow to navigate results. If you don\'t find an ' + - 'acceptable option, you can enter an alternative.' - - return div({className: this.getClassName()}, - this.props.value, - this.state.inputValue, - input({ - ref: 'input', - autoComplete: 'off', - spellCheck: 'false', - 'aria-label': ariaLabel, - 'aria-expanded': this.state.isOpen+'', - 'aria-haspopup': 'true', - 'aria-activedescendant': this.state.menu.activedescendant, - 'aria-autocomplete': 'list', - 'aria-owns': this.state.listId, - id: this.props.id, - disabled: this.props.isDisabled, - className: 'ic-tokeninput-input', - onFocus: this.handleInputFocus, - onClick: this.handleInputClick, - onChange: this.handleInputChange, - onBlur: this.handleInputBlur, - onKeyDown: this.handleKeydown, - onKeyUp: this.handleInputKeyUp, - placeholder: this.props.placeholder, - role: 'combobox' - }), - span({ - 'aria-hidden': 'true', - className: 'ic-tokeninput-button', - onClick: this.handleButtonClick - }, '▾'), - div({ - id: this.state.listId, - ref: 'list', - className: 'ic-tokeninput-list', - role: 'listbox' - }, this.state.menu.children) + 'acceptable option, you can enter an alternative.'; + + return ( +
+ {this.props.value} + {this.state.inputValue} + , + +
{this.state.menu.children}
+
); } -}); - -function getLabel(component) { - return component.props.label || component.props.children; } -function matchFragment(userInput, firstChildLabel) { - userInput = userInput.toLowerCase(); - firstChildLabel = firstChildLabel.toLowerCase(); - if (userInput === '' || userInput === firstChildLabel) - return false; - if (firstChildLabel.toLowerCase().indexOf(userInput.toLowerCase()) === -1) - return false; - return true; -} +ComboBox.defaultProps = { + autocomplete: 'both', + onInput: emptyFunction, + onSelect: emptyFunction, + value: null, + showListOnFocus: false, +}; + +ComboBox.propTypes = { + /** + * Called when the combobox receives user input, this is your chance to + * filter the data and rerender the options. + * + * Signature: + * + * ```js + * function(userInput){} + * ``` + */ + onInput: React.PropTypes.func, + + /** + * Called when the combobox receives a selection. You probably want to reset + * the options to the full list at this point. + * + * Signature: + * + * ```js + * function(selectedValue){} + * ``` + */ + onSelect: React.PropTypes.func, + + /** + * Shown when the combobox is empty. + */ + placeholder: React.PropTypes.string, + + value: React.PropTypes.any, + children: React.PropTypes.node, + isDisabled: React.PropTypes.bool, + id: React.PropTypes.string, + className: React.PropTypes.string, + ariaLabel: React.PropTypes.string, + onRemoveLast: React.PropTypes.func, + autocomplete: React.PropTypes.string, + showListOnFocus: React.PropTypes.bool, +}; diff --git a/lib/main.js b/lib/main.js index e2af985..151c3d9 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,93 +1,105 @@ -var React = require('react'); -var Combobox = React.createFactory(require('./combobox')); -var Token = React.createFactory(require('./token')); -var classnames = require('classnames'); +import React from 'react'; +import Combobox from './combobox'; +import Token from './token'; +import classnames from 'classnames'; -var ul = React.DOM.ul; -var li = React.DOM.li; +export default class TokenInput extends React.Component { + constructor(props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + this.handleInput = this.handleInput.bind(this); + this.handleRemove = this.handleRemove.bind(this); + this.handleRemoveLast = this.handleRemoveLast.bind(this); + this.handleSelect = this.handleSelect.bind(this); -module.exports = React.createClass({ - propTypes: { - isLoading: React.PropTypes.bool, - loadingComponent: React.PropTypes.any, - onInput: React.PropTypes.func, - onSelect: React.PropTypes.func.isRequired, - onRemove: React.PropTypes.func.isRequired, - selected: React.PropTypes.array.isRequired, - menuContent: React.PropTypes.any, - showListOnFocus: React.PropTypes.bool, - placeholder: React.PropTypes.string - }, - - getInitialState: function() { - return { - selectedToken: null + this.state = { + selectedToken: null, }; - }, + } - handleClick: function() { + handleClick() { // TODO: Expand combobox API for focus this.refs['combo-li'].querySelector('input').focus(); - }, + } - handleInput: function(inputValue) { + handleInput(inputValue) { this.props.onInput(inputValue); - }, + } - handleSelect: function(event) { - var input = this.refs['combo-li'].querySelector('input'); - this.props.onSelect(event) + handleSelect(event) { + const input = this.refs['combo-li'].querySelector('input'); + this.props.onSelect(event); this.setState({ - selectedToken: null - }) + selectedToken: null, + }); this.props.onInput(input.value); - }, + } - handleRemove: function(value) { - var input = this.refs['combo-li'].querySelector('input'); + handleRemove(value) { + const input = this.refs['combo-li'].querySelector('input'); this.props.onRemove(value); input.focus(); - }, + } - handleRemoveLast: function() { + handleRemoveLast() { this.props.onRemove(this.props.selected[this.props.selected.length - 1]); - }, + } + + render() { + const { isDisabled } = this.props; - render: function() { - var isDisabled = this.props.isDisabled; - var tokens = this.props.selected.map(function(token) { - return ( - Token({ - onRemove: this.handleRemove, - value: token, - name: token.name, - key: token.id}) - ) - }.bind(this)) + const tokens = this.props.selected.map(token => ( + + )); - var classes = classnames('ic-tokens flex', { - 'ic-tokens-disabled': isDisabled + const classes = classnames('ic-tokens flex', { + 'ic-tokens-disabled': isDisabled, }); - return ul({className: classes, onClick: this.handleClick}, - tokens, - li({className: 'inline-flex', ref: 'combo-li'}, - Combobox({ - id: this.props.id, - ariaLabel: this.props['combobox-aria-label'], - ariaDisabled: isDisabled, - onInput: this.handleInput, - showListOnFocus: this.props.showListOnFocus, - onSelect: this.handleSelect, - onRemoveLast: this.handleRemoveLast, - value: this.state.selectedToken, - isDisabled: isDisabled, - placeholder: this.props.placeholder - }, - this.props.menuContent - ) - ), - this.props.isLoading && li({className: 'ic-tokeninput-loading flex'}, this.props.loadingComponent) + const loadingWidget = this.props.isLoading + &&
  • {this.props.loadingComponent}
  • ; + + return ( +
      + {tokens} +
    • + + {this.props.menuContent} + +
    • , + {loadingWidget} +
    ); } -}) +} + +TokenInput.propTypes = { + isLoading: React.PropTypes.bool, + loadingComponent: React.PropTypes.any, + onInput: React.PropTypes.func, + onSelect: React.PropTypes.func.isRequired, + onRemove: React.PropTypes.func.isRequired, + selected: React.PropTypes.array.isRequired, + menuContent: React.PropTypes.any, + showListOnFocus: React.PropTypes.bool, + placeholder: React.PropTypes.string, + isDisabled: React.PropTypes.bool, + ariaLabel: React.PropTypes.string, + id: React.PropTypes.string, +}; diff --git a/lib/option.js b/lib/option.js index 76c5080..35071bf 100644 --- a/lib/option.js +++ b/lib/option.js @@ -1,40 +1,46 @@ -var React = require('react'); -var addClass = require('./add-class'); -var div = React.createFactory('div'); - -module.exports = React.createClass({ - - propTypes: { - - /** - * The value that will be sent to the `onSelect` handler of the - * parent Combobox. - */ - value: React.PropTypes.any.isRequired, - - /** - * What value to put into the input element when this option is - * selected, defaults to its children coerced to a string. - */ - label: React.PropTypes.string - }, - - getDefaultProps: function() { - return { - role: 'option', - tabIndex: '-1', - className: 'ic-tokeninput-option', - isSelected: false - }; - }, - - render: function() { - var props = this.props; - if (props.isSelected) { - props.className = addClass(props.className, 'ic-tokeninput-selected'); - props.ariaSelected = true; +import React from 'react'; +import classnames from 'classnames'; + +export default function TokenInputOption(props) { + const { className, isSelected } = props; + const divProps = {}; + + for (const prop in props) { + if (!props.hasOwnProperty(prop)) { + continue; + } + + if (prop !== 'className' && prop !== 'aiaSelected') { + divProps[prop] = props[prop]; } - return div(props); } -}); + const divClassName = classnames(className, 'ic-tokeninput-selected', { + 'ic-tokeninput-selected': isSelected, + }); + return
    ; +} + +TokenInputOption.propTypes = { + /** + * The value that will be sent to the `onSelect` handler of the + * parent Combobox. + */ + value: React.PropTypes.any.isRequired, + + /** + * What value to put into the input element when this option is + * selected, defaults to its children coerced to a string. + */ + label: React.PropTypes.string, + + className: React.PropTypes.string, + isSelected: React.PropTypes.bool, +}; + +TokenInputOption.defaultProps = { + role: 'option', + tabIndex: '-1', + className: 'ic-tokeninput-option', + isSelected: false, +}; diff --git a/lib/token.js b/lib/token.js index c750772..1392489 100644 --- a/lib/token.js +++ b/lib/token.js @@ -1,32 +1,39 @@ -var React = require('react'); -var span = React.DOM.span; -var li = React.createFactory('li'); +import React from 'react'; -module.exports = React.createClass({ - handleClick: function() { - this.props.onRemove(this.props.value) - }, +const K_ENTER = 13; - handleKeyDown: function(key) { - var enterKey = 13; - if(key.keyCode === enterKey) this.props.onRemove(this.props.value) - }, +function handleClick({ onRemove, value }) { + if (typeof onRemove === 'function') { + onRemove(value); + } +} - render: function() { - return ( - li({ - className: "ic-token inline-flex" - }, - span({ - role: 'button', - onClick: this.handleClick, - onKeyDown: this.handleKeyDown, - 'aria-label': 'Remove \'' + this.props.name + '\'', - className: "ic-token-delete-button", - tabIndex: 0 - }, "✕"), - span({className: "ic-token-label"}, this.props.name) - ) - ) +function handleKeyDown(onRemove, value, event) { + if (event.keyCode === K_ENTER && typeof onRemove === 'function') { + onRemove(value); } -}) +} + +export default function Token({ name, value, onRemove }) { + const clickHandler = handleClick.bind(this, onRemove, value); + const keyDownHandler = handleKeyDown.bind(this, onRemove, value); + + return ( +
  • + x +
  • + ); +} + +Token.propTypes = { + name: React.PropTypes.string, + value: React.PropTypes.any, + onRemove: React.PropTypes.func, +}; diff --git a/package.json b/package.json index a6805a5..c44320d 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "build": "NODE_ENV=production webpack index.js dist/react-tokeninput.js && NODE_ENV=production webpack index.js dist/react-tokeninput.min.js --optimize-minimize", "test": "node_modules/.bin/karma start", - "dev": "webpack --watch --config example/webpack.config.js" + "dev": "webpack-dev-server", + "example": "webpack-dev-server --config webpack.example.config.js" }, "repository": { "type": "git", @@ -19,12 +20,18 @@ }, "homepage": "https://github.com/instructure/react-tokeninput", "devDependencies": { - "babel-core": "^4.7.16", - "babel-eslint": "^4.1.5", - "babel-loader": "^4.2.0", + "babel-core": "^6.7.6", + "babel-eslint": "^6.0.3", + "babel-loader": "^6.2.4", + "babel-preset-es2015": "^6.6.0", + "babel-preset-react": "^6.5.0", "chai": "^1.10.0", - "eslint": "^1.10.1", - "jsx-loader": "^0.13.2", + "eslint": "^2.8.0", + "eslint-config-airbnb": "^7.0.0", + "eslint-config-airbnb-base": "^1.0.2", + "eslint-plugin-import": "^1.5.0", + "eslint-plugin-jsx-a11y": "^0.6.2", + "eslint-plugin-react": "^4.3.0", "karma": "^0.13.0", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^0.2.1", @@ -33,13 +40,13 @@ "karma-jasmine": "^0.2.3", "karma-mocha": "^0.2.0", "karma-webpack": "^1.3.1", - "lodash-node": "^2.4.1", + "lodash": "^4.11.1", "mocha": "^2.0.1", "react": "^0.14.0", "react-addons-test-utils": "^0.14.6", "react-dom": "^0.14.0", - "webpack": "^1.12.2", - "webpack-dev-server": "^1.12.0" + "webpack": "^1.13.0", + "webpack-dev-server": "^1.14.1" }, "dependencies": { "classnames": "^2.2.1" diff --git a/webpack.config.js b/webpack.config.js index 7aa946e..6708f08 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,10 @@ +/* eslint-disable */ + module.exports = { - entry: "./example/main.js", + entry: "./index.js", output: { - library: 'TokenInput', - libraryTarget: 'umd' + library: "TokenInput", + libraryTarget: "umd" }, externals: [ @@ -12,14 +14,23 @@ module.exports = { commonjs2: "react", commonjs: "react", amd: "react" - } + }, + + "classnames": true } ], debug: true, - devtool: '#source-map', + devtool: "source-map", module: { loaders: [ - {test: /\.js$/, loader: 'jsx-loader'} + { + test: /\.js$/, + loader: "babel", + exclude: /node_modules|example/, + query: { + presets: ["es2015", "react"] + } + } ] } }; From 51466b4ba8c92b9625ea819b5489f0ea0034b77a Mon Sep 17 00:00:00 2001 From: Rodolfo Carvalho Date: Wed, 20 Apr 2016 20:12:17 -0400 Subject: [PATCH 2/5] Working on the examples code --- dist/react-tokeninput.js | 23877 +---------------------------- dist/react-tokeninput.js.map | 2 +- dist/react-tokeninput.min.js | 26 +- dist/react-tokeninput.min.js.map | 2 +- example/index.html | 2 +- example/main.js | 6 +- index.js | 8 +- lib/{main.js => token-input.js} | 0 webpack.example.config.js | 29 + 9 files changed, 828 insertions(+), 23124 deletions(-) rename lib/{main.js => token-input.js} (100%) create mode 100644 webpack.example.config.js diff --git a/dist/react-tokeninput.js b/dist/react-tokeninput.js index 6afeabf..c1b3e0f 100644 --- a/dist/react-tokeninput.js +++ b/dist/react-tokeninput.js @@ -1,13 +1,13 @@ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(require("react")); + module.exports = factory(require("react"), require("classnames")); else if(typeof define === 'function' && define.amd) - define(["react"], factory); + define(["react", "classnames"], factory); else if(typeof exports === 'object') - exports["TokenInput"] = factory(require("react")); + exports["TokenInput"] = factory(require("react"), require("classnames")); else - root["TokenInput"] = factory(root["React"]); -})(this, function(__WEBPACK_EXTERNAL_MODULE_2__) { + root["TokenInput"] = factory(root["React"], root["classnames"]); +})(this, function(__WEBPACK_EXTERNAL_MODULE_3__, __WEBPACK_EXTERNAL_MODULE_5__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; @@ -55,23210 +55,907 @@ return /******/ (function(modules) { // webpackBootstrap /***/ function(module, exports, __webpack_require__) { __webpack_require__(1); - module.exports = __webpack_require__(149); + module.exports = __webpack_require__(1); /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { - var React = __webpack_require__(2) - var ReactDOM = __webpack_require__(3) - var TokenInput = __webpack_require__(149) - var ComboboxOption = __webpack_require__(149).Option + 'use strict'; - var without = __webpack_require__(156) - var uniq = __webpack_require__(169) - var names = __webpack_require__(194) + /* eslint no-var:0 */ - var App = React.createClass({displayName: "App", - getInitialState: function() { - return { - input: '', - loading: false, - selected: [], - options: names - }; - }, + var TokenInput = __webpack_require__(2); + TokenInput.Option = __webpack_require__(6); + + module.exports = TokenInput; + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; - handleChange: function(value) { - this.setState({ - selected: value - }) - }, + Object.defineProperty(exports, "__esModule", { + value: true + }); - handleRemove: function(value) { - var selectedOptions = uniq(without(this.state.selected,value)) - this.handleChange(selectedOptions) - }, + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - handleSelect: function(value, combobox) { - if(typeof value === 'string') { - value = {id: value, name: value}; - } + var _react = __webpack_require__(3); - var selected = uniq(this.state.selected.concat([value])) - this.setState({ - selected: selected, - selectedToken: null - }) + var _react2 = _interopRequireDefault(_react); - this.handleChange(selected) - }, + var _combobox = __webpack_require__(4); - handleInput: function(userInput) { - this.setState({ - input: userInput, - loading: true, - options: [] - }) - setTimeout(function () { - this.filterTags(this.state.input) - this.setState({ - loading: false - }) - }.bind(this), 500) - }, + var _combobox2 = _interopRequireDefault(_combobox); - filterTags: function(userInput) { - if (userInput === '') - return this.setState({options: []}); - var filter = new RegExp('^'+userInput, 'i'); - var filteredNames = names.filter(function(state) { - return filter.test(state.name); // || filter.test(state.id); - }).filter(function(state) { - return this.state.selected - .map(function(value) { return value.name }) - .indexOf(state.name) === -1 - }.bind(this)) - this.setState({ - options: filteredNames - }); - }, + var _token = __webpack_require__(7); - renderComboboxOptions: function() { - return this.state.options.map(function(name) { - return ( - React.createElement(ComboboxOption, { - key: name.id, - value: name - }, name.name) - ); - }); - }, + var _token2 = _interopRequireDefault(_token); - render: function() { - var selectedNames = this.state.selected.map(function(tag) { - return React.createElement("li", {key: tag.id}, tag.name) - }) + var _classnames = __webpack_require__(5); - var options = this.state.options.length ? - this.renderComboboxOptions() : []; + var _classnames2 = _interopRequireDefault(_classnames); - const loadingComponent = ( - React.createElement("img", {src: "spinner.gif"}) - ) + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - return ( - React.createElement("div", null, - React.createElement("h1", null, "React TokenInput Example"), + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - React.createElement(TokenInput, { - isLoading: this.state.loading, - loadingComponent: loadingComponent, - menuContent: options, - onChange: this.handleChange, - onInput: this.handleInput, - onSelect: this.handleSelect, - onRemove: this.handleRemove, - selected: this.state.selected, - placeholder: "Enter tokens here"} - ), + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - React.createElement("h2", null, "Selected"), - React.createElement("ul", null, - selectedNames - ) - ) - ); + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + var TokenInput = function (_React$Component) { + _inherits(TokenInput, _React$Component); + + function TokenInput(props, context) { + _classCallCheck(this, TokenInput); + + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(TokenInput).call(this, props, context)); + + _this.handleClick = _this.handleClick.bind(_this); + _this.handleInput = _this.handleInput.bind(_this); + _this.handleRemove = _this.handleRemove.bind(_this); + _this.handleRemoveLast = _this.handleRemoveLast.bind(_this); + _this.handleSelect = _this.handleSelect.bind(_this); + + _this.state = { + selectedToken: null + }; + return _this; } - }) - ReactDOM.render(React.createElement(App, null), document.getElementById('application')) - + _createClass(TokenInput, [{ + key: 'handleClick', + value: function handleClick() { + // TODO: Expand combobox API for focus + this.refs['combo-li'].querySelector('input').focus(); + } + }, { + key: 'handleInput', + value: function handleInput(inputValue) { + this.props.onInput(inputValue); + } + }, { + key: 'handleSelect', + value: function handleSelect(event) { + var input = this.refs['combo-li'].querySelector('input'); + this.props.onSelect(event); + this.setState({ + selectedToken: null + }); + this.props.onInput(input.value); + } + }, { + key: 'handleRemove', + value: function handleRemove(value) { + var input = this.refs['combo-li'].querySelector('input'); + this.props.onRemove(value); + input.focus(); + } + }, { + key: 'handleRemoveLast', + value: function handleRemoveLast() { + this.props.onRemove(this.props.selected[this.props.selected.length - 1]); + } + }, { + key: 'render', + value: function render() { + var _this2 = this; + + var isDisabled = this.props.isDisabled; + + + var tokens = this.props.selected.map(function (token) { + return _react2.default.createElement(_token2.default, { + onRemove: _this2.handleRemove, + value: token, + name: token.name, + key: token.id + }); + }); + + var classes = (0, _classnames2.default)('ic-tokens flex', { + 'ic-tokens-disabled': isDisabled + }); + + var loadingWidget = this.props.isLoading && _react2.default.createElement( + 'li', + { className: 'ic-tokeninput-loading flex' }, + this.props.loadingComponent + ); + + return _react2.default.createElement( + 'ul', + { className: classes, onClick: this.handleClick }, + tokens, + _react2.default.createElement( + 'li', + { className: 'inline-flex', ref: 'combo-li' }, + _react2.default.createElement( + _combobox2.default, + { + id: this.props.id, + ariaLabel: this.props.ariaLabel, + ariaDisabled: isDisabled, + onInput: this.handleInput, + showListOnFocus: this.props.showListOnFocus, + onSelect: this.handleSelect, + onRemoveLast: this.handleRemoveLast, + value: this.state.selectedToken, + isDisabled: isDisabled, + placeholder: this.props.placeholder + }, + this.props.menuContent + ) + ), + ',', + loadingWidget + ); + } + }]); + + return TokenInput; + }(_react2.default.Component); + + exports.default = TokenInput; + + + TokenInput.propTypes = { + isLoading: _react2.default.PropTypes.bool, + loadingComponent: _react2.default.PropTypes.any, + onInput: _react2.default.PropTypes.func, + onSelect: _react2.default.PropTypes.func.isRequired, + onRemove: _react2.default.PropTypes.func.isRequired, + selected: _react2.default.PropTypes.array.isRequired, + menuContent: _react2.default.PropTypes.any, + showListOnFocus: _react2.default.PropTypes.bool, + placeholder: _react2.default.PropTypes.string, + isDisabled: _react2.default.PropTypes.bool, + ariaLabel: _react2.default.PropTypes.string, + id: _react2.default.PropTypes.string + }; /***/ }, -/* 2 */ +/* 3 */ /***/ function(module, exports) { - module.exports = __WEBPACK_EXTERNAL_MODULE_2__; + module.exports = __WEBPACK_EXTERNAL_MODULE_3__; /***/ }, -/* 3 */ +/* 4 */ /***/ function(module, exports, __webpack_require__) { 'use strict'; - module.exports = __webpack_require__(4); - - -/***/ }, -/* 4 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactDOM - */ + Object.defineProperty(exports, "__esModule", { + value: true + }); - /* globals __REACT_DEVTOOLS_GLOBAL_HOOK__*/ + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - 'use strict'; + var _inputKeydownMap; + + var _react = __webpack_require__(3); + + var _react2 = _interopRequireDefault(_react); - var ReactCurrentOwner = __webpack_require__(6); - var ReactDOMTextComponent = __webpack_require__(7); - var ReactDefaultInjection = __webpack_require__(72); - var ReactInstanceHandles = __webpack_require__(46); - var ReactMount = __webpack_require__(29); - var ReactPerf = __webpack_require__(19); - var ReactReconciler = __webpack_require__(51); - var ReactUpdates = __webpack_require__(55); - var ReactVersion = __webpack_require__(147); + var _classnames = __webpack_require__(5); - var findDOMNode = __webpack_require__(92); - var renderSubtreeIntoContainer = __webpack_require__(148); - var warning = __webpack_require__(26); + var _classnames2 = _interopRequireDefault(_classnames); - ReactDefaultInjection.inject(); + var _option = __webpack_require__(6); - var render = ReactPerf.measure('React', 'render', ReactMount.render); + var _option2 = _interopRequireDefault(_option); - var React = { - findDOMNode: findDOMNode, - render: render, - unmountComponentAtNode: ReactMount.unmountComponentAtNode, - version: ReactVersion, + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - /* eslint-disable camelcase */ - unstable_batchedUpdates: ReactUpdates.batchedUpdates, - unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + var guid = 0; + + var K_BACKSPACE = 8; + + function emptyFunction() {} + + var KEYDOWN_MAPS = { + inputKeydownMap: (_inputKeydownMap = {}, _defineProperty(_inputKeydownMap, K_BACKSPACE, 'removeLastToken'), _defineProperty(_inputKeydownMap, 13, 'selectOnEnter'), _defineProperty(_inputKeydownMap, 27, 'hideOnEscape'), _defineProperty(_inputKeydownMap, 38, 'focusPrevious'), _defineProperty(_inputKeydownMap, 40, 'focusNext'), _inputKeydownMap), + + optionKeydownMap: { + 13: 'selectOption', + 27: 'hideOnEscape', + 38: 'focusPrevious', + 40: 'focusNext' + } }; - // Inject the runtime into a devtools global hook regardless of browser. - // Allows for debugging when the hook is injected on the page. - /* eslint-enable camelcase */ - if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') { - __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ - CurrentOwner: ReactCurrentOwner, - InstanceHandles: ReactInstanceHandles, - Mount: ReactMount, - Reconciler: ReactReconciler, - TextComponent: ReactDOMTextComponent - }); - } + var ComboBox = function (_React$Component) { + _inherits(ComboBox, _React$Component); + + function ComboBox(props, context) { + _classCallCheck(this, ComboBox); + + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(ComboBox).call(this, props, context)); - if (process.env.NODE_ENV !== 'production') { - var ExecutionEnvironment = __webpack_require__(10); - if (ExecutionEnvironment.canUseDOM && window.top === window.self) { + _this.focusOption = _this.focusOption.bind(_this); + _this.handleButtonClick = _this.handleButtonClick.bind(_this); + _this.handleInputBlur = _this.handleInputBlur.bind(_this); + _this.handleInputChange = _this.handleInputChange.bind(_this); + _this.handleInputClick = _this.handleInputClick.bind(_this); + _this.handleInputFocus = _this.handleInputFocus.bind(_this); + _this.handleInputKeyUp = _this.handleInputKeyUp.bind(_this); + _this.handleKeydown = _this.handleKeydown.bind(_this); + _this.handleOptionBlur = _this.handleOptionBlur.bind(_this); + _this.handleOptionFocus = _this.handleOptionFocus.bind(_this); + _this.handleOptionKeyDown = _this.handleOptionKeyDown.bind(_this); + _this.handleOptionMouseEnter = _this.handleOptionMouseEnter.bind(_this); + _this.hideList = _this.hideList.bind(_this); + _this.selectOption = _this.selectOption.bind(_this); - // First check if devtools is not installed - if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { - // If we're in Chrome or Firefox, provide a download link if not installed. - if (navigator.userAgent.indexOf('Chrome') > -1 && navigator.userAgent.indexOf('Edge') === -1 || navigator.userAgent.indexOf('Firefox') > -1) { - console.debug('Download the React DevTools for a better development experience: ' + 'https://fb.me/react-devtools'); + _this.state = { + value: props.value, + // the value displayed in the input + inputValue: _this.findInitialInputValue(), + isOpen: false, + focusedIndex: null, + matchedAutocompleteOption: null, + // this prevents crazy jumpiness since we focus options on mouseenter + usingKeyboard: false, + activedescendant: null, + listId: 'ic-tokeninput-list-' + ++guid, + menu: { + children: [], + activedescendant: null, + isEmpty: true } + }; + return _this; + } + + _createClass(ComboBox, [{ + key: 'componentWillMount', + value: function componentWillMount() { + this.setState({ menu: this.makeMenu(this.props.children) }); } + }, { + key: 'componentWillReceiveProps', + value: function componentWillReceiveProps(newProps) { + var _this2 = this; - // If we're in IE8, check to see if we are in compatibility mode and provide - // information on preventing compatibility mode - var ieCompatibilityMode = document.documentMode && document.documentMode < 8; + this.setState({ menu: this.makeMenu(newProps.children) }, function () { + if (newProps.children.length && (_this2.isOpen || document.activeElement === _this2.refs.input)) { + if (!_this2.state.menu.children.length) { + return; + } + _this2.setState({ isOpen: true }, function () { + _this2.refs.list.scrollTop = 0; + }); + } else { + _this2.hideList(); + } + }); + } + }, { + key: 'getClassName', + value: function getClassName() { + return (0, _classnames2.default)(this.props.className, 'ic-tokeninput', { + 'ic-tokeninput-is-open': this.state.isOpen + }); + } - process.env.NODE_ENV !== 'production' ? warning(!ieCompatibilityMode, 'Internet Explorer is running in compatibility mode; please add the ' + 'following tag to your HTML to prevent this from happening: ' + '') : undefined; + /** + * We don't create the components, the user supplies them, + * so before rendering we attach handlers to facilitate communication from + * the ComboboxOption to the Combobox. + */ - var expectedFeatures = [ - // shims - Array.isArray, Array.prototype.every, Array.prototype.forEach, Array.prototype.indexOf, Array.prototype.map, Date.now, Function.prototype.bind, Object.keys, String.prototype.split, String.prototype.trim, + }, { + key: 'makeMenu', + value: function makeMenu(children) { + var _this3 = this; + + var activedescendant = void 0; + var isEmpty = true; + + // Should this instead use React.addons.cloneWithProps or React.cloneElement? + var _children = _react2.default.Children.map(children, function (child, index) { + // console.log(child.type, ComboboxOption.type) + if (child.type !== _option2.default) { + // allow random elements to live in this list + return child; + } + isEmpty = false; + // TODO: cloneWithProps and map instead of altering the children in-place + var props = child.props; + + var newProps = { + onBlur: _this3.handleOptionBlur, + onClick: _this3.selectOption.bind(_this3, child), + onFocus: _this3.handleOptionFocus, + onKeyDown: _this3.handleOptionKeyDown.bind(_this3, child), + onMouseEnter: _this3.handleOptionMouseEnter.bind(_this3, index), + isSelected: _this3.state.value === props.value + }; - // shams - Object.create, Object.freeze]; + if (_this3.state.value === props.value) { + // need an ID for WAI-ARIA + newProps.id = props.id || 'ic-tokeninput-selected-' + ++guid; + activedescendant = props.id; + } - for (var i = 0; i < expectedFeatures.length; i++) { - if (!expectedFeatures[i]) { - console.error('One or more ES5 shim/shams expected by React are not available: ' + 'https://fb.me/react-warning-polyfills'); - break; - } + return _react2.default.cloneElement(child, newProps); + }); + + return { + children: _children, + activedescendant: activedescendant, + isEmpty: isEmpty + }; } - } - } - module.exports = React; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 5 */ -/***/ function(module, exports) { - - // shim for using process in browser + /** + * When the user begins typing again we need to clear out any state that has + * to do with an existing or potential selection. + */ + + }, { + key: 'clearSelectedState', + value: function clearSelectedState(cb) { + this.setState({ + focusedIndex: null, + inputValue: null, + value: null, + matchedAutocompleteOption: null, + activedescendant: null + }, cb); + } + }, { + key: 'handleInputChange', + value: function handleInputChange() { + var _this4 = this; + + var value = this.refs.input.value; + + this.clearSelectedState(function () { + _this4.props.onInput(value); + }); + } + }, { + key: 'handleInputFocus', + value: function handleInputFocus() { + this.maybeShowList(); + } + }, { + key: 'handleInputClick', + value: function handleInputClick() { + this.maybeShowList(); + } + }, { + key: 'maybeShowList', + value: function maybeShowList() { + if (this.props.showListOnFocus) { + this.showList(); + } + } + }, { + key: 'handleInputBlur', + value: function handleInputBlur() { + var focusedAnOption = this.state.focusedIndex !== null && this.state.focusedIndex !== undefined; + if (focusedAnOption) { + return; + } + this.maybeSelectAutocompletedOption(); + this.hideList(); + } + }, { + key: 'handleOptionBlur', + value: function handleOptionBlur() { + // don't want to hide the list if we focused another option + this.blurTimer = setTimeout(this.hideList, 0); + } + }, { + key: 'handleOptionFocus', + value: function handleOptionFocus() { + // see `handleOptionBlur` + clearTimeout(this.blurTimer); + } + }, { + key: 'handleInputKeyUp', + value: function handleInputKeyUp(event) { + if (this.state.menu.isEmpty + // autocompleting while backspacing feels super weird, so let's not + || event.keyCode === K_BACKSPACE || !this.props.autocomplete.match(/both|inline/)) return; + } + }, { + key: 'handleButtonClick', + value: function handleButtonClick() { + if (this.state.isOpen) { + this.hideList(); + } else { + this.showList(); + } + this.focusInput(); + } + }, { + key: 'showList', + value: function showList() { + if (!this.state.menu.children.length) { + return; + } + this.setState({ isOpen: true }); + } + }, { + key: 'hideList', + value: function hideList() { + this.setState({ + isOpen: false, + focusedIndex: null + }); + } + }, { + key: 'hideOnEscape', + value: function hideOnEscape(event) { + this.hideList(); + this.focusInput(); + event.preventDefault(); + } + }, { + key: 'focusInput', + value: function focusInput() { + this.refs.input.focus(); + } + }, { + key: 'selectInput', + value: function selectInput() { + this.refs.input.select(); + } + }, { + key: 'handleKeydown', + value: function handleKeydown(event) { + var handlerName = KEYDOWN_MAPS.inputKeydownMap[event.keyCode]; + if (!handlerName) { + return null; + } + this.setState({ usingKeyboard: true }); + return this[handlerName].call(this, event); + } + }, { + key: 'handleOptionKeyDown', + value: function handleOptionKeyDown(child, event) { + var handlerName = KEYDOWN_MAPS.optionKeydownMap[event.keyCode]; + if (!handlerName) { + // if the user starts typing again while focused on an option, move focus + // to the inpute, select so it wipes out any existing value + this.selectInput(); + return; + } + event.preventDefault(); + this.setState({ usingKeyboard: true }); + this[handlerName].call(this, child); + } + }, { + key: 'handleOptionMouseEnter', + value: function handleOptionMouseEnter(index) { + if (this.state.usingKeyboard) { + this.setState({ usingKeyboard: false }); + } else { + this.focusOptionAtIndex(index); + } + } + }, { + key: 'selectOnEnter', + value: function selectOnEnter(event) { + event.preventDefault(); + this.maybeSelectAutocompletedOption(); + } + }, { + key: 'maybeSelectAutocompletedOption', + value: function maybeSelectAutocompletedOption() { + if (!this.state.matchedAutocompleteOption) { + this.selectText(); + } else { + this.selectOption(this.state.matchedAutocompleteOption, { focus: false }); + } + } + }, { + key: 'selectOption', + value: function selectOption(child) { + var _this5 = this; - var process = module.exports = {}; - var queue = []; - var draining = false; - var currentQueue; - var queueIndex = -1; + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - function cleanUpNextTick() { - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; + this.setState({ + // value: child.props.value, + // inputValue: getLabel(child), + matchedAutocompleteOption: null + }, function () { + _this5.props.onSelect(child.props.value, child); + _this5.hideList(); + _this5.clearSelectedState(); // added + if (options.focus !== false) { + _this5.selectInput(); + } + }); + this.refs.input.value = ''; // added } - if (queue.length) { - drainQueue(); + }, { + key: 'selectText', + value: function selectText() { + var value = this.refs.input.value; + if (!value) { + return; + } + this.props.onSelect(value); + this.clearSelectedState(); + this.refs.input.value = ''; // added } - } + }, { + key: 'focusNext', + value: function focusNext(event) { + if (event.preventDefault) { + event.preventDefault(); + } - function drainQueue() { - if (draining) { + if (this.state.menu.isEmpty) { return; + } + + var index = this.state.focusedIndex === null ? 0 : this.state.focusedIndex + 1; + this.focusOptionAtIndex(index); } - var timeout = setTimeout(cleanUpNextTick); - draining = true; + }, { + key: 'removeLastToken', + value: function removeLastToken() { + if (this.props.onRemoveLast && !this.refs.input.value) { + this.props.onRemoveLast(); + } + return true; + } + }, { + key: 'focusPrevious', + value: function focusPrevious(event) { + if (event.preventDefault) event.preventDefault(); + if (this.state.menu.isEmpty) return; + var last = this.props.children.length - 1; + var index = this.state.focusedIndex === null ? last : this.state.focusedIndex - 1; + this.focusOptionAtIndex(index); + } + }, { + key: 'focusSelectedOption', + value: function focusSelectedOption() { + var _this6 = this; - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } + var selectedIndex = void 0; + _react2.default.Children.forEach(this.props.children, function (child, index) { + if (child.props.value === _this6.state.value) { + selectedIndex = index; } - queueIndex = -1; - len = queue.length; + }); + this.showList(); + this.setState({ + focusedIndex: selectedIndex + }, this.focusOption); } - currentQueue = null; - draining = false; - clearTimeout(timeout); - } + }, { + key: 'findInitialInputValue', + value: function findInitialInputValue() { + var _this7 = this; - process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; + // TODO: might not need this, we should know this in `makeMenu` + var inputValue = void 0; + _react2.default.Children.forEach(this.props.children, function (child) { + if (child.props.value === _this7.props.value) { + inputValue = child.props.label || child.props.children; } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - setTimeout(drainQueue, 0); - } - }; + }); + return inputValue; + } + }, { + key: 'focusOptionAtIndex', + value: function focusOptionAtIndex(index) { + var focusedIndex = index; + if (!this.state.isOpen && this.state.value) { + this.focusSelectedOption(); + return; + } - // v8 likes predictible objects - function Item(fun, array) { - this.fun = fun; - this.array = array; - } - Item.prototype.run = function () { - this.fun.apply(null, this.array); - }; - process.title = 'browser'; - process.browser = true; - process.env = {}; - process.argv = []; - process.version = ''; // empty string to avoid regexp issues - process.versions = {}; + this.showList(); + var length = this.props.children.length; + + + if (focusedIndex === -1) { + focusedIndex = length - 1; + } else if (index === length) { + focusedIndex = 0; + } + this.setState({ focusedIndex: focusedIndex }, this.focusOption); + } + }, { + key: 'focusOption', + value: function focusOption() { + var index = this.state.focusedIndex; + this.refs.list.childNodes[index].focus(); + } + }, { + key: 'render', + value: function render() { + var ariaLabel = this.props.ariaLabel || 'Start typing to search. ' + 'Press the down arrow to navigate results. If you don\'t find an ' + 'acceptable option, you can enter an alternative.'; + + return _react2.default.createElement( + 'div', + { className: this.getClassName() }, + this.props.value, + this.state.inputValue, + _react2.default.createElement('input', { + ref: 'input', + autoComplete: 'off', + spellCheck: 'false', + ariaLabel: ariaLabel, + ariaExpanded: '' + this.state.isOpen, + ariaHaspopup: 'true', + ariaActivedescendant: this.state.menu.activedescendant, + ariaAutocomplete: 'list', + ariaOwns: this.state.listId, + id: this.props.id, + disabled: this.props.isDisabled, + className: 'ic-tokeninput-input', + onFocus: this.handleInputFocus, + onClick: this.handleInputClick, + onChange: this.handleInputChange, + onBlur: this.handleInputBlur, + onKeyDown: this.handleKeydown, + onKeyUp: this.handleInputKeyUp, + placeholder: this.props.placeholder, + role: 'combobox' + }), + ',', + _react2.default.createElement( + 'span', + { + ariaHidden: 'true', + className: 'ic-tokeninput-button', + onClick: this.handleButtonClick + }, + '▾' + ), + _react2.default.createElement( + 'div', + { + id: this.state.listId, + ref: 'list', + className: 'ic-tokeninput-list', + role: 'listbox' + }, + this.state.menu.children + ) + ); + } + }]); - function noop() {} + return ComboBox; + }(_react2.default.Component); - process.on = noop; - process.addListener = noop; - process.once = noop; - process.off = noop; - process.removeListener = noop; - process.removeAllListeners = noop; - process.emit = noop; + exports.default = ComboBox; - process.binding = function (name) { - throw new Error('process.binding is not supported'); - }; - process.cwd = function () { return '/' }; - process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); + ComboBox.defaultProps = { + autocomplete: 'both', + onInput: emptyFunction, + onSelect: emptyFunction, + value: null, + showListOnFocus: false }; - process.umask = function() { return 0; }; - - -/***/ }, -/* 6 */ -/***/ function(module, exports) { - - /** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactCurrentOwner - */ - 'use strict'; + ComboBox.propTypes = { + /** + * Called when the combobox receives user input, this is your chance to + * filter the data and rerender the options. + * + * Signature: + * + * ```js + * function(userInput){} + * ``` + */ + onInput: _react2.default.PropTypes.func, - /** - * Keeps track of the current owner. - * - * The current owner is the component who should own any components that are - * currently being constructed. - */ - var ReactCurrentOwner = { + /** + * Called when the combobox receives a selection. You probably want to reset + * the options to the full list at this point. + * + * Signature: + * + * ```js + * function(selectedValue){} + * ``` + */ + onSelect: _react2.default.PropTypes.func, /** - * @internal - * @type {ReactComponent} - */ - current: null + * Shown when the combobox is empty. + */ + placeholder: _react2.default.PropTypes.string, + value: _react2.default.PropTypes.any, + children: _react2.default.PropTypes.node, + isDisabled: _react2.default.PropTypes.bool, + id: _react2.default.PropTypes.string, + className: _react2.default.PropTypes.string, + ariaLabel: _react2.default.PropTypes.string, + onRemoveLast: _react2.default.PropTypes.func, + autocomplete: _react2.default.PropTypes.string, + showListOnFocus: _react2.default.PropTypes.bool }; - - module.exports = ReactCurrentOwner; /***/ }, -/* 7 */ +/* 5 */ +/***/ function(module, exports) { + + module.exports = __WEBPACK_EXTERNAL_MODULE_5__; + +/***/ }, +/* 6 */ /***/ function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactDOMTextComponent - * @typechecks static-only - */ - 'use strict'; - var DOMChildrenOperations = __webpack_require__(8); - var DOMPropertyOperations = __webpack_require__(23); - var ReactComponentBrowserEnvironment = __webpack_require__(27); - var ReactMount = __webpack_require__(29); + Object.defineProperty(exports, "__esModule", { + value: true + }); - var assign = __webpack_require__(40); - var escapeTextContentForBrowser = __webpack_require__(22); - var setTextContent = __webpack_require__(21); - var validateDOMNesting = __webpack_require__(71); + var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - /** - * 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 ReactDOMTextComponent - * @extends ReactComponent - * @internal - */ - var ReactDOMTextComponent = function (props) { - // This constructor and its argument is currently used by mocks. - }; + exports.default = TokenInputOption; - assign(ReactDOMTextComponent.prototype, { + var _react = __webpack_require__(3); - /** - * @param {ReactText} text - * @internal - */ - construct: function (text) { - // TODO: This is really a ReactText (ReactNode), not a ReactElement - this._currentElement = text; - this._stringText = '' + text; + var _react2 = _interopRequireDefault(_react); - // Properties - this._rootNodeID = null; - this._mountIndex = 0; - }, + var _classnames = __webpack_require__(5); - /** - * 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. - * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction - * @return {string} Markup for this text node. - * @internal - */ - mountComponent: function (rootID, transaction, context) { - if (process.env.NODE_ENV !== 'production') { - if (context[validateDOMNesting.ancestorInfoContextKey]) { - validateDOMNesting('span', null, context[validateDOMNesting.ancestorInfoContextKey]); - } - } + var _classnames2 = _interopRequireDefault(_classnames); - this._rootNodeID = rootID; - if (transaction.useCreateElement) { - var ownerDocument = context[ReactMount.ownerDocumentContextKey]; - var el = ownerDocument.createElement('span'); - DOMPropertyOperations.setAttributeForID(el, rootID); - // Populate node cache - ReactMount.getID(el); - setTextContent(el, this._stringText); - return el; - } else { - var escapedText = escapeTextContentForBrowser(this._stringText); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - if (transaction.renderToStaticMarkup) { - // Normally we'd wrap this in a `span` for the reasons stated above, but - // since this is a situation where React won't take over (static pages), - // we can simply return the text as it is. - return escapedText; - } + function TokenInputOption(props) { + var className = props.className; + var isSelected = props.isSelected; - return '' + escapedText + ''; - } - }, + var divProps = {}; - /** - * Updates this component by updating the text content. - * - * @param {ReactText} nextText The next text content - * @param {ReactReconcileTransaction} transaction - * @internal - */ - receiveComponent: function (nextText, transaction) { - if (nextText !== this._currentElement) { - this._currentElement = nextText; - var nextStringText = '' + nextText; - if (nextStringText !== this._stringText) { - // TODO: Save this as pending props and use performUpdateIfNecessary - // and/or updateComponent to do the actual update for consistency with - // other component types? - this._stringText = nextStringText; - var node = ReactMount.getNode(this._rootNodeID); - DOMChildrenOperations.updateTextContent(node, nextStringText); - } + for (var prop in props) { + if (!props.hasOwnProperty(prop)) { + continue; } - }, - unmountComponent: function () { - ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID); + if (prop !== 'className' && prop !== 'aiaSelected') { + divProps[prop] = props[prop]; + } } - }); + var divClassName = (0, _classnames2.default)(className, 'ic-tokeninput-selected', { + 'ic-tokeninput-selected': isSelected + }); + return _react2.default.createElement('div', _extends({ ariaSelected: isSelected, className: divClassName }, divProps)); + } + + TokenInputOption.propTypes = { + /** + * The value that will be sent to the `onSelect` handler of the + * parent Combobox. + */ + value: _react2.default.PropTypes.any.isRequired, + + /** + * What value to put into the input element when this option is + * selected, defaults to its children coerced to a string. + */ + label: _react2.default.PropTypes.string, + + className: _react2.default.PropTypes.string, + isSelected: _react2.default.PropTypes.bool + }; - module.exports = ReactDOMTextComponent; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) + TokenInputOption.defaultProps = { + role: 'option', + tabIndex: '-1', + className: 'ic-tokeninput-option', + isSelected: false + }; /***/ }, -/* 8 */ +/* 7 */ /***/ function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule DOMChildrenOperations - * @typechecks static-only - */ - 'use strict'; - var Danger = __webpack_require__(9); - var ReactMultiChildUpdateTypes = __webpack_require__(17); - var ReactPerf = __webpack_require__(19); + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.default = Token; + + var _react = __webpack_require__(3); - var setInnerHTML = __webpack_require__(20); - var setTextContent = __webpack_require__(21); - var invariant = __webpack_require__(14); + var _react2 = _interopRequireDefault(_react); - /** - * 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) { - // By exploiting arrays returning `undefined` for an undefined index, we can - // rely exclusively on `insertBefore(node, null)` instead of also using - // `appendChild(node)`. However, using `undefined` is not allowed by all - // browsers so we must replace it with `null`. + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - // fix render order error in safari - // IE8 will throw error when index out of list size. - var beforeChild = index >= parentNode.childNodes.length ? null : parentNode.childNodes.item(index); + var K_ENTER = 13; - parentNode.insertBefore(childNode, beforeChild); + function handleClick(_ref) { + var onRemove = _ref.onRemove; + var value = _ref.value; + + if (typeof onRemove === 'function') { + onRemove(value); + } } - /** - * Operations for updating with DOM children. - */ - var DOMChildrenOperations = { + function handleKeyDown(onRemove, value, event) { + if (event.keyCode === K_ENTER && typeof onRemove === 'function') { + onRemove(value); + } + } - dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup, + function Token(_ref2) { + var name = _ref2.name; + var value = _ref2.value; + var onRemove = _ref2.onRemove; - updateTextContent: setTextContent, + var clickHandler = handleClick.bind(this, onRemove, value); + var keyDownHandler = handleKeyDown.bind(this, onRemove, value); - /** - * Updates a component's children by processing a series of updates. The - * update configurations are each expected to have a `parentNode` property. - * - * @param {array} updates List of update configurations. - * @param {array} 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; i < updates.length; i++) { - update = updates[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; - - !updatedChild ? process.env.NODE_ENV !== 'production' ? invariant(false, 'processUpdates(): Unable to find child %s of 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`.', updatedIndex, parentID) : invariant(false) : undefined; - - initialChildren = initialChildren || {}; - initialChildren[parentID] = initialChildren[parentID] || []; - initialChildren[parentID][updatedIndex] = updatedChild; - - updatedChildren = updatedChildren || []; - updatedChildren.push(updatedChild); - } - } - - var renderedMarkup; - // markupList is either a list of markup or just a list of elements - if (markupList.length && typeof markupList[0] === 'string') { - renderedMarkup = Danger.dangerouslyRenderMarkup(markupList); - } else { - renderedMarkup = 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; k < updates.length; k++) { - update = updates[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.SET_MARKUP: - setInnerHTML(update.parentNode, update.content); - break; - case ReactMultiChildUpdateTypes.TEXT_CONTENT: - setTextContent(update.parentNode, update.content); - break; - case ReactMultiChildUpdateTypes.REMOVE_NODE: - // Already removed by the for-loop above. - break; - } - } - } - - }; - - ReactPerf.measureMethods(DOMChildrenOperations, 'DOMChildrenOperations', { - updateTextContent: 'updateTextContent' - }); - - module.exports = DOMChildrenOperations; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 9 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule Danger - * @typechecks static-only - */ - - 'use strict'; - - var ExecutionEnvironment = __webpack_require__(10); - - var createNodesFromMarkup = __webpack_require__(11); - var emptyFunction = __webpack_require__(16); - var getMarkupWrap = __webpack_require__(15); - var invariant = __webpack_require__(14); - - 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} markupList List of markup strings to render. - * @return {array} List of rendered nodes. - * @internal - */ - dangerouslyRenderMarkup: function (markupList) { - !ExecutionEnvironment.canUseDOM ? process.env.NODE_ENV !== 'production' ? invariant(false, 'dangerouslyRenderMarkup(...): Cannot render markup in a worker ' + 'thread. Make sure `window` and `document` are available globally ' + 'before requiring React when unit testing or use ' + 'ReactDOMServer.renderToString for server rendering.') : invariant(false) : undefined; - var nodeName; - var markupByNodeName = {}; - // Group markup by `nodeName` if a wrap is necessary, else by '*'. - for (var i = 0; i < markupList.length; i++) { - !markupList[i] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'dangerouslyRenderMarkup(...): Missing markup.') : invariant(false) : undefined; - 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. - var resultIndex; - for (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 ', '
    ']; - var trWrap = [3, '', '
    ']; - - var svgWrap = [1, '', '']; - - var markupWrap = { - '*': [1, '?

    '], - - 'area': [1, '', ''], - 'col': [2, '', '
    '], - 'legend': [1, '
    ', '
    '], - 'param': [1, '', ''], - 'tr': [2, '', '
    '], - - 'optgroup': selectWrap, - 'option': selectWrap, - - 'caption': tableWrap, - 'colgroup': tableWrap, - 'tbody': tableWrap, - 'tfoot': tableWrap, - 'thead': tableWrap, - - 'td': trWrap, - 'th': trWrap - }; - - // Initialize the SVG elements since we know they'll always need to be wrapped - // consistently. If they are created inside a
    they will be initialized in - // the wrong namespace (and will not display). - var svgElements = ['circle', 'clipPath', 'defs', 'ellipse', 'g', 'image', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'text', 'tspan']; - svgElements.forEach(function (nodeName) { - markupWrap[nodeName] = svgWrap; - shouldWrap[nodeName] = true; - }); - - /** - * 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) { - !!!dummyNode ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Markup wrapping node not initialized') : invariant(false) : undefined; - if (!markupWrap.hasOwnProperty(nodeName)) { - nodeName = '*'; - } - if (!shouldWrap.hasOwnProperty(nodeName)) { - if (nodeName === '*') { - dummyNode.innerHTML = ''; - } else { - dummyNode.innerHTML = '<' + nodeName + '>'; - } - shouldWrap[nodeName] = !dummyNode.firstChild; - } - return shouldWrap[nodeName] ? markupWrap[nodeName] : null; - } - - module.exports = getMarkupWrap; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 16 */ -/***/ function(module, exports) { - - /** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule emptyFunction - */ - - "use strict"; - - 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() {} - - emptyFunction.thatReturns = makeEmptyFunction; - emptyFunction.thatReturnsFalse = makeEmptyFunction(false); - emptyFunction.thatReturnsTrue = makeEmptyFunction(true); - emptyFunction.thatReturnsNull = makeEmptyFunction(null); - emptyFunction.thatReturnsThis = function () { - return this; - }; - emptyFunction.thatReturnsArgument = function (arg) { - return arg; - }; - - module.exports = emptyFunction; - -/***/ }, -/* 17 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactMultiChildUpdateTypes - */ - - 'use strict'; - - var keyMirror = __webpack_require__(18); - - /** - * 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, - SET_MARKUP: null, - TEXT_CONTENT: null - }); - - module.exports = ReactMultiChildUpdateTypes; - -/***/ }, -/* 18 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule keyMirror - * @typechecks static-only - */ - - 'use strict'; - - var invariant = __webpack_require__(14); - - /** - * 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; - !(obj instanceof Object && !Array.isArray(obj)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'keyMirror(...): Argument must be an object.') : invariant(false) : undefined; - for (key in obj) { - if (!obj.hasOwnProperty(key)) { - continue; - } - ret[key] = key; - } - return ret; - }; - - module.exports = keyMirror; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 19 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactPerf - * @typechecks static-only - */ - - 'use strict'; - - /** - * ReactPerf is a general AOP system designed to measure performance. This - * module only has the hooks: see ReactDefaultPerf for the analysis tool. - */ - 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, - - /** - * @param {object} object - * @param {string} objectName - * @param {object} methodNames - */ - measureMethods: function (object, objectName, methodNames) { - if (process.env.NODE_ENV !== 'production') { - for (var key in methodNames) { - if (!methodNames.hasOwnProperty(key)) { - continue; - } - object[key] = ReactPerf.measure(objectName, methodNames[key], object[key]); - } - } - }, - - /** - * Use this to wrap methods you want to measure. Zero overhead in production. - * - * @param {string} objName - * @param {string} fnName - * @param {function} func - * @return {function} - */ - measure: function (objName, fnName, func) { - if (process.env.NODE_ENV !== 'production') { - var measuredFunc = null; - var wrapper = function () { - if (ReactPerf.enableMeasure) { - if (!measuredFunc) { - measuredFunc = ReactPerf.storedMeasure(objName, fnName, func); - } - return measuredFunc.apply(this, arguments); - } - return func.apply(this, arguments); - }; - wrapper.displayName = objName + '_' + fnName; - return wrapper; - } - return func; - }, - - injection: { - /** - * @param {function} measure - */ - injectMeasure: function (measure) { - ReactPerf.storedMeasure = measure; - } - } - }; - - /** - * 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; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 20 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule setInnerHTML - */ - - /* globals MSApp */ - - 'use strict'; - - var ExecutionEnvironment = __webpack_require__(10); - - var WHITESPACE_TEST = /^[ \r\n\t\f]/; - var NONVISIBLE_TEST = /<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/; - - /** - * Set the innerHTML property of a node, ensuring that whitespace is preserved - * even in IE8. - * - * @param {DOMElement} node - * @param {string} html - * @internal - */ - var setInnerHTML = function (node, html) { - node.innerHTML = html; + Token.propTypes = { + name: _react2.default.PropTypes.string, + value: _react2.default.PropTypes.any, + onRemove: _react2.default.PropTypes.func }; - - // Win8 apps: Allow all html to be inserted - if (typeof MSApp !== 'undefined' && MSApp.execUnsafeLocalFunction) { - setInnerHTML = function (node, html) { - MSApp.execUnsafeLocalFunction(function () { - node.innerHTML = html; - }); - }; - } - - if (ExecutionEnvironment.canUseDOM) { - // IE8: When updating a just created node with innerHTML only leading - // whitespace is removed. When updating an existing node with innerHTML - // whitespace in root TextNodes is also collapsed. - // @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html - - // Feature detection; only IE8 is known to behave improperly like this. - var testElement = document.createElement('div'); - testElement.innerHTML = ' '; - if (testElement.innerHTML === '') { - setInnerHTML = function (node, html) { - // Magic theory: IE8 supposedly differentiates between added and updated - // nodes when processing innerHTML, innerHTML on updated nodes suffers - // from worse whitespace behavior. Re-adding a node like this triggers - // the initial and more favorable whitespace behavior. - // TODO: What to do on a detached node? - if (node.parentNode) { - node.parentNode.replaceChild(node, node); - } - - // We also implement a workaround for non-visible tags disappearing into - // thin air on IE8, this only happens if there is no visible text - // in-front of the non-visible tags. Piggyback on the whitespace fix - // and simply check if any non-visible tags appear in the source. - if (WHITESPACE_TEST.test(html) || html[0] === '<' && NONVISIBLE_TEST.test(html)) { - // Recover leading whitespace by temporarily prepending any character. - // \uFEFF has the potential advantage of being zero-width/invisible. - // UglifyJS drops U+FEFF chars when parsing, so use String.fromCharCode - // in hopes that this is preserved even if "\uFEFF" is transformed to - // the actual Unicode character (by Babel, for example). - // https://github.com/mishoo/UglifyJS2/blob/v2.4.20/lib/parse.js#L216 - node.innerHTML = String.fromCharCode(0xFEFF) + html; - - // deleteData leaves an empty `TextNode` which offsets the index of all - // children. Definitely want to avoid this. - var textNode = node.firstChild; - if (textNode.data.length === 1) { - node.removeChild(textNode); - } else { - textNode.deleteData(0, 1); - } - } else { - node.innerHTML = html; - } - }; - } - } - - module.exports = setInnerHTML; - -/***/ }, -/* 21 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule setTextContent - */ - - 'use strict'; - - var ExecutionEnvironment = __webpack_require__(10); - var escapeTextContentForBrowser = __webpack_require__(22); - var setInnerHTML = __webpack_require__(20); - - /** - * Set the textContent property of a node, ensuring that whitespace is preserved - * even in IE8. innerText is a poor substitute for textContent and, among many - * issues, inserts
    instead of the literal newline chars. innerHTML behaves - * as it should. - * - * @param {DOMElement} node - * @param {string} text - * @internal - */ - var setTextContent = function (node, text) { - node.textContent = text; - }; - - if (ExecutionEnvironment.canUseDOM) { - if (!('textContent' in document.documentElement)) { - setTextContent = function (node, text) { - setInnerHTML(node, escapeTextContentForBrowser(text)); - }; - } - } - - module.exports = setTextContent; - -/***/ }, -/* 22 */ -/***/ function(module, exports) { - - /** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule escapeTextContentForBrowser - */ - - 'use strict'; - - var ESCAPE_LOOKUP = { - '&': '&', - '>': '>', - '<': '<', - '"': '"', - '\'': ''' - }; - - var ESCAPE_REGEX = /[&><"']/g; - - function escaper(match) { - return ESCAPE_LOOKUP[match]; - } - - /** - * Escapes text to prevent scripting attacks. - * - * @param {*} text Text value to escape. - * @return {string} An escaped string. - */ - function escapeTextContentForBrowser(text) { - return ('' + text).replace(ESCAPE_REGEX, escaper); - } - - module.exports = escapeTextContentForBrowser; - -/***/ }, -/* 23 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule DOMPropertyOperations - * @typechecks static-only - */ - - 'use strict'; - - var DOMProperty = __webpack_require__(24); - var ReactPerf = __webpack_require__(19); - - var quoteAttributeValueForBrowser = __webpack_require__(25); - var warning = __webpack_require__(26); - - // Simplified subset - var VALID_ATTRIBUTE_NAME_REGEX = /^[a-zA-Z_][\w\.\-]*$/; - var illegalAttributeNameCache = {}; - var validatedAttributeNameCache = {}; - - function isAttributeNameSafe(attributeName) { - if (validatedAttributeNameCache.hasOwnProperty(attributeName)) { - return true; - } - if (illegalAttributeNameCache.hasOwnProperty(attributeName)) { - return false; - } - if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) { - validatedAttributeNameCache[attributeName] = true; - return true; - } - illegalAttributeNameCache[attributeName] = true; - process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid attribute name: `%s`', attributeName) : undefined; - return false; - } - - function shouldIgnoreValue(propertyInfo, value) { - return value == null || propertyInfo.hasBooleanValue && !value || propertyInfo.hasNumericValue && isNaN(value) || propertyInfo.hasPositiveNumericValue && value < 1 || propertyInfo.hasOverloadedBooleanValue && value === false; - } - - if (process.env.NODE_ENV !== 'production') { - var reactProps = { - children: true, - dangerouslySetInnerHTML: true, - key: true, - ref: true - }; - var warnedProperties = {}; - - var warnUnknownProperty = function (name) { - if (reactProps.hasOwnProperty(name) && reactProps[name] || warnedProperties.hasOwnProperty(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.hasOwnProperty(lowerCasedName) ? DOMProperty.getPossibleStandardName[lowerCasedName] : null; - - // For now, only warn when we have a suggested correction. This prevents - // logging too much when using transferPropsTo. - process.env.NODE_ENV !== 'production' ? warning(standardName == null, 'Unknown DOM property %s. Did you mean %s?', name, standardName) : undefined; - }; - } - - /** - * Operations for dealing with DOM properties. - */ - var DOMPropertyOperations = { - - /** - * Creates markup for the ID property. - * - * @param {string} id Unescaped ID. - * @return {string} Markup string. - */ - createMarkupForID: function (id) { - return DOMProperty.ID_ATTRIBUTE_NAME + '=' + quoteAttributeValueForBrowser(id); - }, - - setAttributeForID: function (node, id) { - node.setAttribute(DOMProperty.ID_ATTRIBUTE_NAME, id); - }, - - /** - * 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) { - var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; - if (propertyInfo) { - if (shouldIgnoreValue(propertyInfo, value)) { - return ''; - } - var attributeName = propertyInfo.attributeName; - if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) { - return attributeName + '=""'; - } - return attributeName + '=' + quoteAttributeValueForBrowser(value); - } else if (DOMProperty.isCustomAttribute(name)) { - if (value == null) { - return ''; - } - return name + '=' + quoteAttributeValueForBrowser(value); - } else if (process.env.NODE_ENV !== 'production') { - warnUnknownProperty(name); - } - return null; - }, - - /** - * Creates markup for a custom property. - * - * @param {string} name - * @param {*} value - * @return {string} Markup string, or empty string if the property was invalid. - */ - createMarkupForCustomAttribute: function (name, value) { - if (!isAttributeNameSafe(name) || value == null) { - return ''; - } - return name + '=' + quoteAttributeValueForBrowser(value); - }, - - /** - * Sets the value for a property on a node. - * - * @param {DOMElement} node - * @param {string} name - * @param {*} value - */ - setValueForProperty: function (node, name, value) { - var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; - if (propertyInfo) { - var mutationMethod = propertyInfo.mutationMethod; - if (mutationMethod) { - mutationMethod(node, value); - } else if (shouldIgnoreValue(propertyInfo, value)) { - this.deleteValueForProperty(node, name); - } else if (propertyInfo.mustUseAttribute) { - var attributeName = propertyInfo.attributeName; - var namespace = propertyInfo.attributeNamespace; - // `setAttribute` with objects becomes only `[object]` in IE8/9, - // ('' + value) makes it output the correct toString()-value. - if (namespace) { - node.setAttributeNS(namespace, attributeName, '' + value); - } else if (propertyInfo.hasBooleanValue || propertyInfo.hasOverloadedBooleanValue && value === true) { - node.setAttribute(attributeName, ''); - } else { - node.setAttribute(attributeName, '' + value); - } - } else { - var propName = propertyInfo.propertyName; - // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the - // property type before comparing; only `value` does and is string. - if (!propertyInfo.hasSideEffects || '' + node[propName] !== '' + value) { - // Contrary to `setAttribute`, object properties are properly - // `toString`ed by IE8/9. - node[propName] = value; - } - } - } else if (DOMProperty.isCustomAttribute(name)) { - DOMPropertyOperations.setValueForAttribute(node, name, value); - } else if (process.env.NODE_ENV !== 'production') { - warnUnknownProperty(name); - } - }, - - setValueForAttribute: function (node, name, value) { - if (!isAttributeNameSafe(name)) { - return; - } - if (value == null) { - node.removeAttribute(name); - } else { - node.setAttribute(name, '' + value); - } - }, - - /** - * Deletes the value for a property on a node. - * - * @param {DOMElement} node - * @param {string} name - */ - deleteValueForProperty: function (node, name) { - var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; - if (propertyInfo) { - var mutationMethod = propertyInfo.mutationMethod; - if (mutationMethod) { - mutationMethod(node, undefined); - } else if (propertyInfo.mustUseAttribute) { - node.removeAttribute(propertyInfo.attributeName); - } else { - var propName = propertyInfo.propertyName; - var defaultValue = DOMProperty.getDefaultValueForProperty(node.nodeName, propName); - if (!propertyInfo.hasSideEffects || '' + node[propName] !== defaultValue) { - node[propName] = defaultValue; - } - } - } else if (DOMProperty.isCustomAttribute(name)) { - node.removeAttribute(name); - } else if (process.env.NODE_ENV !== 'production') { - warnUnknownProperty(name); - } - } - - }; - - ReactPerf.measureMethods(DOMPropertyOperations, 'DOMPropertyOperations', { - setValueForProperty: 'setValueForProperty', - setValueForAttribute: 'setValueForAttribute', - deleteValueForProperty: 'deleteValueForProperty' - }); - - module.exports = DOMPropertyOperations; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 24 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule DOMProperty - * @typechecks static-only - */ - - 'use strict'; - - var invariant = __webpack_require__(14); - - function checkMask(value, bitmask) { - return (value & bitmask) === bitmask; - } - - 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_SIDE_EFFECTS: 0x4, - HAS_BOOLEAN_VALUE: 0x8, - HAS_NUMERIC_VALUE: 0x10, - HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10, - HAS_OVERLOADED_BOOLEAN_VALUE: 0x40, - - /** - * 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. - * - * DOMAttributeNamespaces: object mapping React attribute name to the DOM - * attribute namespace URL. (Attribute names not specified use no namespace.) - * - * 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 Injection = DOMPropertyInjection; - var Properties = domPropertyConfig.Properties || {}; - var DOMAttributeNamespaces = domPropertyConfig.DOMAttributeNamespaces || {}; - 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) { - !!DOMProperty.properties.hasOwnProperty(propName) ? process.env.NODE_ENV !== 'production' ? invariant(false, '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) : invariant(false) : undefined; - - var lowerCased = propName.toLowerCase(); - var propConfig = Properties[propName]; - - var propertyInfo = { - attributeName: lowerCased, - attributeNamespace: null, - propertyName: propName, - mutationMethod: null, - - mustUseAttribute: checkMask(propConfig, Injection.MUST_USE_ATTRIBUTE), - mustUseProperty: checkMask(propConfig, Injection.MUST_USE_PROPERTY), - hasSideEffects: checkMask(propConfig, Injection.HAS_SIDE_EFFECTS), - hasBooleanValue: checkMask(propConfig, Injection.HAS_BOOLEAN_VALUE), - hasNumericValue: checkMask(propConfig, Injection.HAS_NUMERIC_VALUE), - hasPositiveNumericValue: checkMask(propConfig, Injection.HAS_POSITIVE_NUMERIC_VALUE), - hasOverloadedBooleanValue: checkMask(propConfig, Injection.HAS_OVERLOADED_BOOLEAN_VALUE) - }; - - !(!propertyInfo.mustUseAttribute || !propertyInfo.mustUseProperty) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'DOMProperty: Cannot require using both attribute and property: %s', propName) : invariant(false) : undefined; - !(propertyInfo.mustUseProperty || !propertyInfo.hasSideEffects) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'DOMProperty: Properties that have side effects must use property: %s', propName) : invariant(false) : undefined; - !(propertyInfo.hasBooleanValue + propertyInfo.hasNumericValue + propertyInfo.hasOverloadedBooleanValue <= 1) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'DOMProperty: Value can be one of boolean, overloaded boolean, or ' + 'numeric value, but not a combination: %s', propName) : invariant(false) : undefined; - - if (process.env.NODE_ENV !== 'production') { - DOMProperty.getPossibleStandardName[lowerCased] = propName; - } - - if (DOMAttributeNames.hasOwnProperty(propName)) { - var attributeName = DOMAttributeNames[propName]; - propertyInfo.attributeName = attributeName; - if (process.env.NODE_ENV !== 'production') { - DOMProperty.getPossibleStandardName[attributeName] = propName; - } - } - - if (DOMAttributeNamespaces.hasOwnProperty(propName)) { - propertyInfo.attributeNamespace = DOMAttributeNamespaces[propName]; - } - - if (DOMPropertyNames.hasOwnProperty(propName)) { - propertyInfo.propertyName = DOMPropertyNames[propName]; - } - - if (DOMMutationMethods.hasOwnProperty(propName)) { - propertyInfo.mutationMethod = DOMMutationMethods[propName]; - } - - DOMProperty.properties[propName] = propertyInfo; - } - } - }; - 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 = { - - ID_ATTRIBUTE_NAME: 'data-reactid', - - /** - * Map from property "standard name" to an object with info about how to set - * the property in the DOM. Each object contains: - * - * attributeName: - * Used when rendering markup or with `*Attribute()`. - * attributeNamespace - * propertyName: - * Used on DOM node instances. (This includes properties that mutate due to - * external factors.) - * mutationMethod: - * If non-null, used instead of the property or `setAttribute()` after - * initial render. - * mustUseAttribute: - * Whether the property must be accessed and mutated using `*Attribute()`. - * (This includes anything that fails ` in `.) - * mustUseProperty: - * Whether the property must be accessed and mutated as an object property. - * hasSideEffects: - * Whether or not setting a value causes side effects such as triggering - * resources to be loaded or text selection changes. If true, we read from - * the DOM before updating to ensure that the value is only set if it has - * changed. - * hasBooleanValue: - * Whether the property should be removed when set to a falsey value. - * hasNumericValue: - * Whether the property must be numeric or parse as a numeric and should be - * removed when set to a falsey value. - * hasPositiveNumericValue: - * Whether the property must be positive numeric or parse as a positive - * numeric and should be removed when set to a falsey value. - * hasOverloadedBooleanValue: - * Whether the property can be used as a flag as well as with a value. - * Removed when strictly equal to false; present without a value when - * strictly equal to true; present with a value otherwise. - */ - properties: {}, - - /** - * Mapping from lowercase property names to the properly cased version, used - * to warn in the case of missing properties. Available only in __DEV__. - * @type {Object} - */ - getPossibleStandardName: process.env.NODE_ENV !== 'production' ? {} : null, - - /** - * All of the isCustomAttribute() functions that have been injected. - */ - _isCustomAttributeFunctions: [], - - /** - * Checks whether a property name is a custom attribute. - * @method - */ - isCustomAttribute: function (attributeName) { - for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) { - var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i]; - if (isCustomAttributeFn(attributeName)) { - return true; - } - } - return false; - }, - - /** - * 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; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 25 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule quoteAttributeValueForBrowser - */ - - 'use strict'; - - var escapeTextContentForBrowser = __webpack_require__(22); - - /** - * Escapes attribute value to prevent scripting attacks. - * - * @param {*} value Value to escape. - * @return {string} An escaped string. - */ - function quoteAttributeValueForBrowser(value) { - return '"' + escapeTextContentForBrowser(value) + '"'; - } - - module.exports = quoteAttributeValueForBrowser; - -/***/ }, -/* 26 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2014-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule warning - */ - - 'use strict'; - - var emptyFunction = __webpack_require__(16); - - /** - * Similar to invariant but only logs a warning if the condition is not met. - * This can be used to log issues in development environments in critical - * paths. Removing the logging code for production environments will keep the - * same logic and follow the same code paths. - */ - - var warning = emptyFunction; - - if (process.env.NODE_ENV !== 'production') { - warning = function (condition, format) { - for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { - args[_key - 2] = arguments[_key]; - } - - if (format === undefined) { - throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument'); - } - - if (format.indexOf('Failed Composite propType: ') === 0) { - return; // Ignore CompositeComponent proptype check. - } - - if (!condition) { - var argIndex = 0; - var message = 'Warning: ' + format.replace(/%s/g, function () { - return args[argIndex++]; - }); - if (typeof console !== 'undefined') { - console.error(message); - } - try { - // --- Welcome to debugging React --- - // This error was thrown as a convenience so that you can use this stack - // to find the callsite that caused this warning to fire. - throw new Error(message); - } catch (x) {} - } - }; - } - - module.exports = warning; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 27 */ -/***/ function(module, exports, __webpack_require__) { - - /** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactComponentBrowserEnvironment - */ - - 'use strict'; - - var ReactDOMIDOperations = __webpack_require__(28); - var ReactMount = __webpack_require__(29); - - /** - * Abstracts away all functionality of the reconciler that requires knowledge of - * the browser context. TODO: These callers should be refactored to avoid the - * need for this injection. - */ - var ReactComponentBrowserEnvironment = { - - processChildrenUpdates: ReactDOMIDOperations.dangerouslyProcessChildrenUpdates, - - replaceNodeWithMarkupByID: ReactDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, - - /** - * 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); - } - - }; - - module.exports = ReactComponentBrowserEnvironment; - -/***/ }, -/* 28 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactDOMIDOperations - * @typechecks static-only - */ - - 'use strict'; - - var DOMChildrenOperations = __webpack_require__(8); - var DOMPropertyOperations = __webpack_require__(23); - var ReactMount = __webpack_require__(29); - var ReactPerf = __webpack_require__(19); - - var invariant = __webpack_require__(14); - - /** - * 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()`.' - }; - - /** - * Operations used to process updates to DOM nodes. - */ - 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); - !!INVALID_PROPERTY_ERRORS.hasOwnProperty(name) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'updatePropertyByID(...): %s', INVALID_PROPERTY_ERRORS[name]) : invariant(false) : undefined; - - // 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); - } - }, - - /** - * 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} updates List of update configurations. - * @param {array} 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); - } - }; - - ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', { - dangerouslyReplaceNodeWithMarkupByID: 'dangerouslyReplaceNodeWithMarkupByID', - dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates' - }); - - module.exports = ReactDOMIDOperations; - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) - -/***/ }, -/* 29 */ -/***/ function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(process) {/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactMount - */ - - 'use strict'; - - var DOMProperty = __webpack_require__(24); - var ReactBrowserEventEmitter = __webpack_require__(30); - var ReactCurrentOwner = __webpack_require__(6); - var ReactDOMFeatureFlags = __webpack_require__(42); - var ReactElement = __webpack_require__(43); - var ReactEmptyComponentRegistry = __webpack_require__(45); - var ReactInstanceHandles = __webpack_require__(46); - var ReactInstanceMap = __webpack_require__(48); - var ReactMarkupChecksum = __webpack_require__(49); - var ReactPerf = __webpack_require__(19); - var ReactReconciler = __webpack_require__(51); - var ReactUpdateQueue = __webpack_require__(54); - var ReactUpdates = __webpack_require__(55); - - var assign = __webpack_require__(40); - var emptyObject = __webpack_require__(59); - var containsNode = __webpack_require__(60); - var instantiateReactComponent = __webpack_require__(63); - var invariant = __webpack_require__(14); - var setInnerHTML = __webpack_require__(20); - var shouldUpdateReactComponent = __webpack_require__(68); - var validateDOMNesting = __webpack_require__(71); - var warning = __webpack_require__(26); - - 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 = '__ReactMount_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 (process.env.NODE_ENV !== 'production') { - /** __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; - } - - if (container.nodeType === DOC_NODE_TYPE) { - return container.documentElement; - } else { - return container.firstChild; - } - } - - /** - * @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) { - !!isValid(cached, id) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactMount: Two valid but unequal nodes with the same `%s`: %s', ATTR_NAME, id) : invariant(false) : undefined; - - 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]; - } - - /** - * 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] = 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) { - !(internalGetID(node) === id) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactMount: Unexpected modification of `%s`', ATTR_NAME) : invariant(false) : undefined; - - var container = ReactMount.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 (process.env.NODE_ENV !== 'production') { - 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; - ReactMount._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 {ReactMount.unmountComponentAtNode} - */ - function unmountComponentFromNode(instance, container) { - ReactReconciler.unmountComponent(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) { - if (node.nodeType !== 1) { - // Not a DOMElement, therefore not a React component - continue; - } - 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); - - if (current === containersByReactRootID[reactRootID]) { - 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.prototype.isReactComponent = {}; - if (process.env.NODE_ENV !== 'production') { - 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. - * - * ReactMount.render( - * component, - * document.getElementById('container') - * ); - * - *
    <-- Supplied `container`. - *
    <-- Rendered reactRoot of React - * // ... component. - *
    - *
    - * - * Inside of `container`, the first element rendered is the "reactRoot". - */ - var ReactMount = { - - TopLevelWrapper: TopLevelWrapper, - - /** 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(); - }, - - /** - * 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) { - ReactMount.scrollMonitor(container, function () { - ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement); - if (callback) { - ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); - } - }); - - if (process.env.NODE_ENV !== 'production') { - // 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) { - !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '_registerComponent(...): Target container is not a DOM element.') : invariant(false) : undefined; - - ReactBrowserEventEmitter.ensureScrollValueMonitoring(); - - var reactRootID = ReactMount.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. - process.env.NODE_ENV !== 'production' ? 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') : undefined; - - var componentInstance = instantiateReactComponent(nextElement, null); - var reactRootID = ReactMount._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 (process.env.NODE_ENV !== 'production') { - // 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) { - !(parentComponent != null && parentComponent._reactInternalInstance != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'parentComponent must be a valid React Component') : invariant(false) : undefined; - return ReactMount._renderSubtreeIntoContainer(parentComponent, nextElement, container, callback); - }, - - _renderSubtreeIntoContainer: function (parentComponent, nextElement, container, callback) { - !ReactElement.isValidElement(nextElement) ? process.env.NODE_ENV !== 'production' ? invariant(false, '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.' : '') : invariant(false) : undefined; - - process.env.NODE_ENV !== 'production' ? 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.') : undefined; - - 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)) { - var publicInst = prevComponent._renderedComponent.getPublicInstance(); - var updatedCallback = callback && function () { - callback.call(publicInst); - }; - ReactMount._updateRootComponent(prevComponent, nextWrappedElement, container, updatedCallback); - return publicInst; - } else { - ReactMount.unmountComponentAtNode(container); - } - } - - var reactRootElement = getReactRootElementInContainer(container); - var containerHasReactMarkup = reactRootElement && !!internalGetID(reactRootElement); - var containerHasNonRootReactChild = hasNonRootReactChild(container); - - if (process.env.NODE_ENV !== 'production') { - process.env.NODE_ENV !== 'production' ? 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.') : undefined; - - if (!containerHasReactMarkup || reactRootElement.nextSibling) { - var rootElementSibling = reactRootElement; - while (rootElementSibling) { - if (internalGetID(rootElementSibling)) { - process.env.NODE_ENV !== 'production' ? 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.') : undefined; - break; - } - rootElementSibling = rootElementSibling.nextSibling; - } - } - } - - var shouldReuseMarkup = containerHasReactMarkup && !prevComponent && !containerHasNonRootReactChild; - var component = ReactMount._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, container, callback) { - return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback); - }, - - /** - * 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.) - process.env.NODE_ENV !== 'production' ? 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') : undefined; - - !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'unmountComponentAtNode(...): Target container is not a DOM element.') : invariant(false) : undefined; - - 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 (process.env.NODE_ENV !== 'production') { - process.env.NODE_ENV !== 'production' ? 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.') : undefined; - } - - return false; - } - ReactUpdates.batchedUpdates(unmountComponentFromNode, component, container); - delete instancesByReactRootID[reactRootID]; - delete containersByReactRootID[reactRootID]; - if (process.env.NODE_ENV !== 'production') { - 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 (process.env.NODE_ENV !== 'production') { - var rootElement = rootElementsByReactRootID[reactRootID]; - if (rootElement && rootElement.parentNode !== container) { - process.env.NODE_ENV !== 'production' ? warning( - // Call internalGetID here because getID calls isValid which calls - // findReactContainerForID (this function). - internalGetID(rootElement) === reactRootID, 'ReactMount: Root element ID differed from reactRootID.') : undefined; - 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 { - process.env.NODE_ENV !== 'production' ? warning(false, 'ReactMount: Root element has been removed from its original ' + 'container. New container: %s', rootElement.parentNode) : undefined; - } - } - } - - 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); - }, - - /** - * 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 (process.env.NODE_ENV !== 'production') { - // This will throw on the next line; give an early warning - process.env.NODE_ENV !== 'production' ? 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) : undefined; - } - - firstChildren[0] = deepestAncestor.firstChild; - firstChildren.length = 1; - - while (childIndex < firstChildren.length) { - var child = firstChildren[childIndex++]; - var targetChild; - - while (child) { - var childID = ReactMount.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; - - true ? process.env.NODE_ENV !== 'production' ? 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, ReactMount.getID(ancestorNode)) : invariant(false) : undefined; - }, - - _mountImageIntoNode: function (markup, container, shouldReuseMarkup, transaction) { - !(container && (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE || container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'mountComponentIntoNode(...): Target container is not valid.') : invariant(false) : undefined; - - 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 (process.env.NODE_ENV !== 'production') { - // 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