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');