devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
598 lines (596 loc) • 21.8 kB
JavaScript
/**
* DevExtreme (cjs/__internal/ui/text_box/text_editor.mask.js)
* Version: 25.2.7
* Build date: Tue May 05 2026
*
* Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _events_engine = _interopRequireDefault(require("../../../common/core/events/core/events_engine"));
var _wheel = require("../../../common/core/events/core/wheel");
var _index = require("../../../common/core/events/utils/index");
var _message = _interopRequireDefault(require("../../../common/core/localization/message"));
var _renderer = _interopRequireDefault(require("../../../core/renderer"));
var _extend = require("../../../core/utils/extend");
var _string = require("../../../core/utils/string");
var _type = require("../../../core/utils/type");
var _m_selectors = require("../../core/utils/m_selectors");
var _m_text_editor = _interopRequireDefault(require("../../ui/text_box/m_text_editor.base"));
var _text_editorMask = require("../../ui/text_box/text_editor.mask.rule");
var _text_editorMask2 = _interopRequireDefault(require("../../ui/text_box/text_editor.mask.strategy"));
var _utils = _interopRequireDefault(require("../../ui/text_box/utils.caret"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : {
default: e
}
}
const EMPTY_CHAR = " ";
const ESCAPED_CHAR = "\\";
const TEXTEDITOR_MASKED_CLASS = "dx-texteditor-masked";
const FORWARD_DIRECTION = "forward";
const BACKWARD_DIRECTION = "backward";
const DROP_EVENT_NAME = "drop";
const isNumericChar = char => /[0-9]/.test(char);
const isLiteralChar = char => {
const code = char.charCodeAt(0);
return code > 64 && code < 91 || code > 96 && code < 123 || code > 127
};
const isSpaceChar = char => " " === char;
const buildInMaskRules = {
0: /[0-9]/,
9: /[0-9\s]/,
"#": /[-+0-9\s]/,
L: char => isLiteralChar(char),
l: char => isLiteralChar(char) || isSpaceChar(char),
C: /\S/,
c: /./,
A: char => isLiteralChar(char) || isNumericChar(char),
a: char => isLiteralChar(char) || isNumericChar(char) || isSpaceChar(char)
};
class TextEditorMask extends _m_text_editor.default {
_getDefaultOptions() {
return Object.assign({}, super._getDefaultOptions(), {
mask: "",
maskChar: "_",
maskRules: {},
maskInvalidMessage: _message.default.format("validation-mask"),
useMaskedValue: false,
showMaskMode: "always"
})
}
_supportedKeys() {
const result = super._supportedKeys();
const keyHandlerMap = {
del: this._maskStrategy.getHandler("del"),
enter: this._changeHandler
};
Object.entries(keyHandlerMap).forEach(_ref => {
let [key, handler] = _ref;
const parentHandler = result[key];
result[key] = e => {
const {
mask: mask
} = this.option();
if (mask && handler) {
handler.call(this, e)
}
null === parentHandler || void 0 === parentHandler || parentHandler(e)
}
});
return result
}
_getSubmitElement() {
const {
mask: mask
} = this.option();
const submitElement = !mask ? super._getSubmitElement() : this._$hiddenElement;
return submitElement
}
_init() {
super._init();
this._initMaskStrategy()
}
_initMaskStrategy() {
this._maskStrategy = new _text_editorMask2.default(this)
}
_initMarkup() {
this._renderHiddenElement();
super._initMarkup()
}
_attachMouseWheelEventHandlers() {
if (!this._hasMouseWheelHandler()) {
return
}
const input = this._input();
const eventName = (0, _index.addNamespace)(_wheel.name, this.NAME);
const mouseWheelAction = this._createAction(e => {
const {
event: event
} = e;
if ((0, _m_selectors.focused)(input) && !(0, _index.isCommandKeyPressed)(event)) {
this._onMouseWheel(event);
event.preventDefault();
event.stopPropagation()
}
});
_events_engine.default.off(input, eventName);
_events_engine.default.on(input, eventName, e => {
mouseWheelAction({
event: e
})
})
}
_hasMouseWheelHandler() {
return false
}
_onMouseWheel(e) {}
_useMaskBehavior() {
const {
mask: mask
} = this.option();
return Boolean(mask)
}
_attachDropEventHandler() {
const useMaskBehavior = this._useMaskBehavior();
if (!useMaskBehavior) {
return
}
const eventName = (0, _index.addNamespace)("drop", this.NAME);
const input = this._input();
_events_engine.default.off(input, eventName);
_events_engine.default.on(input, eventName, e => {
e.preventDefault()
})
}
_render() {
this._attachMouseWheelEventHandlers();
this._renderMask();
super._render();
this._attachDropEventHandler()
}
_renderHiddenElement() {
const {
mask: mask
} = this.option();
if (mask) {
this._$hiddenElement = (0, _renderer.default)("<input>").attr("type", "hidden").appendTo(this._inputWrapper())
}
}
_removeHiddenElement() {
var _this$_$hiddenElement;
null === (_this$_$hiddenElement = this._$hiddenElement) || void 0 === _this$_$hiddenElement || _this$_$hiddenElement.remove()
}
_renderMask() {
this.$element().removeClass("dx-texteditor-masked");
this._maskRulesChain = null;
this._maskStrategy.detachEvents();
const {
mask: mask
} = this.option();
if (!mask) {
return
}
this.$element().addClass("dx-texteditor-masked");
this._maskStrategy.attachEvents();
this._parseMask();
this._renderMaskedValue()
}
_changeHandler(e) {
const $input = this._input();
const inputValue = $input.val();
if (inputValue === this._changedValue) {
return
}
this._changedValue = inputValue;
const changeEvent = (0, _index.createEvent)(e, {
type: "change"
});
_events_engine.default.trigger($input, changeEvent)
}
_parseMask() {
const {
maskRules: maskRules
} = this.option();
this._maskRules = (0, _extend.extend)({}, buildInMaskRules, maskRules);
this._maskRulesChain = this._parseMaskRule(0)
}
_parseMaskRule(index) {
const {
mask: mask
} = this.option();
if (!(0, _type.isDefined)(mask) || index >= mask.length) {
return new _text_editorMask.EmptyMaskRule({})
}
const currentMaskChar = mask[index];
const isEscapedChar = "\\" === currentMaskChar;
const result = isEscapedChar ? new _text_editorMask.StubMaskRule({
maskChar: mask[index + 1]
}) : this._getMaskRule(currentMaskChar);
const nextIndex = index + 1 + Number(isEscapedChar);
const recursiveResult = this._parseMaskRule(nextIndex);
result.next(recursiveResult);
return result
}
_getMaskRule(pattern) {
if (!this._maskRules) {
return new _text_editorMask.StubMaskRule({
maskChar: pattern
})
}
const matchingEntry = Object.entries(this._maskRules).find(_ref2 => {
let [rulePattern] = _ref2;
return rulePattern === pattern
});
if (matchingEntry) {
const [, allowedChars] = matchingEntry;
const ruleConfig = {
pattern: pattern,
allowedChars: allowedChars
};
const {
maskChar: maskChar
} = this.option();
return new _text_editorMask.MaskRule((0, _extend.extend)({
maskChar: maskChar || " "
}, ruleConfig))
}
return new _text_editorMask.StubMaskRule({
maskChar: pattern
})
}
_renderMaskedValue() {
if (!this._maskRulesChain) {
return
}
const {
value: optionValue
} = this.option();
const value = optionValue || "";
this._maskRulesChain.clear(this._normalizeChainArguments());
const chainArgs = {
length: null === value || void 0 === value ? void 0 : value.length
};
const prop = this._isMaskedValueMode() ? "text" : "value";
chainArgs[prop] = value;
this._handleChain(chainArgs);
this._displayMask()
}
_replaceSelectedText(text, selection, char) {
if (void 0 === char) {
return text
}
const textBefore = text.slice(0, selection.start);
const textAfter = text.slice(selection.end);
const edited = `${textBefore}${char}${textAfter}`;
return edited
}
_isMaskedValueMode() {
const {
useMaskedValue: useMaskedValue
} = this.option();
return Boolean(useMaskedValue)
}
_displayMask(caret) {
const currentCaret = caret ?? this._caret();
const finalCaret = {
start: (null === currentCaret || void 0 === currentCaret ? void 0 : currentCaret.start) ?? 0,
end: (null === currentCaret || void 0 === currentCaret ? void 0 : currentCaret.end) ?? 0
};
this._renderValue();
this._caret(finalCaret)
}
_isValueEmpty() {
return (0, _string.isEmpty)(this._value)
}
_shouldShowMask() {
const {
showMaskMode: showMaskMode
} = this.option();
if ("onFocus" === showMaskMode) {
return (0, _m_selectors.focused)(this._input()) || !this._isValueEmpty()
}
return true
}
_showMaskPlaceholder() {
if (this._shouldShowMask()) {
var _this$_maskRulesChain;
const text = null === (_this$_maskRulesChain = this._maskRulesChain) || void 0 === _this$_maskRulesChain ? void 0 : _this$_maskRulesChain.text();
this.option({
text: text
});
const {
showMaskMode: showMaskMode
} = this.option();
if ("onFocus" === showMaskMode) {
this._renderDisplayText(text)
}
}
}
_renderValue() {
if (this._maskRulesChain) {
this._showMaskPlaceholder();
if (this._$hiddenElement) {
const value = this._maskRulesChain.value();
const submitElementValue = !(0, _string.isEmpty)(value) ? this._getPreparedValue() : "";
this._$hiddenElement.val(submitElementValue)
}
}
return super._renderValue()
}
_getPreparedValue() {
return this._convertToValue().replace(/\s+$/, "")
}
_valueChangeEventHandler() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key]
}
if (!this._maskRulesChain) {
super._valueChangeEventHandler(...args);
return
}
const [e] = args;
this._saveValueChangeEvent(e);
const preparedValue = this._getPreparedValue();
this.option({
value: preparedValue
})
}
_isControlKeyFired(e) {
const normalizedKeyName = (0, _index.normalizeKeyName)(e);
const isControlKey = (0, _type.isDefined)(normalizedKeyName) ? this._isControlKey(normalizedKeyName) : false;
return isControlKey || (0, _index.isCommandKeyPressed)(e)
}
_handleChain(args) {
var _this$_maskRulesChain2;
const handledCount = (null === (_this$_maskRulesChain2 = this._maskRulesChain) || void 0 === _this$_maskRulesChain2 ? void 0 : _this$_maskRulesChain2.handle(this._normalizeChainArguments(args))) ?? 0;
this._updateMaskInfo();
return handledCount
}
_normalizeChainArguments(args) {
var _this$_maskRulesChain3;
return Object.assign({}, args, {
index: 0,
fullText: null === (_this$_maskRulesChain3 = this._maskRulesChain) || void 0 === _this$_maskRulesChain3 ? void 0 : _this$_maskRulesChain3.text()
})
}
_convertToValue(text) {
if (this._isMaskedValueMode()) {
return this._replaceMaskCharWithEmpty(text || this._textValue || "")
}
return text || this._value || ""
}
_replaceMaskCharWithEmpty(text) {
const {
maskChar: maskChar
} = this.option();
return text.replace(new RegExp(maskChar, "g"), " ")
}
_maskKeyHandler(e, keyHandler) {
const {
readOnly: readOnly
} = this.option();
if (readOnly) {
return
}
this.setForwardDirection();
e.preventDefault();
this._handleSelection();
const previousText = this._input().val();
const raiseInputEvent = () => {
if (previousText !== this._input().val()) {
_events_engine.default.trigger(this._input(), "input")
}
};
const handled = keyHandler();
if (handled) {
handled.then(raiseInputEvent)
} else {
var _this$_maskRulesChain4;
this.setForwardDirection();
this._adjustCaret();
this._displayMask();
null === (_this$_maskRulesChain4 = this._maskRulesChain) || void 0 === _this$_maskRulesChain4 || _this$_maskRulesChain4.reset();
raiseInputEvent()
}
}
_handleKey(key, direction) {
this._direction(direction || "forward");
this._adjustCaret(key);
this._handleKeyChain(key);
this._moveCaret()
}
_handleSelection() {
if (!this._hasSelection()) {
return
}
const caret = this._caret();
const caretStart = (null === caret || void 0 === caret ? void 0 : caret.start) ?? 0;
const caretEnd = (null === caret || void 0 === caret ? void 0 : caret.end) ?? 0;
const emptyChars = new Array(caretEnd - caretStart + 1).join(" ");
this._handleKeyChain(emptyChars)
}
_handleKeyChain(chars) {
const caret = this._caret();
const caretStart = (null === caret || void 0 === caret ? void 0 : caret.start) ?? 0;
const caretEnd = (null === caret || void 0 === caret ? void 0 : caret.end) ?? 0;
const start = this.isForwardDirection() ? caretStart : caretStart - 1;
const end = this.isForwardDirection() ? caretEnd : caretEnd - 1;
const length = start === end ? 1 : end - start;
this._handleChain({
text: chars,
start: start,
length: length
})
}
_tryMoveCaretBackward() {
var _this$_caret, _this$_caret2;
this.setBackwardDirection();
const currentCaret = null === (_this$_caret = this._caret()) || void 0 === _this$_caret ? void 0 : _this$_caret.start;
this._adjustCaret();
return !currentCaret || currentCaret !== (null === (_this$_caret2 = this._caret()) || void 0 === _this$_caret2 ? void 0 : _this$_caret2.start)
}
_adjustCaret(char) {
var _this$_caret3, _this$_maskRulesChain5;
const caretStart = (null === (_this$_caret3 = this._caret()) || void 0 === _this$_caret3 ? void 0 : _this$_caret3.start) ?? 0;
const isForwardDirection = this.isForwardDirection();
const caret = null === (_this$_maskRulesChain5 = this._maskRulesChain) || void 0 === _this$_maskRulesChain5 ? void 0 : _this$_maskRulesChain5.adjustedCaret(caretStart, isForwardDirection, char ?? "");
this._caret({
start: caret,
end: caret
})
}
_moveCaret() {
var _this$_caret4, _this$_maskRulesChain6;
const currentCaret = (null === (_this$_caret4 = this._caret()) || void 0 === _this$_caret4 ? void 0 : _this$_caret4.start) ?? 0;
const maskRuleIndex = currentCaret + (this.isForwardDirection() ? 0 : -1);
const caret = null !== (_this$_maskRulesChain6 = this._maskRulesChain) && void 0 !== _this$_maskRulesChain6 && _this$_maskRulesChain6.isAccepted(maskRuleIndex) ? currentCaret + (this.isForwardDirection() ? 1 : -1) : currentCaret;
this._caret({
start: caret,
end: caret
})
}
_caret(position, force) {
const $input = this._input();
if (!$input.length) {
return
}
if (arguments.length > 0) {
(0, _utils.default)($input, position, force);
return
}
return (0, _utils.default)($input)
}
_hasSelection() {
const caret = this._caret();
return (null === caret || void 0 === caret ? void 0 : caret.start) !== (null === caret || void 0 === caret ? void 0 : caret.end)
}
_direction(direction) {
if (!arguments.length) {
return this._typingDirection
}
this._typingDirection = direction
}
setForwardDirection() {
this._direction("forward")
}
setBackwardDirection() {
this._direction("backward")
}
isForwardDirection() {
return "forward" === this._direction()
}
_updateMaskInfo() {
var _this$_maskRulesChain7, _this$_maskRulesChain8;
this._textValue = null === (_this$_maskRulesChain7 = this._maskRulesChain) || void 0 === _this$_maskRulesChain7 ? void 0 : _this$_maskRulesChain7.text();
this._value = null === (_this$_maskRulesChain8 = this._maskRulesChain) || void 0 === _this$_maskRulesChain8 ? void 0 : _this$_maskRulesChain8.value()
}
_clean() {
var _this$_maskStrategy;
null === (_this$_maskStrategy = this._maskStrategy) || void 0 === _this$_maskStrategy || _this$_maskStrategy.clean();
super._clean()
}
_validateMask() {
if (!this._maskRulesChain) {
return
}
const {
maskInvalidMessage: maskInvalidMessage,
value: value
} = this.option();
const defaultValidationError = {
editorSpecific: true,
message: maskInvalidMessage
};
const isValid = (0, _string.isEmpty)(value) || this._maskRulesChain.isValid(this._normalizeChainArguments());
const validationError = isValid ? null : defaultValidationError;
this.option({
isValid: isValid,
validationError: validationError
})
}
_updateHiddenElement() {
this._removeHiddenElement();
const {
mask: mask
} = this.option();
if (mask) {
this._input().removeAttr("name");
this._renderHiddenElement()
}
const {
name: name
} = this.option();
this._setSubmitElementName(name)
}
_updateMaskOption() {
this._updateHiddenElement();
this._renderMask();
this._validateMask();
this._refreshValueChangeEvent()
}
_processEmptyMask(mask) {
if (mask) {
return
}
const {
value: value
} = this.option();
this.option({
text: value,
isValid: true,
validationError: null
});
this.validationRequest.fire({
value: value,
editor: this
});
this._renderValue()
}
_optionChanged(args) {
switch (args.name) {
case "mask":
this._updateMaskOption();
this._processEmptyMask(args.value);
break;
case "maskChar":
case "maskRules":
case "useMaskedValue":
this._updateMaskOption();
break;
case "value":
this._renderMaskedValue();
this._validateMask();
super._optionChanged(args);
this._changedValue = this._input().val();
break;
case "maskInvalidMessage":
break;
case "showMaskMode":
this.option({
text: ""
});
this._renderValue();
break;
default:
super._optionChanged(args)
}
}
clear() {
const {
value: defaultValue
} = this._getDefaultOptions();
const {
value: value
} = this.option();
if (value === defaultValue) {
this._renderMaskedValue()
}
super.clear()
}
}
var _default = exports.default = TextEditorMask;