devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
281 lines (280 loc) • 11 kB
JavaScript
/**
* DevExtreme (esm/__internal/ui/text_box/text_editor.mask.strategy.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/
*/
import EventsEngine from "../../../common/core/events/core/events_engine";
import {
addNamespace
} from "../../../common/core/events/utils/index";
import browser from "../../../core/utils/browser";
import {
clipboardText as getClipboardText
} from "../../../core/utils/dom";
const MASK_EVENT_NAMESPACE = "dxMask";
const BLUR_EVENT = "blur beforedeactivate";
const EMPTY_CHAR = " ";
const DELETE_INPUT_TYPES = ["deleteContentBackward", "deleteSoftLineBackward", "deleteContent", "deleteHardLineBackward"];
const HISTORY_INPUT_TYPES = ["historyUndo", "historyRedo"];
const EVENT_NAMES = ["focusIn", "focusOut", "input", "paste", "cut", "drop", "beforeInput"];
const getEmptyString = length => " ".repeat(length);
export default class MaskStrategy {
constructor(editor) {
this.editor = editor
}
_editorOption(name, value) {
return this.editor.option(...arguments)
}
_editorInput() {
return this.editor._input()
}
_editorCaret(newCaret) {
if (!newCaret) {
return this.editor._caret()
}
this.editor._caret(newCaret)
}
_attachChangeEventHandler() {
const valueChangeEvent = this._editorOption("valueChangeEvent");
if (!(null !== valueChangeEvent && void 0 !== valueChangeEvent && valueChangeEvent.split(" ").includes("change"))) {
return
}
const $input = this._editorInput();
const namespace = addNamespace(BLUR_EVENT, "dxMask");
EventsEngine.on($input, namespace, e => {
this.editor._changeHandler(e)
})
}
_beforeInputHandler() {
this._previousText = this._editorOption("text");
this._prevCaret = this._editorCaret()
}
_inputHandler(event) {
const {
originalEvent: originalEvent
} = event;
if (!originalEvent) {
return
}
const {
inputType: inputType
} = originalEvent;
if (HISTORY_INPUT_TYPES.includes(inputType)) {
this._handleHistoryInputEvent()
} else if (DELETE_INPUT_TYPES.includes(inputType)) {
this._handleBackwardDeleteInputEvent()
} else {
const currentCaret = this._editorCaret();
if (!(null !== currentCaret && void 0 !== currentCaret && currentCaret.end)) {
return
}
this._clearSelectedText();
this._autoFillHandler(originalEvent);
this._editorCaret(currentCaret);
this._handleInsertTextInputEvent(originalEvent.data)
}
if (this._editorOption("text") === this._previousText) {
event.stopImmediatePropagation()
}
}
_handleHistoryInputEvent() {
const caret = this._editorCaret();
this._updateEditorMask({
start: null === caret || void 0 === caret ? void 0 : caret.start,
length: ((null === caret || void 0 === caret ? void 0 : caret.end) ?? 0) - ((null === caret || void 0 === caret ? void 0 : caret.start) ?? 0),
text: ""
});
this._editorCaret(this._prevCaret)
}
_handleBackwardDeleteInputEvent() {
this._clearSelectedText(true);
const caret = this._editorCaret();
this.editor.setForwardDirection();
this.editor._adjustCaret();
const adjustedForwardCaret = this._editorCaret();
if ((null === adjustedForwardCaret || void 0 === adjustedForwardCaret ? void 0 : adjustedForwardCaret.start) !== (null === caret || void 0 === caret ? void 0 : caret.start)) {
this.editor.setBackwardDirection();
this.editor._adjustCaret()
}
}
_clearSelectedText(isDeleteInputEvent) {
const selectionLength = this._prevCaret && (this._prevCaret.end ?? 0) - (this._prevCaret.start ?? 0);
const length = selectionLength || Number(Boolean(isDeleteInputEvent));
const caret = this._editorCaret();
if (!this._isAutoFill()) {
this.editor.setBackwardDirection();
this._updateEditorMask({
start: null === caret || void 0 === caret ? void 0 : caret.start,
length: length,
text: getEmptyString(length)
})
}
}
_handleInsertTextInputEvent(data) {
var _this$_prevCaret;
const text = data ?? "";
this.editor.setForwardDirection();
const hasValidChars = this._updateEditorMask({
start: (null === (_this$_prevCaret = this._prevCaret) || void 0 === _this$_prevCaret ? void 0 : _this$_prevCaret.start) ?? 0,
length: text.length || 1,
text: text
});
if (!hasValidChars) {
this._editorCaret(this._prevCaret)
}
}
_updateEditorMask(args) {
var _args$text;
const textLength = (null === (_args$text = args.text) || void 0 === _args$text ? void 0 : _args$text.length) ?? 0;
const processedCharsCount = this.editor._handleChain(args) ?? 0;
this.editor._displayMask();
if (this.editor.isForwardDirection()) {
const {
start: start = 0,
end: end = 0
} = this._editorCaret() ?? {};
const correction = processedCharsCount - textLength;
const hasSkippedStub = processedCharsCount > 1;
if (hasSkippedStub && 1 === textLength) {
this._editorCaret({
start: start + correction,
end: end + correction
})
}
this.editor._adjustCaret()
}
return Boolean(processedCharsCount)
}
_focusInHandler() {
this.editor._showMaskPlaceholder();
this.editor.setForwardDirection();
if (!this.editor._isValueEmpty() && this._editorOption("isValid")) {
this.editor._adjustCaret()
} else {
if (!this.editor._maskRulesChain) {
return
}
const caret = this.editor._maskRulesChain.first();
this._caretTimeout = setTimeout(() => {
this._editorCaret({
start: caret,
end: caret
})
}, 0)
}
}
_focusOutHandler(event) {
this.editor._changeHandler(event);
if ("onFocus" === this._editorOption("showMaskMode") && this.editor._isValueEmpty()) {
this._editorOption("text", "");
this.editor._renderDisplayText("")
}
}
_delHandler(event) {
this.editor._maskKeyHandler(event, () => {
if (!this.editor._hasSelection()) {
this.editor._handleKey(" ")
}
return
})
}
_cutHandler(event) {
const caret = this._editorCaret();
const inputVal = this._editorInput().val();
const selectedText = inputVal.substring(null === caret || void 0 === caret ? void 0 : caret.start, null === caret || void 0 === caret ? void 0 : caret.end);
this.editor._maskKeyHandler(event, () => getClipboardText(event, selectedText))
}
_dropHandler() {
this._clearDragTimer();
this._dragTimer = setTimeout(() => {
const value = this.editor._convertToValue(this._editorInput().val());
this._editorOption("value", value)
})
}
_pasteHandler(event) {
if (this._editorOption("disabled")) {
return
}
const caret = this._editorCaret();
this.editor._maskKeyHandler(event, () => {
var _this$editor$_maskRul;
const pastedText = getClipboardText(event);
if (!pastedText) {
return
}
const restText = null === (_this$editor$_maskRul = this.editor._maskRulesChain) || void 0 === _this$editor$_maskRul ? void 0 : _this$editor$_maskRul.text().substring((null === caret || void 0 === caret ? void 0 : caret.end) ?? 0);
const accepted = this.editor._handleChain({
text: pastedText,
start: null === caret || void 0 === caret ? void 0 : caret.start,
length: pastedText.length
});
const newCaret = ((null === caret || void 0 === caret ? void 0 : caret.start) ?? 0) + accepted;
this.editor._handleChain({
text: restText,
start: newCaret,
length: null === restText || void 0 === restText ? void 0 : restText.length
});
this.editor._caret({
start: newCaret,
end: newCaret
});
return
})
}
_autoFillHandler(event) {
const inputVal = this._editorInput().val();
this._inputHandlerTimer = setTimeout(() => {
if (this._isAutoFill()) {
this.editor._maskKeyHandler(event, () => {
this.editor._handleChain({
text: inputVal,
start: 0,
length: inputVal.length
});
return
});
this.editor._validateMask()
}
})
}
_isAutoFill() {
const $input = this._editorInput();
if (browser.webkit) {
const input = $input.get(0);
return (null === input || void 0 === input ? void 0 : input.matches(":-webkit-autofill")) ?? false
}
return false
}
_clearDragTimer() {
clearTimeout(this._dragTimer)
}
_clearTimers() {
this._clearDragTimer();
clearTimeout(this._caretTimeout);
clearTimeout(this._inputHandlerTimer)
}
getHandler(handlerName) {
return args => {
var _this;
null === (_this = this[`_${handlerName}Handler`]) || void 0 === _this || _this.call(this, args)
}
}
attachEvents() {
const $input = this._editorInput();
EVENT_NAMES.forEach(eventName => {
const namespace = addNamespace(eventName.toLowerCase(), "dxMask");
EventsEngine.on($input, namespace, this.getHandler(eventName))
});
this._attachChangeEventHandler()
}
detachEvents() {
this._clearTimers();
EventsEngine.off(this._editorInput(), ".dxMask")
}
clean() {
this._clearTimers()
}
}