UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

667 lines (665 loc) • 29 kB
/** * DevExtreme (cjs/ui/number_box/number_box.mask.js) * Version: 23.2.6 * Build date: Wed May 01 2024 * * Copyright (c) 2012 - 2024 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; exports.default = void 0; var _events_engine = _interopRequireDefault(require("../../events/core/events_engine")); var _double_click = require("../../events/double_click"); var _extend = require("../../core/utils/extend"); var _type = require("../../core/utils/type"); var _devices = _interopRequireDefault(require("../../core/devices")); var _math = require("../../core/utils/math"); var _number = _interopRequireDefault(require("../../localization/number")); var _number_box = require("./number_box.caret"); var _number2 = require("../../localization/ldml/number"); var _number_box2 = _interopRequireDefault(require("./number_box.base")); var _index = require("../../events/utils/index"); var _common = require("../../core/utils/common"); var _utils = require("./utils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj } } const NUMBER_FORMATTER_NAMESPACE = "dxNumberFormatter"; const MOVE_FORWARD = 1; const MOVE_BACKWARD = -1; const MINUS = "-"; const MINUS_KEY = "minus"; const INPUT_EVENT = "input"; const NUMPAD_DOT_KEY_CODE = 110; const CARET_TIMEOUT_DURATION = 0; const NumberBoxMask = _number_box2.default.inherit({ _getDefaultOptions: function() { return (0, _extend.extend)(this.callBase(), { useMaskBehavior: true, format: null }) }, _isDeleteKey: function(key) { return "del" === key }, _supportedKeys: function() { if (!this._useMaskBehavior()) { return this.callBase() } return (0, _extend.extend)(this.callBase(), { minus: this._revertSign.bind(this), del: this._removeHandler.bind(this), backspace: this._removeHandler.bind(this), leftArrow: this._arrowHandler.bind(this, -1), rightArrow: this._arrowHandler.bind(this, 1), home: this._moveCaretToBoundaryEventHandler.bind(this, 1), enter: this._updateFormattedValue.bind(this), end: this._moveCaretToBoundaryEventHandler.bind(this, -1) }) }, _getTextSeparatorIndex: function(text) { const decimalSeparator = _number.default.getDecimalSeparator(); const realSeparatorOccurrenceIndex = (0, _utils.getRealSeparatorIndex)(this.option("format")).occurrence; return (0, _utils.getNthOccurrence)(text, decimalSeparator, realSeparatorOccurrenceIndex) }, _focusInHandler: function(e) { if (!this._preventNestedFocusEvent(e)) { this.clearCaretTimeout(); this._caretTimeout = setTimeout(function() { this._caretTimeout = void 0; const caret = this._caret(); if (caret.start === caret.end && this._useMaskBehavior()) { const text = this._getInputVal(); const decimalSeparatorIndex = this._getTextSeparatorIndex(text); if (decimalSeparatorIndex >= 0) { this._caret({ start: decimalSeparatorIndex, end: decimalSeparatorIndex }) } else { this._moveCaretToBoundaryEventHandler(-1, e) } } }.bind(this), 0) } this.callBase(e) }, _focusOutHandler: function(e) { const shouldHandleEvent = !this._preventNestedFocusEvent(e); if (shouldHandleEvent) { this._focusOutOccurs = true; if (this._useMaskBehavior()) { this._updateFormattedValue() } } this.callBase(e); if (shouldHandleEvent) { this._focusOutOccurs = false } }, _hasValueBeenChanged(inputValue) { const format = this._getFormatPattern(); const value = this.option("value"); const formatted = this._format(value, format) || ""; return formatted !== inputValue }, _updateFormattedValue: function() { const inputValue = this._getInputVal(); if (this._hasValueBeenChanged(inputValue)) { this._updateParsedValue(); this._adjustParsedValue(); this._setTextByParsedValue(); if (this._parsedValue !== this.option("value")) { _events_engine.default.trigger(this._input(), "change") } } }, _arrowHandler: function(step, e) { if (!this._useMaskBehavior()) { return } const text = this._getInputVal(); const format = this._getFormatPattern(); let nextCaret = (0, _number_box.getCaretWithOffset)(this._caret(), step); if (!(0, _number_box.isCaretInBoundaries)(nextCaret, text, format)) { nextCaret = 1 === step ? nextCaret.end : nextCaret.start; e.preventDefault(); this._caret((0, _number_box.getCaretInBoundaries)(nextCaret, text, format)) } }, _moveCaretToBoundary: function(direction) { const boundaries = (0, _number_box.getCaretBoundaries)(this._getInputVal(), this._getFormatPattern()); const newCaret = (0, _number_box.getCaretWithOffset)(1 === direction ? boundaries.start : boundaries.end, 0); this._caret(newCaret) }, _moveCaretToBoundaryEventHandler: function(direction, e) { if (!this._useMaskBehavior() || e && e.shiftKey) { return } this._moveCaretToBoundary(direction); e && e.preventDefault() }, _shouldMoveCaret: function(text, caret) { const decimalSeparator = _number.default.getDecimalSeparator(); const isDecimalSeparatorNext = text.charAt(caret.end) === decimalSeparator; const moveToFloat = (this._lastKey === decimalSeparator || "." === this._lastKey || "," === this._lastKey) && isDecimalSeparatorNext; return moveToFloat }, _getInputVal: function() { return _number.default.convertDigits(this._input().val(), true) }, _keyboardHandler: function(e) { this.clearCaretTimeout(); this._lastKey = _number.default.convertDigits((0, _index.getChar)(e), true); this._lastKeyName = (0, _index.normalizeKeyName)(e); if (!this._shouldHandleKey(e.originalEvent)) { return this.callBase(e) } const normalizedText = this._getInputVal(); const caret = this._caret(); let enteredChar; if ("minus" === this._lastKeyName) { enteredChar = "" } else { enteredChar = 110 === e.which ? _number.default.getDecimalSeparator() : this._lastKey } const newValue = this._tryParse(normalizedText, caret, enteredChar); if (this._shouldMoveCaret(normalizedText, caret)) { this._moveCaret(1); e.originalEvent.preventDefault() } if (void 0 === newValue) { if ("minus" !== this._lastKeyName) { e.originalEvent.preventDefault() } } else { this._parsedValue = newValue } return this.callBase(e) }, _keyPressHandler: function(e) { if (!this._useMaskBehavior()) { this.callBase(e) } }, _removeHandler: function(e) { const caret = this._caret(); const text = this._getInputVal(); let start = caret.start; let end = caret.end; this._lastKey = (0, _index.getChar)(e); this._lastKeyName = (0, _index.normalizeKeyName)(e); const isDeleteKey = this._isDeleteKey(this._lastKeyName); const isBackspaceKey = !isDeleteKey; if (start === end) { const caretPosition = start; const canDelete = isBackspaceKey && caretPosition > 0 || isDeleteKey && caretPosition < text.length; if (canDelete) { isDeleteKey && end++; isBackspaceKey && start-- } else { e.preventDefault(); return } } const char = text.slice(start, end); if (this._isStub(char)) { this._moveCaret(isDeleteKey ? 1 : -1); if (this._parsedValue < 0 || 1 / this._parsedValue === -1 / 0) { this._revertSign(e); this._setTextByParsedValue(); const shouldTriggerInputEvent = this.option("valueChangeEvent").split(" ").includes("input"); if (shouldTriggerInputEvent) { _events_engine.default.trigger(this._input(), "input") } } e.preventDefault(); return } const decimalSeparator = _number.default.getDecimalSeparator(); if (char === decimalSeparator) { const decimalSeparatorIndex = text.indexOf(decimalSeparator); if (this._isNonStubAfter(decimalSeparatorIndex + 1)) { this._moveCaret(isDeleteKey ? 1 : -1); e.preventDefault() } return } if (end - start < text.length) { const editedText = this._replaceSelectedText(text, { start: start, end: end }, ""); const noDigits = editedText.search(/[0-9]/) < 0; if (noDigits && this._isValueInRange(0)) { this._parsedValue = this._parsedValue < 0 || 1 / this._parsedValue === -1 / 0 ? -0 : 0; return } } const valueAfterRemoving = this._tryParse(text, { start: start, end: end }, ""); if (void 0 === valueAfterRemoving) { e.preventDefault() } else { this._parsedValue = valueAfterRemoving } }, _isPercentFormat: function() { const format = this._getFormatPattern(); const noEscapedFormat = format.replace(/'[^']+'/g, ""); return -1 !== noEscapedFormat.indexOf("%") }, _parse: function(text, format) { const formatOption = this.option("format"); const isCustomParser = (0, _type.isFunction)(formatOption.parser); const parser = isCustomParser ? formatOption.parser : _number.default.parse; let integerPartStartIndex = 0; if (!isCustomParser) { const formatPointIndex = (0, _utils.getRealSeparatorIndex)(format).index; const textPointIndex = this._getTextSeparatorIndex(text); const formatIntegerPartLength = -1 !== formatPointIndex ? formatPointIndex : format.length; const textIntegerPartLength = -1 !== textPointIndex ? textPointIndex : text.length; if (textIntegerPartLength > formatIntegerPartLength && -1 === format.indexOf("#")) { integerPartStartIndex = textIntegerPartLength - formatIntegerPartLength } } text = text.substr(integerPartStartIndex); return parser(text, format) }, _format: function(value, format) { const formatOption = this.option("format"); const customFormatter = (null === formatOption || void 0 === formatOption ? void 0 : formatOption.formatter) || formatOption; const formatter = (0, _type.isFunction)(customFormatter) ? customFormatter : _number.default.format; const formattedValue = null === value ? "" : formatter(value, format); return formattedValue }, _getFormatPattern: function() { if (!this._currentFormat) { this._updateFormat() } return this._currentFormat }, _updateFormat: function() { const format = this.option("format"); const isCustomParser = (0, _type.isFunction)(null === format || void 0 === format ? void 0 : format.parser); const isLDMLPattern = (0, _type.isString)(format) && (format.indexOf("0") >= 0 || format.indexOf("#") >= 0); const isExponentialFormat = "exponential" === format || "exponential" === (null === format || void 0 === format ? void 0 : format.type); const shouldUseFormatAsIs = isCustomParser || isLDMLPattern || isExponentialFormat; this._currentFormat = shouldUseFormatAsIs ? format : (0, _number2.getFormat)(value => { const text = this._format(value, format); return _number.default.convertDigits(text, true) }) }, _getFormatForSign: function(text) { const format = this._getFormatPattern(); if ((0, _type.isString)(format)) { const signParts = format.split(";"); const sign = _number.default.getSign(text, format); signParts[1] = signParts[1] || "-" + signParts[0]; return sign < 0 ? signParts[1] : signParts[0] } else { const sign = _number.default.getSign(text); return sign < 0 ? "-" : "" } }, _removeStubs: function(text, excludeComma) { const format = this._getFormatForSign(text); const thousandsSeparator = _number.default.getThousandsSeparator(); const stubs = this._getStubs(format); let result = text; if (stubs.length) { const prefixStubs = stubs[0]; const postfixRegex = new RegExp("(" + (0, _common.escapeRegExp)(stubs[1] || "") + ")$", "g"); const decoratorsRegex = new RegExp("[-" + (0, _common.escapeRegExp)(excludeComma ? "" : thousandsSeparator) + "]", "g"); result = result.replace(prefixStubs, "").replace(postfixRegex, "").replace(decoratorsRegex, "") } return result }, _getStubs: function(format) { const regExpResult = /[^']([#0.,]+)/g.exec(format); const pattern = regExpResult && regExpResult[0].trim(); return format.split(pattern).map((function(stub) { return stub.replace(/'/g, "") })) }, _truncateToPrecision: function(value, maxPrecision) { if ((0, _type.isDefined)(value)) { const strValue = value.toString(); const decimalSeparatorIndex = strValue.indexOf("."); if (strValue && decimalSeparatorIndex > -1) { const parsedValue = parseFloat(strValue.substr(0, decimalSeparatorIndex + maxPrecision + 1)); return isNaN(parsedValue) ? value : parsedValue } } return value }, _tryParse: function(text, selection, char) { const isTextSelected = selection.start !== selection.end; const isWholeTextSelected = isTextSelected && 0 === selection.start && selection.end === text.length; const decimalSeparator = _number.default.getDecimalSeparator(); if (isWholeTextSelected && char === decimalSeparator) { return 0 } const editedText = this._replaceSelectedText(text, selection, char); const format = this._getFormatPattern(); let parsedValue = this._getParsedValue(editedText, format); const maxPrecision = !format.parser && this._getPrecisionLimits(editedText).max; const isValueChanged = parsedValue !== this._parsedValue; const isDecimalPointRestricted = char === decimalSeparator && 0 === maxPrecision; const isUselessCharRestricted = !isTextSelected && !isValueChanged && "-" !== char && this._isStub(char); if (isDecimalPointRestricted || isUselessCharRestricted) { return } if ("" === this._removeStubs(editedText)) { parsedValue = Math.abs(0 * this._parsedValue) } if (isNaN(parsedValue)) { return } const value = null === parsedValue ? this._parsedValue : parsedValue; parsedValue = maxPrecision ? this._truncateToPrecision(value, maxPrecision) : parsedValue; return !format.parser && this._isPercentFormat() ? (0, _utils.adjustPercentValue)(parsedValue, maxPrecision) : parsedValue }, _getParsedValue: function(text, format) { const sign = _number.default.getSign(text, (null === format || void 0 === format ? void 0 : format.formatter) || format); const textWithoutStubs = this._removeStubs(text, true); const parsedValue = this._parse(textWithoutStubs, format); const parsedValueSign = parsedValue < 0 ? -1 : 1; const parsedValueWithSign = (0, _type.isNumeric)(parsedValue) && sign !== parsedValueSign ? sign * parsedValue : parsedValue; return parsedValueWithSign }, _isValueIncomplete: function(text) { if (!this._useMaskBehavior()) { return this.callBase(text) } const caret = this._caret(); const point = _number.default.getDecimalSeparator(); const pointIndex = this._getTextSeparatorIndex(text); const isCaretOnFloat = pointIndex >= 0 && pointIndex < caret.start; const textParts = this._removeStubs(text, true).split(point); if (!isCaretOnFloat || 2 !== textParts.length) { return false } const floatLength = textParts[1].length; const format = this._getFormatPattern(); const isCustomParser = !!format.parser; const precision = !isCustomParser && this._getPrecisionLimits(this._getFormatPattern(), text); const isPrecisionInRange = isCustomParser ? true : (0, _math.inRange)(floatLength, precision.min, precision.max); const endsWithZero = "0" === textParts[1].charAt(floatLength - 1); return isPrecisionInRange && (endsWithZero || !floatLength) }, _isValueInRange: function(value) { const min = (0, _common.ensureDefined)(this.option("min"), -1 / 0); const max = (0, _common.ensureDefined)(this.option("max"), 1 / 0); return (0, _math.inRange)(value, min, max) }, _setInputText: function(text) { const normalizedText = _number.default.convertDigits(text, true); const newCaret = (0, _number_box.getCaretAfterFormat)(this._getInputVal(), normalizedText, this._caret(), this._getFormatPattern()); this._input().val(text); this._toggleEmptinessEventHandler(); this._formattedValue = text; if (!this._focusOutOccurs) { this._caret(newCaret) } }, _useMaskBehavior: function() { return !!this.option("format") && this.option("useMaskBehavior") }, _renderInputType: function() { const isNumberType = "number" === this.option("mode"); const isDesktop = "desktop" === _devices.default.real().deviceType; if (this._useMaskBehavior() && isNumberType) { this._setInputType(isDesktop || this._isSupportInputMode() ? "text" : "tel") } else { this.callBase() } }, _isChar: function(str) { return (0, _type.isString)(str) && 1 === str.length }, _moveCaret: function(offset) { if (!offset) { return } const newCaret = (0, _number_box.getCaretWithOffset)(this._caret(), offset); const adjustedCaret = (0, _number_box.getCaretInBoundaries)(newCaret, this._getInputVal(), this._getFormatPattern()); this._caret(adjustedCaret) }, _shouldHandleKey: function(e) { const keyName = (0, _index.normalizeKeyName)(e); const isSpecialChar = (0, _index.isCommandKeyPressed)(e) || e.altKey || e.shiftKey || !this._isChar(keyName); const isMinusKey = "minus" === keyName; const useMaskBehavior = this._useMaskBehavior(); return useMaskBehavior && !isSpecialChar && !isMinusKey }, _renderInput: function() { this.callBase(); this._renderFormatter() }, _renderFormatter: function() { this._clearCache(); this._detachFormatterEvents(); if (this._useMaskBehavior()) { this._attachFormatterEvents() } }, _detachFormatterEvents: function() { _events_engine.default.off(this._input(), ".dxNumberFormatter") }, _isInputFromPaste: function(e) { const inputType = e.originalEvent && e.originalEvent.inputType; if ((0, _type.isDefined)(inputType)) { return "insertFromPaste" === inputType } else { return this._isValuePasted } }, _attachFormatterEvents: function() { const $input = this._input(); _events_engine.default.on($input, (0, _index.addNamespace)("input", "dxNumberFormatter"), function(e) { this._formatValue(e); this._isValuePasted = false }.bind(this)); _events_engine.default.on($input, (0, _index.addNamespace)("dxclick", "dxNumberFormatter"), function() { if (!this._caretTimeout) { this._caretTimeout = setTimeout(function() { this._caretTimeout = void 0; this._caret((0, _number_box.getCaretInBoundaries)(this._caret(), this._getInputVal(), this._getFormatPattern())) }.bind(this), 0) } }.bind(this)); _events_engine.default.on($input, _double_click.name, function() { this.clearCaretTimeout() }.bind(this)) }, clearCaretTimeout: function() { clearTimeout(this._caretTimeout); this._caretTimeout = void 0 }, _forceRefreshInputValue: function() { if (!this._useMaskBehavior()) { return this.callBase() } }, _isNonStubAfter: function(index) { const text = this._getInputVal().slice(index); return text && !this._isStub(text, true) }, _isStub: function(str, isString) { const escapedDecimalSeparator = (0, _common.escapeRegExp)(_number.default.getDecimalSeparator()); const regExpString = "^[^0-9" + escapedDecimalSeparator + "]+$"; const stubRegExp = new RegExp(regExpString, "g"); return stubRegExp.test(str) && (isString || this._isChar(str)) }, _parseValue: function(text) { if (!this._useMaskBehavior()) { return this.callBase(text) } return this._parsedValue }, _getPrecisionLimits: function(text) { const currentFormat = this._getFormatForSign(text); const realSeparatorIndex = (0, _utils.getRealSeparatorIndex)(currentFormat).index; const floatPart = ((0, _utils.splitByIndex)(currentFormat, realSeparatorIndex)[1] || "").replace(/[^#0]/g, ""); const minPrecision = floatPart.replace(/^(0*)#*/, "$1").length; const maxPrecision = floatPart.length; return { min: minPrecision, max: maxPrecision } }, _revertSign: function(e) { if (!this._useMaskBehavior()) { return } const caret = this._caret(); if (caret.start !== caret.end) { if ("minus" === (0, _index.normalizeKeyName)(e)) { this._applyRevertedSign(e, caret, true); return } else { this._caret((0, _number_box.getCaretInBoundaries)(0, this._getInputVal(), this._getFormatPattern())) } } this._applyRevertedSign(e, caret) }, _applyRevertedSign: function(e, caret, preserveSelectedText) { const newValue = -1 * (0, _common.ensureDefined)(this._parsedValue, null); if (this._isValueInRange(newValue) || 0 === newValue) { this._parsedValue = newValue; if (preserveSelectedText) { const format = this._getFormatPattern(); const previousText = this._getInputVal(); this._setTextByParsedValue(); e.preventDefault(); const currentText = this._getInputVal(); const offset = (0, _number_box.getCaretOffset)(previousText, currentText, format); caret = (0, _number_box.getCaretWithOffset)(caret, offset); const caretInBoundaries = (0, _number_box.getCaretInBoundaries)(caret, currentText, format); this._caret(caretInBoundaries) } } }, _removeMinusFromText: function(text, caret) { const isMinusPressed = "minus" === this._lastKeyName && "-" === text.charAt(caret.start - 1); return isMinusPressed ? this._replaceSelectedText(text, { start: caret.start - 1, end: caret.start }, "") : text }, _setTextByParsedValue: function() { const format = this._getFormatPattern(); const parsed = this._parseValue(); const formatted = this._format(parsed, format) || ""; this._setInputText(formatted) }, _formatValue: function(e) { let normalizedText = this._getInputVal(); const caret = this._caret(); const textWithoutMinus = this._removeMinusFromText(normalizedText, caret); const wasMinusRemoved = textWithoutMinus !== normalizedText; normalizedText = textWithoutMinus; if (!this._isInputFromPaste(e) && this._isValueIncomplete(textWithoutMinus)) { this._formattedValue = normalizedText; if (wasMinusRemoved) { this._setTextByParsedValue() } return } const textWasChanged = _number.default.convertDigits(this._formattedValue, true) !== normalizedText; if (textWasChanged) { const value = this._tryParse(normalizedText, caret, ""); if ((0, _type.isDefined)(value)) { this._parsedValue = value } } this._setTextByParsedValue() }, _renderDisplayText: function() { if (this._useMaskBehavior()) { this._toggleEmptinessEventHandler() } else { this.callBase.apply(this, arguments) } }, _renderValue: function() { if (this._useMaskBehavior()) { this._parsedValue = this.option("value"); this._setTextByParsedValue() } return this.callBase() }, _updateParsedValue: function() { const inputValue = this._getInputVal(); this._parsedValue = this._tryParse(inputValue, this._caret()) }, _adjustParsedValue: function() { if (!this._useMaskBehavior()) { return } const clearedText = this._removeStubs(this._getInputVal()); const parsedValue = clearedText ? this._parseValue() : null; if (!(0, _type.isNumeric)(parsedValue)) { this._parsedValue = parsedValue; return } this._parsedValue = (0, _math.fitIntoRange)(parsedValue, this.option("min"), this.option("max")) }, _valueChangeEventHandler: function(e) { if (!this._useMaskBehavior()) { return this.callBase(e) } const caret = this._caret(); this._saveValueChangeEvent(e); this._lastKey = null; this._lastKeyName = null; this._updateParsedValue(); this._adjustParsedValue(); this.option("value", this._parsedValue); if (caret) { this._caret(caret) } }, _optionChanged: function(args) { switch (args.name) { case "format": case "useMaskBehavior": this._renderInputType(); this._updateFormat(); this._renderFormatter(); this._renderValue(); this._refreshValueChangeEvent(); this._refreshEvents(); break; case "min": case "max": this._adjustParsedValue(); this.callBase(args); break; default: this.callBase(args) } }, _clearCache: function() { delete this._formattedValue; delete this._lastKey; delete this._lastKeyName; delete this._parsedValue; delete this._focusOutOccurs; clearTimeout(this._caretTimeout); delete this._caretTimeout }, _clean: function() { this._clearCache(); this.callBase() } }); var _default = NumberBoxMask; exports.default = _default; module.exports = exports.default; module.exports.default = exports.default;