<commit_before>/**
 * @file Checkbox component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import IconButton from '../IconButton';
import Theme from '../Theme';
import TipProvider from '../TipProvider';

import Position from '../_statics/Position';

import Util from '../_vendors/Util';

class Checkbox extends Component {

    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            checked: !!props.checked
        };

    }

    clickHandler = () => {

        const checked = !this.state.checked;

        this.setState({
            checked
        }, () => {

            const {onChange, onCheck, onUncheck} = this.props;

            onChange && onChange(checked);

            if (checked) {
                onCheck && onCheck();
            } else {
                onUncheck && onUncheck();
            }

        });

    };

    mouseDownHandler = e => {

        if (this.props.disabled) {
            return;
        }

        this.refs.checkboxIcon.startRipple(e);
        this.clickHandler();

    };

    mouseUpHandler = () => {

        if (this.props.disabled) {
            return;
        }

        this.refs.checkboxIcon.endRipple();

    };

    componentWillReceiveProps(nextProps) {
        if (nextProps.checked !== this.state.checked) {
            this.setState({
                checked: !!nextProps.checked
            });
        }
    }

    render() {

        const {
                className, style, theme, name, label, value, disabled, disableTouchRipple,
                indeterminate, uncheckedIconCls, checkedIconCls, indeterminateIconCls, tip, tipPosition
            } = this.props,
            {checked} = this.state,

            checkboxClassName = classNames('checkbox', {
                activated: checked,
                indeterminated: indeterminate,
                [`theme-${theme}`]: theme,
                [className]: className
            });

        return (
            <TipProvider text={tip}
                         position={tipPosition}>

                    {
                        name ?
                            <input className="hidden-checkbox"
                                   type="checkbox"
                                   name={name}
                                   value={value}
                                   checked={checked}
                                   onChange={() => {
                                   }}/>
                            :
                            null
                    }

                        <IconButton ref="checkboxIcon"
                                    className="checkbox-bg-icon"
                                    iconCls={uncheckedIconCls}
                                    disabled={disabled}
                                    disableTouchRipple={disableTouchRipple}/>
                        <IconButton className="checkbox-indeterminate-icon"
                                    iconCls={indeterminateIconCls}
                                    disabled={disabled}
                                    disableTouchRipple={disableTouchRipple}/>
                        <IconButton className="checkbox-icon"
                                    iconCls={checkedIconCls}
                                    disabled={disabled}
                                    disableTouchRipple={disableTouchRipple}/>

                    {
                        label ?

                                {label}

                            :
                            null
                    }

            </TipProvider>
        );

    }
}

Checkbox.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The Checkbox theme.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The name of the checkbox.
     */
    name: PropTypes.string,

    /**
     * Label for checkbox.
     */
    label: PropTypes.any,

    /**
     * Value for checkbox.
     */
    value: PropTypes.any,

    /**
     * If true,the checkbox will be checked.
     */
    checked: PropTypes.bool,

    indeterminate: PropTypes.bool,

    uncheckedIconCls: PropTypes.string,
    checkedIconCls: PropTypes.string,
    indeterminateIconCls: PropTypes.string,

    /**
     * If true, the checkbox will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * If true,the element's ripple effect will be disabled.
     */
    disableTouchRipple: PropTypes.bool,

    tip: PropTypes.any,
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    /**
     * Callback function fired when the checkbox status change.
     */
    onChange: PropTypes.func,

    onCheck: PropTypes.func,

    onUncheck: PropTypes.func

};

Checkbox.defaultProps = {

    theme: Theme.DEFAULT,

    value: '',
    checked: false,
    indeterminate: false,
    uncheckedIconCls: 'far fa-square',
    checkedIconCls: 'fas fa-check-square',
    indeterminateIconCls: 'fas fa-minus-square',
    disabled: false,
    disableTouchRipple: false,

    tipPosition: Position.BOTTOM

};

export default Checkbox;<commit_msg>Checkbox bug fixed<commit_after>/**
 * @file Checkbox component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import IconButton from '../IconButton';
import Theme from '../Theme';
import TipProvider from '../TipProvider';

import Position from '../_statics/Position';

import Util from '../_vendors/Util';

class Checkbox extends Component {

    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            checked: !!props.checked
        };

    }

    clickHandler = () => {

        if (this.props.disabled) {
            return;
        }

        const checked = !this.state.checked;

        this.setState({
            checked
        }, () => {

            const {onChange, onCheck, onUncheck} = this.props;

            onChange && onChange(checked);

            if (checked) {
                onCheck && onCheck();
            } else {
                onUncheck && onUncheck();
            }

        });

    };

    mouseDownHandler = e => {

        if (this.props.disabled) {
            return;
        }

        this.refs.checkboxIcon.startRipple(e);
        this.clickHandler();

    };

    mouseUpHandler = () => {

        if (this.props.disabled) {
            return;
        }

        this.refs.checkboxIcon.endRipple();

    };

    componentWillReceiveProps(nextProps) {
        if (nextProps.checked !== this.state.checked) {
            this.setState({
                checked: !!nextProps.checked
            });
        }
    }

    render() {

        const {
                className, style, theme, name, label, value, disabled, disableTouchRipple,
                indeterminate, uncheckedIconCls, checkedIconCls, indeterminateIconCls, tip, tipPosition
            } = this.props,
            {checked} = this.state,

            checkboxClassName = classNames('checkbox', {
                activated: checked,
                indeterminated: indeterminate,
                [`theme-${theme}`]: theme,
                [className]: className
            });

        return (
            <TipProvider text={tip}
                         position={tipPosition}>

                    {
                        name ?
                            <input className="hidden-checkbox"
                                   type="checkbox"
                                   name={name}
                                   value={value}
                                   checked={checked}
                                   onChange={() => {
                                   }}/>
                            :
                            null
                    }

                        <IconButton ref="checkboxIcon"
                                    className="checkbox-bg-icon"
                                    iconCls={uncheckedIconCls}
                                    disabled={disabled}
                                    disableTouchRipple={disableTouchRipple}/>
                        <IconButton className="checkbox-indeterminate-icon"
                                    iconCls={indeterminateIconCls}
                                    disabled={disabled}
                                    disableTouchRipple={disableTouchRipple}/>
                        <IconButton className="checkbox-icon"
                                    iconCls={checkedIconCls}
                                    disabled={disabled}
                                    disableTouchRipple={disableTouchRipple}/>

                    {
                        label ?

                                {label}

                            :
                            null
                    }

            </TipProvider>
        );

    }
}

Checkbox.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The Checkbox theme.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The name of the checkbox.
     */
    name: PropTypes.string,

    /**
     * Label for checkbox.
     */
    label: PropTypes.any,

    /**
     * Value for checkbox.
     */
    value: PropTypes.any,

    /**
     * If true,the checkbox will be checked.
     */
    checked: PropTypes.bool,

    indeterminate: PropTypes.bool,

    uncheckedIconCls: PropTypes.string,
    checkedIconCls: PropTypes.string,
    indeterminateIconCls: PropTypes.string,

    /**
     * If true, the checkbox will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * If true,the element's ripple effect will be disabled.
     */
    disableTouchRipple: PropTypes.bool,

    tip: PropTypes.any,
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    /**
     * Callback function fired when the checkbox status change.
     */
    onChange: PropTypes.func,

    onCheck: PropTypes.func,

    onUncheck: PropTypes.func

};

Checkbox.defaultProps = {

    theme: Theme.DEFAULT,

    value: '',
    checked: false,
    indeterminate: false,
    uncheckedIconCls: 'far fa-square',
    checkedIconCls: 'fas fa-check-square',
    indeterminateIconCls: 'fas fa-minus-square',
    disabled: false,
    disableTouchRipple: false,

    tipPosition: Position.BOTTOM

};

export default Checkbox;<|endoftext|>


