office-ui-fabric-react
Version: 
Reusable React components for building experiences for Office 365.
315 lines • 15.6 kB
JavaScript
import * as tslib_1 from "tslib";
import * as React from 'react';
import { IconButton } from '../../Button';
import { Label } from '../../Label';
import { Icon } from '../../Icon';
import { BaseComponent, getId, autobind, customizable, calculatePrecision, precisionRound } from '../../Utilities';
import { Position } from '../../utilities/positioning';
import { getStyles, getArrowButtonStyles } from './SpinButton.styles';
import { getClassNames } from './SpinButton.classNames';
export var KeyboardSpinDirection;
(function (KeyboardSpinDirection) {
    KeyboardSpinDirection[KeyboardSpinDirection["down"] = -1] = "down";
    KeyboardSpinDirection[KeyboardSpinDirection["notSpinning"] = 0] = "notSpinning";
    KeyboardSpinDirection[KeyboardSpinDirection["up"] = 1] = "up";
})(KeyboardSpinDirection || (KeyboardSpinDirection = {}));
var SpinButton = /** @class */ (function (_super) {
    tslib_1.__extends(SpinButton, _super);
    function SpinButton(props) {
        var _this = _super.call(this, props) || this;
        _this._initialStepDelay = 400;
        _this._stepDelay = 75;
        /**
         * Validate function to use if one is not passed in
         */
        _this._defaultOnValidate = function (value) {
            if (isNaN(Number(value))) {
                return _this._lastValidValue;
            }
            var newValue = Math.min(_this.props.max, Math.max(_this.props.min, Number(value)));
            return String(newValue);
        };
        /**
         * Increment function to use if one is not passed in
         */
        _this._defaultOnIncrement = function (value) {
            var newValue = Math.min(Number(value) + Number(_this.props.step), _this.props.max);
            newValue = precisionRound(newValue, _this.state.precision);
            return String(newValue);
        };
        /**
         * Increment function to use if one is not passed in
         */
        _this._defaultOnDecrement = function (value) {
            var newValue = Math.max(Number(value) - Number(_this.props.step), _this.props.min);
            newValue = precisionRound(newValue, _this.state.precision);
            return String(newValue);
        };
        _this._warnMutuallyExclusive({
            'value': 'defaultValue'
        });
        var value = props.value || props.defaultValue || String(props.min) || '0';
        _this._lastValidValue = value;
        // Ensure that the autocalculated precision is not negative.
        var precision = props.precision || Math.max(calculatePrecision(props.step), 0);
        _this.state = {
            isFocused: false,
            value: value,
            keyboardSpinDirection: KeyboardSpinDirection.notSpinning,
            precision: precision
        };
        _this._currentStepFunctionHandle = -1;
        _this._labelId = getId('Label');
        _this._inputId = getId('input');
        _this._spinningByMouse = false;
        if (!props.defaultValue && props.value !== undefined) {
            _this._onValidate = props.onValidate;
            _this._onIncrement = props.onIncrement;
            _this._onDecrement = props.onDecrement;
        }
        else {
            _this._onValidate = _this._defaultOnValidate;
            _this._onIncrement = _this._defaultOnIncrement;
            _this._onDecrement = _this._defaultOnDecrement;
        }
        return _this;
    }
    /**
    * Invoked when a component is receiving new props. This method is not called for the initial render.
    */
    SpinButton.prototype.componentWillReceiveProps = function (newProps) {
        this._lastValidValue = this.state.value;
        var value = newProps.value ? newProps.value : String(newProps.min);
        if (newProps.defaultValue) {
            value = String(Math.max(newProps.min, Math.min(newProps.max, Number(newProps.defaultValue))));
        }
        this.setState({
            value: value,
            precision: newProps.precision || this.state.precision
        });
    };
    SpinButton.prototype.render = function () {
        var _a = this.props, disabled = _a.disabled, label = _a.label, min = _a.min, max = _a.max, labelPosition = _a.labelPosition, iconProps = _a.iconProps, incrementButtonIcon = _a.incrementButtonIcon, incrementButtonAriaLabel = _a.incrementButtonAriaLabel, decrementButtonIcon = _a.decrementButtonIcon, decrementButtonAriaLabel = _a.decrementButtonAriaLabel, title = _a.title, ariaLabel = _a.ariaLabel, customStyles = _a.styles, customUpArrowButtonStyles = _a.upArrowButtonStyles, customDownArrowButtonStyles = _a.downArrowButtonStyles, theme = _a.theme, ariaPositionInSet = _a.ariaPositionInSet, ariaSetSize = _a.ariaSetSize;
        var _b = this.state, isFocused = _b.isFocused, value = _b.value, keyboardSpinDirection = _b.keyboardSpinDirection;
        var classNames = this.props.getClassNames ?
            this.props.getClassNames(theme, !!disabled, !!isFocused, keyboardSpinDirection, labelPosition) : getClassNames(getStyles(theme, customStyles), !!disabled, !!isFocused, keyboardSpinDirection, labelPosition);
        return (React.createElement("div", { className: classNames.root },
            labelPosition !== Position.bottom && React.createElement("div", { className: classNames.labelWrapper },
                iconProps && React.createElement(Icon, { iconName: iconProps.iconName, className: classNames.icon, "aria-hidden": 'true' }),
                label &&
                    React.createElement(Label, { id: this._labelId, htmlFor: this._inputId, className: classNames.label }, label)),
            React.createElement("div", { className: classNames.spinButtonWrapper, title: title && title, "aria-label": ariaLabel && ariaLabel, "aria-posinset": ariaPositionInSet, "aria-setsize": ariaSetSize },
                React.createElement("input", { value: value, id: this._inputId, onChange: this._onChange, onInput: this._onInputChange, className: classNames.input, type: 'text', role: 'spinbutton', "aria-labelledby": label && this._labelId, "aria-valuenow": value, "aria-valuemin": min && String(min), "aria-valuemax": max && String(max), onBlur: this._onBlur, ref: this._resolveRef('_input'), onFocus: this._onFocus, onKeyDown: this._handleKeyDown, onKeyUp: this._handleKeyUp, readOnly: disabled, disabled: disabled, "aria-disabled": disabled, "data-lpignore": true }),
                React.createElement("span", { className: classNames.arrowBox },
                    React.createElement(IconButton, { styles: getArrowButtonStyles(theme, true, customUpArrowButtonStyles), className: 'ms-UpButton', checked: keyboardSpinDirection === KeyboardSpinDirection.up, disabled: disabled, iconProps: incrementButtonIcon, onMouseDown: this._onIncrementMouseDown, onMouseLeave: this._stop, onMouseUp: this._stop, tabIndex: -1, ariaLabel: incrementButtonAriaLabel }),
                    React.createElement(IconButton, { styles: getArrowButtonStyles(theme, false, customDownArrowButtonStyles), className: 'ms-DownButton', checked: keyboardSpinDirection === KeyboardSpinDirection.down, disabled: disabled, iconProps: decrementButtonIcon, onMouseDown: this._onDecrementMouseDown, onMouseLeave: this._stop, onMouseUp: this._stop, tabIndex: -1, ariaLabel: decrementButtonAriaLabel }))),
            labelPosition === Position.bottom && React.createElement("div", { className: classNames.labelWrapper },
                iconProps && React.createElement(Icon, { iconName: iconProps.iconName, className: classNames.icon, "aria-hidden": 'true' }),
                label &&
                    React.createElement(Label, { id: this._labelId, htmlFor: this._inputId, className: classNames.label }, label))));
    };
    SpinButton.prototype.focus = function () {
        if (this._input) {
            this._input.focus();
        }
    };
    SpinButton.prototype._onFocus = function (ev) {
        if (this._spinningByMouse || this.state.keyboardSpinDirection !== KeyboardSpinDirection.notSpinning) {
            this._stop();
        }
        this._input.select();
        this.setState({ isFocused: true });
        if (this.props.onFocus) {
            this.props.onFocus(ev);
        }
    };
    SpinButton.prototype._onBlur = function (ev) {
        this._validate(ev);
        this.setState({ isFocused: false });
        if (this.props.onBlur) {
            this.props.onBlur(ev);
        }
    };
    Object.defineProperty(SpinButton.prototype, "value", {
        /**
         * Gets the value of the spin button.
         */
        get: function () {
            return this.props.value === undefined ? this.state.value : this.props.value;
        },
        enumerable: true,
        configurable: true
    });
    SpinButton.prototype._onChange = function () {
        /**
         * A noop input change handler.
         * https://github.com/facebook/react/issues/7027.
         * Using the native onInput handler fixes the issue but onChange
         * still need to be wired to avoid React console errors
         * TODO: Check if issue is resolved when React 16 is available.
         */
    };
    /**
     * This is used when validating text entry
     * in the input (not when changed via the buttons)
     * @param event - the event that fired
     */
    SpinButton.prototype._validate = function (event) {
        var element = event.target;
        var value = element.value;
        if (this.state.value) {
            var newValue = this._onValidate(value);
            if (newValue) {
                this._lastValidValue = newValue;
                this.setState({ value: newValue });
            }
        }
    };
    /**
     * The method is needed to ensure we are updating the actual input value.
     * without this our value will never change (and validation will not have the correct number)
     * @param event - the event that was fired
     */
    SpinButton.prototype._onInputChange = function (event) {
        var element = event.target;
        var value = element.value;
        this.setState({
            value: value,
        });
    };
    /**
     * Update the value with the given stepFunction
     * @param shouldSpin - should we fire off another updateValue when we are done here? This should be true
     * when spinning in response to a mouseDown
     * @param stepFunction - function to use to step by
     */
    SpinButton.prototype._updateValue = function (shouldSpin, stepDelay, stepFunction) {
        var _this = this;
        var newValue = stepFunction(this.state.value);
        if (newValue) {
            this._lastValidValue = newValue;
            this.setState({ value: newValue });
        }
        if (this._spinningByMouse !== shouldSpin) {
            this._spinningByMouse = shouldSpin;
        }
        if (shouldSpin) {
            this._currentStepFunctionHandle = this._async.setTimeout(function () { _this._updateValue(shouldSpin, _this._stepDelay, stepFunction); }, stepDelay);
        }
    };
    /**
     * Stop spinning (clear any currently pending update and set spinning to false)
     */
    SpinButton.prototype._stop = function () {
        if (this._currentStepFunctionHandle >= 0) {
            this._async.clearTimeout(this._currentStepFunctionHandle);
            this._currentStepFunctionHandle = -1;
        }
        if (this._spinningByMouse || this.state.keyboardSpinDirection !== KeyboardSpinDirection.notSpinning) {
            this._spinningByMouse = false;
            this.setState({ keyboardSpinDirection: KeyboardSpinDirection.notSpinning });
        }
    };
    /**
     * Handle keydown on the text field. We need to update
     * the value when up or down arrow are depressed
     * @param event - the keyboardEvent that was fired
     */
    SpinButton.prototype._handleKeyDown = function (event) {
        if (this.props.disabled) {
            this._stop();
            // eat the up and down arrow keys to keep the page from scrolling
            if (event.which === 38 /* up */ || event.which === 40 /* down */) {
                event.preventDefault();
                event.stopPropagation();
            }
            return;
        }
        var spinDirection = KeyboardSpinDirection.notSpinning;
        if (event.which === 38 /* up */) {
            spinDirection = KeyboardSpinDirection.up;
            this._updateValue(false /* shouldSpin */, this._initialStepDelay, this._onIncrement);
        }
        else if (event.which === 40 /* down */) {
            spinDirection = KeyboardSpinDirection.down;
            this._updateValue(false /* shouldSpin */, this._initialStepDelay, this._onDecrement);
        }
        else if (event.which === 13 /* enter */) {
            event.currentTarget.blur();
            this.focus();
        }
        else if (event.which === 27 /* escape */) {
            if (this.state.value !== this._lastValidValue) {
                this.setState({ value: this._lastValidValue });
            }
        }
        // style the increment/decrement button to look active
        // when the corresponding up/down arrow keys trigger a step
        if (this.state.keyboardSpinDirection !== spinDirection) {
            this.setState({ keyboardSpinDirection: spinDirection });
        }
    };
    /**
     * Make sure that we have stopped spinning on keyUp
     * if the up or down arrow fired this event
     * @param event stop spinning if we
     */
    SpinButton.prototype._handleKeyUp = function (event) {
        if (this.props.disabled || event.which === 38 /* up */ || event.which === 40 /* down */) {
            this._stop();
            return;
        }
    };
    SpinButton.prototype._onIncrementMouseDown = function () {
        this._updateValue(true /* shouldSpin */, this._initialStepDelay, this._onIncrement);
    };
    SpinButton.prototype._onDecrementMouseDown = function () {
        this._updateValue(true /* shouldSpin */, this._initialStepDelay, this._onDecrement);
    };
    SpinButton.defaultProps = {
        step: 1,
        min: 0,
        max: 100,
        disabled: false,
        labelPosition: Position.start,
        label: '',
        incrementButtonIcon: { iconName: 'ChevronUpSmall' },
        decrementButtonIcon: { iconName: 'ChevronDownSmall' }
    };
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_onFocus", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_onBlur", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_validate", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_onInputChange", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_updateValue", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_stop", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_handleKeyDown", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_handleKeyUp", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_onIncrementMouseDown", null);
    tslib_1.__decorate([
        autobind
    ], SpinButton.prototype, "_onDecrementMouseDown", null);
    SpinButton = tslib_1.__decorate([
        customizable('SpinButton', ['theme'])
    ], SpinButton);
    return SpinButton;
}(BaseComponent));
export { SpinButton };
//# sourceMappingURL=SpinButton.js.map