UNPKG

22.6 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright 2017 Palantir Technologies, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.NumericInput = void 0;
19var tslib_1 = require("tslib");
20var classnames_1 = tslib_1.__importDefault(require("classnames"));
21var React = tslib_1.__importStar(require("react"));
22var common_1 = require("../../common");
23var Errors = tslib_1.__importStar(require("../../common/errors"));
24var buttonGroup_1 = require("../button/buttonGroup");
25var buttons_1 = require("../button/buttons");
26var controlGroup_1 = require("./controlGroup");
27var inputGroup_1 = require("./inputGroup");
28var numericInputUtils_1 = require("./numericInputUtils");
29var IncrementDirection;
30(function (IncrementDirection) {
31 IncrementDirection[IncrementDirection["DOWN"] = -1] = "DOWN";
32 IncrementDirection[IncrementDirection["UP"] = 1] = "UP";
33})(IncrementDirection || (IncrementDirection = {}));
34var NON_HTML_PROPS = [
35 "allowNumericCharactersOnly",
36 "buttonPosition",
37 "clampValueOnBlur",
38 "className",
39 "defaultValue",
40 "majorStepSize",
41 "minorStepSize",
42 "onButtonClick",
43 "onValueChange",
44 "selectAllOnFocus",
45 "selectAllOnIncrement",
46 "stepSize",
47];
48/**
49 * Numeric input component.
50 *
51 * @see https://blueprintjs.com/docs/#core/components/numeric-input
52 */
53var NumericInput = /** @class */ (function (_super) {
54 tslib_1.__extends(NumericInput, _super);
55 function NumericInput() {
56 var _this = this;
57 var _a;
58 _this = _super.apply(this, arguments) || this;
59 _this.numericInputId = common_1.Utils.uniqueId("numericInput");
60 _this.state = {
61 currentImeInputInvalid: false,
62 shouldSelectAfterUpdate: false,
63 stepMaxPrecision: NumericInput.getStepMaxPrecision(_this.props),
64 value: (0, numericInputUtils_1.getValueOrEmptyValue)((_a = _this.props.value) !== null && _a !== void 0 ? _a : _this.props.defaultValue),
65 };
66 // updating these flags need not trigger re-renders, so don't include them in this.state.
67 _this.didPasteEventJustOccur = false;
68 _this.delta = 0;
69 _this.inputElement = null;
70 _this.inputRef = (0, common_1.refHandler)(_this, "inputElement", _this.props.inputRef);
71 _this.incrementButtonHandlers = _this.getButtonEventHandlers(IncrementDirection.UP);
72 _this.decrementButtonHandlers = _this.getButtonEventHandlers(IncrementDirection.DOWN);
73 _this.getCurrentValueAsNumber = function () { return Number((0, numericInputUtils_1.parseStringToStringNumber)(_this.state.value, _this.props.locale)); };
74 _this.handleButtonClick = function (e, direction) {
75 var _a, _b;
76 var delta = _this.updateDelta(direction, e);
77 var nextValue = _this.incrementValue(delta);
78 (_b = (_a = _this.props).onButtonClick) === null || _b === void 0 ? void 0 : _b.call(_a, Number((0, numericInputUtils_1.parseStringToStringNumber)(nextValue, _this.props.locale)), nextValue);
79 };
80 _this.stopContinuousChange = function () {
81 _this.delta = 0;
82 _this.clearTimeouts();
83 clearInterval(_this.intervalId);
84 document.removeEventListener("mouseup", _this.stopContinuousChange);
85 };
86 _this.handleContinuousChange = function () {
87 var _a, _b, _c, _d;
88 // If either min or max prop is set, when reaching the limit
89 // the button will be disabled and stopContinuousChange will be never fired,
90 // hence the need to check on each iteration to properly clear the timeout
91 if (_this.props.min !== undefined || _this.props.max !== undefined) {
92 var min = (_a = _this.props.min) !== null && _a !== void 0 ? _a : -Infinity;
93 var max = (_b = _this.props.max) !== null && _b !== void 0 ? _b : Infinity;
94 var valueAsNumber = _this.getCurrentValueAsNumber();
95 if (valueAsNumber <= min || valueAsNumber >= max) {
96 _this.stopContinuousChange();
97 return;
98 }
99 }
100 var nextValue = _this.incrementValue(_this.delta);
101 (_d = (_c = _this.props).onButtonClick) === null || _d === void 0 ? void 0 : _d.call(_c, Number((0, numericInputUtils_1.parseStringToStringNumber)(nextValue, _this.props.locale)), nextValue);
102 };
103 // Callbacks - Input
104 // =================
105 _this.handleInputFocus = function (e) {
106 var _a, _b;
107 // update this state flag to trigger update for input selection (see componentDidUpdate)
108 _this.setState({ shouldSelectAfterUpdate: _this.props.selectAllOnFocus });
109 (_b = (_a = _this.props).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, e);
110 };
111 _this.handleInputBlur = function (e) {
112 var _a, _b;
113 // always disable this flag on blur so it's ready for next time.
114 _this.setState({ shouldSelectAfterUpdate: false });
115 if (_this.props.clampValueOnBlur) {
116 var value = e.target.value;
117 _this.handleNextValue(_this.roundAndClampValue(value));
118 }
119 (_b = (_a = _this.props).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, e);
120 };
121 _this.handleInputKeyDown = function (e) {
122 var _a, _b;
123 if (_this.props.disabled || _this.props.readOnly) {
124 return;
125 }
126 // eslint-disable-next-line deprecation/deprecation
127 var keyCode = e.keyCode;
128 var direction;
129 if (keyCode === common_1.Keys.ARROW_UP) {
130 direction = IncrementDirection.UP;
131 }
132 else if (keyCode === common_1.Keys.ARROW_DOWN) {
133 direction = IncrementDirection.DOWN;
134 }
135 if (direction !== undefined) {
136 // when the input field has focus, some key combinations will modify
137 // the field's selection range. we'll actually want to select all
138 // text in the field after we modify the value on the following
139 // lines. preventing the default selection behavior lets us do that
140 // without interference.
141 e.preventDefault();
142 var delta = _this.updateDelta(direction, e);
143 _this.incrementValue(delta);
144 }
145 (_b = (_a = _this.props).onKeyDown) === null || _b === void 0 ? void 0 : _b.call(_a, e);
146 };
147 _this.handleCompositionEnd = function (e) {
148 if (_this.props.allowNumericCharactersOnly) {
149 _this.handleNextValue((0, numericInputUtils_1.sanitizeNumericInput)(e.data, _this.props.locale));
150 _this.setState({ currentImeInputInvalid: false });
151 }
152 };
153 _this.handleCompositionUpdate = function (e) {
154 if (_this.props.allowNumericCharactersOnly) {
155 var data = e.data;
156 var sanitizedValue = (0, numericInputUtils_1.sanitizeNumericInput)(data, _this.props.locale);
157 if (sanitizedValue.length === 0 && data.length > 0) {
158 _this.setState({ currentImeInputInvalid: true });
159 }
160 else {
161 _this.setState({ currentImeInputInvalid: false });
162 }
163 }
164 };
165 _this.handleInputKeyPress = function (e) {
166 var _a, _b;
167 // we prohibit keystrokes in onKeyPress instead of onKeyDown, because
168 // e.key is not trustworthy in onKeyDown in all browsers.
169 if (_this.props.allowNumericCharactersOnly && !(0, numericInputUtils_1.isValidNumericKeyboardEvent)(e, _this.props.locale)) {
170 e.preventDefault();
171 }
172 // eslint-disable-next-line deprecation/deprecation
173 (_b = (_a = _this.props).onKeyPress) === null || _b === void 0 ? void 0 : _b.call(_a, e);
174 };
175 _this.handleInputPaste = function (e) {
176 var _a, _b;
177 _this.didPasteEventJustOccur = true;
178 (_b = (_a = _this.props).onPaste) === null || _b === void 0 ? void 0 : _b.call(_a, e);
179 };
180 _this.handleInputChange = function (e) {
181 var value = e.target.value;
182 var nextValue = value;
183 if (_this.props.allowNumericCharactersOnly && _this.didPasteEventJustOccur) {
184 _this.didPasteEventJustOccur = false;
185 nextValue = (0, numericInputUtils_1.sanitizeNumericInput)(value, _this.props.locale);
186 }
187 _this.handleNextValue(nextValue);
188 _this.setState({ shouldSelectAfterUpdate: false });
189 };
190 return _this;
191 }
192 NumericInput.getDerivedStateFromProps = function (props, state) {
193 var _a, _b;
194 var nextState = {
195 prevMaxProp: props.max,
196 prevMinProp: props.min,
197 };
198 var didMinChange = props.min !== state.prevMinProp;
199 var didMaxChange = props.max !== state.prevMaxProp;
200 var didBoundsChange = didMinChange || didMaxChange;
201 // in controlled mode, use props.value
202 // in uncontrolled mode, if state.value has not been assigned yet (upon initial mount), use props.defaultValue
203 var value = (_b = (_a = props.value) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : state.value;
204 var stepMaxPrecision = NumericInput.getStepMaxPrecision(props);
205 var sanitizedValue = value !== NumericInput.VALUE_EMPTY
206 ? NumericInput.roundAndClampValue(value, stepMaxPrecision, props.min, props.max, 0, props.locale)
207 : NumericInput.VALUE_EMPTY;
208 // if a new min and max were provided that cause the existing value to fall
209 // outside of the new bounds, then clamp the value to the new valid range.
210 if (didBoundsChange && sanitizedValue !== state.value) {
211 return tslib_1.__assign(tslib_1.__assign({}, nextState), { stepMaxPrecision: stepMaxPrecision, value: sanitizedValue });
212 }
213 return tslib_1.__assign(tslib_1.__assign({}, nextState), { stepMaxPrecision: stepMaxPrecision, value: value });
214 };
215 // Value Helpers
216 // =============
217 NumericInput.getStepMaxPrecision = function (props) {
218 if (props.minorStepSize != null) {
219 return common_1.Utils.countDecimalPlaces(props.minorStepSize);
220 }
221 else {
222 return common_1.Utils.countDecimalPlaces(props.stepSize);
223 }
224 };
225 NumericInput.roundAndClampValue = function (value, stepMaxPrecision, min, max, delta, locale) {
226 if (delta === void 0) { delta = 0; }
227 if (!(0, numericInputUtils_1.isValueNumeric)(value, locale)) {
228 return NumericInput.VALUE_EMPTY;
229 }
230 var currentValue = (0, numericInputUtils_1.parseStringToStringNumber)(value, locale);
231 var nextValue = (0, numericInputUtils_1.toMaxPrecision)(Number(currentValue) + delta, stepMaxPrecision);
232 var clampedValue = (0, numericInputUtils_1.clampValue)(nextValue, min, max);
233 return (0, numericInputUtils_1.toLocaleString)(clampedValue, locale);
234 };
235 NumericInput.prototype.render = function () {
236 var _a;
237 var _b = this.props, buttonPosition = _b.buttonPosition, className = _b.className, fill = _b.fill, large = _b.large;
238 var containerClasses = (0, classnames_1.default)(common_1.Classes.NUMERIC_INPUT, (_a = {}, _a[common_1.Classes.LARGE] = large, _a), className);
239 var buttons = this.renderButtons();
240 return (React.createElement(controlGroup_1.ControlGroup, { className: containerClasses, fill: fill },
241 buttonPosition === common_1.Position.LEFT && buttons,
242 this.renderInput(),
243 buttonPosition === common_1.Position.RIGHT && buttons));
244 };
245 NumericInput.prototype.componentDidUpdate = function (prevProps, prevState) {
246 var _a, _b, _c;
247 _super.prototype.componentDidUpdate.call(this, prevProps, prevState);
248 if (prevProps.inputRef !== this.props.inputRef) {
249 (0, common_1.setRef)(prevProps.inputRef, null);
250 this.inputRef = (0, common_1.refHandler)(this, "inputElement", this.props.inputRef);
251 (0, common_1.setRef)(this.props.inputRef, this.inputElement);
252 }
253 if (this.state.shouldSelectAfterUpdate) {
254 (_a = this.inputElement) === null || _a === void 0 ? void 0 : _a.setSelectionRange(0, this.state.value.length);
255 }
256 var didMinChange = this.props.min !== prevProps.min;
257 var didMaxChange = this.props.max !== prevProps.max;
258 var didBoundsChange = didMinChange || didMaxChange;
259 var didLocaleChange = this.props.locale !== prevProps.locale;
260 var didValueChange = this.state.value !== prevState.value;
261 if ((didBoundsChange && didValueChange) || (didLocaleChange && prevState.value !== NumericInput.VALUE_EMPTY)) {
262 // we clamped the value due to a bounds change, so we should fire the change callback
263 var valueToParse = didLocaleChange ? prevState.value : this.state.value;
264 var valueAsString = (0, numericInputUtils_1.parseStringToStringNumber)(valueToParse, prevProps.locale);
265 var localizedValue = (0, numericInputUtils_1.toLocaleString)(+valueAsString, this.props.locale);
266 (_c = (_b = this.props).onValueChange) === null || _c === void 0 ? void 0 : _c.call(_b, +valueAsString, localizedValue, this.inputElement);
267 }
268 };
269 NumericInput.prototype.validateProps = function (nextProps) {
270 var majorStepSize = nextProps.majorStepSize, max = nextProps.max, min = nextProps.min, minorStepSize = nextProps.minorStepSize, stepSize = nextProps.stepSize, value = nextProps.value;
271 if (min != null && max != null && min > max) {
272 console.error(Errors.NUMERIC_INPUT_MIN_MAX);
273 }
274 if (stepSize <= 0) {
275 console.error(Errors.NUMERIC_INPUT_STEP_SIZE_NON_POSITIVE);
276 }
277 if (minorStepSize && minorStepSize <= 0) {
278 console.error(Errors.NUMERIC_INPUT_MINOR_STEP_SIZE_NON_POSITIVE);
279 }
280 if (majorStepSize && majorStepSize <= 0) {
281 console.error(Errors.NUMERIC_INPUT_MAJOR_STEP_SIZE_NON_POSITIVE);
282 }
283 if (minorStepSize && minorStepSize > stepSize) {
284 console.error(Errors.NUMERIC_INPUT_MINOR_STEP_SIZE_BOUND);
285 }
286 if (majorStepSize && majorStepSize < stepSize) {
287 console.error(Errors.NUMERIC_INPUT_MAJOR_STEP_SIZE_BOUND);
288 }
289 // controlled mode
290 if (value != null) {
291 var stepMaxPrecision = NumericInput.getStepMaxPrecision(nextProps);
292 var sanitizedValue = NumericInput.roundAndClampValue(value.toString(), stepMaxPrecision, min, max, 0, this.props.locale);
293 var valueDoesNotMatch = sanitizedValue !== value.toString();
294 var localizedValue = (0, numericInputUtils_1.toLocaleString)(Number((0, numericInputUtils_1.parseStringToStringNumber)(value, this.props.locale)), this.props.locale);
295 var isNotLocalized = sanitizedValue !== localizedValue;
296 if (valueDoesNotMatch && isNotLocalized) {
297 console.warn(Errors.NUMERIC_INPUT_CONTROLLED_VALUE_INVALID);
298 }
299 }
300 };
301 // Render Helpers
302 // ==============
303 NumericInput.prototype.renderButtons = function () {
304 var _a = this.props, intent = _a.intent, max = _a.max, min = _a.min, locale = _a.locale;
305 var value = (0, numericInputUtils_1.parseStringToStringNumber)(this.state.value, locale);
306 var disabled = this.props.disabled || this.props.readOnly;
307 var isIncrementDisabled = max !== undefined && value !== "" && +value >= max;
308 var isDecrementDisabled = min !== undefined && value !== "" && +value <= min;
309 return (React.createElement(buttonGroup_1.ButtonGroup, { className: common_1.Classes.FIXED, key: "button-group", vertical: true },
310 React.createElement(buttons_1.Button, tslib_1.__assign({ "aria-label": "increment", "aria-controls": this.numericInputId, disabled: disabled || isIncrementDisabled, icon: "chevron-up", intent: intent }, this.incrementButtonHandlers)),
311 React.createElement(buttons_1.Button, tslib_1.__assign({ "aria-label": "decrement", "aria-controls": this.numericInputId, disabled: disabled || isDecrementDisabled, icon: "chevron-down", intent: intent }, this.decrementButtonHandlers))));
312 };
313 NumericInput.prototype.renderInput = function () {
314 var inputGroupHtmlProps = (0, common_1.removeNonHTMLProps)(this.props, NON_HTML_PROPS, true);
315 var valueAsNumber = this.getCurrentValueAsNumber();
316 return (React.createElement(inputGroup_1.InputGroup, tslib_1.__assign({ asyncControl: this.props.asyncControl, autoComplete: "off", id: this.numericInputId, role: this.props.allowNumericCharactersOnly ? "spinbutton" : undefined }, inputGroupHtmlProps, { "aria-valuemax": this.props.max, "aria-valuemin": this.props.min, "aria-valuenow": valueAsNumber, intent: this.state.currentImeInputInvalid ? common_1.Intent.DANGER : this.props.intent, inputRef: this.inputRef, large: this.props.large, leftElement: this.props.leftElement, leftIcon: this.props.leftIcon, onFocus: this.handleInputFocus, onBlur: this.handleInputBlur, onChange: this.handleInputChange, onCompositionEnd: this.handleCompositionEnd, onCompositionUpdate: this.handleCompositionUpdate, onKeyDown: this.handleInputKeyDown, onKeyPress: this.handleInputKeyPress, onPaste: this.handleInputPaste, rightElement: this.props.rightElement, value: this.state.value })));
317 };
318 // Callbacks - Buttons
319 // ===================
320 NumericInput.prototype.getButtonEventHandlers = function (direction) {
321 var _this = this;
322 return {
323 // keydown is fired repeatedly when held so it's implicitly continuous
324 onKeyDown: function (evt) {
325 // eslint-disable-next-line deprecation/deprecation
326 if (!_this.props.disabled && common_1.Keys.isKeyboardClick(evt.keyCode)) {
327 _this.handleButtonClick(evt, direction);
328 }
329 },
330 onMouseDown: function (evt) {
331 if (!_this.props.disabled) {
332 _this.handleButtonClick(evt, direction);
333 _this.startContinuousChange();
334 }
335 },
336 };
337 };
338 NumericInput.prototype.startContinuousChange = function () {
339 var _this = this;
340 // The button's onMouseUp event handler doesn't fire if the user
341 // releases outside of the button, so we need to watch all the way
342 // from the top.
343 document.addEventListener("mouseup", this.stopContinuousChange);
344 // Initial delay is slightly longer to prevent the user from
345 // accidentally triggering the continuous increment/decrement.
346 this.setTimeout(function () {
347 _this.intervalId = window.setInterval(_this.handleContinuousChange, NumericInput.CONTINUOUS_CHANGE_INTERVAL);
348 }, NumericInput.CONTINUOUS_CHANGE_DELAY);
349 };
350 // Data logic
351 // ==========
352 NumericInput.prototype.handleNextValue = function (valueAsString) {
353 var _a, _b;
354 if (this.props.value == null) {
355 this.setState({ value: valueAsString });
356 }
357 (_b = (_a = this.props).onValueChange) === null || _b === void 0 ? void 0 : _b.call(_a, Number((0, numericInputUtils_1.parseStringToStringNumber)(valueAsString, this.props.locale)), valueAsString, this.inputElement);
358 };
359 NumericInput.prototype.incrementValue = function (delta) {
360 // pretend we're incrementing from 0 if currValue is empty
361 var currValue = this.state.value === NumericInput.VALUE_EMPTY ? NumericInput.VALUE_ZERO : this.state.value;
362 var nextValue = this.roundAndClampValue(currValue, delta);
363 if (nextValue !== this.state.value) {
364 this.handleNextValue(nextValue);
365 this.setState({ shouldSelectAfterUpdate: this.props.selectAllOnIncrement });
366 }
367 // return value used in continuous change updates
368 return nextValue;
369 };
370 NumericInput.prototype.getIncrementDelta = function (direction, isShiftKeyPressed, isAltKeyPressed) {
371 var _a = this.props, majorStepSize = _a.majorStepSize, minorStepSize = _a.minorStepSize, stepSize = _a.stepSize;
372 if (isShiftKeyPressed && majorStepSize != null) {
373 return direction * majorStepSize;
374 }
375 else if (isAltKeyPressed && minorStepSize != null) {
376 return direction * minorStepSize;
377 }
378 else {
379 return direction * stepSize;
380 }
381 };
382 NumericInput.prototype.roundAndClampValue = function (value, delta) {
383 if (delta === void 0) { delta = 0; }
384 return NumericInput.roundAndClampValue(value, this.state.stepMaxPrecision, this.props.min, this.props.max, delta, this.props.locale);
385 };
386 NumericInput.prototype.updateDelta = function (direction, e) {
387 this.delta = this.getIncrementDelta(direction, e.shiftKey, e.altKey);
388 return this.delta;
389 };
390 NumericInput.displayName = "".concat(common_1.DISPLAYNAME_PREFIX, ".NumericInput");
391 NumericInput.VALUE_EMPTY = "";
392 NumericInput.VALUE_ZERO = "0";
393 NumericInput.defaultProps = {
394 allowNumericCharactersOnly: true,
395 buttonPosition: common_1.Position.RIGHT,
396 clampValueOnBlur: false,
397 defaultValue: NumericInput.VALUE_EMPTY,
398 large: false,
399 majorStepSize: 10,
400 minorStepSize: 0.1,
401 selectAllOnFocus: false,
402 selectAllOnIncrement: false,
403 stepSize: 1,
404 };
405 NumericInput.CONTINUOUS_CHANGE_DELAY = 300;
406 NumericInput.CONTINUOUS_CHANGE_INTERVAL = 100;
407 return NumericInput;
408}(common_1.AbstractPureComponent2));
409exports.NumericInput = NumericInput;
410//# sourceMappingURL=numericInput.js.map
\No newline at end of file