diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.mask.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.mask.ts index aaefdb7ce034..60dfad80e5d9 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.mask.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.mask.ts @@ -48,6 +48,8 @@ class DateBoxMask extends DateBoxBase { _isIMECommitPending?: boolean; + _wasAllSelectedBeforeComposition = false; + _supportedKeys(): Record boolean> { const originalHandlers = super._supportedKeys(); const callOriginalHandler = (e) => { @@ -218,15 +220,15 @@ class DateBoxMask extends DateBoxBase { }; } - const isBackwardDeletion = inputType === 'deleteContentBackward'; - const isForwardDeletion = inputType === 'deleteContentForward'; - if (isBackwardDeletion || isForwardDeletion) { - const direction = isBackwardDeletion ? BACKWARD : FORWARD; - this._maskInputHandler = () => { - this._revertPart(); - this._selectNextPart(direction); - }; - } + // const isBackwardDeletion = inputType === 'deleteContentBackward'; + // const isForwardDeletion = inputType === 'deleteContentForward'; + // if (isBackwardDeletion || isForwardDeletion) { + // const direction = isBackwardDeletion ? BACKWARD : FORWARD; + // this._maskInputHandler = () => { + // this._revertPart(); + // this._selectNextPart(direction); + // }; + // } if (!this._useMaskBehavior() || !this._isSingleCharKey(e)) { return; @@ -246,6 +248,26 @@ class DateBoxMask extends DateBoxBase { _keyPressHandler(e: { originalEvent: InputEvent & KeyboardEvent }): void { const { originalEvent: event } = e; + const isBackwardDeletion = event?.inputType === 'deleteContentBackward'; + const isForwardDeletion = event?.inputType === 'deleteContentForward'; + + if (this._useMaskBehavior() && (isBackwardDeletion || isForwardDeletion)) { + const isInputCleared = this._input().val() === '' as any; + + if (this._wasAllSelectedBeforeComposition || isInputCleared) { + super._keyPressHandler(e); + } else { + const direction = isBackwardDeletion ? BACKWARD : FORWARD; + + this._revertPart(direction); + this._syncInputWithMask(); + } + + this._wasAllSelectedBeforeComposition = false; + + return; + } + const isCompositionDigit = event?.inputType === 'insertCompositionText' && this._isSingleDigitKey(e); @@ -255,9 +277,15 @@ class DateBoxMask extends DateBoxBase { if (isCompositionDigit && event.data) { if (!this._isIMEDigitProcessed) { + if (this._wasAllSelectedBeforeComposition || this._isAllSelected()) { + this._clearSearchValue(); + this._selectFirstPart(); + } + this._processInputKey(event.data); this._isIMEDigitProcessed = true; this._isIMECommitPending = true; + this._wasAllSelectedBeforeComposition = false; } this._syncInputWithMask(); @@ -268,7 +296,7 @@ class DateBoxMask extends DateBoxBase { if (isIMECommitDigit) { this._isIMECommitPending = false; this._pendingIMEDigit = null; - + this._wasAllSelectedBeforeComposition = false; this._syncInputWithMask(); return; @@ -279,6 +307,8 @@ class DateBoxMask extends DateBoxBase { this._maskInputHandler(); this._maskInputHandler = null; } + + this._wasAllSelectedBeforeComposition = false; } _processInputKey(key: string): void { @@ -666,6 +696,7 @@ class DateBoxMask extends DateBoxBase { _maskCompositionStartHandler(): void { this._isIMEDigitProcessed = false; this._isIMECommitPending = false; + this._wasAllSelectedBeforeComposition = this._isAllSelected(); } _maskCompositionEndHandler(): void { @@ -673,6 +704,7 @@ class DateBoxMask extends DateBoxBase { this._caret(this._getActivePartProp('caret')); this._maskInputHandler = null; + this._wasAllSelectedBeforeComposition = false; } _maskPasteHandler(e): void { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js index 119bf21221a9..f0adffbaf25f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js @@ -797,6 +797,27 @@ module('Keyboard navigation', setupModule, () => { assert.deepEqual(this.keyboard.caret(), { start: 9, end: 11 }, 'first group has been filled again'); }); + test('Windows Bopomofo IME changes selection to last active part before insertCompositionText — first date part should still be updated (T1331089)', function(assert) { + this.instance.option({ + displayFormat: 'MM/dd/yyyy', + value: new Date(2025, 9, 16), + }); + + this.keyboard.type('1016'); + + this.keyboard.caret({ start: 0, end: this.instance.option('text').length }); + + this.$input.trigger($.Event('compositionstart')); + + this.keyboard.caret({ start: 6, end: 10 }); + + this.keyboard.input('1', 'insertCompositionText'); + + assert.strictEqual(this.instance.option('text'), '01/16/2025', 'month (first part) is updated, not year'); + assert.deepEqual(this.keyboard.caret(), { start: 0, end: 2 }, 'month part is active'); + }); + + test('enter should clear search value', function(assert) { this.keyboard.type('1'); @@ -1354,6 +1375,53 @@ module('Search', setupModule, () => { assert.strictEqual(this.$input.val(), '2012/10/10', 'year was not changed'); }); + + test('deleteContentBackward input event should revert the active date part to its minimum value without clearing the value (Chinese MS IME composition backspace) (T1331089)', function(assert) { + this.instance.option({ + displayFormat: 'MM/dd/yyyy', + value: new Date(2025, 9, 16), + }); + + this.$input.get(0).focus(); + + this.$input.trigger($.Event('compositionstart')); + + this.keyboard.input(null, 'deleteContentBackward'); + + assert.strictEqual(this.instance.option('text'), '01/16/2025', 'text is not cleared, month is reset'); + assert.deepEqual(this.keyboard.caret(), { start: 0, end: 2 }, 'first date part (month) is still active'); + }); + + test('deleteContentBackward during IME composition on non-first date part should revert that part to minimum without affecting other parts (T1331089)', function(assert) { + this.instance.option({ + displayFormat: 'MM/dd/yyyy', + value: new Date(2025, 9, 16), + }); + + this.keyboard.type('10'); + + this.$input.trigger($.Event('compositionstart')); + + this.keyboard.input(null, 'deleteContentBackward'); + + assert.strictEqual(this.instance.option('text'), '10/01/2025', 'day is reverted to minimum, month is unchanged'); + }); + + test('IME digit on non-first date part without all-selected state should update that specific part and not reset to first (T1331089 regression)', function(assert) { + this.instance.option({ + displayFormat: 'MM/dd/yyyy', + value: new Date(2025, 9, 16), + }); + + this.keyboard.type('10'); + + this.$input.trigger($.Event('compositionstart')); + + this.keyboard.input('2', 'insertCompositionText'); + + assert.strictEqual(this.instance.option('text'), '10/02/2025', 'day part is updated, month is not reset to first'); + assert.deepEqual(this.keyboard.caret(), { start: 3, end: 5 }, 'day part remains active'); + }); }); module('Date AM/PM Handling', setupModule, () => {