<commit_before>let AdventureGameObject = AdventureGameObject || {}
let AdventureScreenObject = AdventureScreenObject || {}
;(function (undefined) {
  let Game = AdventureGameObject
  let Screen = AdventureScreenObject
  Screen.previousCommands = []
  let previousCommandCounter = Screen.previousCommands.length
  Screen.userTextToScreen = function () {
    let userInputField = document.getElementById('text-input')
    userInputField.onkeydown = function (e) {
      let userInput
      let event = e || window.event
      let keyCode = event.keyCode || event.which
      if (keyCode === 38) {
        let value = Screen.previousCommands[previousCommandCounter - 1]
        userInputField.value = value
        previousCommandCounter++
      }
      if (keyCode === 13) {
        userInput = userInputField.value
        Screen.previousCommands.push(userInput)
        previousCommandCounter = Screen.previousCommands.length
        console.log(Screen.previousCommands)
        userInputField.value = ''
        return Screen.addUserText(userInput)
      }
    }
  }

  Screen.addUserText = function (userInput) {
    let userInputDiv = document.createElement('div')
    let newUserText = document.createTextNode('> ' + userInput)
    let textHolder = document.getElementById('text-holder')
    let text = document.getElementById('text')
    userInputDiv.classList.add('pb-5')
    document.getElementById('text').appendChild(userInputDiv)
    Game.parseText(userInput)
    userInputDiv.appendChild(newUserText)
    textHolder.scrollTop = text.clientHeight
  }

  Screen.displayConsoleMessage = function (message) {
    let consoleDiv = document.createElement('div')
    let consoleText = document.createTextNode(message)
    let newBreak = document.createElement('br')
    consoleDiv.classList.add('pb-5')
    document.getElementById('text').appendChild(consoleDiv)
    document.getElementById('text').appendChild(newBreak)
    consoleDiv.appendChild(consoleText)
  }

  Screen.addAreaText = function (locationName) {
    let newTitleDiv = document.createElement('div')
    let newTitleText = document.createTextNode(locationName.name)
    let newDescDiv = document.createElement('div')
    let newDescText = document.createTextNode(locationName.desc)
    let newBreak = document.createElement('br')
    newTitleDiv.classList.add('ta-c', 'pb-5')
    document.getElementById('text').appendChild(newTitleDiv)
    document.getElementById('text').appendChild(newBreak)
    document.getElementById('text').appendChild(newDescDiv)
    document.getElementById('text').appendChild(newBreak)
    newTitleDiv.appendChild(newTitleText)
    newDescDiv.appendChild(newDescText)
  }
})()
<commit_msg>added past few commands and working on opening up locked spaces<commit_after>let AdventureGameObject = AdventureGameObject || {}
let AdventureScreenObject = AdventureScreenObject || {}
;(function (undefined) {
  let Game = AdventureGameObject
  let Screen = AdventureScreenObject
  Screen.previousCommands = []
  let previousCommandCounter = Screen.previousCommands.length
  Screen.userTextToScreen = function () {
    let userInputField = document.getElementById('text-input')
    userInputField.onkeydown = function (e) {
      let userInput
      let event = e || window.event
      let keyCode = event.keyCode || event.which
      if (keyCode === 38) {
        if (previousCommandCounter !== 0) {
          let value = Screen.previousCommands[previousCommandCounter - 1]
          userInputField.value = value
          console.log(previousCommandCounter)
          previousCommandCounter--
        }
      }

      if (keyCode === 40) {
        if (previousCommandCounter !== Screen.previousCommands.length - 1) {
          let value = Screen.previousCommands[previousCommandCounter + 1]
          userInputField.value = value
          console.log(previousCommandCounter)
          previousCommandCounter++
        }
      }
      if (keyCode === 13) {
        userInput = userInputField.value
        Screen.previousCommands.push(userInput)
        previousCommandCounter = Screen.previousCommands.length
        console.log(Screen.previousCommands)
        userInputField.value = ''
        return Screen.addUserText(userInput)
      }
    }
  }

  Screen.addUserText = function (userInput) {
    let userInputDiv = document.createElement('div')
    let newUserText = document.createTextNode('> ' + userInput)
    let textHolder = document.getElementById('text-holder')
    let text = document.getElementById('text')
    userInputDiv.classList.add('pb-5')
    document.getElementById('text').appendChild(userInputDiv)
    Game.parseText(userInput)
    userInputDiv.appendChild(newUserText)
    textHolder.scrollTop = text.clientHeight
  }

  Screen.displayConsoleMessage = function (message) {
    let consoleDiv = document.createElement('div')
    let consoleText = document.createTextNode(message)
    let newBreak = document.createElement('br')
    consoleDiv.classList.add('pb-5')
    document.getElementById('text').appendChild(consoleDiv)
    document.getElementById('text').appendChild(newBreak)
    consoleDiv.appendChild(consoleText)
  }

  Screen.addAreaText = function (locationName) {
    let newTitleDiv = document.createElement('div')
    let newTitleText = document.createTextNode(locationName.name)
    let newDescDiv = document.createElement('div')
    let newDescText = document.createTextNode(locationName.desc)
    let newBreak = document.createElement('br')
    newTitleDiv.classList.add('ta-c', 'pb-5')
    document.getElementById('text').appendChild(newTitleDiv)
    document.getElementById('text').appendChild(newBreak)
    document.getElementById('text').appendChild(newDescDiv)
    document.getElementById('text').appendChild(newBreak)
    newTitleDiv.appendChild(newTitleText)
    newDescDiv.appendChild(newDescText)
  }
})()
<|endoftext|>


<commit_before>//var React = require('react');
import React from 'react';
var utils = require('./utils');

export default ComposedComponent => class extends React.Component {

    constructor(props) {
        super(props);
        this.onChangeValidate = this.onChangeValidate.bind(this);
        let value = this.defaultValue(this.props);
        let validationResult = utils.validate(this.props.form, value);
        this.state = {
            value: value,
            valid: !!(validationResult.valid || !value),
            error: !validationResult.valid && value ? validationResult.error.message : null
        };
    }

    componentWillReceiveProps(nextProps) {
      let value = this.defaultValue(nextProps);
      let validationResult = utils.validate(nextProps.form, value);
      this.setState({
        value: value,
        valid: !!(validationResult.valid || !value),
        error: !validationResult.valid && value ? validationResult.error.message : null
      });
    }

    /**
     * Called when <input> value changes.
     * @param e The input element, or something.
     */
    onChangeValidate(e) {
        //console.log('onChangeValidate e', e);
        let value = null;
        switch(this.props.form.schema.type) {
          case 'integer':
          case 'number':
            if (e.target.value.indexOf('.') == -1) {
                value = parseInt(e.target.value);
            } else {
                value = parseFloat(e.target.value);
            }

            if (isNaN(value)) {
              value = undefined;
            }
            break;
          case 'boolean':
            value = e.target.checked;
            break;
          case 'object':
          case 'date':
          case 'array':
            value = e;
            break
          default:
            value = e.target.value;
        }
        //console.log('onChangeValidate this.props.form, value', this.props.form, value);
        let validationResult = utils.validate(this.props.form, value);
        this.setState({
            value: value,
            valid: validationResult.valid,
            error: validationResult.valid ? null : validationResult.error.message
        });
        //console.log('conhangeValidate this.props.form.key, value', this.props.form.key, value);
        this.props.onChange(this.props.form.key, value);
    }

    defaultValue(props) {
        // check if there is a value in the model, if there is, display it. Otherwise, check if
        // there is a default value, display it.
        //console.log('Text.defaultValue key', this.props.form.key);
        //console.log('Text.defaultValue model', this.props.model);
        let value = utils.selectOrSet(props.form.key, props.model);
        //console.log('Text defaultValue value = ', value);

        // check if there is a default value
        if(!value && props.form['default']) {
            value = props.form['default'];
        }

        if(!value && props.form.schema && props.form.schema['default']) {
            value = props.form.schema['default'];
        }

        // Support for Select
        // The first value in the option will be the default.
        if(!value && props.form.titleMap && props.form.titleMap[0].value) {
            value = props.form.titleMap[0].value;
        }
        //console.log('value', value);
        return value;
    }

    render() {
        return <ComposedComponent {...this.props} {...this.state} onChangeValidate={this.onChangeValidate}/>;
    }
};
<commit_msg>ComposedComponent: add defaultProps for composed component customization<commit_after>//var React = require('react');
import React from 'react';
var utils = require('./utils');

export default (ComposedComponent, defaultProps = {}) => class extends React.Component {

    constructor(props) {
        super(props);
        this.onChangeValidate = this.onChangeValidate.bind(this);
        let value = this.defaultValue(this.props);
        let validationResult = utils.validate(this.props.form, value);
        this.state = {
            value: value,
            valid: !!(validationResult.valid || !value),
            error: !validationResult.valid && value ? validationResult.error.message : null
        };
    }

    componentWillReceiveProps(nextProps) {
      let value = this.defaultValue(nextProps);
      let validationResult = utils.validate(nextProps.form, value);
      this.setState({
        value: value,
        valid: !!(validationResult.valid || !value),
        error: !validationResult.valid && value ? validationResult.error.message : null
      });
    }

    /**
     * Called when <input> value changes.
     * @param e The input element, or something.
     */
    onChangeValidate(e) {
        //console.log('onChangeValidate e', e);
        let value = null;
        switch(this.props.form.schema.type) {
          case 'integer':
          case 'number':
            if (e.target.value.indexOf('.') == -1) {
                value = parseInt(e.target.value);
            } else {
                value = parseFloat(e.target.value);
            }

            if (isNaN(value)) {
              value = undefined;
            }
            break;
          case 'boolean':
            value = e.target.checked;
            break;
          case 'object':
          case 'date':
          case 'array':
            value = e;
            break
          default:
            value = e.target.value;
        }
        //console.log('onChangeValidate this.props.form, value', this.props.form, value);
        let validationResult = utils.validate(this.props.form, value);
        this.setState({
            value: value,
            valid: validationResult.valid,
            error: validationResult.valid ? null : validationResult.error.message
        });
        //console.log('conhangeValidate this.props.form.key, value', this.props.form.key, value);
        this.props.onChange(this.props.form.key, value);
    }

    defaultValue(props) {
        // check if there is a value in the model, if there is, display it. Otherwise, check if
        // there is a default value, display it.
        //console.log('Text.defaultValue key', this.props.form.key);
        //console.log('Text.defaultValue model', this.props.model);
        let value = utils.selectOrSet(props.form.key, props.model);
        //console.log('Text defaultValue value = ', value);

        // check if there is a default value
        if(!value && props.form['default']) {
            value = props.form['default'];
        }

        if(!value && props.form.schema && props.form.schema['default']) {
            value = props.form.schema['default'];
        }

        // Support for Select
        // The first value in the option will be the default.
        if(!value && props.form.titleMap && props.form.titleMap[0].value) {
            value = props.form.titleMap[0].value;
        }
        //console.log('value', value);
        return value;
    }

    render() {
        return <ComposedComponent {...defaultProps} {...this.props} {...this.state} onChangeValidate={this.onChangeValidate}/>;
    }
};
<|endoftext|>


<commit_before>/**
 * @file TipProvider component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component, cloneElement} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';

import Tip from '../Tip';
import Theme from '../Theme';

import Util from '../_vendors/Util';

class TipProvider extends Component {

    static Position = Tip.Position;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            tipVisible: false
        };

        this.showTip = ::this.showTip;
        this.hideTip = ::this.hideTip;

    }

    showTip() {
        if (!this.state.tipVisible) {
            this.setState({
                tipVisible: true
            });
        }
    }

    hideTip() {
        this.setState({
            tipVisible: false
        });
    }

    componentDidMount() {
        this.refs.trigger && (this.triggerEl = findDOMNode(this.refs.trigger));
    }

    render() {

        const {children, text, onTipRender, ...restProps} = this.props,
            {tipVisible} = this.state;

        if (!text) {
            return children;
        }

        return (

                    {cloneElement(children, {
                        ref: 'trigger'
                    })}

                <Tip {...restProps}
                     triggerEl={this.triggerEl}
                     visible={tipVisible}
                     onRender={onTipRender}>
                    {text}
                </Tip>

        );
    }

}

TipProvider.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the popover.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the popover is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the popover will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    /**
     * The popover theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The popover alignment.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Tip.Position)),

    /**
     * If true, menu will have animation effects.
     */
    isAnimated: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    isTriggerPositionFixed: PropTypes.bool,

    /**
     * The function of tip render.
     */
    onTipRender: PropTypes.func,

    /**
     * Callback function fired when the popover is requested to be closed.
     */
    onRequestClose: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func,

    text: PropTypes.any

};

