diff --git a/packages/devextreme/js/__internal/ui/date_box/date_box.mask.ts b/packages/devextreme/js/__internal/ui/date_box/date_box.mask.ts index b580e412fdef..793ff01cd2d4 100644 --- a/packages/devextreme/js/__internal/ui/date_box/date_box.mask.ts +++ b/packages/devextreme/js/__internal/ui/date_box/date_box.mask.ts @@ -291,7 +291,10 @@ class DateBoxMask extends DateBoxBase { const caret = this._caret(); const { text = '' } = this.option(); - return caret.end - caret.start === text.length; + const caretStart = caret?.start ?? 0; + const caretEnd = caret?.end ?? 0; + + return caretEnd - caretStart === text.length; } _getFormatPattern(): string { @@ -661,7 +664,10 @@ class DateBoxMask extends DateBoxBase { this._loadMaskValue(this._maskValue); const { text } = this.option(); if (text) { - this._activePartIndex = getDatePartIndexByPosition(this._dateParts, this._caret().start); + this._activePartIndex = getDatePartIndexByPosition( + this._dateParts, + this._caret()?.start ?? 0, + ); if (!this._isAllSelected()) { this._clearSearchValue(); @@ -686,6 +692,7 @@ class DateBoxMask extends DateBoxBase { _maskPasteHandler(e: DxEvent): void { const { text } = this.option(); + // @ts-expect-error text const newText = this._replaceSelectedText(text, this._caret(), clipboardText(e)); const date = dateLocalization.parse(newText, this._getFormatPattern()); diff --git a/packages/devextreme/js/__internal/ui/number_box/m_number_box.mask.ts b/packages/devextreme/js/__internal/ui/number_box/m_number_box.mask.ts index 01259a2ba4bf..fbb61ae97899 100644 --- a/packages/devextreme/js/__internal/ui/number_box/m_number_box.mask.ts +++ b/packages/devextreme/js/__internal/ui/number_box/m_number_box.mask.ts @@ -105,7 +105,7 @@ class NumberBoxMask extends NumberBoxBase { this._caretTimeout = undefined; const caret = this._caret(); - if (caret.start === caret.end && this._useMaskBehavior()) { + if (caret?.start === caret?.end && this._useMaskBehavior()) { const text = this._getInputVal(); const decimalSeparatorIndex = this._getTextSeparatorIndex(text); @@ -254,8 +254,8 @@ class NumberBoxMask extends NumberBoxBase { _removeHandler(e) { const caret = this._caret(); const text = this._getInputVal(); - let { start } = caret; - let { end } = caret; + + let { start = 0, end = 0 } = caret ?? {}; this._lastKey = getChar(e); this._lastKeyName = normalizeKeyName(e); @@ -265,7 +265,11 @@ class NumberBoxMask extends NumberBoxBase { if (start === end) { const caretPosition = start; - const canDelete = isBackspaceKey && caretPosition > 0 || isDeleteKey && caretPosition < text.length; + + const canDelete = isBackspaceKey + && caretPosition > 0 + || isDeleteKey + && caretPosition < text.length; if (canDelete) { isDeleteKey && end++; @@ -520,7 +524,7 @@ class NumberBoxMask extends NumberBoxBase { const caret = this._caret(); const point = number.getDecimalSeparator(); const pointIndex = this._getTextSeparatorIndex(text); - const isCaretOnFloat = pointIndex >= 0 && pointIndex < caret.start; + const isCaretOnFloat = pointIndex >= 0 && pointIndex < (caret?.start ?? 0); const textParts = this._removeStubs(text, true).split(point); if (!isCaretOnFloat || textParts.length !== 2) { @@ -702,7 +706,8 @@ class NumberBoxMask extends NumberBoxBase { } const caret = this._caret(); - if (caret.start !== caret.end) { + + if (caret?.start !== caret?.end) { if (normalizeKeyName(e) === MINUS_KEY) { this._applyRevertedSign(e, caret, true); return; diff --git a/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.rule.ts b/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.rule.ts index 1cdb7981822b..76b1b2b2f97a 100644 --- a/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.rule.ts +++ b/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.rule.ts @@ -1,7 +1,34 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable class-methods-use-this */ /* eslint-disable max-classes-per-file */ import { extend } from '@js/core/utils/extend'; import { isFunction } from '@js/core/utils/type'; +type AllowedCharsFunction = ( + char: string, index?: number, fullText?: string, +) => boolean; + +type AllowedChars = RegExp + | AllowedCharsFunction + | string[] + | string; + +export interface MaskRuleConfig { + maskChar?: string; + pattern?: string; + allowedChars?: AllowedChars; +} + +export interface HandlingArgs { + value?: string; + text?: string; + start?: number; + str?: string; + length?: number; + index?: number; + fullText?: string; +} + const EMPTY_CHAR = ' '; class BaseMaskRule { @@ -9,119 +36,159 @@ class BaseMaskRule { maskChar?: string; - _next?: any; + _next?: BaseMaskRule; - constructor(config) { + constructor(config: MaskRuleConfig) { this._value = EMPTY_CHAR; extend(this, config); } - next(rule?: any) { + next(): BaseMaskRule; + next(rule: BaseMaskRule): undefined; + next(rule?: BaseMaskRule): BaseMaskRule | undefined { if (!arguments.length) { return this._next; } this._next = rule; + + return undefined; } - _prepareHandlingArgs(args, config?: any) { - config = config || {}; + _prepareHandlingArgs( + args: HandlingArgs, + config?: HandlingArgs, + ): HandlingArgs { + const configuration = config ?? {}; const handlingProperty = Object.prototype.hasOwnProperty.call(args, 'value') ? 'value' : 'text'; - args[handlingProperty] = config.str ?? args[handlingProperty]; - args.start = config.start ?? args.start; - args.length = config.length ?? args.length; - args.index += 1; - return args; + + const finalConfig = { + ...args, + start: configuration.start ?? args.start, + length: configuration.length ?? args.length, + index: (args.index ?? 0) + 1, + }; + + finalConfig[handlingProperty] = configuration.str ?? args[handlingProperty]; + + return finalConfig; } - first(index) { - index = index || 0; - return this.next().first(index + 1); + first(index = 0): number { + const newIndex = index + 1; + + return this.next().first(newIndex); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - isAccepted(caret?: any) { + isAccepted(caret?: number): boolean { return false; } - adjustedCaret(caret, isForwardDirection, char) { + adjustedCaret( + caret: number, + isForwardDirection: boolean, + char: string, + ): number { return isForwardDirection ? this._adjustedForward(caret, 0, char) : this._adjustedBackward(caret, 0, char); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _adjustedForward(caret, index, char) {} + _adjustedForward( + caret: number, + index: number, + char?: string, + // @ts-expect-error return type + ): number {} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _adjustedBackward(caret, index, char?: string) {} + _adjustedBackward( + caret: number, + index: number, + char?: string, + // @ts-expect-error return type + ): number {} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - isValid(args: any) {} + // @ts-expect-error return type + isValid(args: HandlingArgs): boolean {} - reset() {} + reset(): void {} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - clear(args?: any) {} + clear(args?: HandlingArgs): void {} - text() {} + // @ts-expect-error return type + text(): string {} - value() {} + // @ts-expect-error return type + value(): string {} - rawValue() {} + // @ts-expect-error return type + rawValue(): string {} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - handle(args) {} + // @ts-expect-error return type + handle(args: HandlingArgs): number {} } export class EmptyMaskRule extends BaseMaskRule { + // @ts-expect-error Liskov + // eslint-disable-next-line @stylistic/max-len + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types next() {} - handle() { + handle(): number { return 0; } - text() { + text(): string { return ''; } - value() { + value(): string { return ''; } - first() { + first(): number { return 0; } - rawValue() { + rawValue(): string { return ''; } - adjustedCaret() { + adjustedCaret(): number { return 0; } - isValid() { + isValid(): boolean { return true; } } export class MaskRule extends BaseMaskRule { + allowedChars?: AllowedChars; + _isAccepted?: boolean; - text() { - return (this._value !== EMPTY_CHAR ? this._value : this.maskChar) + this.next().text(); + text(): string { + const isValueEqualEmptyChar = this._value === EMPTY_CHAR; + const value = isValueEqualEmptyChar ? this.maskChar : this._value; + const finalValue = `${value}${(this.next().text() ?? '')}`; + + return finalValue; } - value() { - return this._value + this.next().value(); + value(): string { + const finalValue = `${this._value}${(this.next().value() ?? '')}`; + + return finalValue; } - rawValue() { - return this._value + this.next().rawValue(); + rawValue(): string { + const finalValue = `${this._value}${(this.next().rawValue() ?? '')}`; + + return finalValue; } - handle(args) { + handle(args: HandlingArgs): number { const str = Object.prototype.hasOwnProperty.call(args, 'value') ? args.value : args.text; if (!str || !str.length || !args.length) { return 0; @@ -136,48 +203,63 @@ export class MaskRule extends BaseMaskRule { this._tryAcceptChar(char, args); - return this._accepted() - ? this.next().handle(this._prepareHandlingArgs(args, { str: rest, length: args.length - 1 })) + 1 - : this.handle(this._prepareHandlingArgs(args, { str: rest, length: args.length - 1 })); + const isAccepted = this._accepted(); + + const rule = isAccepted ? this.next() : this; + const handlingArgs = this._prepareHandlingArgs(args, { str: rest, length: args.length - 1 }); + + const handledResult = rule.handle(handlingArgs); + const result = isAccepted ? handledResult + 1 : handledResult; + + return result; } - clear(args) { + clear(args: HandlingArgs): void { this._tryAcceptChar(EMPTY_CHAR, args); this.next().clear(this._prepareHandlingArgs(args)); } - reset() { + reset(): void { this._accepted(false); this.next().reset(); } - _tryAcceptChar(char, args) { + _tryAcceptChar(char: string, args: HandlingArgs): void { this._accepted(false); if (!this._isAllowed(char, args)) { return; } - const acceptedChar = char === EMPTY_CHAR ? this.maskChar : char; - args.fullText = args.fullText.substring(0, args.index) + acceptedChar + args.fullText.substring(args.index + 1); + + const acceptedChar: string = char === EMPTY_CHAR ? this.maskChar ?? '' : char; + + const fullTextSubstring1 = args.fullText?.substring(0, args.index) ?? ''; + const fullTextSubstring2 = args.fullText?.substring((args.index ?? 0) + 1) ?? ''; + + args.fullText = `${fullTextSubstring1}${acceptedChar}${fullTextSubstring2}`; + this._accepted(true); this._value = char; } - // @ts-expect-error - _accepted(value?: any) { + _accepted(): boolean; + _accepted(value: boolean): void; + // eslint-disable-next-line consistent-return + _accepted(value?: boolean): boolean | void { if (!arguments.length) { return !!this._isAccepted; } + this._isAccepted = !!value; } - first(index) { + first(index = 0): number { return this._value === EMPTY_CHAR - ? index || 0 + ? index : super.first(index); } - _isAllowed(char, args?: any) { + _isAllowed(char: string, args?: HandlingArgs): boolean { if (char === EMPTY_CHAR) { return true; } @@ -185,8 +267,7 @@ export class MaskRule extends BaseMaskRule { return this._isValid(char, args); } - _isValid(char, args) { - // @ts-expect-error + _isValid(char: string, args?: HandlingArgs): boolean { const { allowedChars } = this; if (allowedChars instanceof RegExp) { @@ -194,7 +275,7 @@ export class MaskRule extends BaseMaskRule { } if (isFunction(allowedChars)) { - return allowedChars(char, args.index, args.fullText); + return allowedChars(char, args?.index, args?.fullText); } if (Array.isArray(allowedChars)) { @@ -204,13 +285,17 @@ export class MaskRule extends BaseMaskRule { return allowedChars === char; } - isAccepted(caret) { - return caret === 0 + isAccepted(caret: number): boolean { + return Boolean(caret === 0 ? this._accepted() - : this.next().isAccepted(caret - 1); + : this.next().isAccepted(caret - 1)); } - _adjustedForward(caret, index, char) { + _adjustedForward( + caret: number, + index: number, + char: string, + ): number { if (index >= caret) { return index; } @@ -218,7 +303,7 @@ export class MaskRule extends BaseMaskRule { return this.next()._adjustedForward(caret, index + 1, char) || index + 1; } - _adjustedBackward(caret, index) { + _adjustedBackward(caret: number, index: number): number { if (index >= caret - 1) { return caret; } @@ -226,25 +311,29 @@ export class MaskRule extends BaseMaskRule { return this.next()._adjustedBackward(caret, index + 1) || index + 1; } - isValid(args) { - return this._isValid(this._value, args) && this.next().isValid(this._prepareHandlingArgs(args)); + isValid(args: HandlingArgs): boolean { + return this._isValid(this._value, args) + && this.next().isValid(this._prepareHandlingArgs(args)); } } export class StubMaskRule extends MaskRule { - value() { + value(): string { return this.next().value(); } - handle(args) { + handle(args: HandlingArgs): number { const hasValueProperty = Object.prototype.hasOwnProperty.call(args, 'value'); const str = hasValueProperty ? args.value : args.text; - if (!str.length || !args.length) { + + if (!str?.length || !args.length) { return 0; } if (args.start || hasValueProperty) { - return this.next().handle(this._prepareHandlingArgs(args, { start: args.start && args.start - 1 })); + const handlingArgs = this._prepareHandlingArgs(args, { start: args.start && args.start - 1 }); + + return this.next().handle(handlingArgs); } const char = str[0]; @@ -252,29 +341,37 @@ export class StubMaskRule extends MaskRule { this._tryAcceptChar(char); - const nextArgs = this._isAllowed(char) ? this._prepareHandlingArgs(args, { str: rest, length: args.length - 1 }) : args; - return this.next().handle(nextArgs) + 1; + const nextArgs = this._isAllowed(char) + ? this._prepareHandlingArgs(args, { str: rest, length: args.length - 1 }) + : args; + + return (this.next().handle(nextArgs) ?? 0) + 1; } - clear(args) { + clear(args: HandlingArgs): void { this._accepted(false); this.next().clear(this._prepareHandlingArgs(args)); } - _tryAcceptChar(char) { + _tryAcceptChar(char: string): void { this._accepted(this._isValid(char)); } - _isValid(char) { + _isValid(char: string): boolean { return char === this.maskChar; } - first(index) { - index = index || 0; - return this.next().first(index + 1); + first(index = 0): number { + const newIndex = index + 1; + + return this.next().first(newIndex); } - _adjustedForward(caret, index, char) { + _adjustedForward( + caret: number, + index: number, + char: string, + ): number { if (index >= caret && char === this.maskChar) { return index; } @@ -282,10 +379,11 @@ export class StubMaskRule extends MaskRule { if (caret === (index + 1) && this._accepted()) { return caret; } + return this.next()._adjustedForward(caret, index + 1, char); } - _adjustedBackward(caret, index) { + _adjustedBackward(caret: number, index: number): number { if (index >= caret - 1) { return 0; } @@ -293,7 +391,7 @@ export class StubMaskRule extends MaskRule { return this.next()._adjustedBackward(caret, index + 1); } - isValid(args) { - return this.next().isValid(this._prepareHandlingArgs(args)); + isValid(args: HandlingArgs): boolean { + return Boolean(this.next().isValid(this._prepareHandlingArgs(args))); } } diff --git a/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.strategy.ts b/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.strategy.ts index 6d0d81f2605f..45ebb21506f5 100644 --- a/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.strategy.ts +++ b/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.strategy.ts @@ -1,7 +1,13 @@ import EventsEngine from '@js/common/core/events/core/events_engine'; import { addNamespace } from '@js/common/core/events/utils/index'; +import type { dxElementWrapper } from '@js/core/renderer'; import browser from '@js/core/utils/browser'; import { clipboardText as getClipboardText } from '@js/core/utils/dom'; +import type { DxEvent } from '@js/events'; +import type { TextEditorBaseProperties } from '@ts/ui/text_box/m_text_editor.base'; +import type TextEditorMask from '@ts/ui/text_box/m_text_editor.mask'; +import type { HandlingArgs } from '@ts/ui/text_box/m_text_editor.mask.rule'; +import type { CaretRange } from '@ts/ui/text_box/utils.caret'; const MASK_EVENT_NAMESPACE = 'dxMask'; const BLUR_EVENT = 'blur beforedeactivate'; @@ -10,36 +16,48 @@ const DELETE_INPUT_TYPES = ['deleteContentBackward', 'deleteSoftLineBackward', ' const HISTORY_INPUT_TYPES = ['historyUndo', 'historyRedo']; const EVENT_NAMES = ['focusIn', 'focusOut', 'input', 'paste', 'cut', 'drop', 'beforeInput']; -function getEmptyString(length) { - return EMPTY_CHAR.repeat(length); -} +const getEmptyString = (length: number): string => EMPTY_CHAR.repeat(length); export default class MaskStrategy { - editor: any; + editor: TextEditorMask; - _dragTimer?: any; + _dragTimer?: ReturnType; - _inputHandlerTimer?: any; + _inputHandlerTimer?: ReturnType; - _caretTimeout?: any; + _caretTimeout?: ReturnType; - _prevCaret?: any; + _prevCaret?: CaretRange; _previousText?: string; - constructor(editor) { + constructor(editor: TextEditorMask) { this.editor = editor; } - _editorOption() { + _editorOption(name: K): TextEditorBaseProperties[K]; + _editorOption( + name: K, + value: TextEditorBaseProperties[K], + ): void; + _editorOption( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + name: K, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + value?: TextEditorBaseProperties[K], + ): TextEditorBaseProperties[K] | void { + // eslint-disable-next-line prefer-rest-params return this.editor.option(...arguments); } - _editorInput() { + _editorInput(): dxElementWrapper { return this.editor._input(); } - _editorCaret(newCaret?: any) { + _editorCaret(): CaretRange; + _editorCaret(newCaret: CaretRange | undefined): void; + // eslint-disable-next-line consistent-return + _editorCaret(newCaret?: CaretRange | undefined): CaretRange | void { if (!newCaret) { return this.editor._caret(); } @@ -47,27 +65,29 @@ export default class MaskStrategy { this.editor._caret(newCaret); } - _attachChangeEventHandler() { - // @ts-expect-error - if (!this._editorOption('valueChangeEvent').split(' ').includes('change')) { + _attachChangeEventHandler(): void { + const valueChangeEvent = this._editorOption('valueChangeEvent'); + + if (!valueChangeEvent?.split(' ').includes('change')) { return; } const $input = this._editorInput(); const namespace = addNamespace(BLUR_EVENT, MASK_EVENT_NAMESPACE); + EventsEngine.on($input, namespace, (e) => { this.editor._changeHandler(e); }); } - _beforeInputHandler() { - // @ts-expect-error + _beforeInputHandler(): void { this._previousText = this._editorOption('text'); this._prevCaret = this._editorCaret(); } - _inputHandler(event) { + _inputHandler(event: DxEvent): void { const { originalEvent } = event; + if (!originalEvent) { return; } @@ -81,7 +101,7 @@ export default class MaskStrategy { } else { const currentCaret = this._editorCaret(); - if (!currentCaret.end) { + if (!currentCaret?.end) { return; } @@ -90,55 +110,61 @@ export default class MaskStrategy { this._editorCaret(currentCaret); this._handleInsertTextInputEvent(originalEvent.data); } - // @ts-expect-error + if (this._editorOption('text') === this._previousText) { event.stopImmediatePropagation(); } } - _handleHistoryInputEvent() { + _handleHistoryInputEvent(): void { const caret = this._editorCaret(); this._updateEditorMask({ - start: caret.start, - length: caret.end - caret.start, + start: caret?.start, + length: (caret?.end ?? 0) - (caret?.start ?? 0), text: '', }); this._editorCaret(this._prevCaret); } - _handleBackwardDeleteInputEvent() { + _handleBackwardDeleteInputEvent(): void { this._clearSelectedText(true); const caret = this._editorCaret(); + this.editor.setForwardDirection(); this.editor._adjustCaret(); const adjustedForwardCaret = this._editorCaret(); - if (adjustedForwardCaret.start !== caret.start) { + + if (adjustedForwardCaret?.start !== caret?.start) { this.editor.setBackwardDirection(); this.editor._adjustCaret(); } } _clearSelectedText(isDeleteInputEvent?: boolean): void { - const selectionLength = this._prevCaret && (this._prevCaret.end - this._prevCaret.start); - const length = selectionLength || Number(isDeleteInputEvent); + const selectionLength = this._prevCaret + && ((this._prevCaret.end ?? 0) - (this._prevCaret.start ?? 0)); + + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const length = selectionLength || Number(Boolean(isDeleteInputEvent)); const caret = this._editorCaret(); if (!this._isAutoFill()) { this.editor.setBackwardDirection(); + this._updateEditorMask({ - start: caret.start, + start: caret?.start, length, text: getEmptyString(length), }); } } - _handleInsertTextInputEvent(data) { + _handleInsertTextInputEvent(data: DxEvent['data']): void { // NOTE: data has length > 1 when autosuggestion is applied. const text = data ?? ''; @@ -146,6 +172,7 @@ export default class MaskStrategy { const hasValidChars = this._updateEditorMask({ start: this._prevCaret?.start ?? 0, + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing length: text.length || 1, text, }); @@ -155,111 +182,143 @@ export default class MaskStrategy { } } - _updateEditorMask(args) { - const textLength = args.text.length; - const processedCharsCount = this.editor._handleChain(args); + _updateEditorMask(args: HandlingArgs): boolean { + const textLength = args.text?.length ?? 0; + const processedCharsCount = this.editor._handleChain(args) ?? 0; this.editor._displayMask(); if (this.editor.isForwardDirection()) { - const { start, end } = this._editorCaret(); - const correction = processedCharsCount - textLength; + const { start = 0, end = 0 } = this._editorCaret() ?? {}; + const correction = processedCharsCount - textLength; const hasSkippedStub = processedCharsCount > 1; + if (hasSkippedStub && textLength === 1) { - this._editorCaret({ start: start + correction, end: end + correction }); + this._editorCaret({ + start: start + correction, + end: end + correction, + }); } this.editor._adjustCaret(); } - return !!processedCharsCount; + return Boolean(processedCharsCount); } - _focusInHandler() { + _focusInHandler(): void { this.editor._showMaskPlaceholder(); this.editor.setForwardDirection(); - // @ts-expect-error + if (!this.editor._isValueEmpty() && this._editorOption('isValid')) { this.editor._adjustCaret(); } else { + if (!this.editor._maskRulesChain) { + return; + } + const caret = this.editor._maskRulesChain.first(); + + // eslint-disable-next-line no-restricted-globals this._caretTimeout = setTimeout(() => { this._editorCaret({ start: caret, end: caret }); }, 0); } } - _focusOutHandler(event) { + _focusOutHandler(event: DxEvent): void { this.editor._changeHandler(event); - // @ts-expect-error + if (this._editorOption('showMaskMode') === 'onFocus' && this.editor._isValueEmpty()) { - // @ts-expect-error this._editorOption('text', ''); this.editor._renderDisplayText(''); } } - _delHandler(event) { - const { editor } = this; - - editor._maskKeyHandler(event, () => { - if (!editor._hasSelection()) { - editor._handleKey(EMPTY_CHAR); + _delHandler(event: DxEvent): void { + this.editor._maskKeyHandler(event, () => { + if (!this.editor._hasSelection()) { + // @ts-expect-error bad editor type + this.editor._handleKey(EMPTY_CHAR); } + + return undefined; }); } - _cutHandler(event) { + _cutHandler(event: DxEvent): void { const caret = this._editorCaret(); - const selectedText = this._editorInput().val().substring(caret.start, caret.end); + const inputVal = this._editorInput().val(); + + // @ts-expect-error dxElementWrapper.val() should return string + const selectedText = inputVal.substring(caret?.start, caret?.end); - this.editor._maskKeyHandler(event, () => getClipboardText(event, selectedText)); + this.editor._maskKeyHandler( + event, + () => (getClipboardText(event, selectedText)) as Promise, + ); } - _dropHandler() { + _dropHandler(): void { this._clearDragTimer(); + + // eslint-disable-next-line no-restricted-globals this._dragTimer = setTimeout(() => { + // @ts-expect-error dxElementWrapper.val() const value = this.editor._convertToValue(this._editorInput().val()); - // @ts-expect-error + this._editorOption('value', value); }); } - _pasteHandler(event) { - const { editor } = this; - // @ts-expect-error + _pasteHandler(event: DxEvent): void { if (this._editorOption('disabled')) { return; } const caret = this._editorCaret(); - editor._maskKeyHandler(event, () => { + this.editor._maskKeyHandler(event, () => { const pastedText = getClipboardText(event); - const restText = editor._maskRulesChain.text().substring(caret.end); - const accepted = editor._handleChain({ text: pastedText, start: caret.start, length: pastedText.length }); - const newCaret = caret.start + accepted; + const restText = this.editor._maskRulesChain?.text().substring(caret?.end ?? 0); + const accepted = this.editor._handleChain({ + text: pastedText, + start: caret?.start, + length: pastedText.length, + }); + const newCaret = (caret?.start ?? 0) + accepted; + + this.editor._handleChain({ text: restText, start: newCaret, length: restText?.length }); + this.editor._caret({ start: newCaret, end: newCaret }); - editor._handleChain({ text: restText, start: newCaret, length: restText.length }); - editor._caret({ start: newCaret, end: newCaret }); + return undefined; }); } - _autoFillHandler(event) { - const { editor } = this; + _autoFillHandler(event: InputEvent): void { const inputVal = this._editorInput().val(); + + // eslint-disable-next-line no-restricted-globals this._inputHandlerTimer = setTimeout(() => { if (this._isAutoFill()) { - editor._maskKeyHandler(event, () => { - editor._handleChain({ text: inputVal, start: 0, length: inputVal.length }); + this.editor._maskKeyHandler(event, () => { + this.editor._handleChain({ + // @ts-expect-error dxElementWrapper.val() + text: inputVal, + start: 0, + length: inputVal.length, + }); + + return undefined; }); - editor._validateMask(); + + this.editor._validateMask(); } }); } - _isAutoFill() { + _isAutoFill(): boolean { const $input = this._editorInput(); if (browser.webkit) { @@ -270,23 +329,23 @@ export default class MaskStrategy { return false; } - _clearDragTimer() { + _clearDragTimer(): void { clearTimeout(this._dragTimer); } - _clearTimers() { + _clearTimers(): void { this._clearDragTimer(); clearTimeout(this._caretTimeout); clearTimeout(this._inputHandlerTimer); } - getHandler(handlerName) { + getHandler(handlerName: string): (args: unknown) => void { return (args) => { this[`_${handlerName}Handler`]?.(args); }; } - attachEvents() { + attachEvents(): void { const $input = this._editorInput(); EVENT_NAMES.forEach((eventName) => { @@ -297,12 +356,13 @@ export default class MaskStrategy { this._attachChangeEventHandler(); } - detachEvents() { + detachEvents(): void { this._clearTimers(); + EventsEngine.off(this._editorInput(), `.${MASK_EVENT_NAMESPACE}`); } - clean() { + clean(): void { this._clearTimers(); } } diff --git a/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.ts b/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.ts index 1683e0d01821..fc9306d0a5ce 100644 --- a/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.ts +++ b/packages/devextreme/js/__internal/ui/text_box/m_text_editor.mask.ts @@ -1,3 +1,4 @@ +/* eslint-disable class-methods-use-this */ import eventsEngine from '@js/common/core/events/core/events_engine'; import { name as wheelEventName } from '@js/common/core/events/core/wheel'; import { @@ -8,23 +9,25 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import type { DeferredObj } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; -import { each } from '@js/core/utils/iterator'; import { isEmpty } from '@js/core/utils/string'; import { isDefined } from '@js/core/utils/type'; +import type { DxEvent } from '@js/events'; import { focused } from '@ts/core/utils/m_selectors'; import type { OptionChanged } from '@ts/core/widget/types'; import type { SupportedKeys } from '@ts/core/widget/widget'; - -import type { TextEditorBaseProperties } from './m_text_editor.base'; -import TextEditorBase from './m_text_editor.base'; -import { EmptyMaskRule, MaskRule, StubMaskRule } from './m_text_editor.mask.rule'; -import MaskStrategy from './m_text_editor.mask.strategy'; -import type { CaretRange } from './utils.caret'; -import caretUtils from './utils.caret'; +import type { ValueChangedEvent } from '@ts/ui/editor/editor'; +import type { DxMouseWheelEvent } from '@ts/ui/scroll_view/types'; +import type { TextEditorBaseProperties } from '@ts/ui/text_box/m_text_editor.base'; +import TextEditorBase from '@ts/ui/text_box/m_text_editor.base'; +import type { HandlingArgs } from '@ts/ui/text_box/m_text_editor.mask.rule'; +import { EmptyMaskRule, MaskRule, StubMaskRule } from '@ts/ui/text_box/m_text_editor.mask.rule'; +import MaskStrategy from '@ts/ui/text_box/m_text_editor.mask.strategy'; +import type { CaretRange } from '@ts/ui/text_box/utils.caret'; +import caretUtils from '@ts/ui/text_box/utils.caret'; type MaskRules = Record boolean)>; -const caret = caretUtils; +type CaretDirection = 'forward' | 'backward'; const EMPTY_CHAR = ' '; const ESCAPED_CHAR = '\\'; @@ -35,6 +38,16 @@ const BACKWARD_DIRECTION = 'backward'; const DROP_EVENT_NAME = 'drop'; +const isNumericChar = (char: string): boolean => /[0-9]/.test(char); + +const isLiteralChar = (char: string): boolean => { + const code = char.charCodeAt(0); + + return (code > 64 && code < 91) || (code > 96 && code < 123) || code > 127; +}; + +const isSpaceChar = (char: string): boolean => char === ' '; + const buildInMaskRules: MaskRules = { 0: /[0-9]/, 9: /[0-9\s]/, @@ -55,69 +68,56 @@ const buildInMaskRules: MaskRules = { }, }; -function isNumericChar(char): boolean { - return /[0-9]/.test(char); -} - -function isLiteralChar(char): boolean { - const code = char.charCodeAt(); - return code > 64 && code < 91 || code > 96 && code < 123 || code > 127; -} - -function isSpaceChar(char): boolean { - return char === ' '; -} - class TextEditorMask< TProperties extends TextEditorBaseProperties= TextEditorBaseProperties, > extends TextEditorBase { - _changedValue?: any; + _changedValue?: string; _maskStrategy!: MaskStrategy; _$hiddenElement!: dxElementWrapper; - _typingDirection?: 'forward' | 'backward'; + _typingDirection?: CaretDirection; - _maskRulesChain?: any; + _maskRulesChain?: EmptyMaskRule | StubMaskRule | MaskRule | null; _maskRules?: MaskRules; - _textValue?: any; + _textValue?: string; - _value?: any; + _value?: string; _getDefaultOptions(): TProperties { return { ...super._getDefaultOptions(), mask: '', - maskChar: '_', - maskRules: {}, - maskInvalidMessage: messageLocalization.format('validation-mask'), - useMaskedValue: false, - showMaskMode: 'always', }; } _supportedKeys(): SupportedKeys { - const that = this; + const result = super._supportedKeys(); const keyHandlerMap = { - del: that._maskStrategy.getHandler('del'), - enter: that._changeHandler, + del: this._maskStrategy.getHandler('del'), + enter: this._changeHandler, }; - const result = super._supportedKeys(); - each(keyHandlerMap, (key, callback) => { + Object.entries(keyHandlerMap).forEach(([key, handler]) => { const parentHandler = result[key]; - result[key] = function (e) { - that.option('mask') && callback.call(that, e); - parentHandler && parentHandler(e); + + result[key] = (e: DxEvent): void => { + const { mask } = this.option(); + + if (mask && handler) { + handler.call(this, e); + } + + parentHandler?.(e); }; }); @@ -125,7 +125,11 @@ class TextEditorMask< } _getSubmitElement(): dxElementWrapper { - return !this.option('mask') ? super._getSubmitElement() : this._$hiddenElement; + const { mask } = this.option(); + + const submitElement = !mask ? super._getSubmitElement() : this._$hiddenElement; + + return submitElement; } _init(): void { @@ -135,6 +139,7 @@ class TextEditorMask< } _initMaskStrategy(): void { + // @ts-expect-error expected this._maskStrategy = new MaskStrategy(this); } @@ -149,13 +154,15 @@ class TextEditorMask< } const input = this._input(); - // @ts-expect-error ts-error + // @ts-expect-error addNamespace with second argument const eventName = addNamespace(wheelEventName, this.NAME); - const mouseWheelAction = this._createAction((e) => { + + const mouseWheelAction = this._createAction((e: { event: DxMouseWheelEvent }) => { const { event } = e; if (focused(input) && !isCommandKeyPressed(event)) { this._onMouseWheel(event); + event.preventDefault(); event.stopPropagation(); } @@ -172,10 +179,12 @@ class TextEditorMask< } // eslint-disable-next-line @typescript-eslint/no-unused-vars - _onMouseWheel(e?): void {} + _onMouseWheel(e?: DxMouseWheelEvent): void {} _useMaskBehavior(): boolean { - return Boolean(this.option('mask')); + const { mask } = this.option(); + + return Boolean(mask); } _attachDropEventHandler(): void { @@ -184,12 +193,13 @@ class TextEditorMask< if (!useMaskBehavior) { return; } - // @ts-expect-error + + // @ts-expect-error addNamespace with second argument const eventName = addNamespace(DROP_EVENT_NAME, this.NAME); const input = this._input(); eventsEngine.off(input, eventName); - eventsEngine.on(input, eventName, (e) => e.preventDefault()); + eventsEngine.on(input, eventName, (e: DxEvent) => { e.preventDefault(); }); } _render(): void { @@ -200,7 +210,9 @@ class TextEditorMask< } _renderHiddenElement(): void { - if (this.option('mask')) { + const { mask } = this.option(); + + if (mask) { this._$hiddenElement = $('') .attr('type', 'hidden') .appendTo(this._inputWrapper()); @@ -208,80 +220,103 @@ class TextEditorMask< } _removeHiddenElement(): void { - this._$hiddenElement && this._$hiddenElement.remove(); + this._$hiddenElement?.remove(); } _renderMask(): void { this.$element().removeClass(TEXTEDITOR_MASKED_CLASS); this._maskRulesChain = null; - this._maskStrategy.detachEvents(); - if (!this.option('mask')) { + const { mask } = this.option(); + + if (!mask) { return; } this.$element().addClass(TEXTEDITOR_MASKED_CLASS); - this._maskStrategy.attachEvents(); this._parseMask(); this._renderMaskedValue(); } - _changeHandler(e): void { + _changeHandler(e: DxEvent): void { const $input = this._input(); - const inputValue = $input.val(); + + // @ts-expect-error dxElementWrapper.val() should return string + const inputValue = $input.val() as string; if (inputValue === this._changedValue) { return; } this._changedValue = inputValue; + const changeEvent = createEvent(e, { type: 'change' }); - // @ts-expect-error + + // @ts-expect-error eventsEngine with trigger eventsEngine.trigger($input, changeEvent); } _parseMask(): void { - this._maskRules = extend({}, buildInMaskRules, this.option('maskRules')); + const { maskRules } = this.option(); + + this._maskRules = extend({}, buildInMaskRules, maskRules); this._maskRulesChain = this._parseMaskRule(0); } _parseMaskRule(index: number): EmptyMaskRule | StubMaskRule | MaskRule { const { mask } = this.option(); - // @ts-expect-error ts-error - if (index >= mask.length) { - // @ts-expect-error ts-error - return new EmptyMaskRule(); + + if (!isDefined(mask) || index >= mask.length) { + return new EmptyMaskRule({}); } - // @ts-expect-error + const currentMaskChar = mask[index]; const isEscapedChar = currentMaskChar === ESCAPED_CHAR; + const result = isEscapedChar - // @ts-expect-error ? new StubMaskRule({ maskChar: mask[index + 1] }) : this._getMaskRule(currentMaskChar); - // @ts-expect-error - result.next(this._parseMaskRule(index + 1 + isEscapedChar)); + + const nextIndex = index + 1 + Number(isEscapedChar); + const recursiveResult = this._parseMaskRule(nextIndex); + + // @ts-expect-error EmptyMaskRule Liskov + result.next(recursiveResult); + return result; } - _getMaskRule(pattern) { - let ruleConfig; - // @ts-expect-error - each(this._maskRules, (rulePattern, allowedChars) => { - if (rulePattern === pattern) { - ruleConfig = { - pattern: rulePattern, - allowedChars, - }; - return false; - } - }); + _getMaskRule(pattern: string): MaskRule | StubMaskRule { + if (!this._maskRules) { + return new StubMaskRule({ maskChar: pattern }); + } + + const matchingEntry = Object.entries(this._maskRules).find( + ([rulePattern]) => rulePattern === pattern, + ); + + if (matchingEntry) { + const [, allowedChars] = matchingEntry; - return isDefined(ruleConfig) - ? new MaskRule(extend({ maskChar: this.option('maskChar') || ' ' }, ruleConfig)) - : new StubMaskRule({ maskChar: pattern }); + const ruleConfig = { + pattern, + allowedChars, + }; + + const { maskChar } = this.option(); + + return new MaskRule( + extend( + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + { maskChar: maskChar || ' ' }, + ruleConfig, + ), + ); + } + + return new StubMaskRule({ maskChar: pattern }); } _renderMaskedValue(): void { @@ -289,39 +324,56 @@ class TextEditorMask< return; } - const value = this.option('value') || ''; + const { value: optionValue } = this.option(); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const value = optionValue || ''; + this._maskRulesChain.clear(this._normalizeChainArguments()); - // @ts-expect-error ts-error - const chainArgs = { length: value.length }; - chainArgs[this._isMaskedValueMode() ? 'text' : 'value'] = value; + + const chainArgs = { length: value?.length }; + const prop = this._isMaskedValueMode() ? 'text' : 'value'; + + chainArgs[prop] = value; this._handleChain(chainArgs); this._displayMask(); } - _replaceSelectedText(text, selection, char) { + _replaceSelectedText( + text: string, + selection: CaretRange, + char: string, + ): string { if (char === undefined) { return text; } const textBefore = text.slice(0, selection.start); const textAfter = text.slice(selection.end); - const edited = textBefore + char + textAfter; + + const edited = `${textBefore}${char}${textAfter}`; return edited; } - _isMaskedValueMode() { - return this.option('useMaskedValue'); + _isMaskedValueMode(): boolean { + const { useMaskedValue } = this.option(); + + return Boolean(useMaskedValue); } - _displayMask(caret?): void { - caret = caret || this._caret(); + _displayMask(caret?: CaretRange): void { + const currentCaret = caret ?? this._caret(); + const finalCaret = { + start: currentCaret?.start ?? 0, + end: currentCaret?.end ?? 0, + }; + this._renderValue(); - this._caret(caret); + this._caret(finalCaret); } - _isValueEmpty() { + _isValueEmpty(): boolean { return isEmpty(this._value); } @@ -337,8 +389,9 @@ class TextEditorMask< _showMaskPlaceholder(): void { if (this._shouldShowMask()) { - const text = this._maskRulesChain.text(); - this.option('text', text); + const text = this._maskRulesChain?.text(); + + this.option({ text }); const { showMaskMode } = this.option(); @@ -364,71 +417,90 @@ class TextEditorMask< return super._renderValue(); } - _getPreparedValue() { + _getPreparedValue(): string { return this._convertToValue().replace(/\s+$/, ''); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _valueChangeEventHandler(e, value?): void { + _valueChangeEventHandler(...args: unknown[]): void { if (!this._maskRulesChain) { - // @ts-expect-error - super._valueChangeEventHandler.apply(this, arguments); + // @ts-expect-error _valueChangeEventHandler + super._valueChangeEventHandler(...args); return; } - this._saveValueChangeEvent(e); + const [e] = args; + + this._saveValueChangeEvent(e as ValueChangedEvent); + + const preparedValue = this._getPreparedValue(); - this.option('value', this._getPreparedValue()); + this.option({ value: preparedValue }); } - _isControlKeyFired(e) { - // @ts-expect-error - return this._isControlKey(normalizeKeyName(e)) || isCommandKeyPressed(e); + _isControlKeyFired(e: KeyboardEvent): boolean { + const normalizedKeyName = normalizeKeyName(e); + + const isControlKey = isDefined(normalizedKeyName) + ? this._isControlKey(normalizedKeyName) + : false; + + return isControlKey || isCommandKeyPressed(e); } - _handleChain(args) { - const handledCount = this._maskRulesChain.handle(this._normalizeChainArguments(args)); + _handleChain(args: HandlingArgs): number { + const handledCount = this._maskRulesChain?.handle(this._normalizeChainArguments(args)) ?? 0; + this._updateMaskInfo(); + return handledCount; } - _normalizeChainArguments(args?) { - args = args || {}; - args.index = 0; - args.fullText = this._maskRulesChain.text(); - return args; + _normalizeChainArguments(args?: HandlingArgs): HandlingArgs { + return { + ...args, + index: 0, + fullText: this._maskRulesChain?.text(), + }; } - _convertToValue(text?) { + _convertToValue(text?: string): string { if (this._isMaskedValueMode()) { - text = this._replaceMaskCharWithEmpty(text || this._textValue || ''); - } else { - text = text || this._value || ''; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + return this._replaceMaskCharWithEmpty(text || this._textValue || ''); } - return text; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + return text || this._value || ''; } - _replaceMaskCharWithEmpty(text) { + _replaceMaskCharWithEmpty(text: string): string { const { maskChar } = this.option(); + // @ts-expect-error ts-error return text.replace(new RegExp(maskChar, 'g'), EMPTY_CHAR); } - _maskKeyHandler(e, keyHandler): void { - if (this.option('readOnly')) { + _maskKeyHandler( + e: KeyboardEvent | ClipboardEvent | InputEvent, + keyHandler: () => Promise | undefined, + ): void { + const { readOnly } = this.option(); + + if (readOnly) { return; } this.setForwardDirection(); + e.preventDefault(); this._handleSelection(); const previousText = this._input().val(); - const raiseInputEvent = () => { + + const raiseInputEvent = (): void => { if (previousText !== this._input().val()) { - // @ts-expect-error + // @ts-expect-error trigger eventsEngine.trigger(this._input(), 'input'); } }; @@ -436,17 +508,18 @@ class TextEditorMask< const handled = keyHandler(); if (handled) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises handled.then(raiseInputEvent); } else { this.setForwardDirection(); this._adjustCaret(); this._displayMask(); - this._maskRulesChain.reset(); + this._maskRulesChain?.reset(); raiseInputEvent(); } } - _handleKey(key, direction): void { + _handleKey(key: string, direction: CaretDirection): void { this._direction(direction || FORWARD_DIRECTION); this._adjustCaret(key); this._handleKeyChain(key); @@ -459,14 +532,24 @@ class TextEditorMask< } const caret = this._caret(); - const emptyChars = new Array(caret.end - caret.start + 1).join(EMPTY_CHAR); + + const caretStart = caret?.start ?? 0; + const caretEnd = caret?.end ?? 0; + + const emptyChars = new Array(caretEnd - caretStart + 1).join(EMPTY_CHAR); + this._handleKeyChain(emptyChars); } - _handleKeyChain(chars): void { + _handleKeyChain(chars: string): void { const caret = this._caret(); - const start = this.isForwardDirection() ? caret.start : caret.start - 1; - const end = this.isForwardDirection() ? caret.end : caret.end - 1; + + const caretStart = caret?.start ?? 0; + const caretEnd = 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, length }); @@ -474,24 +557,28 @@ class TextEditorMask< _tryMoveCaretBackward(): boolean { this.setBackwardDirection(); - const currentCaret = this._caret().start; + + const currentCaret = this._caret()?.start; + this._adjustCaret(); - return !currentCaret || currentCaret !== this._caret().start; + + return !currentCaret || currentCaret !== this._caret()?.start; } - _adjustCaret(char?): void { - const caretStart = this._caret().start; + _adjustCaret(char?: string): void { + const caretStart = this._caret()?.start ?? 0; const isForwardDirection = this.isForwardDirection(); - const caret = this._maskRulesChain.adjustedCaret(caretStart, isForwardDirection, char); + const caret = this._maskRulesChain?.adjustedCaret(caretStart, isForwardDirection, char ?? ''); + this._caret({ start: caret, end: caret }); } _moveCaret(): void { - const currentCaret = this._caret().start; + const currentCaret = this._caret()?.start ?? 0; const maskRuleIndex = currentCaret + (this.isForwardDirection() ? 0 : -1); - const caret = this._maskRulesChain.isAccepted(maskRuleIndex) + const caret = this._maskRulesChain?.isAccepted(maskRuleIndex) ? currentCaret + (this.isForwardDirection() ? 1 : -1) : currentCaret; @@ -499,32 +586,31 @@ class TextEditorMask< } _caret( - position?: { start: number; end: number }, - force?, - // @ts-expect-error - ): CaretRange { + position?: CaretRange, + force?: boolean, + ): CaretRange | undefined { const $input = this._input(); if (!$input.length) { - // @ts-expect-error - return; + return undefined; } - if (!arguments.length) { - // @ts-expect-error - return caret($input); + if (arguments.length > 0) { + caretUtils($input, position, force); + return undefined; } - caret($input, position, force); + + return caretUtils($input); } _hasSelection(): boolean { const caret = this._caret(); - return caret.start !== caret.end; + return caret?.start !== caret?.end; } // eslint-disable-next-line @typescript-eslint/no-invalid-void-type, consistent-return - _direction(direction?: 'forward' | 'backward'): 'forward' | 'backward' | void { + _direction(direction?: CaretDirection): CaretDirection | void { if (!arguments.length) { return this._typingDirection; } @@ -545,12 +631,13 @@ class TextEditorMask< } _updateMaskInfo(): void { - this._textValue = this._maskRulesChain.text(); - this._value = this._maskRulesChain.value(); + this._textValue = this._maskRulesChain?.text(); + this._value = this._maskRulesChain?.value(); } _clean(): void { - this._maskStrategy && this._maskStrategy.clean(); + this._maskStrategy?.clean(); + super._clean(); } @@ -558,18 +645,29 @@ class TextEditorMask< if (!this._maskRulesChain) { return; } - const isValid = isEmpty(this.option('value')) || this._maskRulesChain.isValid(this._normalizeChainArguments()); + + const { maskInvalidMessage, value } = this.option(); + + const defaultValidationError = { + editorSpecific: true, + message: maskInvalidMessage, + }; + + const isValid = isEmpty(value) || this._maskRulesChain.isValid(this._normalizeChainArguments()); + const validationError = isValid ? null : defaultValidationError; this.option({ isValid, - validationError: isValid ? null : { editorSpecific: true, message: this.option('maskInvalidMessage') }, + validationError, }); } _updateHiddenElement(): void { this._removeHiddenElement(); - if (this.option('mask')) { + const { mask } = this.option(); + + if (mask) { this._input().removeAttr('name'); this._renderHiddenElement(); } @@ -586,10 +684,12 @@ class TextEditorMask< this._refreshValueChangeEvent(); } - _processEmptyMask(mask): void { - if (mask) return; + _processEmptyMask(mask: string): void { + if (mask) { + return; + } - const value = this.option('value'); + const { value } = this.option(); this.option({ text: value, @@ -609,6 +709,7 @@ class TextEditorMask< switch (args.name) { case 'mask': this._updateMaskOption(); + // @ts-expect-error as string this._processEmptyMask(args.value); break; case 'maskChar': @@ -621,12 +722,13 @@ class TextEditorMask< this._validateMask(); super._optionChanged(args); - this._changedValue = this._input().val(); + // @ts-expect-error dxElementWrapper.val() should return string + this._changedValue = this._input().val() as string; break; case 'maskInvalidMessage': break; case 'showMaskMode': - this.option('text', ''); + this.option({ text: '' }); this._renderValue(); break; default: @@ -636,9 +738,12 @@ class TextEditorMask< clear(): void { const { value: defaultValue } = this._getDefaultOptions(); - if (this.option('value') === defaultValue) { + const { value } = this.option(); + + if (value === defaultValue) { this._renderMaskedValue(); } + super.clear(); } } diff --git a/packages/devextreme/js/__internal/ui/text_box/utils.caret.ts b/packages/devextreme/js/__internal/ui/text_box/utils.caret.ts index 7a3f7b9f9279..f7be9b3d27b2 100644 --- a/packages/devextreme/js/__internal/ui/text_box/utils.caret.ts +++ b/packages/devextreme/js/__internal/ui/text_box/utils.caret.ts @@ -5,8 +5,8 @@ import $ from '@js/core/renderer'; import { isDefined } from '@js/core/utils/type'; export interface CaretRange { - start: number; - end: number; + start?: number; + end?: number; } // @ts-expect-error mac should be correctly typed in environment.d.ts @@ -41,8 +41,8 @@ export const setCaret = ( selection: CaretRange, ): void => { try { - input.selectionStart = selection.start; - input.selectionEnd = selection.end; + input.selectionStart = selection.start ?? null; + input.selectionEnd = selection.end ?? null; } catch { /** empty */ } };