From 7cec6e5f5bb95144b2a66701e8162846a449411b Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov Date: Thu, 25 Jun 2026 18:10:38 +0300 Subject: [PATCH 1/7] DateBox: fix mask behavior with Chinese MS IME backspace and refocus (T1331089) --- .../__internal/ui/date_box/date_box.mask.ts | 30 ++++++++++++++++ .../datebox.mask.tests.js | 34 +++++++++++++++++++ 2 files changed, 64 insertions(+) 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 66c6ac1e3226..67e5a23240ee 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 @@ -4,6 +4,7 @@ import { getFormat } from '@js/common/core/localization/ldml/date.format'; import { getRegExpInfo } from '@js/common/core/localization/ldml/date.parser'; import numberLocalization from '@js/common/core/localization/number'; import devices from '@js/core/devices'; +import type { dxElementWrapper } from '@js/core/renderer'; import browser from '@js/core/utils/browser'; import { clipboardText } from '@js/core/utils/dom'; import { fitIntoRange, inRange, sign } from '@js/core/utils/math'; @@ -15,6 +16,7 @@ import type { DateLike, Properties } from '@js/ui/date_box'; import dateLocalization from '@ts/core/localization/date'; import type { OptionChanged } from '@ts/core/widget/types'; import type { KeyboardKeyDownEvent } from '@ts/events/core/m_keyboard_processor'; +import type { ValueChangedEvent } from '@ts/ui/editor/editor'; import type { DxMouseWheelEvent } from '../scroll_view/types'; import type { DateBoxBaseProperties } from './date_box.base'; @@ -25,6 +27,7 @@ const MASK_EVENT_NAMESPACE = 'dateBoxMask'; const FORWARD = 1; const BACKWARD = -1; const IME_DIGIT_CODE_REGEXP = /^(?:Digit|Numpad)(\d)$/; +const IME_BACKSPACE_INPUT_TYPE = 'deleteContentBackward'; export interface DateBoxMaskProperties extends Properties { emptyDateValue?: Date; @@ -52,6 +55,8 @@ class DateBoxMask extends DateBoxBase { _isIMECommitPending?: boolean; + _isClearingValue?: boolean; + _supportedKeys(): Record boolean | undefined> { const originalHandlers = super._supportedKeys(); const callOriginalHandler = (e: KeyboardEvent): boolean | undefined => { @@ -307,6 +312,17 @@ class DateBoxMask extends DateBoxBase { return; } + + if (this._useMaskBehavior() && event?.inputType === IME_BACKSPACE_INPUT_TYPE) { + if (this._isAllSelected()) { + this._selectFirstPart(); + } + this._revertPart(BACKWARD); + this._syncInputWithMask(); + + return; + } + super._keyPressHandler(e); if (this._maskInputHandler) { @@ -792,6 +808,20 @@ class DateBoxMask extends DateBoxBase { } } + _clearValueHandler(e: ValueChangedEvent & DxEvent): void { + this._isClearingValue = true; + super._clearValueHandler(e); + this._isClearingValue = false; + } + + _focusInHandler(e: DxEvent & { relatedTarget: Element | dxElementWrapper }): void { + super._focusInHandler(e); + + if (this._useMaskBehavior() && !e.isDefaultPrevented() && !this._isClearingValue) { + this._selectFirstPart(); + } + } + _focusOutHandler(e: DxEvent): void { const shouldFireChangeEvent = this._useMaskBehavior() && !e.isDefaultPrevented(); 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 e43ab2f2eb97..b04eb40bdb1d 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 @@ -787,6 +787,26 @@ module('Keyboard navigation', setupModule, () => { assert.strictEqual(this.instance.option('value'), null, 'value has been cleared'); }); + 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), // Oct 16, 2025; text = '10/16/2025' + }); + + this.$input.get(0).focus(); + + this.$input.trigger($.Event('input', { + type: 'input', + originalEvent: $.Event('input', { + inputType: 'deleteContentBackward', + }) + })); + + assert.ok(this.instance.option('text').length > 0, 'text is not cleared after IME composition backspace'); + assert.deepEqual(this.keyboard.caret(), { start: 0, end: 2 }, 'first date part (month) is still active'); + assert.strictEqual(this.instance.option('text'), '01/16/2025', 'month part is reset to minimum value, other parts unchanged'); + }); + QUnit.testInActiveWindow('focusout should clear search value', function(assert) { this.keyboard.type('1'); assert.strictEqual(this.instance.option('text'), 'January 10 2012', 'text has been changed'); @@ -798,6 +818,20 @@ module('Keyboard navigation', setupModule, () => { assert.deepEqual(this.keyboard.caret(), { start: 9, end: 11 }, 'first group has been filled again'); }); + QUnit.testInActiveWindow('first part should be active when re-focusing after all parts are completed (T1331089)', function(assert) { + this.instance.option({ + displayFormat: 'MM/dd/yyyy', + value: new Date(2025, 0, 1), + }); + + this.keyboard.type('10162025'); // Oct 16, 2025 + + this.$input.focusout(); + this.$input.get(0).focus(); + + assert.deepEqual(this.keyboard.caret(), { start: 0, end: 2 }, 'month part is selected after re-focusing'); + }); + test('enter should clear search value', function(assert) { this.keyboard.type('1'); From 0229a17c4ec2337af6b9296b38f7570f13e46bdb Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov Date: Mon, 29 Jun 2026 15:23:16 +0300 Subject: [PATCH 2/7] DateBox: fix IME composition backspace breaking mask and refocus caret (T1331089) --- .../__internal/ui/date_box/date_box.mask.ts | 16 ++++++---- .../datebox.mask.tests.js | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) 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 67e5a23240ee..655f976f5957 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 @@ -55,6 +55,10 @@ class DateBoxMask extends DateBoxBase { _isIMECommitPending?: boolean; + _isComposing?: boolean; + + _wasAllSelectedAtCompositionStart?: boolean; + _isClearingValue?: boolean; _supportedKeys(): Record boolean | undefined> { @@ -313,10 +317,10 @@ class DateBoxMask extends DateBoxBase { return; } - if (this._useMaskBehavior() && event?.inputType === IME_BACKSPACE_INPUT_TYPE) { - if (this._isAllSelected()) { - this._selectFirstPart(); - } + if (this._useMaskBehavior() + && event?.inputType === IME_BACKSPACE_INPUT_TYPE + && this._isComposing + && !this._wasAllSelectedAtCompositionStart) { this._revertPart(BACKWARD); this._syncInputWithMask(); @@ -747,11 +751,14 @@ class DateBoxMask extends DateBoxBase { } _maskCompositionStartHandler(): void { + this._wasAllSelectedAtCompositionStart = this._isAllSelected(); + this._isComposing = true; this._isIMEDigitProcessed = false; this._isIMECommitPending = false; } _maskCompositionEndHandler(): void { + this._isComposing = false; this._input().val(this._getDisplayedText(this._maskValue)); this._caret(this._getActivePartProp('caret')); @@ -816,7 +823,6 @@ class DateBoxMask extends DateBoxBase { _focusInHandler(e: DxEvent & { relatedTarget: Element | dxElementWrapper }): void { super._focusInHandler(e); - if (this._useMaskBehavior() && !e.isDefaultPrevented() && !this._isClearingValue) { this._selectFirstPart(); } 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 b04eb40bdb1d..9659180d59da 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 @@ -795,6 +795,8 @@ module('Keyboard navigation', setupModule, () => { this.$input.get(0).focus(); + this.$input.trigger($.Event('compositionstart')); + this.$input.trigger($.Event('input', { type: 'input', originalEvent: $.Event('input', { @@ -807,6 +809,32 @@ module('Keyboard navigation', setupModule, () => { assert.strictEqual(this.instance.option('text'), '01/16/2025', 'month part is reset to minimum value, other parts unchanged'); }); + test('deleteContentBackward input event during composition with all text selected should clear the value (Chinese MS IME composition backspace) (T1331089)', function(assert) { + this.instance.option({ + displayFormat: 'MM/dd/yyyy', + value: new Date(2025, 9, 16), // Oct 16, 2025; text = '10/16/2025' + }); + + this.$input.get(0).focus(); + this.keyboard.caret({ start: 0, end: 10 }); + + this.$input.trigger($.Event('compositionstart')); + + this.$input.val(''); + + this.$input.trigger($.Event('input', { + type: 'input', + originalEvent: $.Event('input', { + inputType: 'deleteContentBackward', + }) + })); + + this.$input.change(); + + assert.strictEqual(this.instance.option('text'), '', 'text has been cleared'); + assert.strictEqual(this.instance.option('value'), null, 'value has been cleared'); + }); + QUnit.testInActiveWindow('focusout should clear search value', function(assert) { this.keyboard.type('1'); assert.strictEqual(this.instance.option('text'), 'January 10 2012', 'text has been changed'); @@ -830,6 +858,7 @@ module('Keyboard navigation', setupModule, () => { this.$input.get(0).focus(); assert.deepEqual(this.keyboard.caret(), { start: 0, end: 2 }, 'month part is selected after re-focusing'); + }); test('enter should clear search value', function(assert) { From 27e03a7dab0976b1e12b1aff06881a0d42c3e571 Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov Date: Mon, 29 Jun 2026 16:18:11 +0300 Subject: [PATCH 3/7] DateBox: Fix backspace on all selected press issue --- .../js/__internal/ui/date_box/date_box.mask.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 401ab24cc61e..72de615cca08 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 @@ -57,7 +57,7 @@ class DateBoxMask extends DateBoxBase { _isComposing?: boolean; - _wasAllSelectedAtCompositionStart?: boolean; + _hasUserTyped?: boolean; _isClearingValue?: boolean; @@ -319,8 +319,8 @@ class DateBoxMask extends DateBoxBase { if (this._useMaskBehavior() && event?.inputType === IME_BACKSPACE_INPUT_TYPE - && this._isComposing - && !this._wasAllSelectedAtCompositionStart) { + && this._input().val() !== '' + ) { this._revertPart(BACKWARD); this._syncInputWithMask(); @@ -336,6 +336,8 @@ class DateBoxMask extends DateBoxBase { } _processInputKey(key: string): void { + this._hasUserTyped = true; + const hasMultipleParts = this._dateParts?.length > 1; if (this._isAllSelected() && hasMultipleParts) { @@ -751,7 +753,6 @@ class DateBoxMask extends DateBoxBase { } _maskCompositionStartHandler(): void { - this._wasAllSelectedAtCompositionStart = this._isAllSelected(); this._isComposing = true; this._isIMEDigitProcessed = false; this._isIMECommitPending = false; @@ -824,7 +825,11 @@ class DateBoxMask extends DateBoxBase { _focusInHandler(e: DxEvent & { relatedTarget: Element | dxElementWrapper }): void { super._focusInHandler(e); if (this._useMaskBehavior() && !e.isDefaultPrevented() && !this._isClearingValue) { - this._selectFirstPart(); + if (this._hasUserTyped) { + this._selectFirstPart(); + } + + this._hasUserTyped = false; } } From cc4d34c48367664bd26dd637d11fbfef04116075 Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov Date: Mon, 29 Jun 2026 17:02:23 +0300 Subject: [PATCH 4/7] DateBox: Fix incorrect test and use native blur + focus --- .../tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9659180d59da..29e956775ebc 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 @@ -854,7 +854,7 @@ module('Keyboard navigation', setupModule, () => { this.keyboard.type('10162025'); // Oct 16, 2025 - this.$input.focusout(); + this.$input.get(0).blur(); this.$input.get(0).focus(); assert.deepEqual(this.keyboard.caret(), { start: 0, end: 2 }, 'month part is selected after re-focusing'); From 759d0f92d3be69753e23012b930afaae6886943e Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov Date: Mon, 29 Jun 2026 18:49:20 +0300 Subject: [PATCH 5/7] DateBox: Fix re-focus issue --- .../devextreme/js/__internal/ui/date_box/date_box.mask.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 72de615cca08..fbdd938e8d70 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 @@ -57,6 +57,8 @@ class DateBoxMask extends DateBoxBase { _isComposing?: boolean; + _isReFocusing?: boolean; + _hasUserTyped?: boolean; _isClearingValue?: boolean; @@ -825,11 +827,12 @@ class DateBoxMask extends DateBoxBase { _focusInHandler(e: DxEvent & { relatedTarget: Element | dxElementWrapper }): void { super._focusInHandler(e); if (this._useMaskBehavior() && !e.isDefaultPrevented() && !this._isClearingValue) { - if (this._hasUserTyped) { + if (this._isReFocusing && this._hasUserTyped) { this._selectFirstPart(); + this._hasUserTyped = false; } - this._hasUserTyped = false; + this._isReFocusing = false; } } @@ -837,6 +840,7 @@ class DateBoxMask extends DateBoxBase { const shouldFireChangeEvent = this._useMaskBehavior() && !e.isDefaultPrevented(); if (shouldFireChangeEvent) { + this._isReFocusing = true; this._fireChangeEvent(); super._focusOutHandler(e); } else { From 19c77bbbf55a656b8ab97025a10cb94460d4bb6b Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov Date: Mon, 29 Jun 2026 18:58:04 +0300 Subject: [PATCH 6/7] DateBox: Add isComposing guard and fix clearValueHandler --- .../js/__internal/ui/date_box/date_box.mask.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 fbdd938e8d70..1e33c52a23e6 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 @@ -321,6 +321,7 @@ class DateBoxMask extends DateBoxBase { if (this._useMaskBehavior() && event?.inputType === IME_BACKSPACE_INPUT_TYPE + && this._isComposing && this._input().val() !== '' ) { this._revertPart(BACKWARD); @@ -819,9 +820,11 @@ class DateBoxMask extends DateBoxBase { } _clearValueHandler(e: ValueChangedEvent & DxEvent): void { - this._isClearingValue = true; - super._clearValueHandler(e); - this._isClearingValue = false; + try { + super._clearValueHandler(e); + } finally { + this._isClearingValue = false; + } } _focusInHandler(e: DxEvent & { relatedTarget: Element | dxElementWrapper }): void { From d532d2a0551605e326d30dfd72178862f0e0e428 Mon Sep 17 00:00:00 2001 From: Ruslan Farkhutdinov Date: Tue, 30 Jun 2026 11:47:12 +0300 Subject: [PATCH 7/7] DateBox: Fix re-focus from another element issue --- .../js/__internal/ui/date_box/date_box.mask.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 1e33c52a23e6..d463514d80be 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 @@ -4,6 +4,7 @@ import { getFormat } from '@js/common/core/localization/ldml/date.format'; import { getRegExpInfo } from '@js/common/core/localization/ldml/date.parser'; import numberLocalization from '@js/common/core/localization/number'; import devices from '@js/core/devices'; +import domAdapter from '@js/core/dom_adapter'; import type { dxElementWrapper } from '@js/core/renderer'; import browser from '@js/core/utils/browser'; import { clipboardText } from '@js/core/utils/dom'; @@ -57,7 +58,7 @@ class DateBoxMask extends DateBoxBase { _isComposing?: boolean; - _isReFocusing?: boolean; + _isWindowBlurred?: boolean; _hasUserTyped?: boolean; @@ -830,12 +831,16 @@ class DateBoxMask extends DateBoxBase { _focusInHandler(e: DxEvent & { relatedTarget: Element | dxElementWrapper }): void { super._focusInHandler(e); if (this._useMaskBehavior() && !e.isDefaultPrevented() && !this._isClearingValue) { - if (this._isReFocusing && this._hasUserTyped) { + const focusCameFromAnotherElement = isDefined(e.relatedTarget); + const shouldSelectFirstPart = !this._isWindowBlurred + && (focusCameFromAnotherElement || this._hasUserTyped); + + if (shouldSelectFirstPart) { this._selectFirstPart(); - this._hasUserTyped = false; } - this._isReFocusing = false; + this._hasUserTyped = false; + this._isWindowBlurred = false; } } @@ -843,7 +848,7 @@ class DateBoxMask extends DateBoxBase { const shouldFireChangeEvent = this._useMaskBehavior() && !e.isDefaultPrevented(); if (shouldFireChangeEvent) { - this._isReFocusing = true; + this._isWindowBlurred = !domAdapter.getDocument().hasFocus(); this._fireChangeEvent(); super._focusOutHandler(e); } else {