TipProvider.defaultProps = {

    className: '',
    style: null,
    theme: Theme.DARK,

    triggerEl: null,
    visible: false,
    hasTriangle: true,
    position: Tip.Position.BOTTOM,
    isAnimated: true,
    depth: 6,
    shouldPreventContainerScroll: true,
    isTriggerPositionFixed: false,

    text: null

};

export default TipProvider;<commit_msg>update TipProvider<commit_after>/**
 * @file TipProvider component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component, cloneElement} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';

import Tip from '../Tip';
import Theme from '../Theme';

import Util from '../_vendors/Util';

class TipProvider extends Component {

    static Position = Tip.Position;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            tipVisible: false
        };

        this.showTip = ::this.showTip;
        this.hideTip = ::this.hideTip;

    }

    showTip() {
        if (!this.state.tipVisible) {
            this.setState({
                tipVisible: true
            });
        }
    }

    hideTip() {
        this.setState({
            tipVisible: false
        });
    }

    componentDidMount() {
        this.refs.trigger && (this.triggerEl = findDOMNode(this.refs.trigger));
    }

    render() {

        const {children, text, onTipRender, ...restProps} = this.props,
            {tipVisible} = this.state;

        if (!text) {
            return children;
        }

        return (

                    {cloneElement(children, {
                        ref: 'trigger'
                    })}

                <Tip {...restProps}
                     triggerEl={this.triggerEl}
                     visible={tipVisible}
                     onRender={onTipRender}>
                    {text}
                </Tip>

        );
    }

}

TipProvider.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the popover.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the popover is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the popover will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    /**
     * The popover theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The popover alignment.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Tip.Position)),

    /**
     * If true, menu will have animation effects.
     */
    isAnimated: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    isTriggerPositionFixed: PropTypes.bool,

    /**
     * The function of tip render.
     */
    onTipRender: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func,

    text: PropTypes.any

};

TipProvider.defaultProps = {

    className: '',
    style: null,
    theme: Theme.DARK,

    triggerEl: null,
    visible: false,
    hasTriangle: true,
    position: Tip.Position.BOTTOM,
    isAnimated: true,
    depth: 6,
    shouldPreventContainerScroll: true,
    isTriggerPositionFixed: false,

    text: null

};

export default TipProvider;<|endoftext|>


<commit_before>import classNames from 'classnames';
import React from 'react/addons';

import BindMixin from '../Mixin/BindMixin';
import DOMUtil from '../Util/DOMUtil';
import Util from '../Util/Util';

const CSSTransitionGroup = React.addons.CSSTransitionGroup;

export default class Dropdown extends Util.mixin(BindMixin) {
  get methodsToBind() {
    return [
      'handleMenuToggle',
      'handleExternalClick',
      'handleWrapperBlur'
    ];
  }

  constructor() {
    super();
    this.state = {
      menuDirection: 'down',
      menuHeight: null,
      isOpen: false,
      selectedID: null
    };
  }

  componentDidUpdate() {
    // If we don't know the menu height already, we need to calculate it after
    // it's rendered. It's rendered inside a concealed container, so it's okay
    // if it renders in the wrong direction.
    if (this.state.menuHeight == null) {
      let dropdownMenuConcealer = this.refs.dropdownMenuConcealer.getDOMNode();
      let menuDirection = this.state.menuDirection;
      let menuHeight = this.state.menuHeight;

      if (dropdownMenuConcealer != null) {
        // Get the height and direction of the concealed menu.
        menuHeight = dropdownMenuConcealer.firstChild.clientHeight || 0;
        menuDirection = this.getMenuDirection(menuHeight);
      }

      // Setting state with menu height and direction will re-render the
      // dropdown in the correct direction and not concealed.
      this.setState({
        menuDirection,
        menuHeight
      });
    }
  }

  componentWillMount() {
    this.setState({
      selectedID: this.props.selectedID
    });
  }

  getMenuDirection(menuHeight) {
    // If we don't know the menu height, render it down.
    if (menuHeight == null) {
      return 'down';
    }
    // Calculate the space above and below the dropdown button.
    let spaceBelowDropdown = this.getSpaceBelowDropdown();

    if (menuHeight > spaceBelowDropdown) {
      return 'up';
    } else {
      return 'down';
    }
  }

  getMenuItems(items) {
    return items.map((item) => {
      let classSet = classNames(
        {
          'is-selectable': item.selectable !== false,
          'is-selected': item.id === this.state.selectedID
        },
        item.className,
        this.props.dropdownMenuListItemClassName
      );

      let handleUserClick = null;

      if (item.selectable !== false) {
        handleUserClick = this.handleItemClick.bind(this, item);
      }

      return (

          {item.html}

      );
    }, this);
  }

  getSelectedHtml(id, items) {
    let obj = Util.find(items, function (item) {
      return item.id === id;
    });

    return obj.selectedHtml || obj.html;
  }

  getSpaceBelowDropdown() {
    let dropdownWrapper = React.findDOMNode(this.refs.dropdownWrapper);
    let position = dropdownWrapper.getBoundingClientRect();
    let viewportHeight = global.window.innerHeight;

    // Calculate the distance from the bottom of the viewport to the bottom of
    // the dropdown button.
    return viewportHeight - position.bottom;
  }

  handleExternalClick() {
    if (this.state.isOpen) {
      this.setState({
        isOpen: false
      });
    }
  }

  handleItemClick(item) {
    this.props.onItemSelection(item);

    this.setState({
      isOpen: false,
      selectedID: item.id
    });
  }

  handleWrapperBlur(e) {
    let elID = React.findDOMNode(this).dataset.reactid;
    let currentEl = e.relatedTarget;

    // If the blur event fired from within the current dropdown, then the menu
    // needs to remain open. Otherwise it can safely be closed.
    if (currentEl && DOMUtil.closest(currentEl, `[data-reactid="${elID}"]`)) {
      return;
    }

    if (this.state.isOpen === true) {
      this.setState({isOpen: false});
    }
  }

  handleMenuToggle(e) {
    e.stopPropagation();
    let menuDirection = this.state.menuDirection;

    // If the menu isn't open, then we're about to open it and we need to
    // calculate the direction every time.
    if (!this.state.isOpen) {
      menuDirection = this.getMenuDirection(this.state.menuHeight);
    }

    this.setState({
      menuDirection,
      isOpen: !this.state.isOpen
    });
  }

  render() {
    // Set a key based on the menu height so that React knows to keep the
    // the DOM element around while we are measuring it.
    let props = this.props;
    let state = this.state;
    let dropdownKey = 'initial-render';
    let dropdownMenu = null;
    let dropdownMenuClassSet = classNames(
      state.menuDirection,
      props.dropdownMenuClassName
    );
    let dropdownStateClassSet = {
      'open': state.isOpen
    };
    let items = props.items;
    let transitionName =
      `${props.transitionName}-${state.menuDirection}`;
    let wrapperClassSet = classNames(
      state.menuDirection,
      dropdownStateClassSet,
      props.wrapperClassName
    );

    if (state.menuHeight != null) {
      dropdownKey = state.menuHeight;
    }

    if (state.isOpen) {
      dropdownMenu = (

            {this.getMenuItems(props.items)}

      );
    }

    // If we don't know the menu's height, we render it invisibly and then
    // immediately measure its height in #componentDidUpdate, which will change
    // the state and trigger another render.
    if (state.isOpen && state.menuHeight == null) {
      dropdownMenu = (

          {dropdownMenu}

      );
    }

    if (props.transition) {
      dropdownMenu = (
        <CSSTransitionGroup transitionName={transitionName}>
          {dropdownMenu}
        </CSSTransitionGroup>
      );
    }

    return (

        <button className={props.buttonClassName}
          onClick={this.handleMenuToggle}
          ref="button"
          type="button">
          {this.getSelectedHtml(state.selectedID, items)}
        </button>
        {dropdownMenu}

    );
  }
}

Dropdown.defaultProps = {
  transition: false,
  transitionName: 'dropdown-menu',
  onItemSelection: () => {}
};

Dropdown.propTypes = {
  // The items to display in the dropdown.
  items: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      // An optional classname for the menu item.
      className: React.PropTypes.string,
      // A required ID for each item
      id: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number
      ]).isRequired,
      // The HTML (or text) to render for the list item.
      html: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
      ]),
      // Whether or not the user can choose the item.
      selectable: React.PropTypes.bool,
      // The HTML (or text) to display when the option is selected. If this is
      // not provided, the value for the `html` property will be used.
      selectedHtml: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
      ])
    })
  ).isRequired,
  // An optional callback when an item is selected. Will receive an argument
  // containing the selected item as it was supplied via the items array.
  onItemSelection: React.PropTypes.func,
  // The ID of the item that should be selected by default.
  selectedID: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number
  ]).isRequired,
  // Optional transition on the dropdown menu. Must be accompanied
  // by an animation or transition in CSS.
  transition: React.PropTypes.bool,
  // The prefix of the transition classnames.
  transitionName: React.PropTypes.string,

  // Classes:
  // Classname for the element that ther user interacts with to open menu.
  buttonClassName: React.PropTypes.string,
  // Classname for the dropdown menu wrapper.
  dropdownMenuClassName: React.PropTypes.string,
  // Classname for the dropdown list wrapper.
  dropdownMenuListClassName: React.PropTypes.string,
  // Classname for the dropdown list item.
  dropdownMenuListItemClassName: React.PropTypes.string,
  // Classname for the element that wraps the entire component.
  wrapperClassName: React.PropTypes.string
};
<commit_msg>Check to make sure the dropdownMenuConcealer ref exists<commit_after>import classNames from 'classnames';
import React from 'react/addons';

