UNPKG

office-ui-fabric-react

Version:

Reusable React components for building experiences for Office 365.

312 lines • 16.9 kB
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, customizable, calculatePrecision, precisionRound, createRef } from '../../Utilities'; import { Position } from '../../utilities/positioning'; import { getStyles, getArrowButtonStyles } from './SpinButton.styles'; import { getClassNames } from './SpinButton.classNames'; import { KeytipData } from '../../KeytipData'; 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._input = createRef(); _this._initialStepDelay = 400; _this._stepDelay = 75; _this._onFocus = function (ev) { // We can't set focus on a non-existing element if (!_this._input.current) { return; } if (_this._spinningByMouse || _this.state.keyboardSpinDirection !== KeyboardSpinDirection.notSpinning) { _this._stop(); } _this._input.current.select(); _this.setState({ isFocused: true }); if (_this.props.onFocus) { _this.props.onFocus(ev); } }; _this._onBlur = function (ev) { _this._validate(ev); _this.setState({ isFocused: false }); if (_this.props.onBlur) { _this.props.onBlur(ev); } }; _this._onValidate = function (value, event) { if (_this.props.onValidate) { return _this.props.onValidate(value, event); } else { return _this._defaultOnValidate(value); } }; /** * Validate function to use if one is not passed in */ _this._defaultOnValidate = function (value) { if (value === null || value.trim().length === 0 || isNaN(Number(value))) { return _this._lastValidValue; } var newValue = Math.min(_this.props.max, Math.max(_this.props.min, Number(value))); return String(newValue); }; _this._onIncrement = function (value) { if (_this.props.onIncrement) { return _this.props.onIncrement(value); } else { return _this._defaultOnIncrement(value); } }; /** * 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); }; _this._onDecrement = function (value) { if (_this.props.onDecrement) { return _this.props.onDecrement(value); } else { return _this._defaultOnDecrement(value); } }; /** * 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 is used when validating text entry * in the input (not when changed via the buttons) * @param event - the event that fired */ _this._validate = function (event) { if (_this.state.value !== undefined && _this._valueToValidate !== undefined && _this._valueToValidate !== _this._lastValidValue) { var newValue = _this._onValidate(_this._valueToValidate, event); if (newValue) { _this._lastValidValue = newValue; _this._valueToValidate = undefined; _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 */ _this._onInputChange = function (event) { var element = event.target; var value = element.value; _this._valueToValidate = 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 */ _this._updateValue = function (shouldSpin, stepDelay, stepFunction) { 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) */ _this._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 */ _this._handleKeyDown = function (event) { // eat the up and down arrow keys to keep focus in the spinButton // (especially when a spinButton is inside of a FocusZone) if (event.which === 38 /* up */ || event.which === 40 /* down */ || event.which === 13 /* enter */) { event.preventDefault(); event.stopPropagation(); } if (_this.props.disabled) { _this._stop(); return; } var spinDirection = KeyboardSpinDirection.notSpinning; switch (event.which) { case 38 /* up */: spinDirection = KeyboardSpinDirection.up; _this._updateValue(false /* shouldSpin */, _this._initialStepDelay, _this._onIncrement); break; case 40 /* down */: spinDirection = KeyboardSpinDirection.down; _this._updateValue(false /* shouldSpin */, _this._initialStepDelay, _this._onDecrement); break; case 13 /* enter */: case 9 /* tab */: _this._validate(event); break; case 27 /* escape */: if (_this.state.value !== _this._lastValidValue) { _this.setState({ value: _this._lastValidValue }); } break; default: break; } // 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 */ _this._handleKeyUp = function (event) { if (_this.props.disabled || event.which === 38 /* up */ || event.which === 40 /* down */) { _this._stop(); return; } }; _this._onIncrementMouseDown = function () { _this._updateValue(true /* shouldSpin */, _this._initialStepDelay, _this._onIncrement); }; _this._onDecrementMouseDown = function () { _this._updateValue(true /* shouldSpin */, _this._initialStepDelay, _this._onDecrement); }; _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; _this._valueToValidate = undefined; 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 _this = this; 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, ariaValueNow = _a.ariaValueNow, ariaValueText = _a.ariaValueText, keytipProps = _a.keytipProps, className = _a.className; 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, className) : getClassNames(getStyles(theme, customStyles), !!disabled, !!isFocused, keyboardSpinDirection, labelPosition, className); return (React.createElement("div", { className: classNames.root }, labelPosition !== Position.bottom && (React.createElement("div", { className: classNames.labelWrapper }, iconProps && React.createElement(Icon, tslib_1.__assign({}, iconProps, { className: classNames.icon, "aria-hidden": "true" })), label && (React.createElement(Label, { id: this._labelId, htmlFor: this._inputId, className: classNames.label }, label)))), React.createElement(KeytipData, { keytipProps: keytipProps, disabled: disabled }, function (keytipAttributes) { return (React.createElement("div", { className: classNames.spinButtonWrapper, title: title && title, "aria-label": ariaLabel && ariaLabel, "aria-posinset": ariaPositionInSet, "aria-setsize": ariaSetSize, "data-ktp-target": keytipAttributes['data-ktp-target'] }, React.createElement("input", { value: value, id: _this._inputId, onChange: _this._onChange, onInput: _this._onInputChange, className: classNames.input, type: "text", autoComplete: "off", role: "spinbutton", "aria-labelledby": label && _this._labelId, "aria-valuenow": !isNaN(Number(ariaValueNow)) ? ariaValueNow : !isNaN(Number(value)) ? Number(value) : undefined, "aria-valuetext": ariaValueText ? ariaValueText : isNaN(Number(value)) ? value : undefined, "aria-valuemin": min, "aria-valuemax": max, "aria-describedby": keytipAttributes['aria-describedby'], onBlur: _this._onBlur, ref: _this._input, onFocus: _this._onFocus, onKeyDown: _this._handleKeyDown, onKeyUp: _this._handleKeyUp, readOnly: disabled, "aria-disabled": disabled, "data-lpignore": true, "data-ktp-execute-target": keytipAttributes['data-ktp-execute-target'] }), 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, "data-is-focusable": false }), 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, "data-is-focusable": false })))); }), 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.current) { this._input.current.focus(); } }; 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. */ }; SpinButton.defaultProps = { step: 1, min: 0, max: 100, disabled: false, labelPosition: Position.start, label: '', incrementButtonIcon: { iconName: 'ChevronUpSmall' }, decrementButtonIcon: { iconName: 'ChevronDownSmall' } }; SpinButton = tslib_1.__decorate([ customizable('SpinButton', ['theme', 'styles'], true) ], SpinButton); return SpinButton; }(BaseComponent)); export { SpinButton }; //# sourceMappingURL=SpinButton.js.map