@blueprintjs/core
Version:
Core styles & components
328 lines • 16.4 kB
JavaScript
"use strict";
/*
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var classnames_1 = tslib_1.__importDefault(require("classnames"));
var React = tslib_1.__importStar(require("react"));
var react_lifecycles_compat_1 = require("react-lifecycles-compat");
var common_1 = require("../../common");
var Errors = tslib_1.__importStar(require("../../common/errors"));
var buttonGroup_1 = require("../button/buttonGroup");
var buttons_1 = require("../button/buttons");
var controlGroup_1 = require("./controlGroup");
var inputGroup_1 = require("./inputGroup");
var numericInputUtils_1 = require("./numericInputUtils");
var IncrementDirection;
(function (IncrementDirection) {
IncrementDirection[IncrementDirection["DOWN"] = -1] = "DOWN";
IncrementDirection[IncrementDirection["UP"] = 1] = "UP";
})(IncrementDirection || (IncrementDirection = {}));
var NON_HTML_PROPS = [
"allowNumericCharactersOnly",
"buttonPosition",
"clampValueOnBlur",
"className",
"majorStepSize",
"minorStepSize",
"onButtonClick",
"onValueChange",
"selectAllOnFocus",
"selectAllOnIncrement",
"stepSize",
];
var NumericInput = /** @class */ (function (_super) {
tslib_1.__extends(NumericInput, _super);
function NumericInput() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = {
shouldSelectAfterUpdate: false,
stepMaxPrecision: NumericInput_1.getStepMaxPrecision(_this.props),
value: numericInputUtils_1.getValueOrEmptyValue(_this.props.value),
};
// updating these flags need not trigger re-renders, so don't include them in this.state.
_this.didPasteEventJustOccur = false;
_this.delta = 0;
_this.inputElement = null;
_this.intervalId = null;
_this.incrementButtonHandlers = _this.getButtonEventHandlers(IncrementDirection.UP);
_this.decrementButtonHandlers = _this.getButtonEventHandlers(IncrementDirection.DOWN);
_this.inputRef = function (input) {
_this.inputElement = input;
common_1.Utils.safeInvoke(_this.props.inputRef, input);
};
_this.handleButtonClick = function (e, direction) {
var delta = _this.updateDelta(direction, e);
var nextValue = _this.incrementValue(delta);
_this.invokeValueCallback(nextValue, _this.props.onButtonClick);
};
_this.stopContinuousChange = function () {
_this.delta = 0;
_this.clearTimeouts();
clearInterval(_this.intervalId);
document.removeEventListener("mouseup", _this.stopContinuousChange);
};
_this.handleContinuousChange = function () {
var nextValue = _this.incrementValue(_this.delta);
_this.invokeValueCallback(nextValue, _this.props.onButtonClick);
};
// Callbacks - Input
// =================
_this.handleInputFocus = function (e) {
// update this state flag to trigger update for input selection (see componentDidUpdate)
_this.setState({ shouldSelectAfterUpdate: _this.props.selectAllOnFocus });
common_1.Utils.safeInvoke(_this.props.onFocus, e);
};
_this.handleInputBlur = function (e) {
// always disable this flag on blur so it's ready for next time.
_this.setState({ shouldSelectAfterUpdate: false });
if (_this.props.clampValueOnBlur) {
var value = e.target.value;
var sanitizedValue = _this.getSanitizedValue(value);
_this.setState({ value: sanitizedValue });
}
common_1.Utils.safeInvoke(_this.props.onBlur, e);
};
_this.handleInputKeyDown = function (e) {
if (_this.props.disabled || _this.props.readOnly) {
return;
}
var keyCode = e.keyCode;
var direction;
if (keyCode === common_1.Keys.ARROW_UP) {
direction = IncrementDirection.UP;
}
else if (keyCode === common_1.Keys.ARROW_DOWN) {
direction = IncrementDirection.DOWN;
}
if (direction != null) {
// when the input field has focus, some key combinations will modify
// the field's selection range. we'll actually want to select all
// text in the field after we modify the value on the following
// lines. preventing the default selection behavior lets us do that
// without interference.
e.preventDefault();
var delta = _this.updateDelta(direction, e);
_this.incrementValue(delta);
}
common_1.Utils.safeInvoke(_this.props.onKeyDown, e);
};
_this.handleInputKeyPress = function (e) {
// we prohibit keystrokes in onKeyPress instead of onKeyDown, because
// e.key is not trustworthy in onKeyDown in all browsers.
if (_this.props.allowNumericCharactersOnly && !numericInputUtils_1.isValidNumericKeyboardEvent(e)) {
e.preventDefault();
}
common_1.Utils.safeInvoke(_this.props.onKeyPress, e);
};
_this.handleInputPaste = function (e) {
_this.didPasteEventJustOccur = true;
common_1.Utils.safeInvoke(_this.props.onPaste, e);
};
_this.handleInputChange = function (e) {
var value = e.target.value;
var nextValue = value;
if (_this.props.allowNumericCharactersOnly && _this.didPasteEventJustOccur) {
_this.didPasteEventJustOccur = false;
var valueChars = value.split("");
var sanitizedValueChars = valueChars.filter(numericInputUtils_1.isFloatingPointNumericCharacter);
var sanitizedValue = sanitizedValueChars.join("");
nextValue = sanitizedValue;
}
_this.setState({ shouldSelectAfterUpdate: false, value: nextValue });
};
return _this;
}
NumericInput_1 = NumericInput;
NumericInput.getDerivedStateFromProps = function (props, state) {
var nextState = { prevMinProp: props.min, prevMaxProp: props.max, prevValueProp: props.value };
var didMinChange = props.min !== state.prevMinProp;
var didMaxChange = props.max !== state.prevMaxProp;
var didBoundsChange = didMinChange || didMaxChange;
var didValuePropChange = props.value !== state.prevValueProp;
var value = numericInputUtils_1.getValueOrEmptyValue(didValuePropChange ? props.value : state.value);
var sanitizedValue = value !== NumericInput_1.VALUE_EMPTY
? NumericInput_1.getSanitizedValue(value, /* delta */ 0, props.min, props.max)
: NumericInput_1.VALUE_EMPTY;
var stepMaxPrecision = NumericInput_1.getStepMaxPrecision(props);
// if a new min and max were provided that cause the existing value to fall
// outside of the new bounds, then clamp the value to the new valid range.
if (didBoundsChange && sanitizedValue !== state.value) {
return tslib_1.__assign({}, nextState, { stepMaxPrecision: stepMaxPrecision, value: sanitizedValue });
}
else {
return tslib_1.__assign({}, nextState, { stepMaxPrecision: stepMaxPrecision, value: value });
}
};
// Value Helpers
// =============
NumericInput.getStepMaxPrecision = function (props) {
if (props.minorStepSize != null) {
return common_1.Utils.countDecimalPlaces(props.minorStepSize);
}
else {
return common_1.Utils.countDecimalPlaces(props.stepSize);
}
};
NumericInput.getSanitizedValue = function (value, stepMaxPrecision, min, max, delta) {
if (delta === void 0) { delta = 0; }
if (!numericInputUtils_1.isValueNumeric(value)) {
return NumericInput_1.VALUE_EMPTY;
}
var nextValue = numericInputUtils_1.toMaxPrecision(parseFloat(value) + delta, stepMaxPrecision);
return numericInputUtils_1.clampValue(nextValue, min, max).toString();
};
NumericInput.prototype.render = function () {
var _a;
var _b = this.props, buttonPosition = _b.buttonPosition, className = _b.className, fill = _b.fill, large = _b.large;
var containerClasses = classnames_1.default(common_1.Classes.NUMERIC_INPUT, (_a = {}, _a[common_1.Classes.LARGE] = large, _a), className);
var buttons = this.renderButtons();
return (React.createElement(controlGroup_1.ControlGroup, { className: containerClasses, fill: fill },
buttonPosition === common_1.Position.LEFT && buttons,
this.renderInput(),
buttonPosition === common_1.Position.RIGHT && buttons));
};
NumericInput.prototype.componentDidUpdate = function (prevProps, prevState) {
_super.prototype.componentDidUpdate.call(this, prevProps, prevState);
if (this.state.shouldSelectAfterUpdate) {
this.inputElement.setSelectionRange(0, this.state.value.length);
}
var didControlledValueChange = this.props.value !== prevProps.value;
if (!didControlledValueChange && this.state.value !== prevState.value) {
this.invokeValueCallback(this.state.value, this.props.onValueChange);
}
};
NumericInput.prototype.validateProps = function (nextProps) {
var majorStepSize = nextProps.majorStepSize, max = nextProps.max, min = nextProps.min, minorStepSize = nextProps.minorStepSize, stepSize = nextProps.stepSize;
if (min != null && max != null && min > max) {
throw new Error(Errors.NUMERIC_INPUT_MIN_MAX);
}
if (stepSize == null) {
throw new Error(Errors.NUMERIC_INPUT_STEP_SIZE_NULL);
}
if (stepSize <= 0) {
throw new Error(Errors.NUMERIC_INPUT_STEP_SIZE_NON_POSITIVE);
}
if (minorStepSize && minorStepSize <= 0) {
throw new Error(Errors.NUMERIC_INPUT_MINOR_STEP_SIZE_NON_POSITIVE);
}
if (majorStepSize && majorStepSize <= 0) {
throw new Error(Errors.NUMERIC_INPUT_MAJOR_STEP_SIZE_NON_POSITIVE);
}
if (minorStepSize && minorStepSize > stepSize) {
throw new Error(Errors.NUMERIC_INPUT_MINOR_STEP_SIZE_BOUND);
}
if (majorStepSize && majorStepSize < stepSize) {
throw new Error(Errors.NUMERIC_INPUT_MAJOR_STEP_SIZE_BOUND);
}
};
// Render Helpers
// ==============
NumericInput.prototype.renderButtons = function () {
var intent = this.props.intent;
var disabled = this.props.disabled || this.props.readOnly;
return (React.createElement(buttonGroup_1.ButtonGroup, { className: common_1.Classes.FIXED, key: "button-group", vertical: true },
React.createElement(buttons_1.Button, tslib_1.__assign({ disabled: disabled, icon: "chevron-up", intent: intent }, this.incrementButtonHandlers)),
React.createElement(buttons_1.Button, tslib_1.__assign({ disabled: disabled, icon: "chevron-down", intent: intent }, this.decrementButtonHandlers))));
};
NumericInput.prototype.renderInput = function () {
var inputGroupHtmlProps = common_1.removeNonHTMLProps(this.props, NON_HTML_PROPS, true);
return (React.createElement(inputGroup_1.InputGroup, tslib_1.__assign({ autoComplete: "off" }, inputGroupHtmlProps, { intent: this.props.intent, inputRef: this.inputRef, large: this.props.large, leftIcon: this.props.leftIcon, onFocus: this.handleInputFocus, onBlur: this.handleInputBlur, onChange: this.handleInputChange, onKeyDown: this.handleInputKeyDown, onKeyPress: this.handleInputKeyPress, onPaste: this.handleInputPaste, rightElement: this.props.rightElement, value: this.state.value })));
};
// Callbacks - Buttons
// ===================
NumericInput.prototype.getButtonEventHandlers = function (direction) {
var _this = this;
return {
// keydown is fired repeatedly when held so it's implicitly continuous
onKeyDown: function (evt) {
if (common_1.Keys.isKeyboardClick(evt.keyCode)) {
_this.handleButtonClick(evt, direction);
}
},
onMouseDown: function (evt) {
_this.handleButtonClick(evt, direction);
_this.startContinuousChange();
},
};
};
NumericInput.prototype.startContinuousChange = function () {
var _this = this;
// The button's onMouseUp event handler doesn't fire if the user
// releases outside of the button, so we need to watch all the way
// from the top.
document.addEventListener("mouseup", this.stopContinuousChange);
// Initial delay is slightly longer to prevent the user from
// accidentally triggering the continuous increment/decrement.
this.setTimeout(function () {
_this.intervalId = window.setInterval(_this.handleContinuousChange, NumericInput_1.CONTINUOUS_CHANGE_INTERVAL);
}, NumericInput_1.CONTINUOUS_CHANGE_DELAY);
};
NumericInput.prototype.invokeValueCallback = function (value, callback) {
common_1.Utils.safeInvoke(callback, +value, value);
};
NumericInput.prototype.incrementValue = function (delta) {
// pretend we're incrementing from 0 if currValue is empty
var currValue = this.state.value || NumericInput_1.VALUE_ZERO;
var nextValue = this.getSanitizedValue(currValue, delta);
this.setState({ shouldSelectAfterUpdate: this.props.selectAllOnIncrement, value: nextValue });
return nextValue;
};
NumericInput.prototype.getIncrementDelta = function (direction, isShiftKeyPressed, isAltKeyPressed) {
var _a = this.props, majorStepSize = _a.majorStepSize, minorStepSize = _a.minorStepSize, stepSize = _a.stepSize;
if (isShiftKeyPressed && majorStepSize != null) {
return direction * majorStepSize;
}
else if (isAltKeyPressed && minorStepSize != null) {
return direction * minorStepSize;
}
else {
return direction * stepSize;
}
};
NumericInput.prototype.getSanitizedValue = function (value, delta) {
if (delta === void 0) { delta = 0; }
return NumericInput_1.getSanitizedValue(value, this.state.stepMaxPrecision, this.props.min, this.props.max, delta);
};
NumericInput.prototype.updateDelta = function (direction, e) {
this.delta = this.getIncrementDelta(direction, e.shiftKey, e.altKey);
return this.delta;
};
var NumericInput_1;
NumericInput.displayName = common_1.DISPLAYNAME_PREFIX + ".NumericInput";
NumericInput.VALUE_EMPTY = "";
NumericInput.VALUE_ZERO = "0";
NumericInput.defaultProps = {
allowNumericCharactersOnly: true,
buttonPosition: common_1.Position.RIGHT,
clampValueOnBlur: false,
large: false,
majorStepSize: 10,
minorStepSize: 0.1,
selectAllOnFocus: false,
selectAllOnIncrement: false,
stepSize: 1,
value: NumericInput_1.VALUE_EMPTY,
};
NumericInput.CONTINUOUS_CHANGE_DELAY = 300;
NumericInput.CONTINUOUS_CHANGE_INTERVAL = 100;
NumericInput = NumericInput_1 = tslib_1.__decorate([
react_lifecycles_compat_1.polyfill
], NumericInput);
return NumericInput;
}(common_1.AbstractPureComponent2));
exports.NumericInput = NumericInput;
//# sourceMappingURL=numericInput.js.map