import BindMixin from '../Mixin/BindMixin';
import DOMUtil from '../Util/DOMUtil';
import Util from '../Util/Util';

const CSSTransitionGroup = React.addons.CSSTransitionGroup;

export default class Dropdown extends Util.mixin(BindMixin) {
  get methodsToBind() {
    return [
      'handleMenuToggle',
      'handleExternalClick',
      'handleWrapperBlur'
    ];
  }

  constructor() {
    super();
    this.state = {
      menuDirection: 'down',
      menuHeight: null,
      isOpen: false,
      selectedID: null
    };
  }

  componentDidUpdate() {
    // If we don't know the menu height already, we need to calculate it after
    // it's rendered. It's rendered inside a concealed container, so it's okay
    // if it renders in the wrong direction.
    if (this.state.menuHeight == null &&
      this.refs.dropdownMenuConcealer != null) {
      let dropdownMenuConcealer = this.refs.dropdownMenuConcealer.getDOMNode();
      let menuDirection = this.state.menuDirection;
      let menuHeight = this.state.menuHeight;

      if (dropdownMenuConcealer != null) {
        // Get the height and direction of the concealed menu.
        menuHeight = dropdownMenuConcealer.firstChild.clientHeight || 0;
        menuDirection = this.getMenuDirection(menuHeight);
      }

      // Setting state with menu height and direction will re-render the
      // dropdown in the correct direction and not concealed.
      this.setState({
        menuDirection,
        menuHeight
      });
    }
  }

  componentWillMount() {
    this.setState({
      selectedID: this.props.selectedID
    });
  }

  getMenuDirection(menuHeight) {
    // If we don't know the menu height, render it down.
    if (menuHeight == null) {
      return 'down';
    }
    // Calculate the space above and below the dropdown button.
    let spaceBelowDropdown = this.getSpaceBelowDropdown();

    if (menuHeight > spaceBelowDropdown) {
      return 'up';
    } else {
      return 'down';
    }
  }

  getMenuItems(items) {
    return items.map((item) => {
      let classSet = classNames(
        {
          'is-selectable': item.selectable !== false,
          'is-selected': item.id === this.state.selectedID
        },
        item.className,
        this.props.dropdownMenuListItemClassName
      );

      let handleUserClick = null;

      if (item.selectable !== false) {
        handleUserClick = this.handleItemClick.bind(this, item);
      }

      return (

          {item.html}

      );
    }, this);
  }

  getSelectedHtml(id, items) {
    let obj = Util.find(items, function (item) {
      return item.id === id;
    });

    return obj.selectedHtml || obj.html;
  }

  getSpaceBelowDropdown() {
    let dropdownWrapper = React.findDOMNode(this.refs.dropdownWrapper);
    let position = dropdownWrapper.getBoundingClientRect();
    let viewportHeight = global.window.innerHeight;

    // Calculate the distance from the bottom of the viewport to the bottom of
    // the dropdown button.
    return viewportHeight - position.bottom;
  }

  handleExternalClick() {
    if (this.state.isOpen) {
      this.setState({
        isOpen: false
      });
    }
  }

  handleItemClick(item) {
    this.props.onItemSelection(item);

    this.setState({
      isOpen: false,
      selectedID: item.id
    });
  }

  handleWrapperBlur(e) {
    let elID = React.findDOMNode(this).dataset.reactid;
    let currentEl = e.relatedTarget;

    // If the blur event fired from within the current dropdown, then the menu
    // needs to remain open. Otherwise it can safely be closed.
    if (currentEl && DOMUtil.closest(currentEl, `[data-reactid="${elID}"]`)) {
      return;
    }

    if (this.state.isOpen === true) {
      this.setState({isOpen: false});
    }
  }

  handleMenuToggle(e) {
    e.stopPropagation();
    let menuDirection = this.state.menuDirection;

    // If the menu isn't open, then we're about to open it and we need to
    // calculate the direction every time.
    if (!this.state.isOpen) {
      menuDirection = this.getMenuDirection(this.state.menuHeight);
    }

    this.setState({
      menuDirection,
      isOpen: !this.state.isOpen
    });
  }

  render() {
    // Set a key based on the menu height so that React knows to keep the
    // the DOM element around while we are measuring it.
    let props = this.props;
    let state = this.state;
    let dropdownKey = 'initial-render';
    let dropdownMenu = null;
    let dropdownMenuClassSet = classNames(
      state.menuDirection,
      props.dropdownMenuClassName
    );
    let dropdownStateClassSet = {
      'open': state.isOpen
    };
    let items = props.items;
    let transitionName =
      `${props.transitionName}-${state.menuDirection}`;
    let wrapperClassSet = classNames(
      state.menuDirection,
      dropdownStateClassSet,
      props.wrapperClassName
    );

    if (state.menuHeight != null) {
      dropdownKey = state.menuHeight;
    }

    if (state.isOpen) {
      dropdownMenu = (

            {this.getMenuItems(props.items)}

      );
    }

    // If we don't know the menu's height, we render it invisibly and then
    // immediately measure its height in #componentDidUpdate, which will change
    // the state and trigger another render.
    if (state.isOpen && state.menuHeight == null) {
      dropdownMenu = (

          {dropdownMenu}

      );
    }

    if (props.transition) {
      dropdownMenu = (
        <CSSTransitionGroup transitionName={transitionName}>
          {dropdownMenu}
        </CSSTransitionGroup>
      );
    }

    return (

        <button className={props.buttonClassName}
          onClick={this.handleMenuToggle}
          ref="button"
          type="button">
          {this.getSelectedHtml(state.selectedID, items)}
        </button>
        {dropdownMenu}

    );
  }
}

Dropdown.defaultProps = {
  transition: false,
  transitionName: 'dropdown-menu',
  onItemSelection: () => {}
};

Dropdown.propTypes = {
  // The items to display in the dropdown.
  items: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      // An optional classname for the menu item.
      className: React.PropTypes.string,
      // A required ID for each item
      id: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number
      ]).isRequired,
      // The HTML (or text) to render for the list item.
      html: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
      ]),
      // Whether or not the user can choose the item.
      selectable: React.PropTypes.bool,
      // The HTML (or text) to display when the option is selected. If this is
      // not provided, the value for the `html` property will be used.
      selectedHtml: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
      ])
    })
  ).isRequired,
  // An optional callback when an item is selected. Will receive an argument
  // containing the selected item as it was supplied via the items array.
  onItemSelection: React.PropTypes.func,
  // The ID of the item that should be selected by default.
  selectedID: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number
  ]).isRequired,
  // Optional transition on the dropdown menu. Must be accompanied
  // by an animation or transition in CSS.
  transition: React.PropTypes.bool,
  // The prefix of the transition classnames.
  transitionName: React.PropTypes.string,

  // Classes:
  // Classname for the element that ther user interacts with to open menu.
  buttonClassName: React.PropTypes.string,
  // Classname for the dropdown menu wrapper.
  dropdownMenuClassName: React.PropTypes.string,
  // Classname for the dropdown list wrapper.
  dropdownMenuListClassName: React.PropTypes.string,
  // Classname for the dropdown list item.
  dropdownMenuListItemClassName: React.PropTypes.string,
  // Classname for the element that wraps the entire component.
  wrapperClassName: React.PropTypes.string
};
<|endoftext|>


<commit_before>import classNames from 'classnames';
import GeminiScrollbar from 'react-gemini-scrollbar';
import React from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import ReactDOM from 'react-dom';

import BindMixin from '../Mixin/BindMixin';
import DOMUtil from '../Util/DOMUtil';
import KeyDownMixin from '../Mixin/KeyDownMixin';
import Portal from '../Portal/Portal.js';
import Util from '../Util/Util';

class Dropdown extends Util.mixin(BindMixin, KeyDownMixin) {
  get methodsToBind() {
    return [
      'closeDropdown',
      'handleMenuToggle',
      'handleExternalClick',
      'handleMenuRender',
      'handleWrapperBlur'
    ];
  }

  get keysToBind() {
    return {
      esc: this.handleExternalClick
    };
  }

  constructor() {
    super();
    this.container = null;
    this.state = {
      maxDropdownHeight: null,
      menuDirection: 'down',
      menuHeight: null,
      menuPosition: null,
      isOpen: false,
      renderHidden: false,
      selectedID: null
    };
  }

  componentWillMount() {
    super.componentWillMount(...arguments);

    let props = this.props;
    if (!props.persistentID) {
      this.setState({selectedID: props.initialID});
    }
  }

  componentDidMount() {
    this.container = this.getScrollContainer();
  }

  componentWillUpdate(nextProps, nextState) {
    // If the open state changed, add or remove listener as needed.
    if (nextState.isOpen !== this.state.isOpen) {
      if (nextState.isOpen) {
        this.addScrollListener();
      } else {
        this.removeScrollListener();
      }
    }
  }

  determineOptimalMenuLocation() {
    let height = null;
    let direction = this.state.menuDirection;
    let position = {};
    let spaceAroundDropdownButton = DOMUtil
      .getNodeClearance(this.refs.dropdownWrapper);
    let menuHeight = this.state.menuHeight
      || this.refs.dropdownMenu.firstChild.clientHeight;
    let isMenuTallerThanBottom = menuHeight
      > spaceAroundDropdownButton.bottom;
    let isMenuTallerThanTop = menuHeight > spaceAroundDropdownButton.top;
    let isMenuShorterThanTop = !isMenuTallerThanTop;
    let isTopTallerThanBottom = spaceAroundDropdownButton.top
      > spaceAroundDropdownButton.bottom

    // If the menu height is larger than the space available on the bottom and
    // less than the space available on top, then render it up. If the height
    // of the menu exceeds the space below and above, but there is more space
    // above than below, render it up. Otherwise, render down.
    if ((isMenuTallerThanBottom && isMenuShorterThanTop) ||
      (isMenuTallerThanBottom && isMenuTallerThanTop
        && isTopTallerThanBottom)) {
      direction = 'up';
      position.bottom = spaceAroundDropdownButton.bottom
        + spaceAroundDropdownButton.boundingRect.height;
      height = spaceAroundDropdownButton.top;
    } else {
      direction = 'down';
      position.top = spaceAroundDropdownButton.top
        + spaceAroundDropdownButton.boundingRect.height;
      height = spaceAroundDropdownButton.bottom;
    }

    if (this.props.matchButtonWidth) {
      position.left = spaceAroundDropdownButton.left;
      position.right = spaceAroundDropdownButton.right;
    } else if (this.props.anchorRight) {
      position.right = spaceAroundDropdownButton.right;
    } else {
      position.left = spaceAroundDropdownButton.left;
    }

    // We assume that 125 pixels is the smallest height we should render.
    if (height < 125) {
      height = 125;
    }

    this.setState({
      menuDirection: direction,
      maxDropdownHeight: height,
      menuHeight,
      menuPosition: position,
      renderHidden: false
    });
  }

  handleMenuRender() {
    // If the menu is hidden while rendering, then we need to calculate its
    // optimal location and then un-hide it.
    if (this.state.renderHidden) {
      this.determineOptimalMenuLocation();
    }
  }

  addScrollListener() {
    this.container.addEventListener('scroll', this.closeDropdown);
  }

  closeDropdown() {
    if (this.state.isOpen) {
      this.setState({isOpen: false, renderHidden: false});
    }
  }

  getMenuItems(items) {
    let selectedID = this.getSelectedID();

    return items.map((item) => {
      let classSet = classNames(
        {
          'is-selectable': item.selectable !== false,
          'is-selected': item.id === selectedID
        },
        item.className,
        this.props.dropdownMenuListItemClassName
      );

      let handleUserClick = null;

      if (item.selectable !== false) {
        handleUserClick = this.handleItemClick.bind(this, item);
      }

      return (

          {item.html}

      );
    });
  }

  getScrollContainer() {
    let {scrollContainer, scrollContainerParentSelector} = this.props;

    if (typeof scrollContainer === 'string') {
      // Find the closest scrolling element by the specified selector.
      scrollContainer = DOMUtil.closest(ReactDOM.findDOMNode(this),
        scrollContainer) || window;

      let {parentElement} = scrollContainer;

      // If the user specified scrollContainerParentSelector, we check to see
      // if the parent scrolling element matches the specified parent selector.
      if (scrollContainer !== window && scrollContainerParentSelector != null
        && parentElement != null && parentElement[DOMUtil.matchesFn](
          scrollContainerParentSelector
        )) {
        scrollContainer = parentElement;
      }
    }

    return scrollContainer;
  }

  getSelectedHtml(id, items) {
    let obj = Util.find(items, function (item) {
      return item.id === id;
    });

    if (obj != null) {
      return obj.selectedHtml || obj.html;
    }

    return null;
  }

  getSelectedID() {
    return this.props.persistentID || this.state.selectedID;
  }

  removeScrollListener() {
    if (this.container) {
      this.container.removeEventListener('scroll', this.closeDropdown);
    }
  }

  removeBlurTimeout() {
    if (this.currentBlurTimeout) {
      global.clearTimeout(this.currentBlurTimeout);
    }
  }

  handleExternalClick() {
    if (this.state.isOpen) {
      this.setState({
        isOpen: false
      });
    }
  }

  handleItemClick(item) {
    let props = this.props;
    props.onItemSelection(item);

    let newState = {isOpen: false};
    // Only set the selectedID if persistentID is not set
    if (!props.persistentID) {
      newState.selectedID = item.id;
    }

    this.setState(newState);
    this.removeBlurTimeout();
  }

  handleWrapperBlur() {
    this.removeBlurTimeout();

    this.currentBlurTimeout = setTimeout(() => {
      this.closeDropdown();
    }, 150);

    // We need to remove focus from the button to avoid this event firing again
    // when we open the dropdown
    global.focus();
  }

  handleMenuToggle(e) {
    e.stopPropagation();

    if (this.state.isOpen) {
      this.closeDropdown();
    } else {
      this.openDropdown();
    }

    this.removeBlurTimeout();
  }

  openDropdown() {
    let state = Object.assign({}, this.state);

    state.isOpen = true;
    state.renderHidden = true;

    // If we don't already know the menu height, we need to set the menu
    // position to a default sate to trigger its recalculation on the next
    // render.
    if (this.state.menuHeight == null) {
      let buttonPosition = this.refs.dropdownWrapper.getBoundingClientRect();

      state.menuDirection = 'down';
      state.menuPosition = {
        top: buttonPosition.top + buttonPosition.height,
        left: buttonPosition.left
      };
    }

    this.setState(state);
  }

  render() {
    // Set a key based on the menu height so that React knows to keep the
    // the DOM element around while we are measuring it.
    let {props, state} = this;
    let dropdownMenu = null;
    let dropdownMenuClassSet = classNames(
      state.menuDirection,
      props.dropdownMenuClassName
    );
    let {items} = props;
    let transitionName =
      `${props.transitionName}-${state.menuDirection}`;
    let wrapperClassSet = classNames(
      state.menuDirection,
      props.wrapperClassName,
      {
        'open': state.isOpen
      }
    );

    if (state.isOpen) {
      let dropdownMenuItems = (

          {this.getMenuItems(items)}

      );

      // Render with Gemini scrollbar if the dropdown's height should be
      // constrainted.
      if (state.menuHeight >= state.maxDropdownHeight) {
        let height = 'auto';

        // Remove 30 pixels from the dropdown height to account for offset
        // positioning from the dropdown button.
        if (state.maxDropdownHeight > 30) {
          height = state.maxDropdownHeight - 30;
        }

        let dropdownMenuStyle = {height};

        if (props.useGemini && height !== 'auto') {
          dropdownMenuItems = (
            <GeminiScrollbar
              autoshow={true}
              className="container-scrollable"
              style={dropdownMenuStyle}>
              {dropdownMenuItems}
            </GeminiScrollbar>
          );
        } else {
          dropdownMenuItems = (

              {dropdownMenuItems}

          );
        }
      }

      dropdownMenu = (

            {dropdownMenuItems}

      );
    }

    if (state.renderHidden) {
      dropdownMenu = (

          {dropdownMenu}

      );
    }

    if (props.transition) {
      dropdownMenu = (
        <ReactCSSTransitionGroup
          transitionName={transitionName}
          transitionEnterTimeout={props.transitionEnterTimeout}
          transitionLeaveTimeout={props.transitionLeaveTimeout}>
          {dropdownMenu}
        </ReactCSSTransitionGroup>
      );
    }

    return (

        <button className={props.buttonClassName}
          onClick={this.handleMenuToggle}
          ref="button"
          type="button">
          {this.getSelectedHtml(this.getSelectedID(), items)}
        </button>
        <Portal onRender={this.handleMenuRender}>
          {dropdownMenu}
        </Portal>

    );
  }
}

Dropdown.defaultProps = {
  anchorRight: false,
  matchButtonWidth: false,
  scrollContainer: window,
  scrollContainerParentSelector: null,
  transition: false,
  transitionName: 'dropdown-menu',
  transitionEnterTimeout: 250,
  transitionLeaveTimeout: 250,
  onItemSelection: () => {},
  useGemini: true
};

Dropdown.propTypes = {
  // When true, anchors the dropdown to the right of the trigger.
  anchorRight: React.PropTypes.bool,
  // When set it will always set this property as the selected ID.
  // Notice: This property will override the initialID
  persistentID: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number
  ]),
  // The items to display in the dropdown.
  items: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      // An optional classname for the menu item.
      className: React.PropTypes.string,
      // A required ID for each item
      id: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number
      ]).isRequired,
      // The HTML (or text) to render for the list item.
      html: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
      ]),
      // Whether or not the user can choose the item.
      selectable: React.PropTypes.bool,
      // The HTML (or text) to display when the option is selected. If this is
      // not provided, the value for the `html` property will be used.
      selectedHtml: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
      ])
    })
  ).isRequired,
  // The ID of the item that should be selected initially.
  initialID: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number
  ]),
  // When true, the width of the dropdown will match the width of the button.
  matchButtonWidth: React.PropTypes.bool,
  // An optional callback when an item is selected. Will receive an argument
  // containing the selected item as it was supplied via the items array.
  onItemSelection: React.PropTypes.func,
  // The nearest scrolling DOMNode that contains the dropdown. Defaults to
  // window. Also accepts a string, treated as a selector for the node.
  scrollContainer: React.PropTypes.oneOfType([React.PropTypes.object,
    React.PropTypes.string]),
  // Will attach the scroll handler to the the direct parent of scrollContainer
  // if it matches this selector. Defaults to null.
  scrollContainerParentSelector: React.PropTypes.string,
  // Optional transition on the dropdown menu. Must be accompanied
  // by an animation or transition in CSS.
  transition: React.PropTypes.bool,
  // The prefix of the transition classnames.
  transitionName: React.PropTypes.string,
  // Transition lengths
  transitionEnterTimeout: React.PropTypes.number,
  transitionLeaveTimeout: React.PropTypes.number,
  // Option to use Gemini scrollbar. Defaults to true.
  useGemini: React.PropTypes.bool,

  // Classes:
  // Classname for the element that ther user interacts with to open menu.
  buttonClassName: React.PropTypes.string,
  // Classname for the dropdown menu wrapper.
  dropdownMenuClassName: React.PropTypes.string,
  // Classname for the dropdown list wrapper.
  dropdownMenuListClassName: React.PropTypes.string,
  // Classname for the dropdown list item.
  dropdownMenuListItemClassName: React.PropTypes.string,
  // Classname for the element that wraps the entire component.
  wrapperClassName: React.PropTypes.string
};

module.exports = Dropdown;
<commit_msg>Default dropdownMenu to
 in Dropdown<commit_after>import classNames from 'classnames';
import GeminiScrollbar from 'react-gemini-scrollbar';
import React from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import ReactDOM from 'react-dom';

import BindMixin from '../Mixin/BindMixin';
import DOMUtil from '../Util/DOMUtil';
import KeyDownMixin from '../Mixin/KeyDownMixin';
import Portal from '../Portal/Portal.js';
import Util from '../Util/Util';

class Dropdown extends Util.mixin(BindMixin, KeyDownMixin) {
  get methodsToBind() {
    return [
      'closeDropdown',
      'handleMenuToggle',
      'handleExternalClick',
      'handleMenuRender',
      'handleWrapperBlur'
    ];
  }

  get keysToBind() {
    return {
      esc: this.handleExternalClick
    };
  }

  constructor() {
    super();
    this.container = null;
    this.state = {
      maxDropdownHeight: null,
      menuDirection: 'down',
      menuHeight: null,
      menuPosition: null,
      isOpen: false,
      renderHidden: false,
      selectedID: null
    };
  }

  componentWillMount() {
    super.componentWillMount(...arguments);

    let props = this.props;
    if (!props.persistentID) {
      this.setState({selectedID: props.initialID});
    }
  }

  componentDidMount() {
    this.container = this.getScrollContainer();
  }

  componentWillUpdate(nextProps, nextState) {
    // If the open state changed, add or remove listener as needed.
    if (nextState.isOpen !== this.state.isOpen) {
      if (nextState.isOpen) {
        this.addScrollListener();
      } else {
        this.removeScrollListener();
      }
    }
  }

  determineOptimalMenuLocation() {
    let height = null;
    let direction = this.state.menuDirection;
    let position = {};
    let spaceAroundDropdownButton = DOMUtil
      .getNodeClearance(this.refs.dropdownWrapper);
    let menuHeight = this.state.menuHeight
      || this.refs.dropdownMenu.firstChild.clientHeight;
    let isMenuTallerThanBottom = menuHeight
      > spaceAroundDropdownButton.bottom;
    let isMenuTallerThanTop = menuHeight > spaceAroundDropdownButton.top;
    let isMenuShorterThanTop = !isMenuTallerThanTop;
    let isTopTallerThanBottom = spaceAroundDropdownButton.top
      > spaceAroundDropdownButton.bottom

    // If the menu height is larger than the space available on the bottom and
    // less than the space available on top, then render it up. If the height
    // of the menu exceeds the space below and above, but there is more space
    // above than below, render it up. Otherwise, render down.
    if ((isMenuTallerThanBottom && isMenuShorterThanTop) ||
      (isMenuTallerThanBottom && isMenuTallerThanTop
        && isTopTallerThanBottom)) {
      direction = 'up';
      position.bottom = spaceAroundDropdownButton.bottom
        + spaceAroundDropdownButton.boundingRect.height;
      height = spaceAroundDropdownButton.top;
    } else {
      direction = 'down';
      position.top = spaceAroundDropdownButton.top
        + spaceAroundDropdownButton.boundingRect.height;
      height = spaceAroundDropdownButton.bottom;
    }

    if (this.props.matchButtonWidth) {
      position.left = spaceAroundDropdownButton.left;
      position.right = spaceAroundDropdownButton.right;
    } else if (this.props.anchorRight) {
      position.right = spaceAroundDropdownButton.right;
    } else {
      position.left = spaceAroundDropdownButton.left;
    }

    // We assume that 125 pixels is the smallest height we should render.
    if (height < 125) {
      height = 125;
    }

    this.setState({
      menuDirection: direction,
      maxDropdownHeight: height,
      menuHeight,
      menuPosition: position,
      renderHidden: false
    });
  }

  handleMenuRender() {
    // If the menu is hidden while rendering, then we need to calculate its
    // optimal location and then un-hide it.
    if (this.state.renderHidden) {
      this.determineOptimalMenuLocation();
    }
  }

  addScrollListener() {
    this.container.addEventListener('scroll', this.closeDropdown);
  }

  closeDropdown() {
    if (this.state.isOpen) {
      this.setState({isOpen: false, renderHidden: false});
    }
  }

  getMenuItems(items) {
    let selectedID = this.getSelectedID();

    return items.map((item) => {
      let classSet = classNames(
        {
          'is-selectable': item.selectable !== false,
          'is-selected': item.id === selectedID
        },
        item.className,
        this.props.dropdownMenuListItemClassName
      );

      let handleUserClick = null;

      if (item.selectable !== false) {
        handleUserClick = this.handleItemClick.bind(this, item);
      }

      return (

          {item.html}

      );
    });
  }

  getScrollContainer() {
    let {scrollContainer, scrollContainerParentSelector} = this.props;

    if (typeof scrollContainer === 'string') {
      // Find the closest scrolling element by the specified selector.
      scrollContainer = DOMUtil.closest(ReactDOM.findDOMNode(this),
        scrollContainer) || window;

      let {parentElement} = scrollContainer;

      // If the user specified scrollContainerParentSelector, we check to see
      // if the parent scrolling element matches the specified parent selector.
      if (scrollContainer !== window && scrollContainerParentSelector != null
        && parentElement != null && parentElement[DOMUtil.matchesFn](
          scrollContainerParentSelector
        )) {
        scrollContainer = parentElement;
      }
    }

    return scrollContainer;
  }

  getSelectedHtml(id, items) {
    let obj = Util.find(items, function (item) {
      return item.id === id;
    });

    if (obj != null) {
      return obj.selectedHtml || obj.html;
    }

    return null;
  }

  getSelectedID() {
    return this.props.persistentID || this.state.selectedID;
  }

  removeScrollListener() {
    if (this.container) {
      this.container.removeEventListener('scroll', this.closeDropdown);
    }
  }

  removeBlurTimeout() {
    if (this.currentBlurTimeout) {
      global.clearTimeout(this.currentBlurTimeout);
    }
  }

  handleExternalClick() {
    if (this.state.isOpen) {
      this.setState({
        isOpen: false
      });
    }
  }

  handleItemClick(item) {
    let props = this.props;
    props.onItemSelection(item);

    let newState = {isOpen: false};
    // Only set the selectedID if persistentID is not set
    if (!props.persistentID) {
      newState.selectedID = item.id;
    }

    this.setState(newState);
    this.removeBlurTimeout();
  }

  handleWrapperBlur() {
    this.removeBlurTimeout();

    this.currentBlurTimeout = setTimeout(() => {
      this.closeDropdown();
    }, 150);

    // We need to remove focus from the button to avoid this event firing again
    // when we open the dropdown
    global.focus();
  }

  handleMenuToggle(e) {
    e.stopPropagation();

    if (this.state.isOpen) {
      this.closeDropdown();
    } else {
      this.openDropdown();
    }

    this.removeBlurTimeout();
  }

  openDropdown() {
    let state = Object.assign({}, this.state);

    state.isOpen = true;
    state.renderHidden = true;

    // If we don't already know the menu height, we need to set the menu
    // position to a default sate to trigger its recalculation on the next
    // render.
    if (this.state.menuHeight == null) {
      let buttonPosition = this.refs.dropdownWrapper.getBoundingClientRect();

      state.menuDirection = 'down';
      state.menuPosition = {
        top: buttonPosition.top + buttonPosition.height,
        left: buttonPosition.left
      };
    }

    this.setState(state);
  }

  render() {
    // Set a key based on the menu height so that React knows to keep the
    // the DOM element around while we are measuring it.
    let {props, state} = this;
    let dropdownMenu =
;
    let dropdownMenuClassSet = classNames(
      state.menuDirection,
      props.dropdownMenuClassName
    );
    let {items} = props;
    let transitionName =
      `${props.transitionName}-${state.menuDirection}`;
    let wrapperClassSet = classNames(
      state.menuDirection,
      props.wrapperClassName,
      {
        'open': state.isOpen
      }
    );

    if (state.isOpen) {
      let dropdownMenuItems = (

          {this.getMenuItems(items)}

      );

      // Render with Gemini scrollbar if the dropdown's height should be
      // constrainted.
      if (state.menuHeight >= state.maxDropdownHeight) {
        let height = 'auto';

        // Remove 30 pixels from the dropdown height to account for offset
        // positioning from the dropdown button.
        if (state.maxDropdownHeight > 30) {
          height = state.maxDropdownHeight - 30;
        }

        let dropdownMenuStyle = {height};

        if (props.useGemini && height !== 'auto') {
          dropdownMenuItems = (
            <GeminiScrollbar
              autoshow={true}
              className="container-scrollable"
              style={dropdownMenuStyle}>
              {dropdownMenuItems}
            </GeminiScrollbar>
          );
        } else {
          dropdownMenuItems = (

              {dropdownMenuItems}

          );
        }
      }

      dropdownMenu = (

            {dropdownMenuItems}

      );
    }

    if (state.renderHidden) {
      dropdownMenu = (

          {dropdownMenu}

      );
    }

    if (props.transition) {
      dropdownMenu = (
        <ReactCSSTransitionGroup
          transitionName={transitionName}
          transitionEnterTimeout={props.transitionEnterTimeout}
          transitionLeaveTimeout={props.transitionLeaveTimeout}>
          {dropdownMenu}
        </ReactCSSTransitionGroup>
      );
    }

    return (

        <button className={props.buttonClassName}
          onClick={this.handleMenuToggle}
          ref="button"
          type="button">
          {this.getSelectedHtml(this.getSelectedID(), items)}
        </button>
        <Portal onRender={this.handleMenuRender}>
          {dropdownMenu}
        </Portal>

    );
  }
}

Dropdown.defaultProps = {
  anchorRight: false,
  matchButtonWidth: false,
  scrollContainer: window,
  scrollContainerParentSelector: null,
  transition: false,
  transitionName: 'dropdown-menu',
  transitionEnterTimeout: 250,
  transitionLeaveTimeout: 250,
  onItemSelection: () => {},
  useGemini: true
};

Dropdown.propTypes = {
  // When true, anchors the dropdown to the right of the trigger.
  anchorRight: React.PropTypes.bool,
  // When set it will always set this property as the selected ID.
  // Notice: This property will override the initialID
  persistentID: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number
  ]),
  // The items to display in the dropdown.
  items: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      // An optional classname for the menu item.
      className: React.PropTypes.string,
      // A required ID for each item
      id: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.number
      ]).isRequired,
      // The HTML (or text) to render for the list item.
      html: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
      ]),
      // Whether or not the user can choose the item.
      selectable: React.PropTypes.bool,
      // The HTML (or text) to display when the option is selected. If this is
      // not provided, the value for the `html` property will be used.
      selectedHtml: React.PropTypes.oneOfType([
        React.PropTypes.string,
        React.PropTypes.object
      ])
    })
  ).isRequired,
  // The ID of the item that should be selected initially.
  initialID: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.number
  ]),
  // When true, the width of the dropdown will match the width of the button.
  matchButtonWidth: React.PropTypes.bool,
  // An optional callback when an item is selected. Will receive an argument
  // containing the selected item as it was supplied via the items array.
  onItemSelection: React.PropTypes.func,
  // The nearest scrolling DOMNode that contains the dropdown. Defaults to
  // window. Also accepts a string, treated as a selector for the node.
  scrollContainer: React.PropTypes.oneOfType([React.PropTypes.object,
    React.PropTypes.string]),
  // Will attach the scroll handler to the the direct parent of scrollContainer
  // if it matches this selector. Defaults to null.
  scrollContainerParentSelector: React.PropTypes.string,
  // Optional transition on the dropdown menu. Must be accompanied
  // by an animation or transition in CSS.
  transition: React.PropTypes.bool,
  // The prefix of the transition classnames.
  transitionName: React.PropTypes.string,
  // Transition lengths
  transitionEnterTimeout: React.PropTypes.number,
  transitionLeaveTimeout: React.PropTypes.number,
  // Option to use Gemini scrollbar. Defaults to true.
  useGemini: React.PropTypes.bool,

  // Classes:
  // Classname for the element that ther user interacts with to open menu.
  buttonClassName: React.PropTypes.string,
  // Classname for the dropdown menu wrapper.
  dropdownMenuClassName: React.PropTypes.string,
  // Classname for the dropdown list wrapper.
  dropdownMenuListClassName: React.PropTypes.string,
  // Classname for the dropdown list item.
  dropdownMenuListItemClassName: React.PropTypes.string,
  // Classname for the element that wraps the entire component.
  wrapperClassName: React.PropTypes.string
};

module.exports = Dropdown;
<|endoftext|>


<commit_before>/******************************************************************************
 *                                  ~cassie~                                  *
 *                                ‾‾‾‾‾‾‾‾‾‾‾‾                                *
 * Simple future library for JS. Ready to be violently raped by Ajax!         *
 *     _________________________________________________________________      *
 *        Copyright (c) 2011 Quildreen Motta // Licenced under MIT/X11        *
 ******************************************************************************/

//// Module Cassie /////////////////////////////////////////////////////////////
//
// Cassie (short for lil' Cassandra) is a deliciously short future
// library for JavaScript. It provides a simple Promise object that can
// be used as the return value of any asynchronous call.
//
// The idea behind promises is that you can manipulate the return value
// of an asynchronous function before the function has been
// resolved. This allows you to write in a clear chaining DSL rather
// than using a clusterfuck of callbacks.
//
// Take an Ajax request, for example. The usual way::
//
//     var req = ajax.get('data.json'
//               , function (resp) { /* success */
//                   req.update('#data', JSON.parse(resp.responseText))
//               }
//               , function (resp) { /* fail */
//                   req.show_error()
//               })
//     setTimeout(req.fail, 10000)
//
// With promises, you could get this::
//
//     var req = ajax.get('data.json')
//                   .ok().update('#data')
//                   .failed().show_error()
//                   .timeout(10)
//
// You can pass this ``req`` object around, and other functions can add
// their own manipulations to the object as well::
//
//     function log_it(req) {
//         return req.ok(log('success'))
//                   .failed(log('failed'))
//                   .clear_timer() /* disables the timeout */
//     }
//     var req = log_it(ajax.get('data.json').timeout(10))
//

'@cassie',
function (root) {
    if (typeof exports == 'undefined') {
        old    = root.cassie
        cassie = root.cassie = root.cassie || {}
        cassie.clean = function() {
            root.cassie = old
            return cassie
        }}
    else
        cassie = exports

    var cassie, old
      , slice = Array.prototype.slice

      // Constants with values for errors
      , forgotten = {}
      , timeouted = {}

      // Error objects
      , EFulfilled   = new Error('Promise already fulfilled.')
      , EUnfulfilled = new Error('Promise not fulfilled.')

    ///// Object Promise ///////////////////////////////////////////////////////
    //
    // Creates a deferred object, that can be manipulated before the
    // value is actually resolved. As soon as it is fulfilled, this
    // promise will apply the pending actions.
    //
    // You can either return the promise directly, or take full
    // advantage of it by inheriting the ``Promise``'s prototoype and
    // adding your own custom methods.
    //
    function Promise () {
        this.callbacks   = {}
        this.flush_queue = []
        this.value       = null
        this.timer       = null
        this.defaultev   = 'done'
    }
    Promise.prototype = function() {
        return { add:         add
               , flush:       flush
               , done:        done
               , bind:        bind
               , fail:        fail
               , timeout:     timeout
               , clear_timer: clear_timer
               , forget:      forget

               // Shortcuts for add(event[, callback])
               , ok:          ok
               , failed:      failed
               , timeouted:   _timeouted
               , forgotten:   _forgotten }

        ////// Function add_callback ///////////////////////////////////////////
        // ::
        //     add_callback(Obj promise, Str event, Fn callback) → Fn
        //
        // Adds a callback to a ``Promise``.
        //
        // The callback will be called whenever the given event happens
        // within the given promise, in the context of the promise (ie.:
        // ``this`` inside the callback will refer to the given promise)
        // and the arguments provided when the promise was fulfilled.
        //
        // Returns the given callback function.
        //
        function add_callback(promise, event, callback) {
            get_queue(promise, event).push(callback)
            return callback
        }

        function get_queue(promise, event) {
            return promise.callbacks[event]
                || (promise.callbacks[event] = [])
        }

        ////// Method add //////////////////////////////////////////////////////
        // ::
        //     add(Str event[, Fn callback]) → Promise
        //
        // Adds a callback to the ``Promise``.
        //
        // Passing a callback directly to the add method is optional, to
        // leverage more specialized promises, Cassie supports defining
        // a default event to which callbacks are bound.
        //
        // For example, instead of writting the following::
        //
        //     promise.add('ok', foo).add('ok', bar)
        //
        // You could go with::
        //
        //     promise.add('ok').foo().bar()
        //
        // Of course, given you have a specialized promise implementing
        // `foo` and `bar`.
        //
        // The default event persists until another `add` call with an
        // explicit event is issued.
        //
        // See :fn:`add_callback` for more information.
        //
        function add(event, callback) {
            if (typeof event == 'function') {
                callback = event
                event    = this.defaultev }

            _add(this, event, callback)
            return this
        }

        function _add(promise, event, callback) {
            promise.defaultev = event
            if (promise.value)
                fire(promise, event, callback)
            else
                add_callback(promise, event, callback)
        }

        function fire(promise, event, callback) {
            var queue = get_queue(promise, event)
            if (callback && queue.flushed)
                return callback.apply(promise, promise.value)
        }

        ////// Method flush ////////////////////////////////////////////////////
        // ::
        //     flush(Str event) → Promise
        //
        // Calls all callbacks associated with the given event.
        //
        // The callbacks are called in the context of this ``Promise``
        // (ie.: ``this`` inside such callback will refer to this
        // ``Promise``), and with any arguments that have been passed to
        // the promise when it was fulfilled.
        //
        function flush(event) {
            if (!this.value)
                this.flush_queue.push(event)
            else while (event = this.flush_queue.shift())
                flush_ev(this, event)

            return this
        }

        function flush_ev(promise, event) {
            var callbacks = promise.callbacks[event] || []
              , current

            while (current = callbacks.shift())
                current.apply(promise, promise.value)

            callbacks.flushed = true
        }

        ////// Method done /////////////////////////////////////////////////////
        // ::
        //     done(ArrayLike values) → Promise
        //
        // Resolves the promise to the given values and call the
        // callbacks defined for ``done``.
        //
        // The given value array is cloned, then assigned to the
        // promise. This makes it easier for caller functions to just
        // pass the ``arguments`` object straight up to this function.
        //
        // If the promise has already been resolved, this method is a
        // noop.
        //
        function done(values) {
            if (!this.value) {
                this.clear_timer().flush('done')
                this.value = slice.call(values)
                this.flush() }
            return this
        }

        ////// Method fail /////////////////////////////////////////////////////
        // ::
        //     fail(Obj error) → Promise
        //
        // Fails to fulfill the promise, and calls all the ``fail``
        // callbacks passing the error as parameter.
        //
        // See :fn:`done` for more information.
        //
        function fail() {
            return this.flush('fail').done(arguments)
        }

        ////// Method bind /////////////////////////////////////////////////////
        // ::
        //     bind(values...) → Promise
        //
        // Successfully fulfills the promise, and calls the ``bind``
        // callbacks passing the values as parameter.
        //
        // See :fn:`resolve` for more information.
        //
        function bind() {
            return this.flush('ok').done(arguments)
        }

        ////// Method timeout //////////////////////////////////////////////////
        // ::
        //     timeout(Num delay) → Promise
        //
        // Fails to fulfill the promise after the given number of
        // seconds.
        //
        // The promise fails with the value of ``timeouted``.
        //
        function timeout(delay) {
            var promise = this
            this.timer = setTimeout(function(){
                promise.flush('timeouted')
                       .fail(timeouted)
            }, delay * 1000)
            return this
        }

        ////// Method clear_timer //////////////////////////////////////////////
        // ::
        //     clear_timer() → Promise
        //
        // Clears any timer that may exist for this promise.
        //
        function clear_timer() {
            clearTimeout(this.timer)
            return this
        }

        ////// Method forget ///////////////////////////////////////////////////
        // ::
        //     forget() → Promise
        //
        // Cancels the promise, and fails with the value of ``forgotten``.
        //
        function forget() {
            return this.flush('forgotten').fail(forgotten)
        }

        ////// -Shortcuts for add(event[, callback]) //////////////////////////
        function ok(fn)         { return this.add('ok',        fn) }
        function failed(fn)     { return this.add('fail',      fn) }
        function _timeouted(fn) { return this.add('timeouted', fn) }
        function _forgotten(fn) { return this.add('forgotten', fn) }
    }()

    ///// Exṕorts //////////////////////////////////////////////////////////////
    cassie.Promise      = Promise
    cassie.forgotten    = forgotten
    cassie.timeouted    = timeouted
    cassie.EFulfilled   = EFulfilled
    cassie.EUnfulfilled = EUnfulfilled
}(this)<commit_msg>Removes needless Error objects.<commit_after>/******************************************************************************
 *                                  ~cassie~                                  *
 *                                ‾‾‾‾‾‾‾‾‾‾‾‾                                *
 * Simple future library for JS. Ready to be violently raped by Ajax!         *
 *     _________________________________________________________________      *
 *        Copyright (c) 2011 Quildreen Motta // Licenced under MIT/X11        *
 ******************************************************************************/

//// Module Cassie /////////////////////////////////////////////////////////////
//
// Cassie (short for lil' Cassandra) is a deliciously short future
// library for JavaScript. It provides a simple Promise object that can
// be used as the return value of any asynchronous call.
//
// The idea behind promises is that you can manipulate the return value
// of an asynchronous function before the function has been
// resolved. This allows you to write in a clear chaining DSL rather
// than using a clusterfuck of callbacks.
//
// Take an Ajax request, for example. The usual way::
//
//     var req = ajax.get('data.json'
//               , function (resp) { /* success */
//                   req.update('#data', JSON.parse(resp.responseText))
//               }
//               , function (resp) { /* fail */
//                   req.show_error()
//               })
//     setTimeout(req.fail, 10000)
//
// With promises, you could get this::
//
//     var req = ajax.get('data.json')
//                   .ok().update('#data')
//                   .failed().show_error()
//                   .timeout(10)
//
// You can pass this ``req`` object around, and other functions can add
// their own manipulations to the object as well::
//
//     function log_it(req) {
//         return req.ok(log('success'))
//                   .failed(log('failed'))
//                   .clear_timer() /* disables the timeout */
//     }
//     var req = log_it(ajax.get('data.json').timeout(10))
//

'@cassie',
function (root) {
    if (typeof exports == 'undefined') {
        old    = root.cassie
        cassie = root.cassie = root.cassie || {}
        cassie.clean = function() {
            root.cassie = old
            return cassie
        }}
    else
        cassie = exports

    var cassie, old
      , slice = Array.prototype.slice

      // Constants with values for errors
      , forgotten = {}
      , timeouted = {}

    ///// Object Promise ///////////////////////////////////////////////////////
    //
    // Creates a deferred object, that can be manipulated before the
    // value is actually resolved. As soon as it is fulfilled, this
    // promise will apply the pending actions.
    //
    // You can either return the promise directly, or take full
    // advantage of it by inheriting the ``Promise``'s prototoype and
    // adding your own custom methods.
    //
    function Promise () {
        this.callbacks   = {}
        this.flush_queue = []
        this.value       = null
        this.timer       = null
        this.defaultev   = 'done'
    }
    Promise.prototype = function() {
        return { add:         add
               , flush:       flush
               , done:        done
               , bind:        bind
               , fail:        fail
               , timeout:     timeout
               , clear_timer: clear_timer
               , forget:      forget

               // Shortcuts for add(event[, callback])
               , ok:          ok
               , failed:      failed
               , timeouted:   _timeouted
               , forgotten:   _forgotten }

        ////// Function add_callback ///////////////////////////////////////////
        // ::
        //     add_callback(Obj promise, Str event, Fn callback) → Fn
        //
        // Adds a callback to a ``Promise``.
        //
        // The callback will be called whenever the given event happens
        // within the given promise, in the context of the promise (ie.:
        // ``this`` inside the callback will refer to the given promise)
        // and the arguments provided when the promise was fulfilled.
        //
        // Returns the given callback function.
        //
        function add_callback(promise, event, callback) {
            get_queue(promise, event).push(callback)
            return callback
        }

        function get_queue(promise, event) {
            return promise.callbacks[event]
                || (promise.callbacks[event] = [])
        }

        ////// Method add //////////////////////////////////////////////////////
        // ::
        //     add(Str event[, Fn callback]) → Promise
        //
        // Adds a callback to the ``Promise``.
        //
        // Passing a callback directly to the add method is optional, to
        // leverage more specialized promises, Cassie supports defining
        // a default event to which callbacks are bound.
        //
        // For example, instead of writting the following::
        //
        //     promise.add('ok', foo).add('ok', bar)
        //
        // You could go with::
        //
        //     promise.add('ok').foo().bar()
        //
        // Of course, given you have a specialized promise implementing
        // `foo` and `bar`.
        //
        // The default event persists until another `add` call with an
        // explicit event is issued.
        //
        // See :fn:`add_callback` for more information.
        //
        function add(event, callback) {
            if (typeof event == 'function') {
                callback = event
                event    = this.defaultev }

            _add(this, event, callback)
            return this
        }

        function _add(promise, event, callback) {
            promise.defaultev = event
            if (promise.value)
                fire(promise, event, callback)
            else
                add_callback(promise, event, callback)
        }

        function fire(promise, event, callback) {
            var queue = get_queue(promise, event)
            if (callback && queue.flushed)
                return callback.apply(promise, promise.value)
        }

        ////// Method flush ////////////////////////////////////////////////////
        // ::
        //     flush(Str event) → Promise
        //
        // Calls all callbacks associated with the given event.
        //
        // The callbacks are called in the context of this ``Promise``
        // (ie.: ``this`` inside such callback will refer to this
        // ``Promise``), and with any arguments that have been passed to
        // the promise when it was fulfilled.
        //
        function flush(event) {
            if (!this.value)
                this.flush_queue.push(event)
            else while (event = this.flush_queue.shift())
                flush_ev(this, event)

            return this
        }

        function flush_ev(promise, event) {
            var callbacks = promise.callbacks[event] || []
              , current

            while (current = callbacks.shift())
                current.apply(promise, promise.value)

            callbacks.flushed = true
        }

        ////// Method done /////////////////////////////////////////////////////
        // ::
        //     done(ArrayLike values) → Promise
        //
        // Resolves the promise to the given values and call the
        // callbacks defined for ``done``.
        //
        // The given value array is cloned, then assigned to the
        // promise. This makes it easier for caller functions to just
        // pass the ``arguments`` object straight up to this function.
        //
        // If the promise has already been resolved, this method is a
        // noop.
        //
        function done(values) {
            if (!this.value) {
                this.clear_timer().flush('done')
                this.value = slice.call(values)
                this.flush() }
            return this
        }

        ////// Method fail /////////////////////////////////////////////////////
        // ::
        //     fail(Obj error) → Promise
        //
        // Fails to fulfill the promise, and calls all the ``fail``
        // callbacks passing the error as parameter.
        //
        // See :fn:`done` for more information.
        //
        function fail() {
            return this.flush('fail').done(arguments)
        }

        ////// Method bind /////////////////////////////////////////////////////
        // ::
        //     bind(values...) → Promise
        //
        // Successfully fulfills the promise, and calls the ``bind``
        // callbacks passing the values as parameter.
        //
        // See :fn:`resolve` for more information.
        //
        function bind() {
            return this.flush('ok').done(arguments)
        }

        ////// Method timeout //////////////////////////////////////////////////
        // ::
        //     timeout(Num delay) → Promise
        //
        // Fails to fulfill the promise after the given number of
        // seconds.
        //
        // The promise fails with the value of ``timeouted``.
        //
        function timeout(delay) {
            var promise = this
            this.timer = setTimeout(function(){
                promise.flush('timeouted')
                       .fail(timeouted)
            }, delay * 1000)
            return this
        }

        ////// Method clear_timer //////////////////////////////////////////////
        // ::
        //     clear_timer() → Promise
        //
        // Clears any timer that may exist for this promise.
        //
        function clear_timer() {
            clearTimeout(this.timer)
            return this
        }

        ////// Method forget ///////////////////////////////////////////////////
        // ::
        //     forget() → Promise
        //
        // Cancels the promise, and fails with the value of ``forgotten``.
        //
        function forget() {
            return this.flush('forgotten').fail(forgotten)
        }

        ////// -Shortcuts for add(event[, callback]) //////////////////////////
        function ok(fn)         { return this.add('ok',        fn) }
        function failed(fn)     { return this.add('fail',      fn) }
        function _timeouted(fn) { return this.add('timeouted', fn) }
        function _forgotten(fn) { return this.add('forgotten', fn) }
    }()

    ///// Exṕorts //////////////////////////////////////////////////////////////
    cassie.Promise      = Promise
    cassie.forgotten    = forgotten
    cassie.timeouted    = timeouted
    cassie.EFulfilled   = EFulfilled
    cassie.EUnfulfilled = EUnfulfilled
}(this)<|endoftext|>