From 1e43b419a648ed1a35968b08c75179d9af74cdd2 Mon Sep 17 00:00:00 2001 From: EugeniyKiyashko Date: Tue, 14 May 2024 12:03:46 +0400 Subject: [PATCH] DateBox, DateView, DateViewRollers, TimeView: ignore errors after move to TS --- .../__internal/ui/date_box/m_date_box.base.ts | 1351 +++++++++-------- .../ui/date_box/m_date_box.mask.parts.ts | 237 +-- .../__internal/ui/date_box/m_date_box.mask.ts | 1220 ++++++++------- .../date_box/m_date_box.strategy.calendar.ts | 390 ++--- .../m_date_box.strategy.calendar_with_time.ts | 316 ++-- .../date_box/m_date_box.strategy.date_view.ts | 245 +-- .../ui/date_box/m_date_box.strategy.list.ts | 562 +++---- .../ui/date_box/m_date_box.strategy.native.ts | 117 +- .../ui/date_box/m_date_box.strategy.ts | 185 +-- .../js/__internal/ui/date_box/m_date_box.ts | 5 +- .../js/__internal/ui/date_box/m_date_utils.ts | 431 +++--- .../js/__internal/ui/date_box/m_date_view.ts | 642 ++++---- .../ui/date_box/m_date_view_roller.ts | 599 ++++---- .../js/__internal/ui/date_box/m_time_view.ts | 576 +++---- .../strategy/m_rangeCalendar.ts | 2 +- packages/devextreme/js/ui/date_box.js | 4 +- .../devextreme/js/ui/date_box/ui.date_view.js | 3 + .../js/ui/date_box/ui.date_view_roller.js | 3 + .../devextreme/js/ui/date_box/ui.time_view.js | 3 + .../DevExpress.knockout/datebox.tests.js | 2 +- .../dateView.tests.js | 4 +- .../datebox.markup.tests.js | 2 +- .../datebox.mask.tests.js | 2 +- .../datebox.tests.js | 2 +- 24 files changed, 3476 insertions(+), 3427 deletions(-) create mode 100644 packages/devextreme/js/ui/date_box/ui.date_view.js create mode 100644 packages/devextreme/js/ui/date_box/ui.date_view_roller.js create mode 100644 packages/devextreme/js/ui/date_box/ui.time_view.js diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.base.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.base.ts index 4ffd13a2e8a1..0cbf242984d3 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.base.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.base.ts @@ -1,25 +1,26 @@ -import { getWindow, hasWindow } from '../../core/utils/window'; +import config from '@js/core/config'; +import devices from '@js/core/devices'; +import browser from '@js/core/utils/browser'; +import dateUtils from '@js/core/utils/date'; +import dateSerialization from '@js/core/utils/date_serialization'; +import { createTextElementHiddenCopy } from '@js/core/utils/dom'; +import { extend } from '@js/core/utils/extend'; +import { each } from '@js/core/utils/iterator'; +import { inputType } from '@js/core/utils/support'; +import { isDate as isDateType, isNumeric, isString } from '@js/core/utils/type'; +import { getWindow, hasWindow } from '@js/core/utils/window'; +import dateLocalization from '@js/localization/date'; +import messageLocalization from '@js/localization/message'; +import DropDownEditor from '@js/ui/drop_down_editor/ui.drop_down_editor'; + +import Calendar from './m_date_box.strategy.calendar'; +import CalendarWithTime from './m_date_box.strategy.calendar_with_time'; +import DateView from './m_date_box.strategy.date_view'; +import List from './m_date_box.strategy.list'; +import Native from './m_date_box.strategy.native'; +import uiDateUtils from './m_date_utils'; + const window = getWindow(); -import { isDate as isDateType, isString, isNumeric } from '../../core/utils/type'; -import { createTextElementHiddenCopy } from '../../core/utils/dom'; -import { each } from '../../core/utils/iterator'; -import { extend } from '../../core/utils/extend'; -import { inputType } from '../../core/utils/support'; -import devices from '../../core/devices'; -import browser from '../../core/utils/browser'; -import config from '../../core/config'; -import dateUtils from '../../core/utils/date'; -import uiDateUtils from './ui.date_utils'; -import dateSerialization from '../../core/utils/date_serialization'; -import DropDownEditor from '../drop_down_editor/ui.drop_down_editor'; -import dateLocalization from '../../localization/date'; -import messageLocalization from '../../localization/message'; - -import Calendar from './ui.date_box.strategy.calendar'; -import DateView from './ui.date_box.strategy.date_view'; -import Native from './ui.date_box.strategy.native'; -import CalendarWithTime from './ui.date_box.strategy.calendar_with_time'; -import List from './ui.date_box.strategy.list'; const DATEBOX_CLASS = 'dx-datebox'; const DX_AUTO_WIDTH_CLASS = 'dx-auto-width'; @@ -29,769 +30,769 @@ const DATEBOX_WRAPPER_CLASS = 'dx-datebox-wrapper'; const DROPDOWNEDITOR_OVERLAY_CLASS = 'dx-dropdowneditor-overlay'; const PICKER_TYPE = { - calendar: 'calendar', - rollers: 'rollers', - list: 'list', - native: 'native' + calendar: 'calendar', + rollers: 'rollers', + list: 'list', + native: 'native', }; const TYPE = { - date: 'date', - datetime: 'datetime', - time: 'time' + date: 'date', + datetime: 'datetime', + time: 'time', }; const STRATEGY_NAME = { - calendar: 'Calendar', - dateView: 'DateView', - native: 'Native', - calendarWithTime: 'CalendarWithTime', - list: 'List' + calendar: 'Calendar', + dateView: 'DateView', + native: 'Native', + calendarWithTime: 'CalendarWithTime', + list: 'List', }; const STRATEGY_CLASSES = { - Calendar, - DateView, - Native, - CalendarWithTime, - List + Calendar, + DateView, + Native, + CalendarWithTime, + List, }; +// @ts-expect-error const DateBox = DropDownEditor.inherit({ - _supportedKeys: function() { - return extend(this.callBase(), this._strategy.supportedKeys()); - }, - - _renderButtonContainers: function() { - this.callBase.apply(this, arguments); - this._strategy.customizeButtons(); - }, - - _getDefaultOptions: function() { - return extend(this.callBase(), { - type: 'date', - - showAnalogClock: true, - - value: null, - - dateSerializationFormat: undefined, - - min: undefined, - - max: undefined, - - - displayFormat: null, - - interval: 30, - - disabledDates: null, - - pickerType: PICKER_TYPE.calendar, - - invalidDateMessage: messageLocalization.format('dxDateBox-validation-datetime'), - - dateOutOfRangeMessage: messageLocalization.format('validation-range'), - - applyButtonText: messageLocalization.format('OK'), - - - adaptivityEnabled: false, - - calendarOptions: {}, + _supportedKeys() { + return extend(this.callBase(), this._strategy.supportedKeys()); + }, + + _renderButtonContainers() { + this.callBase.apply(this, arguments); + this._strategy.customizeButtons(); + }, + + _getDefaultOptions() { + return extend(this.callBase(), { + type: 'date', + + showAnalogClock: true, + + value: null, + + dateSerializationFormat: undefined, + + min: undefined, + + max: undefined, + + displayFormat: null, + + interval: 30, + + disabledDates: null, + + pickerType: PICKER_TYPE.calendar, + + invalidDateMessage: messageLocalization.format('dxDateBox-validation-datetime'), + + dateOutOfRangeMessage: messageLocalization.format('validation-range'), + + applyButtonText: messageLocalization.format('OK'), + + adaptivityEnabled: false, + + calendarOptions: {}, + + useHiddenSubmitElement: true, + + _showValidationIcon: true, + }); + }, + + _defaultOptionsRules() { + return this.callBase().concat([ + { + device: { platform: 'ios' }, + options: { + 'dropDownOptions.showTitle': true, + }, + }, + { + device: { platform: 'android' }, + options: { + buttonsLocation: 'bottom after', + }, + }, + { + device() { + const realDevice = devices.real(); + const { platform } = realDevice; + return platform === 'ios' || platform === 'android'; + }, + options: { + pickerType: PICKER_TYPE.native, + }, + }, + { + device: { + platform: 'generic', + deviceType: 'desktop', + }, + options: { + buttonsLocation: 'bottom after', + }, + }, + ]); + }, + + _initOptions(options) { + this._userOptions = extend({}, options); + this.callBase(options); + this._updatePickerOptions(); + }, + + _updatePickerOptions() { + let pickerType = this.option('pickerType'); + const type = this.option('type'); + + if (pickerType === PICKER_TYPE.list && (type === TYPE.datetime || type === TYPE.date)) { + pickerType = PICKER_TYPE.calendar; + } - useHiddenSubmitElement: true, + if (type === TYPE.time && pickerType === PICKER_TYPE.calendar) { + pickerType = PICKER_TYPE.list; + } - _showValidationIcon: true - }); - }, + this._pickerType = pickerType; - _defaultOptionsRules: function() { - return this.callBase().concat([ - { - device: { platform: 'ios' }, - options: { - 'dropDownOptions.showTitle': true - } - }, - { - device: { platform: 'android' }, - options: { - buttonsLocation: 'bottom after' - } - }, - { - device: function() { - const realDevice = devices.real(); - const platform = realDevice.platform; - return platform === 'ios' || platform === 'android'; - }, - options: { - pickerType: PICKER_TYPE.native - } - }, - { - device: { - platform: 'generic', - deviceType: 'desktop' - }, - options: { - buttonsLocation: 'bottom after' - } - } - ]); - }, + this._setShowDropDownButtonOption(); + }, - _initOptions: function(options) { - this._userOptions = extend({}, options); - this.callBase(options); - this._updatePickerOptions(); - }, + _setShowDropDownButtonOption() { + const { platform } = devices.real(); + const isMozillaOnAndroid = platform === 'android' && browser.mozilla; + const isNativePickerType = this._isNativeType(); + let showDropDownButton = platform !== 'generic' || !isNativePickerType; - _updatePickerOptions: function() { - let pickerType = this.option('pickerType'); - const type = this.option('type'); + if (isNativePickerType && isMozillaOnAndroid) { // T1197922 + showDropDownButton = false; + } - if(pickerType === PICKER_TYPE.list && (type === TYPE.datetime || type === TYPE.date)) { - pickerType = PICKER_TYPE.calendar; - } + this.option({ showDropDownButton }); + }, - if(type === TYPE.time && pickerType === PICKER_TYPE.calendar) { - pickerType = PICKER_TYPE.list; - } + _init() { + this._initStrategy(); + this.option(extend({}, this._strategy.getDefaultOptions(), this._userOptions)); + delete this._userOptions; + this.callBase(); + }, - this._pickerType = pickerType; + _toLowerCaseFirstLetter(string) { + return string.charAt(0).toLowerCase() + string.substr(1); + }, - this._setShowDropDownButtonOption(); - }, + _initStrategy() { + const strategyName = this._getStrategyName(this._getFormatType()); + const strategy = STRATEGY_CLASSES[strategyName]; - _setShowDropDownButtonOption() { - const platform = devices.real().platform; - const isMozillaOnAndroid = platform === 'android' && browser.mozilla; - const isNativePickerType = this._isNativeType(); - let showDropDownButton = platform !== 'generic' || !isNativePickerType; + if (!(this._strategy && this._strategy.NAME === strategyName)) { + // eslint-disable-next-line new-cap + this._strategy = new strategy(this); + } + }, - if(isNativePickerType && isMozillaOnAndroid) { // T1197922 - showDropDownButton = false; - } + _getFormatType() { + const currentType = this.option('type'); + const isTime = /h|m|s/g.test(currentType); + const isDate = /d|M|Y/g.test(currentType); + let type = ''; - this.option({ showDropDownButton }); - }, + if (isDate) { + type += TYPE.date; + } - _init: function() { - this._initStrategy(); - this.option(extend({}, this._strategy.getDefaultOptions(), this._userOptions)); - delete this._userOptions; - this.callBase(); - }, + if (isTime) { + type += TYPE.time; + } - _toLowerCaseFirstLetter: function(string) { - return string.charAt(0).toLowerCase() + string.substr(1); - }, + return type; + }, - _initStrategy: function() { - const strategyName = this._getStrategyName(this._getFormatType()); - const strategy = STRATEGY_CLASSES[strategyName]; + _getStrategyName(type) { + const pickerType = this._pickerType; - if(!(this._strategy && this._strategy.NAME === strategyName)) { - this._strategy = new strategy(this); - } - }, + if (pickerType === PICKER_TYPE.rollers) { + return STRATEGY_NAME.dateView; + } if (pickerType === PICKER_TYPE.native) { + return STRATEGY_NAME.native; + } - _getFormatType: function() { - const currentType = this.option('type'); - const isTime = /h|m|s/g.test(currentType); - const isDate = /d|M|Y/g.test(currentType); - let type = ''; + if (type === TYPE.date) { + return STRATEGY_NAME.calendar; + } - if(isDate) { - type += TYPE.date; - } + if (type === TYPE.datetime) { + return STRATEGY_NAME.calendarWithTime; + } - if(isTime) { - type += TYPE.time; - } + return STRATEGY_NAME.list; + }, - return type; - }, + _initMarkup() { + this.$element().addClass(DATEBOX_CLASS); - _getStrategyName: function(type) { - const pickerType = this._pickerType; + this.callBase(); - if(pickerType === PICKER_TYPE.rollers) { - return STRATEGY_NAME.dateView; - } else if(pickerType === PICKER_TYPE.native) { - return STRATEGY_NAME['native']; - } + this._refreshFormatClass(); + this._refreshPickerTypeClass(); - if(type === TYPE.date) { - return STRATEGY_NAME.calendar; - } + this._strategy.renderInputMinMax(this._input()); + }, - if(type === TYPE.datetime) { - return STRATEGY_NAME.calendarWithTime; - } + _render() { + this.callBase(); - return STRATEGY_NAME.list; - }, + this._formatValidationIcon(); + }, - _initMarkup: function() { - this.$element().addClass(DATEBOX_CLASS); + _renderDimensions() { + this.callBase(); + this.$element().toggleClass(DX_AUTO_WIDTH_CLASS, !this.option('width')); - this.callBase(); + this._updatePopupWidth(); + this._updatePopupHeight(); + }, - this._refreshFormatClass(); - this._refreshPickerTypeClass(); + _dimensionChanged() { + this.callBase(); - this._strategy.renderInputMinMax(this._input()); - }, + this._updatePopupHeight(); + }, - _render: function() { - this.callBase(); + _updatePopupHeight() { + if (this._popup) { + this._strategy._updatePopupHeight?.(); + } + }, - this._formatValidationIcon(); - }, + _refreshFormatClass() { + const $element = this.$element(); - _renderDimensions: function() { - this.callBase(); - this.$element().toggleClass(DX_AUTO_WIDTH_CLASS, !this.option('width')); + each(TYPE, (_, item) => { + $element.removeClass(`${DATEBOX_CLASS}-${item}`); + }); - this._updatePopupWidth(); - this._updatePopupHeight(); - }, + $element.addClass(`${DATEBOX_CLASS}-${this.option('type')}`); + }, - _dimensionChanged: function() { - this.callBase(); + _refreshPickerTypeClass() { + const $element = this.$element(); - this._updatePopupHeight(); - }, + each(PICKER_TYPE, (_, item) => { + $element.removeClass(`${DATEBOX_CLASS}-${item}`); + }); - _updatePopupHeight: function() { - if(this._popup) { - this._strategy._updatePopupHeight?.(); - } - }, + $element.addClass(`${DATEBOX_CLASS}-${this._pickerType}`); + }, - _refreshFormatClass: function() { - const $element = this.$element(); + _formatValidationIcon() { + if (!hasWindow()) { + return; + } - each(TYPE, function(_, item) { - $element.removeClass(DATEBOX_CLASS + '-' + item); - }); + const inputElement = this._input().get(0); + const isRtlEnabled = this.option('rtlEnabled'); + const clearButtonWidth = this._getClearButtonWidth(); + const longestElementDimensions = this._getLongestElementDimensions(); + const curWidth = parseFloat(window.getComputedStyle(inputElement).width) - clearButtonWidth; + const shouldHideValidationIcon = longestElementDimensions.width > curWidth; + const { style } = inputElement; + + this.$element().toggleClass(DX_INVALID_BADGE_CLASS, !shouldHideValidationIcon && this.option('_showValidationIcon')); + + if (shouldHideValidationIcon) { + if (this._storedPadding === undefined) { + this._storedPadding = isRtlEnabled ? longestElementDimensions.leftPadding : longestElementDimensions.rightPadding; + } + isRtlEnabled ? style.paddingLeft = 0 : style.paddingRight = 0; + } else { + isRtlEnabled ? style.paddingLeft = `${this._storedPadding}px` : style.paddingRight = `${this._storedPadding}px`; + } + }, - $element.addClass(DATEBOX_CLASS + '-' + this.option('type')); - }, + _getClearButtonWidth() { + let clearButtonWidth = 0; + if (this._isClearButtonVisible() && this._input().val() === '') { + const clearButtonElement = this.$element().find(`.${DX_CLEAR_BUTTON_CLASS}`).get(0); + clearButtonWidth = parseFloat(window.getComputedStyle(clearButtonElement).width); + } - _refreshPickerTypeClass: function() { - const $element = this.$element(); + return clearButtonWidth; + }, + + _getLongestElementDimensions() { + const format = this._strategy.getDisplayFormat(this.option('displayFormat')); + const longestValue = dateLocalization.format(uiDateUtils.getLongestDate(format, dateLocalization.getMonthNames(), dateLocalization.getDayNames()), format); + const $input = this._input(); + const inputElement = $input.get(0); + const $longestValueElement = createTextElementHiddenCopy($input, longestValue); + const isPaddingStored = this._storedPadding !== undefined; + const storedPadding = !isPaddingStored ? 0 : this._storedPadding; + + $longestValueElement.appendTo(this.$element()); + const elementWidth = parseFloat(window.getComputedStyle($longestValueElement.get(0)).width); + const rightPadding = parseFloat(window.getComputedStyle(inputElement).paddingRight); + const leftPadding = parseFloat(window.getComputedStyle(inputElement).paddingLeft); + const necessaryWidth = elementWidth + leftPadding + rightPadding + storedPadding; + $longestValueElement.remove(); + + return { + width: necessaryWidth, + leftPadding, + rightPadding, + }; + }, + + _getKeyboardListeners() { + return this.callBase().concat([this._strategy && this._strategy.getKeyboardListener()]); + }, + + _renderPopup() { + this.callBase(); + this._popup.$wrapper().addClass(DATEBOX_WRAPPER_CLASS); + this._renderPopupWrapper(); + }, + + _getPopupToolbarItems() { + const defaultItems = this.callBase(); + + return this._strategy._getPopupToolbarItems?.(defaultItems) ?? defaultItems; + }, + + _popupConfig() { + const popupConfig = this.callBase(); + return extend(this._strategy.popupConfig(popupConfig), { + title: this._getPopupTitle(), + dragEnabled: false, + }); + }, + + _renderPopupWrapper() { + if (!this._popup) { + return; + } - each(PICKER_TYPE, function(_, item) { - $element.removeClass(DATEBOX_CLASS + '-' + item); - }); + const $element = this.$element(); + const classPostfixes = extend({}, TYPE, PICKER_TYPE); + + each(classPostfixes, (_, item) => { + $element.removeClass(`${DATEBOX_WRAPPER_CLASS}-${item}`); + }); + + this._popup.$wrapper() + .addClass(`${DATEBOX_WRAPPER_CLASS}-${this.option('type')}`) + .addClass(`${DATEBOX_WRAPPER_CLASS}-${this._pickerType}`) + .addClass(DROPDOWNEDITOR_OVERLAY_CLASS); + }, + + _renderPopupContent() { + this.callBase(); + this._strategy.renderPopupContent(); + }, + + _popupShowingHandler() { + this.callBase(); + this._strategy.popupShowingHandler(); + }, + + _popupShownHandler() { + this.callBase(); + this._strategy.renderOpenedState(); + }, + + _popupHiddenHandler() { + this.callBase(); + this._strategy.renderOpenedState(); + this._strategy.popupHiddenHandler(); + }, + + _visibilityChanged(visible) { + if (visible) { + this._formatValidationIcon(); + } + }, - $element.addClass(DATEBOX_CLASS + '-' + this._pickerType); - }, + _clearValueHandler(e) { + this.option('text', ''); + this.callBase(e); + }, - _formatValidationIcon: function() { - if(!hasWindow()) { - return; - } + _readOnlyPropValue() { + if (this._pickerType === PICKER_TYPE.rollers) { + return true; + } - const inputElement = this._input().get(0); - const isRtlEnabled = this.option('rtlEnabled'); - const clearButtonWidth = this._getClearButtonWidth(); - const longestElementDimensions = this._getLongestElementDimensions(); - const curWidth = parseFloat(window.getComputedStyle(inputElement).width) - clearButtonWidth; - const shouldHideValidationIcon = (longestElementDimensions.width > curWidth); - const style = inputElement.style; - - this.$element().toggleClass(DX_INVALID_BADGE_CLASS, !shouldHideValidationIcon && this.option('_showValidationIcon')); - - if(shouldHideValidationIcon) { - if(this._storedPadding === undefined) { - this._storedPadding = isRtlEnabled ? longestElementDimensions.leftPadding : longestElementDimensions.rightPadding; - } - isRtlEnabled ? style.paddingLeft = 0 : style.paddingRight = 0; - } else { - isRtlEnabled ? style.paddingLeft = this._storedPadding + 'px' : style.paddingRight = this._storedPadding + 'px'; - } - }, + const { platform } = devices.real(); + const isCustomValueDisabled = this._isNativeType() && (platform === 'ios' || platform === 'android'); - _getClearButtonWidth: function() { - let clearButtonWidth = 0; - if(this._isClearButtonVisible() && this._input().val() === '') { - const clearButtonElement = this.$element().find('.' + DX_CLEAR_BUTTON_CLASS).get(0); - clearButtonWidth = parseFloat(window.getComputedStyle(clearButtonElement).width); - } + if (isCustomValueDisabled) { + return this.option('readOnly'); + } - return clearButtonWidth; - }, - - _getLongestElementDimensions: function() { - const format = this._strategy.getDisplayFormat(this.option('displayFormat')); - const longestValue = dateLocalization.format(uiDateUtils.getLongestDate(format, dateLocalization.getMonthNames(), dateLocalization.getDayNames()), format); - const $input = this._input(); - const inputElement = $input.get(0); - const $longestValueElement = createTextElementHiddenCopy($input, longestValue); - const isPaddingStored = this._storedPadding !== undefined; - const storedPadding = !isPaddingStored ? 0 : this._storedPadding; - - $longestValueElement.appendTo(this.$element()); - const elementWidth = parseFloat(window.getComputedStyle($longestValueElement.get(0)).width); - const rightPadding = parseFloat(window.getComputedStyle(inputElement).paddingRight); - const leftPadding = parseFloat(window.getComputedStyle(inputElement).paddingLeft); - const necessaryWidth = elementWidth + leftPadding + rightPadding + storedPadding; - $longestValueElement.remove(); - - return { - width: necessaryWidth, - leftPadding: leftPadding, - rightPadding: rightPadding - }; - }, - - _getKeyboardListeners() { - return this.callBase().concat([this._strategy && this._strategy.getKeyboardListener()]); - }, - - _renderPopup: function() { - this.callBase(); - this._popup.$wrapper().addClass(DATEBOX_WRAPPER_CLASS); - this._renderPopupWrapper(); - }, + return this.callBase(); + }, - _getPopupToolbarItems() { - const defaultItems = this.callBase(); + _isClearButtonVisible() { + return this.callBase() && !this._isNativeType(); + }, - return this._strategy._getPopupToolbarItems?.(defaultItems) ?? defaultItems; - }, + _renderValue() { + const value = this.dateOption('value'); - _popupConfig: function() { - const popupConfig = this.callBase(); - return extend(this._strategy.popupConfig(popupConfig), { - title: this._getPopupTitle(), - dragEnabled: false - }); - }, + this.option('text', this._getDisplayedText(value)); + this._strategy.renderValue(); - _renderPopupWrapper: function() { - if(!this._popup) { - return; - } + return this.callBase(); + }, - const $element = this.$element(); - const classPostfixes = extend({}, TYPE, PICKER_TYPE); - - each(classPostfixes, (function(_, item) { - $element.removeClass(DATEBOX_WRAPPER_CLASS + '-' + item); - }).bind(this)); - - this._popup.$wrapper() - .addClass(DATEBOX_WRAPPER_CLASS + '-' + this.option('type')) - .addClass(DATEBOX_WRAPPER_CLASS + '-' + this._pickerType) - .addClass(DROPDOWNEDITOR_OVERLAY_CLASS); - }, - - _renderPopupContent: function() { - this.callBase(); - this._strategy.renderPopupContent(); - }, - - _popupShowingHandler: function() { - this.callBase(); - this._strategy.popupShowingHandler(); - }, - - _popupShownHandler: function() { - this.callBase(); - this._strategy.renderOpenedState(); - }, - - _popupHiddenHandler: function() { - this.callBase(); - this._strategy.renderOpenedState(); - this._strategy.popupHiddenHandler(); - }, - - _visibilityChanged: function(visible) { - if(visible) { - this._formatValidationIcon(); - } - }, + _setSubmitValue() { + const value = this.dateOption('value'); + const dateSerializationFormat = this.option('dateSerializationFormat'); + const submitFormat = uiDateUtils.SUBMIT_FORMATS_MAP[this.option('type')]; + const submitValue = dateSerializationFormat ? dateSerialization.serializeDate(value, dateSerializationFormat) : uiDateUtils.toStandardDateFormat(value, submitFormat); - _clearValueHandler: function(e) { - this.option('text', ''); - this.callBase(e); - }, + this._getSubmitElement().val(submitValue); + }, - _readOnlyPropValue: function() { - if(this._pickerType === PICKER_TYPE.rollers) { - return true; - } + _getDisplayedText(value) { + const mode = this.option('mode'); + let displayedText; - const platform = devices.real().platform; - const isCustomValueDisabled = this._isNativeType() && (platform === 'ios' || platform === 'android'); + if (mode === 'text') { + const displayFormat = this._strategy.getDisplayFormat(this.option('displayFormat')); + displayedText = dateLocalization.format(value, displayFormat); + } else { + const format = this._getFormatByMode(mode); - if(isCustomValueDisabled) { - return this.option('readOnly'); - } - - return this.callBase(); - }, - - _isClearButtonVisible: function() { - return this.callBase() && !this._isNativeType(); - }, - - _renderValue: function() { - const value = this.dateOption('value'); + if (format) { + displayedText = dateLocalization.format(value, format); + } else { + displayedText = uiDateUtils.toStandardDateFormat(value, mode); + } + } - this.option('text', this._getDisplayedText(value)); - this._strategy.renderValue(); + return displayedText; + }, - return this.callBase(); - }, + _getFormatByMode(mode) { + return inputType(mode) ? null : uiDateUtils.FORMATS_MAP[mode]; + }, - _setSubmitValue: function() { - const value = this.dateOption('value'); - const dateSerializationFormat = this.option('dateSerializationFormat'); - const submitFormat = uiDateUtils.SUBMIT_FORMATS_MAP[this.option('type')]; - const submitValue = dateSerializationFormat ? dateSerialization.serializeDate(value, dateSerializationFormat) : uiDateUtils.toStandardDateFormat(value, submitFormat); + _valueChangeEventHandler(e) { + const { text, type, validationError } = this.option(); + const currentValue = this.dateOption('value'); - this._getSubmitElement().val(submitValue); - }, + if (text === this._getDisplayedText(currentValue)) { + this._recallInternalValidation(currentValue, validationError); + return; + } - _getDisplayedText: function(value) { - const mode = this.option('mode'); - let displayedText; + const parsedDate = this._getParsedDate(text); + const value = currentValue ?? this._getDateByDefault(); + const newValue = uiDateUtils.mergeDates(value, parsedDate, type); + const date = parsedDate && type === 'time' ? newValue : parsedDate; - if(mode === 'text') { - const displayFormat = this._strategy.getDisplayFormat(this.option('displayFormat')); - displayedText = dateLocalization.format(value, displayFormat); - } else { - const format = this._getFormatByMode(mode); + if (this._applyInternalValidation(date).isValid) { + const displayedText = this._getDisplayedText(newValue); - if(format) { - displayedText = dateLocalization.format(value, format); - } else { - displayedText = uiDateUtils.toStandardDateFormat(value, mode); - } - } + if (value && newValue && value.getTime() === newValue.getTime() && displayedText !== text) { + this._renderValue(); + } else { + this.dateValue(newValue, e); + } + } + }, - return displayedText; - }, + _recallInternalValidation(value, validationError) { + if (!validationError || validationError.editorSpecific) { + this._applyInternalValidation(value); + this._applyCustomValidation(value); + } + }, + + _getDateByDefault() { + return this._strategy.useCurrentDateByDefault() && this._strategy.getDefaultDate(); + }, + + _getParsedDate(text) { + const displayFormat = this._strategy.getDisplayFormat(this.option('displayFormat')); + const parsedText = this._strategy.getParsedText(text, displayFormat); + + return parsedText ?? undefined; + }, + + _applyInternalValidation(value) { + const text = this.option('text'); + const hasText = !!text && value !== null; + const isDate = !!value && isDateType(value) && !isNaN(value.getTime()); + const isDateInRange = isDate && dateUtils.dateInRange(value, this.dateOption('min'), this.dateOption('max'), this.option('type')); + const isValid = !hasText && !value || isDateInRange; + let validationMessage = ''; + + if (!isDate) { + validationMessage = this.option('invalidDateMessage'); + } else if (!isDateInRange) { + validationMessage = this.option('dateOutOfRangeMessage'); + } - _getFormatByMode: function(mode) { - return inputType(mode) ? null : uiDateUtils.FORMATS_MAP[mode]; - }, + this._updateInternalValidationState(isValid, validationMessage); + + return { + isValid, + isDate, + }; + }, + + _updateInternalValidationState(isValid, validationMessage) { + this.option({ + isValid, + validationError: isValid ? null : { + editorSpecific: true, + message: validationMessage, + }, + }); + }, + + _applyCustomValidation(value) { + this.validationRequest.fire({ + editor: this, + value: this._serializeDate(value), + }); + }, + + _isValueChanged(newValue) { + const oldValue = this.dateOption('value'); + const oldTime = oldValue && oldValue.getTime(); + const newTime = newValue && newValue.getTime(); + + return oldTime !== newTime; + }, + + _isTextChanged(newValue) { + const oldText = this.option('text'); + const newText = newValue && this._getDisplayedText(newValue) || ''; + + return oldText !== newText; + }, + + _renderProps() { + this.callBase(); + this._input().attr('autocomplete', 'off'); + }, + + _renderOpenedState() { + if (!this._isNativeType()) { + this.callBase(); + } - _valueChangeEventHandler: function(e) { - const { text, type, validationError } = this.option(); - const currentValue = this.dateOption('value'); + if (this._strategy.isAdaptivityChanged()) { + this._refreshStrategy(); + } + }, - if(text === this._getDisplayedText(currentValue)) { - this._recallInternalValidation(currentValue, validationError); - return; - } + _getPopupTitle() { + const placeholder = this.option('placeholder'); - const parsedDate = this._getParsedDate(text); - const value = currentValue ?? this._getDateByDefault(); - const newValue = uiDateUtils.mergeDates(value, parsedDate, type); - const date = parsedDate && type === 'time' ? newValue : parsedDate; + if (placeholder) { + return placeholder; + } - if(this._applyInternalValidation(date).isValid) { - const displayedText = this._getDisplayedText(newValue); + const type = this.option('type'); - if(value && newValue && value.getTime() === newValue.getTime() && displayedText !== text) { - this._renderValue(); - } else { - this.dateValue(newValue, e); - } - } - }, + if (type === TYPE.time) { + return messageLocalization.format('dxDateBox-simulatedDataPickerTitleTime'); + } - _recallInternalValidation(value, validationError) { - if(!validationError || validationError.editorSpecific) { - this._applyInternalValidation(value); - this._applyCustomValidation(value); - } - }, - - _getDateByDefault: function() { - return this._strategy.useCurrentDateByDefault() && this._strategy.getDefaultDate(); - }, - - _getParsedDate: function(text) { - const displayFormat = this._strategy.getDisplayFormat(this.option('displayFormat')); - const parsedText = this._strategy.getParsedText(text, displayFormat); - - return parsedText ?? undefined; - }, - - _applyInternalValidation(value) { - const text = this.option('text'); - const hasText = !!text && value !== null; - const isDate = !!value && isDateType(value) && !isNaN(value.getTime()); - const isDateInRange = isDate && dateUtils.dateInRange(value, this.dateOption('min'), this.dateOption('max'), this.option('type')); - const isValid = !hasText && !value || isDateInRange; - let validationMessage = ''; - - if(!isDate) { - validationMessage = this.option('invalidDateMessage'); - } else if(!isDateInRange) { - validationMessage = this.option('dateOutOfRangeMessage'); - } + if (type === TYPE.date || type === TYPE.datetime) { + return messageLocalization.format('dxDateBox-simulatedDataPickerTitleDate'); + } - this._updateInternalValidationState(isValid, validationMessage); - - return { - isValid, - isDate - }; - }, - - _updateInternalValidationState(isValid, validationMessage) { - this.option({ - isValid: isValid, - validationError: isValid ? null : { - editorSpecific: true, - message: validationMessage - } - }); - }, - - _applyCustomValidation: function(value) { - this.validationRequest.fire({ - editor: this, - value: this._serializeDate(value) - }); - }, - - _isValueChanged: function(newValue) { - const oldValue = this.dateOption('value'); - const oldTime = oldValue && oldValue.getTime(); - const newTime = newValue && newValue.getTime(); - - return oldTime !== newTime; - }, - - _isTextChanged: function(newValue) { - const oldText = this.option('text'); - const newText = newValue && this._getDisplayedText(newValue) || ''; - - return oldText !== newText; - }, - - _renderProps: function() { - this.callBase(); - this._input().attr('autocomplete', 'off'); - }, - - _renderOpenedState: function() { - if(!this._isNativeType()) { - this.callBase(); - } + return ''; + }, - if(this._strategy.isAdaptivityChanged()) { - this._refreshStrategy(); - } - }, + _refreshStrategy() { + this._strategy.dispose(); + this._initStrategy(); + this.option(this._strategy.getDefaultOptions()); + this._refresh(); + }, - _getPopupTitle: function() { - const placeholder = this.option('placeholder'); + _applyButtonHandler(e) { + const value = this._strategy.getValue(); + this.dateValue(value, e.event); - if(placeholder) { - return placeholder; - } + this.callBase(); + }, - const type = this.option('type'); + _dispose() { + this.callBase(); + this._strategy?.dispose(); + }, - if(type === TYPE.time) { - return messageLocalization.format('dxDateBox-simulatedDataPickerTitleTime'); - } + _isNativeType() { + return this._pickerType === PICKER_TYPE.native; + }, - if(type === TYPE.date || type === TYPE.datetime) { - return messageLocalization.format('dxDateBox-simulatedDataPickerTitleDate'); - } + _updatePopupTitle() { + this._popup?.option('title', this._getPopupTitle()); + }, - return ''; - }, - - _refreshStrategy: function() { - this._strategy.dispose(); - this._initStrategy(); - this.option(this._strategy.getDefaultOptions()); - this._refresh(); - }, - - _applyButtonHandler: function(e) { - const value = this._strategy.getValue(); - this.dateValue(value, e.event); - - this.callBase(); - }, - - _dispose: function() { - this.callBase(); - this._strategy?.dispose(); - }, - - _isNativeType: function() { - return this._pickerType === PICKER_TYPE.native; - }, - - _updatePopupTitle: function() { - this._popup?.option('title', this._getPopupTitle()); - }, - - _optionChanged: function(args) { - switch(args.name) { - case 'showClearButton': - case 'buttons': - this.callBase.apply(this, arguments); - this._formatValidationIcon(); - break; - case 'pickerType': - this._updatePickerOptions({ pickerType: args.value }); - this._refreshStrategy(); - this._refreshPickerTypeClass(); - this._invalidate(); - break; - case 'type': - this._updatePickerOptions({ format: args.value }); - this._refreshStrategy(); - this._refreshFormatClass(); - this._renderPopupWrapper(); - this._formatValidationIcon(); - this._updateValue(); - break; - case 'placeholder': - this.callBase.apply(this, arguments); - this._updatePopupTitle(); - break; - case 'min': - case 'max': { - const isValid = this.option('isValid'); - this._applyInternalValidation(this.dateOption('value')); - if(!isValid) { - this._applyCustomValidation(this.dateOption('value')); - } - this._invalidate(); - break; - } - case 'dateSerializationFormat': - case 'interval': - case 'disabledDates': - case 'calendarOptions': - this._invalidate(); - break; - case 'displayFormat': - this.option('text', this._getDisplayedText(this.dateOption('value'))); - this._renderInputValue(); - break; - case 'text': - this._strategy.textChangedHandler(args.value); - this.callBase.apply(this, arguments); - break; - case 'isValid': - this.callBase.apply(this, arguments); - this._formatValidationIcon(); - break; - case 'showDropDownButton': - this._formatValidationIcon(); - this.callBase.apply(this, arguments); - break; - case 'readOnly': - this.callBase.apply(this, arguments); - this._formatValidationIcon(); - break; - case 'todayButtonText': - this._setPopupOption('toolbarItems', this._getPopupToolbarItems()); - break; - case 'invalidDateMessage': - case 'dateOutOfRangeMessage': - case 'adaptivityEnabled': - case 'showAnalogClock': - case '_showValidationIcon': - break; - default: - this.callBase.apply(this, arguments); - } - }, + _optionChanged(args) { + switch (args.name) { + case 'showClearButton': + case 'buttons': + this.callBase.apply(this, arguments); + this._formatValidationIcon(); + break; + case 'pickerType': + this._updatePickerOptions({ pickerType: args.value }); + this._refreshStrategy(); + this._refreshPickerTypeClass(); + this._invalidate(); + break; + case 'type': + this._updatePickerOptions({ format: args.value }); + this._refreshStrategy(); + this._refreshFormatClass(); + this._renderPopupWrapper(); + this._formatValidationIcon(); + this._updateValue(); + break; + case 'placeholder': + this.callBase.apply(this, arguments); + this._updatePopupTitle(); + break; + case 'min': + case 'max': { + const isValid = this.option('isValid'); + this._applyInternalValidation(this.dateOption('value')); + if (!isValid) { + this._applyCustomValidation(this.dateOption('value')); + } + this._invalidate(); + break; + } + case 'dateSerializationFormat': + case 'interval': + case 'disabledDates': + case 'calendarOptions': + this._invalidate(); + break; + case 'displayFormat': + this.option('text', this._getDisplayedText(this.dateOption('value'))); + this._renderInputValue(); + break; + case 'text': + this._strategy.textChangedHandler(args.value); + this.callBase.apply(this, arguments); + break; + case 'isValid': + this.callBase.apply(this, arguments); + this._formatValidationIcon(); + break; + case 'showDropDownButton': + this._formatValidationIcon(); + this.callBase.apply(this, arguments); + break; + case 'readOnly': + this.callBase.apply(this, arguments); + this._formatValidationIcon(); + break; + case 'todayButtonText': + this._setPopupOption('toolbarItems', this._getPopupToolbarItems()); + break; + case 'invalidDateMessage': + case 'dateOutOfRangeMessage': + case 'adaptivityEnabled': + case 'showAnalogClock': + case '_showValidationIcon': + break; + default: + this.callBase.apply(this, arguments); + } + }, - _getSerializationFormat: function() { - const value = this.option('value'); + _getSerializationFormat() { + const value = this.option('value'); - if(this.option('dateSerializationFormat') && config().forceIsoDateParsing) { - return this.option('dateSerializationFormat'); - } + if (this.option('dateSerializationFormat') && config().forceIsoDateParsing) { + return this.option('dateSerializationFormat'); + } - if(isNumeric(value)) { - return 'number'; - } + if (isNumeric(value)) { + return 'number'; + } - if(!isString(value)) { - return; - } + if (!isString(value)) { + return; + } - return dateSerialization.getDateSerializationFormat(value); - }, + return dateSerialization.getDateSerializationFormat(value); + }, - _updateValue: function(value) { - this.callBase(); - this._applyInternalValidation(value ?? this.dateOption('value')); - }, + _updateValue(value) { + this.callBase(); + this._applyInternalValidation(value ?? this.dateOption('value')); + }, - dateValue: function(value, dxEvent) { - const isValueChanged = this._isValueChanged(value); + dateValue(value, dxEvent) { + const isValueChanged = this._isValueChanged(value); - if(isValueChanged && dxEvent) { - this._saveValueChangeEvent(dxEvent); - } + if (isValueChanged && dxEvent) { + this._saveValueChangeEvent(dxEvent); + } - if(!isValueChanged) { - if(this._isTextChanged(value)) { - this._updateValue(value); - } else if(this.option('text') === '') { - this._applyCustomValidation(value); - } - } + if (!isValueChanged) { + if (this._isTextChanged(value)) { + this._updateValue(value); + } else if (this.option('text') === '') { + this._applyCustomValidation(value); + } + } - return this.dateOption('value', value); - }, + return this.dateOption('value', value); + }, - dateOption: function(optionName, value) { - if(arguments.length === 1) { - return dateSerialization.deserializeDate(this.option(optionName)); - } + dateOption(optionName, value) { + if (arguments.length === 1) { + return dateSerialization.deserializeDate(this.option(optionName)); + } - this.option(optionName, this._serializeDate(value)); - }, + this.option(optionName, this._serializeDate(value)); + }, - _serializeDate: function(date) { - const serializationFormat = this._getSerializationFormat(); - return dateSerialization.serializeDate(date, serializationFormat); - }, + _serializeDate(date) { + const serializationFormat = this._getSerializationFormat(); + return dateSerialization.serializeDate(date, serializationFormat); + }, - _clearValue: function() { - const value = this.option('value'); + _clearValue() { + const value = this.option('value'); - this.callBase(); - if(value === null) { - this._applyCustomValidation(null); - } - }, + this.callBase(); + if (value === null) { + this._applyCustomValidation(null); + } + }, - clear: function() { - const value = this.option('value'); + clear() { + const value = this.option('value'); - this.callBase(); - if(value === null) { - this._applyInternalValidation(null); - } + this.callBase(); + if (value === null) { + this._applyInternalValidation(null); } + }, }); export default DateBox; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.mask.parts.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.mask.parts.ts index 94024e4fd206..7b0f21534d6a 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.mask.parts.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.mask.parts.ts @@ -1,145 +1,152 @@ -import { getPatternSetters } from '../../localization/ldml/date.parser'; -import { extend } from '../../core/utils/extend'; -import { fitIntoRange } from '../../core/utils/math'; -import { noop } from '../../core/utils/common'; +import { noop } from '@js/core/utils/common'; +import { extend } from '@js/core/utils/extend'; +import { fitIntoRange } from '@js/core/utils/math'; +import { getPatternSetters } from '@js/localization/ldml/date.parser'; -const monthGetter = (date) => { - return date.getMonth() + 1; -}; +const monthGetter = (date) => date.getMonth() + 1; const monthSetter = (date, value) => { - const day = date.getDate(); - const monthLimits = getLimits('M', date); - const newValue = fitIntoRange(parseInt(value), monthLimits.min, monthLimits.max); - - date.setMonth(newValue - 1, 1); - - const { min, max } = getLimits('dM', date); - const newDay = fitIntoRange(day, min, max); - - date.setDate(newDay); + const day = date.getDate(); + // @ts-expect-error + const monthLimits = getLimits('M', date); + // eslint-disable-next-line radix + const newValue = fitIntoRange(parseInt(value), monthLimits.min, monthLimits.max); + + date.setMonth(newValue - 1, 1); + // @ts-expect-error + const { min, max } = getLimits('dM', date); + const newDay = fitIntoRange(day, min, max); + + date.setDate(newDay); }; const PATTERN_GETTERS = { - a: (date) => date.getHours() < 12 ? 0 : 1, - E: 'getDay', - y: 'getFullYear', - M: monthGetter, - L: monthGetter, - d: 'getDate', - H: 'getHours', - h: 'getHours', - m: 'getMinutes', - s: 'getSeconds', - S: 'getMilliseconds' + a: (date) => (date.getHours() < 12 ? 0 : 1), + E: 'getDay', + y: 'getFullYear', + M: monthGetter, + L: monthGetter, + d: 'getDate', + H: 'getHours', + h: 'getHours', + m: 'getMinutes', + s: 'getSeconds', + S: 'getMilliseconds', }; const PATTERN_SETTERS = extend({}, getPatternSetters(), { - a: (date, value) => { - const hours = date.getHours(); - const current = hours >= 12; + a: (date, value) => { + const hours = date.getHours(); + const current = hours >= 12; - if(current === !!(parseInt(value))) { - return; - } - - date.setHours((hours + 12) % 24); - }, - d: (date, value) => { - const lastDayInMonth = getLimits('dM', date).max; + // eslint-disable-next-line radix + if (current === !!parseInt(value)) { + return; + } - if(value > lastDayInMonth) { - date.setMonth(date.getMonth() + 1); - } + date.setHours((hours + 12) % 24); + }, + d: (date, value) => { + // @ts-expect-error + const lastDayInMonth = getLimits('dM', date).max; - date.setDate(value); - }, - h: (date, value) => { - const isPM = date.getHours() >= 12; - date.setHours((+value % 12) + (isPM ? 12 : 0)); - }, - M: monthSetter, - L: monthSetter, - E: (date, value) => { - if(value < 0) { - return; - } - date.setDate(date.getDate() - date.getDay() + parseInt(value)); - }, - y: (date, value) => { - const currentYear = date.getFullYear(); - const valueLength = String(value).length; - const maxLimitLength = String(getLimits('y', date).max).length; - const newValue = parseInt(String(currentYear).substr(0, maxLimitLength - valueLength) + value); + if (value > lastDayInMonth) { + date.setMonth(date.getMonth() + 1); + } - date.setFullYear(newValue); + date.setDate(value); + }, + h: (date, value) => { + const isPM = date.getHours() >= 12; + date.setHours((+value % 12) + (isPM ? 12 : 0)); + }, + M: monthSetter, + L: monthSetter, + E: (date, value) => { + if (value < 0) { + return; } + // eslint-disable-next-line radix + date.setDate(date.getDate() - date.getDay() + parseInt(value)); + }, + y: (date, value) => { + const currentYear = date.getFullYear(); + const valueLength = String(value).length; + // @ts-expect-error + const maxLimitLength = String(getLimits('y', date).max).length; + // eslint-disable-next-line radix + const newValue = parseInt(String(currentYear).substr(0, maxLimitLength - valueLength) + value); + + date.setFullYear(newValue); + }, }); const getPatternGetter = (patternChar) => { - const unsupportedCharGetter = () => patternChar; - return PATTERN_GETTERS[patternChar] || unsupportedCharGetter; + const unsupportedCharGetter = () => patternChar; + return PATTERN_GETTERS[patternChar] || unsupportedCharGetter; }; export const renderDateParts = (text, regExpInfo) => { - const result = regExpInfo.regexp.exec(text); - - let start = 0; - let end = 0; - const sections = []; - - for(let i = 1; i < result.length; i++) { - start = end; - end = start + result[i].length; - - const pattern = regExpInfo.patterns[i - 1].replace(/^'|'$/g, ''); - const getter = getPatternGetter(pattern[0]); - - sections.push({ - index: i - 1, - isStub: pattern === result[i], - caret: { start: start, end: end }, - pattern: pattern, - text: result[i], - limits: (...args) => getLimits(pattern[0], ...args), - setter: PATTERN_SETTERS[pattern[0]] || noop, - getter: getter - }); - } - - return sections; + const result = regExpInfo.regexp.exec(text); + + let start = 0; + let end = 0; + const sections = []; + + for (let i = 1; i < result.length; i++) { + start = end; + end = start + result[i].length; + + const pattern = regExpInfo.patterns[i - 1].replace(/^'|'$/g, ''); + const getter = getPatternGetter(pattern[0]); + + // @ts-expect-error + sections.push({ + index: i - 1, + isStub: pattern === result[i], + caret: { start, end }, + pattern, + text: result[i], + // @ts-expect-error + limits: (...args) => getLimits(pattern[0], ...args), + setter: PATTERN_SETTERS[pattern[0]] || noop, + getter, + }); + } + + return sections; }; const getLimits = (pattern, date, forcedPattern) => { - const limits = { - y: { min: 0, max: 9999 }, - M: { min: 1, max: 12 }, - L: { min: 1, max: 12 }, - d: { min: 1, max: 31 }, - dM: { - min: 1, - max: new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate() - }, - E: { min: 0, max: 6 }, - H: { min: 0, max: 23 }, - h: { min: 1, max: 12 }, - m: { min: 0, max: 59 }, - s: { min: 0, max: 59 }, - S: { min: 0, max: 999 }, - a: { min: 0, max: 1 } - }; - - return limits[forcedPattern || pattern] || limits['getAmPm']; + const limits = { + y: { min: 0, max: 9999 }, + M: { min: 1, max: 12 }, + L: { min: 1, max: 12 }, + d: { min: 1, max: 31 }, + dM: { + min: 1, + max: new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(), + }, + E: { min: 0, max: 6 }, + H: { min: 0, max: 23 }, + h: { min: 1, max: 12 }, + m: { min: 0, max: 59 }, + s: { min: 0, max: 59 }, + S: { min: 0, max: 999 }, + a: { min: 0, max: 1 }, + }; + // @ts-expect-error + return limits[forcedPattern || pattern] || limits.getAmPm; }; export const getDatePartIndexByPosition = (dateParts, position) => { - for(let i = 0; i < dateParts.length; i++) { - const caretInGroup = dateParts[i].caret.end >= position; + for (let i = 0; i < dateParts.length; i++) { + const caretInGroup = dateParts[i].caret.end >= position; - if(!dateParts[i].isStub && caretInGroup) { - return i; - } + if (!dateParts[i].isStub && caretInGroup) { + return i; } + } - return null; + return null; }; 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 a95d53cb22fd..3cc127de11ac 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 @@ -1,18 +1,21 @@ -import { addNamespace, normalizeKeyName, isCommandKeyPressed } from '../../events/utils/index'; -import { isFunction, isString, isDate, isDefined } from '../../core/utils/type'; -import { clipboardText } from '../../core/utils/dom'; -import { extend } from '../../core/utils/extend'; -import { fitIntoRange, inRange, sign } from '../../core/utils/math'; -import eventsEngine from '../../events/core/events_engine'; -import { getDatePartIndexByPosition, renderDateParts } from './ui.date_box.mask.parts'; -import dateLocalization from '../../localization/date'; -import { getRegExpInfo } from '../../localization/ldml/date.parser'; -import { getFormat } from '../../localization/ldml/date.format'; -import DateBoxBase from './ui.date_box.base'; -import numberLocalization from '../../localization/number'; -import devices from '../../core/devices'; -import browser from '../../core/utils/browser'; -import defaultDateNames from '../../localization/default_date_names'; +import devices from '@js/core/devices'; +import browser from '@js/core/utils/browser'; +import { clipboardText } from '@js/core/utils/dom'; +import { extend } from '@js/core/utils/extend'; +import { fitIntoRange, inRange, sign } from '@js/core/utils/math'; +import { + isDate, isDefined, isFunction, isString, +} from '@js/core/utils/type'; +import eventsEngine from '@js/events/core/events_engine'; +import { addNamespace, isCommandKeyPressed, normalizeKeyName } from '@js/events/utils/index'; +import dateLocalization from '@js/localization/date'; +import defaultDateNames from '@js/localization/default_date_names'; +import { getFormat } from '@js/localization/ldml/date.format'; +import { getRegExpInfo } from '@js/localization/ldml/date.parser'; +import numberLocalization from '@js/localization/number'; + +import DateBoxBase from './m_date_box.base'; +import { getDatePartIndexByPosition, renderDateParts } from './m_date_box.mask.parts'; const MASK_EVENT_NAMESPACE = 'dateBoxMask'; const FORWARD = 1; @@ -20,685 +23,672 @@ const BACKWARD = -1; const DateBoxMask = DateBoxBase.inherit({ - _supportedKeys(e) { - const originalHandlers = this.callBase(e); - const callOriginalHandler = (e) => { - const originalHandler = originalHandlers[normalizeKeyName(e)]; - return originalHandler && originalHandler.apply(this, [e]); - }; - const applyHandler = (e, maskHandler) => { - if(this._shouldUseOriginalHandler(e)) { - return callOriginalHandler.apply(this, [e]); - } else { - return maskHandler.apply(this, [e]); - } - }; - - return extend({}, originalHandlers, { - del: (e) => { - return applyHandler(e, (event) => { - this._revertPart(FORWARD); - this._isAllSelected() || event.preventDefault(); - }); - }, - backspace: (e) => { - return applyHandler(e, (event) => { - this._revertPart(BACKWARD); - this._isAllSelected() || event.preventDefault(); - }); - }, - home: (e) => { - return applyHandler(e, (event) => { - this._selectFirstPart(); - event.preventDefault(); - }); - }, - end: (e) => { - return applyHandler(e, (event) => { - this._selectLastPart(); - event.preventDefault(); - }); - }, - escape: (e) => { - return applyHandler(e, (event) => { - this._revertChanges(event); - }); - }, - enter: (e) => { - return applyHandler(e, () => { - this._enterHandler(); - }); - }, - leftArrow: (e) => { - return applyHandler(e, (event) => { - this._selectNextPart(BACKWARD); - event.preventDefault(); - }); - }, - rightArrow: (e) => { - return applyHandler(e, (event) => { - this._selectNextPart(FORWARD); - event.preventDefault(); - }); - }, - upArrow: (e) => { - return applyHandler(e, (event) => { - this._upDownArrowHandler(FORWARD); - event.preventDefault(); - }); - }, - downArrow: (e) => { - return applyHandler(e, (event) => { - this._upDownArrowHandler(BACKWARD); - event.preventDefault(); - }); - } - }); - }, - - _shouldUseOriginalHandler(e) { - const keysToHandleByMask = ['backspace', 'del']; - const isNotDeletingInCalendar = this.option('opened') && e && keysToHandleByMask.indexOf(normalizeKeyName(e)) === -1; - - return !this._useMaskBehavior() || isNotDeletingInCalendar || (e && e.altKey); - }, - - _upDownArrowHandler(step) { - this._setNewDateIfEmpty(); - - const originalValue = this._getActivePartValue(this._initialMaskValue); - const currentValue = this._getActivePartValue(); - const delta = currentValue - originalValue; - - this._loadMaskValue(this._initialMaskValue); - - this._changePartValue(delta + step, true); - }, - - _changePartValue(step, lockOtherParts) { - const activePartPattern = this._getActivePartProp('pattern'); - const isAmPmPartActive = /^a{1,5}$/.test(activePartPattern); - - if(isAmPmPartActive) { - this._toggleAmPm(); - } else { - this._partIncrease(step, lockOtherParts); - } - }, - - _toggleAmPm() { - const currentValue = this._getActivePartProp('text'); - const indexOfCurrentValue = defaultDateNames.getPeriodNames().indexOf(currentValue); - const newValue = indexOfCurrentValue ^ 1; - this._setActivePartValue(newValue); - }, - - _getDefaultOptions() { - return extend(this.callBase(), { - - useMaskBehavior: false, - - emptyDateValue: new Date(2000, 0, 1, 0, 0, 0) - }); - }, - - _isSingleCharKey({ originalEvent, alt }) { - const key = originalEvent.data || originalEvent.key; - return typeof key === 'string' && key.length === 1 && !alt && !isCommandKeyPressed(originalEvent); - }, - - _isSingleDigitKey(e) { - const data = e.originalEvent?.data; - return data?.length === 1 && parseInt(data, 10); - }, - - _useBeforeInputEvent: function() { - return devices.real().android; - }, - - _keyInputHandler(e, key) { - const oldInputValue = this._input().val(); - this._processInputKey(key); - e.preventDefault(); - const isValueChanged = oldInputValue !== this._input().val(); - isValueChanged && eventsEngine.trigger(this._input(), 'input'); - }, - - _keyboardHandler(e) { - let key = e.originalEvent.key; - - const result = this.callBase(e); - - if(!this._useMaskBehavior() || this._useBeforeInputEvent()) { - return result; - } - - if((browser.chrome) && e.key === 'Process' && e.code.indexOf('Digit') === 0) { - key = e.code.replace('Digit', ''); - this._processInputKey(key); - this._maskInputHandler = () => { - this._renderSelectedPart(); - }; - } else if(this._isSingleCharKey(e)) { - this._keyInputHandler(e.originalEvent, key); - } - - return result; - }, - - _maskBeforeInputHandler(e) { - this._maskInputHandler = null; - - const { inputType } = e.originalEvent; - - if(inputType === 'insertCompositionText') { - this._maskInputHandler = () => { - this._renderSelectedPart(); - }; - } - - 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; - } + _supportedKeys(e) { + const originalHandlers = this.callBase(e); + const callOriginalHandler = (e) => { + // @ts-expect-error + const originalHandler = originalHandlers[normalizeKeyName(e)]; + return originalHandler && originalHandler.apply(this, [e]); + }; + const applyHandler = (e, maskHandler) => { + if (this._shouldUseOriginalHandler(e)) { + return callOriginalHandler.apply(this, [e]); + } + return maskHandler.apply(this, [e]); + }; + + return extend({}, originalHandlers, { + del: (e) => applyHandler(e, (event) => { + this._revertPart(FORWARD); + this._isAllSelected() || event.preventDefault(); + }), + backspace: (e) => applyHandler(e, (event) => { + this._revertPart(BACKWARD); + this._isAllSelected() || event.preventDefault(); + }), + home: (e) => applyHandler(e, (event) => { + this._selectFirstPart(); + event.preventDefault(); + }), + end: (e) => applyHandler(e, (event) => { + this._selectLastPart(); + event.preventDefault(); + }), + escape: (e) => applyHandler(e, (event) => { + this._revertChanges(event); + }), + enter: (e) => applyHandler(e, () => { + this._enterHandler(); + }), + leftArrow: (e) => applyHandler(e, (event) => { + this._selectNextPart(BACKWARD); + event.preventDefault(); + }), + rightArrow: (e) => applyHandler(e, (event) => { + this._selectNextPart(FORWARD); + event.preventDefault(); + }), + upArrow: (e) => applyHandler(e, (event) => { + this._upDownArrowHandler(FORWARD); + event.preventDefault(); + }), + downArrow: (e) => applyHandler(e, (event) => { + this._upDownArrowHandler(BACKWARD); + event.preventDefault(); + }), + }); + }, + + _shouldUseOriginalHandler(e) { + const keysToHandleByMask = ['backspace', 'del']; + // @ts-expect-error + const isNotDeletingInCalendar = this.option('opened') && e && !keysToHandleByMask.includes(normalizeKeyName(e)); + + return !this._useMaskBehavior() || isNotDeletingInCalendar || (e && e.altKey); + }, + + _upDownArrowHandler(step) { + this._setNewDateIfEmpty(); + + const originalValue = this._getActivePartValue(this._initialMaskValue); + const currentValue = this._getActivePartValue(); + const delta = currentValue - originalValue; + + this._loadMaskValue(this._initialMaskValue); + + this._changePartValue(delta + step, true); + }, + + _changePartValue(step, lockOtherParts) { + const activePartPattern = this._getActivePartProp('pattern'); + const isAmPmPartActive = /^a{1,5}$/.test(activePartPattern); + + if (isAmPmPartActive) { + this._toggleAmPm(); + } else { + this._partIncrease(step, lockOtherParts); + } + }, + + _toggleAmPm() { + const currentValue = this._getActivePartProp('text'); + const indexOfCurrentValue = defaultDateNames.getPeriodNames().indexOf(currentValue); + const newValue = indexOfCurrentValue ^ 1; + this._setActivePartValue(newValue); + }, + + _getDefaultOptions() { + return extend(this.callBase(), { + + useMaskBehavior: false, + + emptyDateValue: new Date(2000, 0, 1, 0, 0, 0), + }); + }, + + _isSingleCharKey({ originalEvent, alt }) { + const key = originalEvent.data || originalEvent.key; + return typeof key === 'string' && key.length === 1 && !alt && !isCommandKeyPressed(originalEvent); + }, + + _isSingleDigitKey(e) { + const data = e.originalEvent?.data; + return data?.length === 1 && parseInt(data, 10); + }, + + _useBeforeInputEvent() { + return devices.real().android; + }, + + _keyInputHandler(e, key) { + const oldInputValue = this._input().val(); + this._processInputKey(key); + e.preventDefault(); + const isValueChanged = oldInputValue !== this._input().val(); + // @ts-expect-error + isValueChanged && eventsEngine.trigger(this._input(), 'input'); + }, + + _keyboardHandler(e) { + let { key } = e.originalEvent; + + const result = this.callBase(e); + + if (!this._useMaskBehavior() || this._useBeforeInputEvent()) { + return result; + } - const key = e.originalEvent.data; - this._keyInputHandler(e, key); + if (browser.chrome && e.key === 'Process' && e.code.indexOf('Digit') === 0) { + key = e.code.replace('Digit', ''); + this._processInputKey(key); + this._maskInputHandler = () => { + this._renderSelectedPart(); + }; + } else if (this._isSingleCharKey(e)) { + this._keyInputHandler(e.originalEvent, key); + } - return true; - }, + return result; + }, - _keyPressHandler(e) { - const { originalEvent: event } = e; - if(event?.inputType === 'insertCompositionText' && this._isSingleDigitKey(e)) { - this._processInputKey(event.data); - this._renderDisplayText(this._getDisplayedText(this._maskValue)); - this._selectNextPart(); - } - this.callBase(e); + _maskBeforeInputHandler(e) { + this._maskInputHandler = null; - if(this._maskInputHandler) { - this._maskInputHandler(); - this._maskInputHandler = null; - } - }, + const { inputType } = e.originalEvent; - _processInputKey(key) { - if(this._isAllSelected()) { - this._activePartIndex = 0; - } - this._setNewDateIfEmpty(); - if(isNaN(parseInt(key))) { - this._searchString(key); - } else { - this._searchNumber(key); - } - }, + if (inputType === 'insertCompositionText') { + this._maskInputHandler = () => { + this._renderSelectedPart(); + }; + } - _isAllSelected() { - const caret = this._caret(); + const isBackwardDeletion = inputType === 'deleteContentBackward'; + const isForwardDeletion = inputType === 'deleteContentForward'; + if (isBackwardDeletion || isForwardDeletion) { + const direction = isBackwardDeletion ? BACKWARD : FORWARD; + this._maskInputHandler = () => { + this._revertPart(); + this._selectNextPart(direction); + }; + } - return caret.end - caret.start === this.option('text').length; - }, + if (!this._useMaskBehavior() || !this._isSingleCharKey(e)) { + return; + } - _getFormatPattern() { - if(this._formatPattern) { - return this._formatPattern; - } + const key = e.originalEvent.data; + this._keyInputHandler(e, key); - const format = this._strategy.getDisplayFormat(this.option('displayFormat')); - const isLDMLPattern = isString(format) && !dateLocalization._getPatternByFormat(format); + return true; + }, - if(isLDMLPattern) { - this._formatPattern = format; - } else { - this._formatPattern = getFormat(function(value) { - return dateLocalization.format(value, format); - }); - } + _keyPressHandler(e) { + const { originalEvent: event } = e; + if (event?.inputType === 'insertCompositionText' && this._isSingleDigitKey(e)) { + this._processInputKey(event.data); + this._renderDisplayText(this._getDisplayedText(this._maskValue)); + this._selectNextPart(); + } + this.callBase(e); - return this._formatPattern; - }, + if (this._maskInputHandler) { + this._maskInputHandler(); + this._maskInputHandler = null; + } + }, - _setNewDateIfEmpty() { - if(!this._maskValue) { - const value = this.option('type') === 'time' ? new Date(null) : new Date(); - this._maskValue = value; - this._initialMaskValue = value; - this._renderDateParts(); - } - }, - - _partLimitsReached(max) { - const maxLimitLength = String(max).length; - const formatLength = this._getActivePartProp('pattern').length; - const isShortFormat = formatLength === 1; - const maxSearchLength = isShortFormat ? maxLimitLength : Math.min(formatLength, maxLimitLength); - const isLengthExceeded = this._searchValue.length === maxSearchLength; - const isValueOverflowed = parseInt(this._searchValue + '0') > max; - - return isLengthExceeded || isValueOverflowed; - }, - - _searchNumber(char) { - const { max } = this._getActivePartLimits(); - const maxLimitLength = String(max).length; - - this._searchValue = (this._searchValue + char).substr(-maxLimitLength); - if(isNaN(this._searchValue)) { - this._searchValue = char; - } + _processInputKey(key) { + if (this._isAllSelected()) { + this._activePartIndex = 0; + } + this._setNewDateIfEmpty(); + // eslint-disable-next-line radix + if (isNaN(parseInt(key))) { + this._searchString(key); + } else { + this._searchNumber(key); + } + }, - this._setActivePartValue(this._searchValue); + _isAllSelected() { + const caret = this._caret(); - if(this._partLimitsReached(max)) { - this._selectNextPart(FORWARD); - } - }, + return caret.end - caret.start === this.option('text').length; + }, - _searchString(char) { - if(!isNaN(parseInt(this._getActivePartProp('text')))) { - return; - } + _getFormatPattern() { + if (this._formatPattern) { + return this._formatPattern; + } - const limits = this._getActivePartProp('limits')(this._maskValue); - const startString = this._searchValue + char.toLowerCase(); - const endLimit = limits.max - limits.min; + const format = this._strategy.getDisplayFormat(this.option('displayFormat')); + // @ts-expect-error + const isLDMLPattern = isString(format) && !dateLocalization._getPatternByFormat(format); - for(let i = 0; i <= endLimit; i++) { - this._loadMaskValue(this._initialMaskValue); + if (isLDMLPattern) { + this._formatPattern = format; + } else { + this._formatPattern = getFormat((value) => dateLocalization.format(value, format)); + } - this._changePartValue(i + 1); + return this._formatPattern; + }, - if(this._getActivePartProp('text').toLowerCase().indexOf(startString) === 0) { - this._searchValue = startString; - return; - } - } + _setNewDateIfEmpty() { + if (!this._maskValue) { + // @ts-expect-error + const value = this.option('type') === 'time' ? new Date(null) : new Date(); + this._maskValue = value; + this._initialMaskValue = value; + this._renderDateParts(); + } + }, + + _partLimitsReached(max) { + const maxLimitLength = String(max).length; + const formatLength = this._getActivePartProp('pattern').length; + const isShortFormat = formatLength === 1; + const maxSearchLength = isShortFormat ? maxLimitLength : Math.min(formatLength, maxLimitLength); + const isLengthExceeded = this._searchValue.length === maxSearchLength; + // eslint-disable-next-line radix + const isValueOverflowed = parseInt(`${this._searchValue}0`) > max; + + return isLengthExceeded || isValueOverflowed; + }, + + _searchNumber(char) { + const { max } = this._getActivePartLimits(); + const maxLimitLength = String(max).length; + + this._searchValue = (this._searchValue + char).substr(-maxLimitLength); + if (isNaN(this._searchValue)) { + this._searchValue = char; + } - this._setNewDateIfEmpty(); + this._setActivePartValue(this._searchValue); - if(this._searchValue) { - this._clearSearchValue(); - this._searchString(char); - } - }, + if (this._partLimitsReached(max)) { + this._selectNextPart(FORWARD); + } + }, - _clearSearchValue() { - this._searchValue = ''; - }, + _searchString(char) { + // eslint-disable-next-line radix + if (!isNaN(parseInt(this._getActivePartProp('text')))) { + return; + } - _revertPart: function(direction) { - if(!this._isAllSelected()) { - const actual = this._getActivePartValue(this.option('emptyDateValue')); - this._setActivePartValue(actual); + const limits = this._getActivePartProp('limits')(this._maskValue); + const startString = this._searchValue + char.toLowerCase(); + const endLimit = limits.max - limits.min; - this._selectNextPart(direction); - } - this._clearSearchValue(); - }, - - _useMaskBehavior() { - return this.option('useMaskBehavior') && this.option('mode') === 'text'; - }, - - _prepareRegExpInfo() { - this._regExpInfo = getRegExpInfo(this._getFormatPattern(), dateLocalization); - const regexp = this._regExpInfo.regexp; - const source = regexp.source; - const flags = regexp.flags; - const quantifierRegexp = new RegExp(/(\{[0-9]+,?[0-9]*\})/); - - const convertedSource = source - .split(quantifierRegexp) - .map((sourcePart) => { - return quantifierRegexp.test(sourcePart) ? - sourcePart : - numberLocalization.convertDigits(sourcePart, false); - }) - .join(''); - this._regExpInfo.regexp = new RegExp(convertedSource, flags); - }, - - _initMaskState() { - this._activePartIndex = 0; - this._formatPattern = null; - this._prepareRegExpInfo(); - this._loadMaskValue(); - }, + for (let i = 0; i <= endLimit; i++) { + this._loadMaskValue(this._initialMaskValue); - _renderMask() { - this.callBase(); - this._detachMaskEvents(); - this._clearMaskState(); + this._changePartValue(i + 1); - if(this._useMaskBehavior()) { - this._attachMaskEvents(); - this._initMaskState(); - this._renderDateParts(); - } - }, + if (this._getActivePartProp('text').toLowerCase().indexOf(startString) === 0) { + this._searchValue = startString; + return; + } + } - _renderDateParts() { - if(!this._useMaskBehavior()) { - return; - } + this._setNewDateIfEmpty(); - const text = this.option('text') || this._getDisplayedText(this._maskValue); + if (this._searchValue) { + this._clearSearchValue(); + this._searchString(char); + } + }, - if(text) { - this._dateParts = renderDateParts(text, this._regExpInfo); - if(!this._input().is(':hidden')) { - this._selectNextPart(); - } - } - }, + _clearSearchValue() { + this._searchValue = ''; + }, - _detachMaskEvents() { - eventsEngine.off(this._input(), '.' + MASK_EVENT_NAMESPACE); - }, + _revertPart(direction) { + if (!this._isAllSelected()) { + const actual = this._getActivePartValue(this.option('emptyDateValue')); + this._setActivePartValue(actual); - _attachMaskEvents() { - eventsEngine.on(this._input(), addNamespace('dxclick', MASK_EVENT_NAMESPACE), this._maskClickHandler.bind(this)); - eventsEngine.on(this._input(), addNamespace('paste', MASK_EVENT_NAMESPACE), this._maskPasteHandler.bind(this)); - eventsEngine.on(this._input(), addNamespace('drop', MASK_EVENT_NAMESPACE), () => { - this._renderSelectedPart(); - }); + this._selectNextPart(direction); + } + this._clearSearchValue(); + }, + + _useMaskBehavior() { + return this.option('useMaskBehavior') && this.option('mode') === 'text'; + }, + + _prepareRegExpInfo() { + this._regExpInfo = getRegExpInfo(this._getFormatPattern(), dateLocalization); + const { regexp } = this._regExpInfo; + const { source } = regexp; + const { flags } = regexp; + const quantifierRegexp = new RegExp(/(\{[0-9]+,?[0-9]*\})/); + + const convertedSource = source + .split(quantifierRegexp) + .map((sourcePart) => (quantifierRegexp.test(sourcePart) + ? sourcePart + : numberLocalization.convertDigits(sourcePart, false))) + .join(''); + this._regExpInfo.regexp = new RegExp(convertedSource, flags); + }, + + _initMaskState() { + this._activePartIndex = 0; + this._formatPattern = null; + this._prepareRegExpInfo(); + this._loadMaskValue(); + }, + + _renderMask() { + this.callBase(); + this._detachMaskEvents(); + this._clearMaskState(); + + if (this._useMaskBehavior()) { + this._attachMaskEvents(); + this._initMaskState(); + this._renderDateParts(); + } + }, - eventsEngine.on(this._input(), addNamespace('compositionend', MASK_EVENT_NAMESPACE), this._maskCompositionEndHandler.bind(this)); + _renderDateParts() { + if (!this._useMaskBehavior()) { + return; + } - if(this._useBeforeInputEvent()) { - eventsEngine.on(this._input(), addNamespace('beforeinput', MASK_EVENT_NAMESPACE), this._maskBeforeInputHandler.bind(this)); - } - }, + const text = this.option('text') || this._getDisplayedText(this._maskValue); - _renderSelectedPart() { - this._renderDisplayText(this._getDisplayedText(this._maskValue)); + if (text) { + this._dateParts = renderDateParts(text, this._regExpInfo); + if (!this._input().is(':hidden')) { this._selectNextPart(); - }, - - _selectLastPart() { - if(this.option('text')) { - this._activePartIndex = this._dateParts.length; - this._selectNextPart(BACKWARD); - } - }, - - _selectFirstPart() { - if(this.option('text')) { - this._activePartIndex = -1; - this._selectNextPart(FORWARD); - } - }, - - _onMouseWheel(e) { - if(this._useMaskBehavior()) { - this._partIncrease(e.delta > 0 ? FORWARD : BACKWARD, e); - } - }, + } + } + }, - _selectNextPart(step = 0) { - if(!this.option('text') || this._disposed) { - return; - } + _detachMaskEvents() { + eventsEngine.off(this._input(), `.${MASK_EVENT_NAMESPACE}`); + }, - if(step) { - this._initialMaskValue = new Date(this._maskValue); - } + _attachMaskEvents() { + eventsEngine.on(this._input(), addNamespace('dxclick', MASK_EVENT_NAMESPACE), this._maskClickHandler.bind(this)); + eventsEngine.on(this._input(), addNamespace('paste', MASK_EVENT_NAMESPACE), this._maskPasteHandler.bind(this)); + eventsEngine.on(this._input(), addNamespace('drop', MASK_EVENT_NAMESPACE), () => { + this._renderSelectedPart(); + }); - let index = fitIntoRange(this._activePartIndex + step, 0, this._dateParts.length - 1); - if(this._dateParts[index].isStub) { - const isBoundaryIndex = index === 0 && step < 0 || index === this._dateParts.length - 1 && step > 0; - if(!isBoundaryIndex) { - this._selectNextPart(step >= 0 ? step + 1 : step - 1); - return; - } else { - index = this._activePartIndex; - } - } + eventsEngine.on(this._input(), addNamespace('compositionend', MASK_EVENT_NAMESPACE), this._maskCompositionEndHandler.bind(this)); - if(this._activePartIndex !== index) { - this._clearSearchValue(); - } - - this._activePartIndex = index; - this._caret(this._getActivePartProp('caret')); - }, + if (this._useBeforeInputEvent()) { + eventsEngine.on(this._input(), addNamespace('beforeinput', MASK_EVENT_NAMESPACE), this._maskBeforeInputHandler.bind(this)); + } + }, - _getRealLimitsPattern() { - if(this._getActivePartProp('pattern')[0] === 'd') { - return 'dM'; - } - }, + _renderSelectedPart() { + this._renderDisplayText(this._getDisplayedText(this._maskValue)); + this._selectNextPart(); + }, - _getActivePartLimits(lockOtherParts) { - const limitFunction = this._getActivePartProp('limits'); - return limitFunction(this._maskValue, lockOtherParts && this._getRealLimitsPattern()); - }, + _selectLastPart() { + if (this.option('text')) { + this._activePartIndex = this._dateParts.length; + this._selectNextPart(BACKWARD); + } + }, - _getActivePartValue(dateValue) { - dateValue = dateValue || this._maskValue; - const getter = this._getActivePartProp('getter'); - return isFunction(getter) ? getter(dateValue) : dateValue[getter](); - }, + _selectFirstPart() { + if (this.option('text')) { + this._activePartIndex = -1; + this._selectNextPart(FORWARD); + } + }, - _addLeadingZeroes(value) { - const zeroes = this._searchValue.match(/^0+/); - const limits = this._getActivePartLimits(); - const maxLimitLength = String(limits.max).length; + _onMouseWheel(e) { + if (this._useMaskBehavior()) { + this._partIncrease(e.delta > 0 ? FORWARD : BACKWARD, e); + } + }, - return ((zeroes && zeroes[0] || '') + String(value)).substr(-maxLimitLength); - }, + _selectNextPart(step = 0) { + if (!this.option('text') || this._disposed) { + return; + } - _setActivePartValue(value, dateValue) { - dateValue = dateValue || this._maskValue; - const setter = this._getActivePartProp('setter'); - const limits = this._getActivePartLimits(); + if (step) { + this._initialMaskValue = new Date(this._maskValue); + } - value = inRange(value, limits.min, limits.max) ? value : value % 10; - value = this._addLeadingZeroes(fitIntoRange(value, limits.min, limits.max)); + let index = fitIntoRange(this._activePartIndex + step, 0, this._dateParts.length - 1); + if (this._dateParts[index].isStub) { + const isBoundaryIndex = index === 0 && step < 0 || index === this._dateParts.length - 1 && step > 0; + if (!isBoundaryIndex) { + this._selectNextPart(step >= 0 ? step + 1 : step - 1); + return; + } + index = this._activePartIndex; + } - isFunction(setter) ? setter(dateValue, value) : dateValue[setter](value); - this._renderDisplayText(this._getDisplayedText(dateValue)); + if (this._activePartIndex !== index) { + this._clearSearchValue(); + } - this._renderDateParts(); - }, + this._activePartIndex = index; + this._caret(this._getActivePartProp('caret')); + }, - _getActivePartProp(property) { - if(!this._dateParts || !this._dateParts[this._activePartIndex]) { - return undefined; - } + // @ts-expect-error + _getRealLimitsPattern() { + if (this._getActivePartProp('pattern')[0] === 'd') { + return 'dM'; + } + }, - return this._dateParts[this._activePartIndex][property]; - }, + _getActivePartLimits(lockOtherParts) { + const limitFunction = this._getActivePartProp('limits'); + return limitFunction(this._maskValue, lockOtherParts && this._getRealLimitsPattern()); + }, - _loadMaskValue(value = this.dateOption('value')) { - this._maskValue = value && new Date(value); - this._initialMaskValue = value && new Date(value); - }, + _getActivePartValue(dateValue) { + dateValue = dateValue || this._maskValue; + const getter = this._getActivePartProp('getter'); + return isFunction(getter) ? getter(dateValue) : dateValue[getter](); + }, - _saveMaskValue() { - const value = this._maskValue && new Date(this._maskValue); - if(value && this.option('type') === 'date') { - value.setHours(0, 0, 0, 0); - } + _addLeadingZeroes(value) { + const zeroes = this._searchValue.match(/^0+/); + const limits = this._getActivePartLimits(); + const maxLimitLength = String(limits.max).length; - this._initialMaskValue = new Date(value); - this.dateOption('value', value); - }, + return ((zeroes && zeroes[0] || '') + String(value)).substr(-maxLimitLength); + }, - _revertChanges() { - this._loadMaskValue(); - this._renderDisplayText(this._getDisplayedText(this._maskValue)); - this._renderDateParts(); - }, + _setActivePartValue(value, dateValue) { + dateValue = dateValue || this._maskValue; + const setter = this._getActivePartProp('setter'); + const limits = this._getActivePartLimits(); - _renderDisplayText(text) { - this.callBase(text); - if(this._useMaskBehavior()) { - this.option('text', text); - } - }, + value = inRange(value, limits.min, limits.max) ? value : value % 10; + value = this._addLeadingZeroes(fitIntoRange(value, limits.min, limits.max)); - _partIncrease(step, lockOtherParts) { - this._setNewDateIfEmpty(); + isFunction(setter) ? setter(dateValue, value) : dateValue[setter](value); + this._renderDisplayText(this._getDisplayedText(dateValue)); - const { max, min } = this._getActivePartLimits(lockOtherParts); + this._renderDateParts(); + }, - let newValue = step + this._getActivePartValue(); + _getActivePartProp(property) { + if (!this._dateParts || !this._dateParts[this._activePartIndex]) { + return undefined; + } - if(newValue > max) { - newValue = this._applyLimits(newValue, { limitBase: min, limitClosest: max, max }); - } else if(newValue < min) { - newValue = this._applyLimits(newValue, { limitBase: max, limitClosest: min, max }); - } + return this._dateParts[this._activePartIndex][property]; + }, - this._setActivePartValue(newValue); - }, - - _applyLimits(newValue, { limitBase, limitClosest, max }) { - const delta = (newValue - limitClosest) % max; - return delta ? limitBase + delta - 1 * sign(delta) : limitClosest; - }, - - _maskClickHandler() { - this._loadMaskValue(this._maskValue); - if(this.option('text')) { - this._activePartIndex = getDatePartIndexByPosition(this._dateParts, this._caret().start); - - if(!this._isAllSelected()) { - if(isDefined(this._activePartIndex)) { - this._caret(this._getActivePartProp('caret')); - } else { - this._selectLastPart(); - } - } - } - }, + _loadMaskValue(value = this.dateOption('value')) { + this._maskValue = value && new Date(value); + this._initialMaskValue = value && new Date(value); + }, - _maskCompositionEndHandler(e) { - this._input().val(this._getDisplayedText(this._maskValue)); - this._selectNextPart(); + _saveMaskValue() { + const value = this._maskValue && new Date(this._maskValue); + if (value && this.option('type') === 'date') { + value.setHours(0, 0, 0, 0); + } - this._maskInputHandler = () => { - this._renderSelectedPart(); - }; - }, + this._initialMaskValue = new Date(value); + this.dateOption('value', value); + }, - _maskPasteHandler(e) { - const newText = this._replaceSelectedText(this.option('text'), this._caret(), clipboardText(e)); - const date = dateLocalization.parse(newText, this._getFormatPattern()); + _revertChanges() { + this._loadMaskValue(); + this._renderDisplayText(this._getDisplayedText(this._maskValue)); + this._renderDateParts(); + }, - if(date && this._isDateValid(date)) { - this._maskValue = date; - this._renderDisplayText(this._getDisplayedText(this._maskValue)); - this._renderDateParts(); - this._selectNextPart(); - } + _renderDisplayText(text) { + this.callBase(text); + if (this._useMaskBehavior()) { + this.option('text', text); + } + }, - e.preventDefault(); - }, + _partIncrease(step, lockOtherParts) { + this._setNewDateIfEmpty(); - _isDateValid(date) { - return isDate(date) && !isNaN(date); - }, + const { max, min } = this._getActivePartLimits(lockOtherParts); - _isValueDirty() { - const value = this.dateOption('value'); - return (this._maskValue && this._maskValue.getTime()) !== (value && value.getTime()); - }, + let newValue = step + this._getActivePartValue(); - _fireChangeEvent() { - this._clearSearchValue(); + if (newValue > max) { + newValue = this._applyLimits(newValue, { limitBase: min, limitClosest: max, max }); + } else if (newValue < min) { + newValue = this._applyLimits(newValue, { limitBase: max, limitClosest: min, max }); + } - if(this._isValueDirty()) { - eventsEngine.trigger(this._input(), 'change'); - } - }, + this._setActivePartValue(newValue); + }, - _enterHandler() { - this._fireChangeEvent(); - this._selectNextPart(FORWARD); - }, + _applyLimits(newValue, { limitBase, limitClosest, max }) { + const delta = (newValue - limitClosest) % max; + return delta ? limitBase + delta - 1 * sign(delta) : limitClosest; + }, - _focusOutHandler(e) { - const shouldFireChangeEvent = this._useMaskBehavior() && !e.isDefaultPrevented(); + _maskClickHandler() { + this._loadMaskValue(this._maskValue); + if (this.option('text')) { + this._activePartIndex = getDatePartIndexByPosition(this._dateParts, this._caret().start); - if(shouldFireChangeEvent) { - this._fireChangeEvent(); - this.callBase(e); - this._selectFirstPart(e); + if (!this._isAllSelected()) { + if (isDefined(this._activePartIndex)) { + this._caret(this._getActivePartProp('caret')); } else { - this.callBase(e); + this._selectLastPart(); } - }, - - _valueChangeEventHandler(e) { - const text = this.option('text'); - - if(this._useMaskBehavior()) { - this._saveValueChangeEvent(e); - if(!text) { - this._maskValue = null; - } else if(this._maskValue === null) { - this._loadMaskValue(text); - } - this._saveMaskValue(); - } else { - this.callBase(e); - } - }, - - _optionChanged(args) { - switch(args.name) { - case 'useMaskBehavior': - this._renderMask(); - break; - case 'displayFormat': - case 'mode': - this.callBase(args); - this._renderMask(); - break; - case 'value': - this._loadMaskValue(); - this.callBase(args); - this._renderDateParts(); - break; - case 'emptyDateValue': - break; - default: - this.callBase(args); - } - }, + } + } + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _maskCompositionEndHandler(e) { + this._input().val(this._getDisplayedText(this._maskValue)); + this._selectNextPart(); + + this._maskInputHandler = () => { + this._renderSelectedPart(); + }; + }, + + _maskPasteHandler(e) { + const newText = this._replaceSelectedText(this.option('text'), this._caret(), clipboardText(e)); + // @ts-expect-error + const date = dateLocalization.parse(newText, this._getFormatPattern()); + + if (date && this._isDateValid(date)) { + this._maskValue = date; + this._renderDisplayText(this._getDisplayedText(this._maskValue)); + this._renderDateParts(); + this._selectNextPart(); + } + + e.preventDefault(); + }, - _clearMaskState() { - this._clearSearchValue(); - delete this._dateParts; - delete this._activePartIndex; - delete this._maskValue; - }, + _isDateValid(date) { + // @ts-expect-error + return isDate(date) && !isNaN(date); + }, - clear() { - this._clearMaskState(); - this._activePartIndex = 0; + _isValueDirty() { + const value = this.dateOption('value'); + return (this._maskValue && this._maskValue.getTime()) !== (value && value.getTime()); + }, - this.callBase(); - }, + _fireChangeEvent() { + this._clearSearchValue(); - _clean() { - this.callBase(); - this._detachMaskEvents(); - this._clearMaskState(); + if (this._isValueDirty()) { + // @ts-expect-error + eventsEngine.trigger(this._input(), 'change'); + } + }, + + _enterHandler() { + this._fireChangeEvent(); + this._selectNextPart(FORWARD); + }, + + _focusOutHandler(e) { + const shouldFireChangeEvent = this._useMaskBehavior() && !e.isDefaultPrevented(); + + if (shouldFireChangeEvent) { + this._fireChangeEvent(); + this.callBase(e); + this._selectFirstPart(e); + } else { + this.callBase(e); + } + }, + + _valueChangeEventHandler(e) { + const text = this.option('text'); + + if (this._useMaskBehavior()) { + this._saveValueChangeEvent(e); + if (!text) { + this._maskValue = null; + } else if (this._maskValue === null) { + this._loadMaskValue(text); + } + this._saveMaskValue(); + } else { + this.callBase(e); + } + }, + + _optionChanged(args) { + switch (args.name) { + case 'useMaskBehavior': + this._renderMask(); + break; + case 'displayFormat': + case 'mode': + this.callBase(args); + this._renderMask(); + break; + case 'value': + this._loadMaskValue(); + this.callBase(args); + this._renderDateParts(); + break; + case 'emptyDateValue': + break; + default: + this.callBase(args); } + }, + + _clearMaskState() { + this._clearSearchValue(); + delete this._dateParts; + delete this._activePartIndex; + delete this._maskValue; + }, + + clear() { + this._clearMaskState(); + this._activePartIndex = 0; + + this.callBase(); + }, + + _clean() { + this.callBase(); + this._detachMaskEvents(); + this._clearMaskState(); + }, }); export default DateBoxMask; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.calendar.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.calendar.ts index 2e1f0871fba7..bec81621a3fc 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.calendar.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.calendar.ts @@ -1,206 +1,210 @@ -import Calendar from '../calendar'; -import DateBoxStrategy from './ui.date_box.strategy'; -import dateUtils from '../../core/utils/date'; -import { splitPair } from '../../core/utils/common'; -import { isFunction, isEmptyObject } from '../../core/utils/type'; -import { extend } from '../../core/utils/extend'; -import messageLocalization from '../../localization/message'; -import { isMaterial } from '../themes'; +// @ts-expect-error +import { splitPair } from '@js/core/utils/common'; +import dateUtils from '@js/core/utils/date'; +import { extend } from '@js/core/utils/extend'; +import { isEmptyObject, isFunction } from '@js/core/utils/type'; +import messageLocalization from '@js/localization/message'; +import Calendar from '@js/ui/calendar'; +import { isMaterial } from '@js/ui/themes'; + +import DateBoxStrategy from './m_date_box.strategy'; const TODAY_BUTTON_CLASS = 'dx-button-today'; const CalendarStrategy = DateBoxStrategy.inherit({ - NAME: 'Calendar', - - getDefaultOptions: function() { - return extend(this.callBase(), { - todayButtonText: messageLocalization.format('dxCalendar-todayButtonText'), - }); - }, - - supportedKeys: function() { - const homeEndHandler = function(e) { - if(this.option('opened')) { - e.preventDefault(); - return true; - } - return false; - }; - - return { - rightArrow: function() { - if(this.option('opened')) { - return true; - } - }, - leftArrow: function() { - if(this.option('opened')) { - return true; - } - }, - enter: (function(e) { - if(this.dateBox.option('opened')) { - e.preventDefault(); - - if(this._widget.option('zoomLevel') === this._widget.option('maxZoomLevel')) { - const viewValue = this._getContouredValue(); - const lastActionElement = this._lastActionElement; - const shouldCloseDropDown = this._closeDropDownByEnter(); - - if(shouldCloseDropDown && viewValue && lastActionElement === 'calendar') { - this.dateBoxValue(viewValue, e); - } - - shouldCloseDropDown && this.dateBox.close(); - this.dateBox._valueChangeEventHandler(e); - - return !shouldCloseDropDown; - } else { - return true; - } - } else { - this.dateBox._valueChangeEventHandler(e); - } - }).bind(this), - home: homeEndHandler, - end: homeEndHandler - }; - }, - - getDisplayFormat: function(displayFormat) { - return displayFormat || 'shortdate'; - }, - - _closeDropDownByEnter: () => true, - - _getWidgetName: function() { - return Calendar; - }, - - _getContouredValue: function() { - return this._widget._view.option('contouredDate'); - }, - - getKeyboardListener() { - return this._widget; - }, - - _getWidgetOptions: function() { - const disabledDates = this.dateBox.option('disabledDates'); - - return extend(this.dateBox.option('calendarOptions'), { - value: this.dateBoxValue() || null, - selectionMode: 'single', - dateSerializationFormat: null, - min: this.dateBox.dateOption('min'), - max: this.dateBox.dateOption('max'), - onValueChanged: this._valueChangedHandler.bind(this), - onCellClick: this._cellClickHandler.bind(this), - disabledDates: isFunction(disabledDates) ? this._injectComponent(disabledDates.bind(this.dateBox)) : disabledDates, - onContouredChanged: this._refreshActiveDescendant.bind(this), - skipFocusCheck: true - }); - }, - - _injectComponent: function(func) { - const that = this; - return function(params) { - extend(params, { component: that.dateBox }); - return func(params); - }; - }, - - _refreshActiveDescendant: function(e) { - this._lastActionElement = 'calendar'; - this.dateBox.setAria('activedescendant', e.actionValue); - }, - - _getTodayButtonConfig() { - const buttonsLocation = this.dateBox.option('buttonsLocation'); - const isButtonsLocationDefault = buttonsLocation === 'default'; - const position = isButtonsLocationDefault ? ['bottom', 'center'] : splitPair(buttonsLocation); - const stylingMode = isMaterial() ? 'text' : 'outlined'; - - return { - widget: 'dxButton', - toolbar: position[0], - location: position[1] === 'after' ? 'before' : position[1], - options: { - onClick: (args) => { this._widget._toTodayView(args); }, - text: this.dateBox.option('todayButtonText'), - elementAttr: { class: TODAY_BUTTON_CLASS }, - stylingMode, - } - }; - }, - - _isCalendarVisible: function() { - const { calendarOptions } = this.dateBox.option(); - - return isEmptyObject(calendarOptions) || calendarOptions.visible !== false; - }, - - _getPopupToolbarItems(toolbarItems) { - const useButtons = this.dateBox.option('applyValueMode') === 'useButtons'; - const shouldRenderTodayButton = useButtons && this._isCalendarVisible(); - - if(shouldRenderTodayButton) { - const todayButton = this._getTodayButtonConfig(); - - return [ - todayButton, - ...toolbarItems, - ]; + NAME: 'Calendar', + + getDefaultOptions() { + return extend(this.callBase(), { + todayButtonText: messageLocalization.format('dxCalendar-todayButtonText'), + }); + }, + + supportedKeys() { + const homeEndHandler = function (e) { + if (this.option('opened')) { + e.preventDefault(); + return true; + } + return false; + }; + + return { + // @ts-expect-error + rightArrow() { + if (this.option('opened')) { + return true; } - - return toolbarItems; - }, - - popupConfig: function(popupConfig) { - return extend(true, popupConfig, { - position: { collision: 'flipfit flip' }, - width: 'auto' - }); - }, - - _valueChangedHandler: function(e) { - const value = e.value; - const prevValue = e.previousValue; - - if(dateUtils.sameDate(value, prevValue) && dateUtils.sameHoursAndMinutes(value, prevValue)) { - return; - } - - if(this.dateBox.option('applyValueMode') === 'instantly') { - this.dateBoxValue(this.getValue(), e.event); - } - }, - - _updateValue: function() { - if(!this._widget) { - return; + }, + // @ts-expect-error + leftArrow() { + if (this.option('opened')) { + return true; } + }, + // @ts-expect-error + enter: function (e) { + if (this.dateBox.option('opened')) { + e.preventDefault(); + + if (this._widget.option('zoomLevel') === this._widget.option('maxZoomLevel')) { + const viewValue = this._getContouredValue(); + const lastActionElement = this._lastActionElement; + const shouldCloseDropDown = this._closeDropDownByEnter(); + + if (shouldCloseDropDown && viewValue && lastActionElement === 'calendar') { + this.dateBoxValue(viewValue, e); + } - this._widget.option('value', this.dateBoxValue()); - }, - - textChangedHandler: function() { - this._lastActionElement = 'input'; - - if(this.dateBox.option('opened') && this._widget) { - this._updateValue(true); - } - }, - - _cellClickHandler: function(e) { - const dateBox = this.dateBox; + shouldCloseDropDown && this.dateBox.close(); + this.dateBox._valueChangeEventHandler(e); - if(dateBox.option('applyValueMode') === 'instantly') { - dateBox.option('opened', false); - this.dateBoxValue(this.getValue(), e.event); + return !shouldCloseDropDown; + } + return true; } - }, + this.dateBox._valueChangeEventHandler(e); + }.bind(this), + home: homeEndHandler, + end: homeEndHandler, + }; + }, + + getDisplayFormat(displayFormat) { + return displayFormat || 'shortdate'; + }, + + _closeDropDownByEnter: () => true, + + _getWidgetName() { + return Calendar; + }, + + _getContouredValue() { + return this._widget._view.option('contouredDate'); + }, + + getKeyboardListener() { + return this._widget; + }, + + _getWidgetOptions() { + const disabledDates = this.dateBox.option('disabledDates'); + + return extend(this.dateBox.option('calendarOptions'), { + value: this.dateBoxValue() || null, + selectionMode: 'single', + dateSerializationFormat: null, + min: this.dateBox.dateOption('min'), + max: this.dateBox.dateOption('max'), + onValueChanged: this._valueChangedHandler.bind(this), + onCellClick: this._cellClickHandler.bind(this), + disabledDates: isFunction(disabledDates) ? this._injectComponent(disabledDates.bind(this.dateBox)) : disabledDates, + onContouredChanged: this._refreshActiveDescendant.bind(this), + skipFocusCheck: true, + }); + }, + + _injectComponent(func) { + const that = this; + return function (params) { + extend(params, { component: that.dateBox }); + return func(params); + }; + }, + + _refreshActiveDescendant(e) { + this._lastActionElement = 'calendar'; + this.dateBox.setAria('activedescendant', e.actionValue); + }, + + _getTodayButtonConfig() { + const buttonsLocation = this.dateBox.option('buttonsLocation'); + const isButtonsLocationDefault = buttonsLocation === 'default'; + const position = isButtonsLocationDefault ? ['bottom', 'center'] : splitPair(buttonsLocation); + // @ts-expect-error + const stylingMode = isMaterial() ? 'text' : 'outlined'; + + return { + widget: 'dxButton', + toolbar: position[0], + location: position[1] === 'after' ? 'before' : position[1], + options: { + onClick: (args) => { this._widget._toTodayView(args); }, + text: this.dateBox.option('todayButtonText'), + elementAttr: { class: TODAY_BUTTON_CLASS }, + stylingMode, + }, + }; + }, + + _isCalendarVisible() { + const { calendarOptions } = this.dateBox.option(); + + return isEmptyObject(calendarOptions) || calendarOptions.visible !== false; + }, + + _getPopupToolbarItems(toolbarItems) { + const useButtons = this.dateBox.option('applyValueMode') === 'useButtons'; + const shouldRenderTodayButton = useButtons && this._isCalendarVisible(); + + if (shouldRenderTodayButton) { + const todayButton = this._getTodayButtonConfig(); + + return [ + todayButton, + ...toolbarItems, + ]; + } + + return toolbarItems; + }, + + popupConfig(popupConfig) { + return extend(true, popupConfig, { + position: { collision: 'flipfit flip' }, + width: 'auto', + }); + }, + + _valueChangedHandler(e) { + const { value } = e; + const prevValue = e.previousValue; + + if (dateUtils.sameDate(value, prevValue) && dateUtils.sameHoursAndMinutes(value, prevValue)) { + return; + } + + if (this.dateBox.option('applyValueMode') === 'instantly') { + this.dateBoxValue(this.getValue(), e.event); + } + }, + + _updateValue() { + if (!this._widget) { + return; + } + + this._widget.option('value', this.dateBoxValue()); + }, + + textChangedHandler() { + this._lastActionElement = 'input'; + + if (this.dateBox.option('opened') && this._widget) { + this._updateValue(true); + } + }, + + _cellClickHandler(e) { + const { dateBox } = this; + + if (dateBox.option('applyValueMode') === 'instantly') { + dateBox.option('opened', false); + this.dateBoxValue(this.getValue(), e.event); + } + }, }); export default CalendarStrategy; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.calendar_with_time.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.calendar_with_time.ts index 2a9de72e7ba4..44daeef1e3bd 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.calendar_with_time.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.calendar_with_time.ts @@ -1,14 +1,16 @@ -import { getWidth } from '../../core/utils/size'; -import $ from '../../core/renderer'; -import { getWindow } from '../../core/utils/window'; +import $ from '@js/core/renderer'; +import dateUtils from '@js/core/utils/date'; +import { extend } from '@js/core/utils/extend'; +import { getWidth } from '@js/core/utils/size'; +import { getWindow } from '@js/core/utils/window'; +import dateLocalization from '@js/localization/date'; +import Box from '@js/ui/box'; + +import CalendarStrategy from './m_date_box.strategy.calendar'; +import uiDateUtils from './m_date_utils'; +import TimeView from './m_time_view'; + const window = getWindow(); -import CalendarStrategy from './ui.date_box.strategy.calendar'; -import TimeView from './ui.time_view'; -import dateLocalization from '../../localization/date'; -import { extend } from '../../core/utils/extend'; -import dateUtils from '../../core/utils/date'; -import Box from '../box'; -import uiDateUtils from './ui.date_utils'; const SHRINK_VIEW_SCREEN_WIDTH = 573; const DATEBOX_ADAPTIVITY_MODE_CLASS = 'dx-datebox-adaptivity-mode'; @@ -16,171 +18,177 @@ const DATEBOX_TIMEVIEW_SIDE_CLASS = 'dx-datebox-datetime-time-side'; const CalendarWithTimeStrategy = CalendarStrategy.inherit({ - NAME: 'CalendarWithTime', - - getDefaultOptions: function() { - return extend(this.callBase(), { - applyValueMode: 'useButtons', - buttonsLocation: 'bottom after', - 'dropDownOptions.showTitle': false - }); - }, - - _closeDropDownByEnter: function() { - return dateUtils.sameDate(this._getContouredValue(), this.widgetOption('value')); - }, - - getDisplayFormat: function(displayFormat) { - return displayFormat || 'shortdateshorttime'; - }, - - _is24HourFormat: function() { - return dateLocalization.is24HourFormat(this.getDisplayFormat(this.dateBox.option('displayFormat'))); - }, - - _getContouredValue: function() { - const viewDate = this.callBase(); - return this._updateDateTime(viewDate); - }, - - _renderWidget: function() { - this.callBase(); - - this._timeView = this.dateBox._createComponent($('
'), TimeView, { - value: this.dateBoxValue(), - _showClock: !this._isShrinkView(), - use24HourFormat: this._is24HourFormat(), - onValueChanged: this._valueChangedHandler.bind(this), - stylingMode: this.dateBox.option('stylingMode') - }); - }, - - renderOpenedState: function() { - this.callBase(); - const popup = this._getPopup(); - - if(popup) { - popup.$wrapper().toggleClass(DATEBOX_ADAPTIVITY_MODE_CLASS, this._isSmallScreen()); - } + NAME: 'CalendarWithTime', + + getDefaultOptions() { + return extend(this.callBase(), { + applyValueMode: 'useButtons', + buttonsLocation: 'bottom after', + 'dropDownOptions.showTitle': false, + }); + }, + + _closeDropDownByEnter() { + return dateUtils.sameDate(this._getContouredValue(), this.widgetOption('value')); + }, + + getDisplayFormat(displayFormat) { + return displayFormat || 'shortdateshorttime'; + }, + + _is24HourFormat() { + // @ts-expect-error + return dateLocalization.is24HourFormat(this.getDisplayFormat(this.dateBox.option('displayFormat'))); + }, + + _getContouredValue() { + const viewDate = this.callBase(); + return this._updateDateTime(viewDate); + }, + + _renderWidget() { + this.callBase(); + + this._timeView = this.dateBox._createComponent($('
'), TimeView, { + value: this.dateBoxValue(), + _showClock: !this._isShrinkView(), + use24HourFormat: this._is24HourFormat(), + onValueChanged: this._valueChangedHandler.bind(this), + stylingMode: this.dateBox.option('stylingMode'), + }); + }, + + renderOpenedState() { + this.callBase(); + const popup = this._getPopup(); + + if (popup) { + popup.$wrapper().toggleClass(DATEBOX_ADAPTIVITY_MODE_CLASS, this._isSmallScreen()); + } - clearTimeout(this._repaintTimer); + clearTimeout(this._repaintTimer); - this._repaintTimer = setTimeout((function() { - this._getPopup() && this._getPopup().repaint(); - }).bind(this), 0); - }, + this._repaintTimer = setTimeout(() => { + this._getPopup() && this._getPopup().repaint(); + }, 0); + }, - isAdaptivityChanged: function() { - const isAdaptiveMode = this._isShrinkView(); - const currentAdaptiveMode = this._currentAdaptiveMode; + isAdaptivityChanged() { + const isAdaptiveMode = this._isShrinkView(); + const currentAdaptiveMode = this._currentAdaptiveMode; - if(isAdaptiveMode !== currentAdaptiveMode) { - this._currentAdaptiveMode = isAdaptiveMode; - return currentAdaptiveMode !== undefined; - } + if (isAdaptiveMode !== currentAdaptiveMode) { + this._currentAdaptiveMode = isAdaptiveMode; + return currentAdaptiveMode !== undefined; + } - return this.callBase(); - }, + return this.callBase(); + }, - _updateValue: function(preventDefaultValue) { - let date = this.dateBoxValue(); + _updateValue(preventDefaultValue) { + let date = this.dateBoxValue(); - if(!date && !preventDefaultValue) { - date = new Date(); - uiDateUtils.normalizeTime(date); - } + if (!date && !preventDefaultValue) { + date = new Date(); + uiDateUtils.normalizeTime(date); + } - this.callBase(); + this.callBase(); - if(this._timeView) { - date && this._timeView.option('value', date); - this._timeView.option('use24HourFormat', this._is24HourFormat()); - } - }, + if (this._timeView) { + date && this._timeView.option('value', date); + this._timeView.option('use24HourFormat', this._is24HourFormat()); + } + }, - _isSmallScreen: function() { - return getWidth(window) <= SHRINK_VIEW_SCREEN_WIDTH; - }, + _isSmallScreen() { + return getWidth(window) <= SHRINK_VIEW_SCREEN_WIDTH; + }, - _isShrinkView: function() { - return !this.dateBox.option('showAnalogClock') || (this.dateBox.option('adaptivityEnabled') && this._isSmallScreen()); - }, + _isShrinkView() { + return !this.dateBox.option('showAnalogClock') || (this.dateBox.option('adaptivityEnabled') && this._isSmallScreen()); + }, - _getBoxItems: function() { - const items = [{ ratio: 0, shrink: 0, baseSize: 'auto', name: 'calendar' }]; + _getBoxItems() { + const items = [{ + ratio: 0, shrink: 0, baseSize: 'auto', name: 'calendar', + }]; - if(!this._isShrinkView()) { - items.push({ ratio: 0, shrink: 0, baseSize: 'auto', name: 'time' }); - } + if (!this._isShrinkView()) { + items.push({ + ratio: 0, shrink: 0, baseSize: 'auto', name: 'time', + }); + } - return items; - }, - - renderPopupContent: function() { - this.callBase(); - this._currentAdaptiveMode = this._isShrinkView(); - - const $popupContent = this._getPopup().$content(); - - this._box = this.dateBox._createComponent($('
').appendTo($popupContent), Box, { - direction: 'row', - crossAlign: 'stretch', - items: this._getBoxItems(), - itemTemplate: (function(data, i, element) { - const $container = $('
'); - - switch(data.name) { - case 'calendar': - $container.append(this._widget.$element()); - if(this._isShrinkView()) { - this._timeView.$element().addClass(DATEBOX_TIMEVIEW_SIDE_CLASS); - $container.append(this._timeView.$element()); - } - break; - case 'time': - $container.append(this._timeView.$element()); - $(element).addClass(DATEBOX_TIMEVIEW_SIDE_CLASS); - break; - } - - return $container; - }).bind(this) - }); - }, - - popupConfig: function(popupConfig) { - const calendarPopupConfig = this.callBase(popupConfig); - return extend(calendarPopupConfig, { width: 'auto' }); - }, - - _preventFocusOnPopup: function(e) { - if(!$(e.target).hasClass('dx-texteditor-input')) { - this.callBase.apply(this, arguments); - if(!this.dateBox._hasFocusClass()) { - this.dateBox.focus(); + return items; + }, + + renderPopupContent() { + this.callBase(); + this._currentAdaptiveMode = this._isShrinkView(); + + const $popupContent = this._getPopup().$content(); + + this._box = this.dateBox._createComponent($('
').appendTo($popupContent), Box, { + direction: 'row', + crossAlign: 'stretch', + items: this._getBoxItems(), + itemTemplate: function (data, i, element) { + const $container = $('
'); + + // eslint-disable-next-line default-case + switch (data.name) { + case 'calendar': + $container.append(this._widget.$element()); + if (this._isShrinkView()) { + this._timeView.$element().addClass(DATEBOX_TIMEVIEW_SIDE_CLASS); + $container.append(this._timeView.$element()); } + break; + case 'time': + $container.append(this._timeView.$element()); + $(element).addClass(DATEBOX_TIMEVIEW_SIDE_CLASS); + break; } - }, - _updateDateTime: function(date) { - const time = this._timeView.option('value'); - date.setHours(time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds()); + return $container; + }.bind(this), + }); + }, + + popupConfig(popupConfig) { + const calendarPopupConfig = this.callBase(popupConfig); + return extend(calendarPopupConfig, { width: 'auto' }); + }, + + _preventFocusOnPopup(e) { + if (!$(e.target).hasClass('dx-texteditor-input')) { + this.callBase.apply(this, arguments); + if (!this.dateBox._hasFocusClass()) { + this.dateBox.focus(); + } + } + }, - return date; - }, + _updateDateTime(date) { + const time = this._timeView.option('value'); + date.setHours(time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds()); - getValue: function() { - let date = this._widget.option('value') ?? this._widget.getContouredDate(); - date = date ? new Date(date) : new Date(); + return date; + }, - return this._updateDateTime(date); - }, + getValue() { + let date = this._widget.option('value') ?? this._widget.getContouredDate(); + date = date ? new Date(date) : new Date(); - dispose: function() { - clearTimeout(this._removeMinWidthTimer); - clearTimeout(this._repaintTimer); - this.callBase(); - } + return this._updateDateTime(date); + }, + + dispose() { + clearTimeout(this._removeMinWidthTimer); + clearTimeout(this._repaintTimer); + this.callBase(); + }, }); export default CalendarWithTimeStrategy; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.date_view.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.date_view.ts index 39ebd141716c..ce25b0f19ac6 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.date_view.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.date_view.ts @@ -1,130 +1,131 @@ -import $ from '../../core/renderer'; -import { getWindow } from '../../core/utils/window'; +import $ from '@js/core/renderer'; +import { extend } from '@js/core/utils/extend'; +import { inputType } from '@js/core/utils/support'; +import { getWindow } from '@js/core/utils/window'; +import messageLocalization from '@js/localization/message'; + +import DateBoxStrategy from './m_date_box.strategy'; +import dateUtils from './m_date_utils'; +import DateView from './m_date_view'; + const window = getWindow(); -import DateView from './ui.date_view'; -import DateBoxStrategy from './ui.date_box.strategy'; -import { inputType } from '../../core/utils/support'; -import { extend } from '../../core/utils/extend'; -import dateUtils from './ui.date_utils'; -import messageLocalization from '../../localization/message'; const DateViewStrategy = DateBoxStrategy.inherit({ - NAME: 'DateView', - - getDefaultOptions: function() { - return extend(this.callBase(), { - openOnFieldClick: true, - applyButtonText: messageLocalization.format('OK'), - 'dropDownOptions.showTitle': true - }); - }, - - getDisplayFormat: function(displayFormat) { - return displayFormat || dateUtils.FORMATS_MAP[this.dateBox.option('type')]; - }, - - popupConfig: function(config) { - - return { - toolbarItems: this.dateBox._popupToolbarItemsConfig(), - onInitialized: config.onInitialized, - - defaultOptionsRules: [ - { - device: { platform: 'android' }, - options: { - width: 333, - height: 331 - } - }, - { - device: function(device) { - const platform = device.platform; - return platform === 'generic' || platform === 'ios'; - }, - options: { - width: 'auto', - height: 'auto' - } - }, - { - device: function(device) { - const platform = device.platform; - const phone = device.phone; - - return platform === 'generic' && phone; - }, - options: { - width: 333, - maxWidth: '100%', - maxHeight: '100%', - height: 'auto', - position: { - collision: 'flipfit flip' - } - } - }, - { - device: { platform: 'ios', phone: true }, - options: { - width: '100%', - position: { - my: 'bottom', - at: 'bottom', - of: window - } - } - } - ] - }; - }, - - _renderWidget: function() { - if(inputType(this.dateBox.option('mode')) && this.dateBox._isNativeType() || this.dateBox.option('readOnly')) { - if(this._widget) { - this._widget.$element().remove(); - this._widget = null; - } - - return; - } - - const popup = this._getPopup(); - - if(this._widget) { - this._widget.option(this._getWidgetOptions()); - } else { - const element = $('
').appendTo(popup.$content()); - this._widget = this._createWidget(element); - } - - this._widget.$element().appendTo(this._getWidgetContainer()); - }, - - _getWidgetName: function() { - return DateView; - }, - - renderOpenedState: function() { - this.callBase(); - - if(this._widget) { - this._widget.option('value', this._widget._getCurrentDate()); - } - }, - - _getWidgetOptions: function() { - return { - value: this.dateBoxValue() || new Date(), - type: this.dateBox.option('type'), - minDate: this.dateBox.dateOption('min') || new Date(1900, 0, 1), - maxDate: this.dateBox.dateOption('max') || new Date(Date.now() + 50 * dateUtils.ONE_YEAR), - onDisposing: (function() { - this._widget = null; - }).bind(this) - }; + NAME: 'DateView', + + getDefaultOptions() { + return extend(this.callBase(), { + openOnFieldClick: true, + applyButtonText: messageLocalization.format('OK'), + 'dropDownOptions.showTitle': true, + }); + }, + + getDisplayFormat(displayFormat) { + return displayFormat || dateUtils.FORMATS_MAP[this.dateBox.option('type')]; + }, + + popupConfig(config) { + return { + toolbarItems: this.dateBox._popupToolbarItemsConfig(), + onInitialized: config.onInitialized, + + defaultOptionsRules: [ + { + device: { platform: 'android' }, + options: { + width: 333, + height: 331, + }, + }, + { + device(device) { + const { platform } = device; + return platform === 'generic' || platform === 'ios'; + }, + options: { + width: 'auto', + height: 'auto', + }, + }, + { + device(device) { + const { platform } = device; + const { phone } = device; + + return platform === 'generic' && phone; + }, + options: { + width: 333, + maxWidth: '100%', + maxHeight: '100%', + height: 'auto', + position: { + collision: 'flipfit flip', + }, + }, + }, + { + device: { platform: 'ios', phone: true }, + options: { + width: '100%', + position: { + my: 'bottom', + at: 'bottom', + of: window, + }, + }, + }, + ], + }; + }, + + _renderWidget() { + if (inputType(this.dateBox.option('mode')) && this.dateBox._isNativeType() || this.dateBox.option('readOnly')) { + if (this._widget) { + this._widget.$element().remove(); + this._widget = null; + } + + return; + } + + const popup = this._getPopup(); + + if (this._widget) { + this._widget.option(this._getWidgetOptions()); + } else { + const element = $('
').appendTo(popup.$content()); + this._widget = this._createWidget(element); + } + + this._widget.$element().appendTo(this._getWidgetContainer()); + }, + + _getWidgetName() { + return DateView; + }, + + renderOpenedState() { + this.callBase(); + + if (this._widget) { + this._widget.option('value', this._widget._getCurrentDate()); } + }, + + _getWidgetOptions() { + return { + value: this.dateBoxValue() || new Date(), + type: this.dateBox.option('type'), + minDate: this.dateBox.dateOption('min') || new Date(1900, 0, 1), + maxDate: this.dateBox.dateOption('max') || new Date(Date.now() + 50 * dateUtils.ONE_YEAR), + onDisposing: function () { + this._widget = null; + }.bind(this), + }; + }, }); export default DateViewStrategy; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.list.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.list.ts index e95743923f87..2fa80ccab316 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.list.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.list.ts @@ -1,291 +1,297 @@ -import { getOuterHeight, getHeight } from '../../core/utils/size'; -import { getWindow } from '../../core/utils/window'; +import '@js/ui/list/modules/selection'; + +import { ensureDefined, noop } from '@js/core/utils/common'; +import dateSerialization from '@js/core/utils/date_serialization'; +import { extend } from '@js/core/utils/extend'; +import { getHeight, getOuterHeight } from '@js/core/utils/size'; +import { isDate } from '@js/core/utils/type'; +import { getWindow } from '@js/core/utils/window'; +import dateLocalization from '@js/localization/date'; +import { getSizeValue } from '@js/ui/drop_down_editor/utils'; +import List from '@js/ui/list_light'; + +import DateBoxStrategy from './m_date_box.strategy'; +import dateUtils from './m_date_utils'; + const window = getWindow(); -import List from '../list_light'; -import '../list/modules/selection'; -import DateBoxStrategy from './ui.date_box.strategy'; -import { noop, ensureDefined } from '../../core/utils/common'; -import { isDate } from '../../core/utils/type'; -import { extend } from '../../core/utils/extend'; -import dateUtils from './ui.date_utils'; -import dateLocalization from '../../localization/date'; -import dateSerialization from '../../core/utils/date_serialization'; -import { getSizeValue } from '../drop_down_editor/utils'; const DATE_FORMAT = 'date'; const BOUNDARY_VALUES = { - 'min': new Date(0, 0, 0, 0, 0), - 'max': new Date(0, 0, 0, 23, 59) + min: new Date(0, 0, 0, 0, 0), + max: new Date(0, 0, 0, 23, 59), }; const ListStrategy = DateBoxStrategy.inherit({ - NAME: 'List', - - supportedKeys: function() { - return { - space: noop, - home: noop, - end: noop - }; - }, - - getDefaultOptions: function() { - return extend(this.callBase(), { - applyValueMode: 'instantly' - }); - }, - - getDisplayFormat: function(displayFormat) { - return displayFormat || 'shorttime'; - }, - - popupConfig: function(popupConfig) { - return popupConfig; - }, - - getValue: function() { - const selectedIndex = this._widget.option('selectedIndex'); - - if(selectedIndex === -1) { - return this.dateBox.option('value'); - } - - const itemData = this._widgetItems[selectedIndex]; - return this._getDateByItemData(itemData); - }, - - useCurrentDateByDefault: function() { - return true; - }, - - getDefaultDate: function() { - return new Date(null); - }, - - popupShowingHandler: function() { - this.dateBox._dimensionChanged(); - }, - - _renderWidget: function() { - this.callBase(); - this._refreshItems(); - }, - - _getWidgetName: function() { - return List; - }, - - _getWidgetOptions: function() { - return { - itemTemplate: this._timeListItemTemplate.bind(this), - onItemClick: this._listItemClickHandler.bind(this), - tabIndex: -1, - onFocusedItemChanged: this._refreshActiveDescendant.bind(this), - selectionMode: 'single' - }; - }, - - _refreshActiveDescendant: function(e) { - this.dateBox.setAria('activedescendant', ''); - this.dateBox.setAria('activedescendant', e.actionValue); - }, - - _refreshItems: function() { - this._widgetItems = this._getTimeListItems(); - this._widget.option('items', this._widgetItems); - }, - - renderOpenedState: function() { - if(!this._widget) { - return; - } - - this._widget.option('focusedElement', null); - - this._setSelectedItemsByValue(); - if(this._widget.option('templatesRenderAsynchronously')) { - this._asyncScrollTimeout = setTimeout(this._scrollToSelectedItem.bind(this)); - } else { - this._scrollToSelectedItem(); - } - }, - - dispose: function() { - this.callBase(); - clearTimeout(this._asyncScrollTimeout); - }, - - _updateValue: function() { - if(!this._widget) { - return; - } - - this._refreshItems(); - - this._setSelectedItemsByValue(); - this._scrollToSelectedItem(); - }, - - _setSelectedItemsByValue: function() { - const value = this.dateBoxValue(); - const dateIndex = this._getDateIndex(value); - - if(dateIndex === -1) { - this._widget.option('selectedItems', []); - } else { - this._widget.option('selectedIndex', dateIndex); - } - }, - - _scrollToSelectedItem: function() { - this._widget.scrollToItem(this._widget.option('selectedIndex')); - }, - - _getDateIndex: function(date) { - let result = -1; - - for(let i = 0, n = this._widgetItems.length; i < n; i++) { - if(this._areDatesEqual(date, this._widgetItems[i])) { - result = i; - break; - } - } - - return result; - }, - - _areDatesEqual: function(first, second) { - return isDate(first) && isDate(second) - && first.getHours() === second.getHours() - && first.getMinutes() === second.getMinutes(); - }, - - _getTimeListItems: function() { - let min = this.dateBox.dateOption('min') || this._getBoundaryDate('min'); - const max = this.dateBox.dateOption('max') || this._getBoundaryDate('max'); - const value = this.dateBox.dateOption('value') || null; - let delta = max - min; - const minutes = min.getMinutes() % this.dateBox.option('interval'); - - if(delta < 0) { - return []; - } - - if(delta > dateUtils.ONE_DAY) { - delta = dateUtils.ONE_DAY; - } - - if(value - min < dateUtils.ONE_DAY) { - return this._getRangeItems(min, new Date(min), delta); - } - - min = this._getBoundaryDate('min'); - min.setMinutes(minutes); - - if(value && Math.abs(value - max) < dateUtils.ONE_DAY) { - delta = (max.getHours() * 60 + Math.abs(max.getMinutes() - minutes)) * dateUtils.ONE_MINUTE; - } - - return this._getRangeItems(min, new Date(min), delta); - }, - - _getRangeItems: function(startValue, currentValue, rangeDuration) { - const rangeItems = []; - const interval = this.dateBox.option('interval'); - - while(currentValue - startValue <= rangeDuration) { - rangeItems.push(new Date(currentValue)); - currentValue.setMinutes(currentValue.getMinutes() + interval); - } - - return rangeItems; - }, - - _getBoundaryDate: function(boundary) { - const boundaryValue = BOUNDARY_VALUES[boundary]; - const currentValue = new Date(ensureDefined(this.dateBox.dateOption('value'), 0)); - - return new Date( - currentValue.getFullYear(), - currentValue.getMonth(), - currentValue.getDate(), - boundaryValue.getHours(), - boundaryValue.getMinutes() - ); - }, - - _timeListItemTemplate: function(itemData) { - const displayFormat = this.dateBox.option('displayFormat'); - return dateLocalization.format(itemData, this.getDisplayFormat(displayFormat)); - }, - - _listItemClickHandler: function(e) { - if(this.dateBox.option('applyValueMode') === 'useButtons') { - return; - } - - const date = this._getDateByItemData(e.itemData); - - this.dateBox.option('opened', false); - this.dateBoxValue(date, e.event); - }, - - _getDateByItemData: function(itemData) { - let date = this.dateBox.option('value'); - const hours = itemData.getHours(); - const minutes = itemData.getMinutes(); - const seconds = itemData.getSeconds(); - const year = itemData.getFullYear(); - const month = itemData.getMonth(); - const day = itemData.getDate(); - - if(date) { - if(this.dateBox.option('dateSerializationFormat')) { - date = dateSerialization.deserializeDate(date); - } else { - date = new Date(date); - } - - date.setHours(hours); - date.setMinutes(minutes); - date.setSeconds(seconds); - date.setFullYear(year); - date.setMonth(month); - date.setDate(day); - } else { - date = new Date(year, month, day, hours, minutes, 0, 0); - } - - return date; - }, - - getKeyboardListener: function() { - return this._widget; - }, - - _updatePopupHeight: function() { - const dropDownOptionsHeight = getSizeValue(this.dateBox.option('dropDownOptions.height')); - if(dropDownOptionsHeight === undefined || dropDownOptionsHeight === 'auto') { - this.dateBox._setPopupOption('height', 'auto'); - const popupHeight = getOuterHeight(this._widget.$element()); - const maxHeight = getHeight(window) * 0.45; - this.dateBox._setPopupOption('height', Math.min(popupHeight, maxHeight)); - } - - this.dateBox._timeList && this.dateBox._timeList.updateDimensions(); - }, - - getParsedText: function(text, format) { - let value = this.callBase(text, format); - - if(value) { - value = dateUtils.mergeDates(value, new Date(null), DATE_FORMAT); - } - - return value; + NAME: 'List', + + supportedKeys() { + return { + space: noop, + home: noop, + end: noop, + }; + }, + + getDefaultOptions() { + return extend(this.callBase(), { + applyValueMode: 'instantly', + }); + }, + + getDisplayFormat(displayFormat) { + return displayFormat || 'shorttime'; + }, + + popupConfig(popupConfig) { + return popupConfig; + }, + + getValue() { + const selectedIndex = this._widget.option('selectedIndex'); + + if (selectedIndex === -1) { + return this.dateBox.option('value'); + } + + const itemData = this._widgetItems[selectedIndex]; + return this._getDateByItemData(itemData); + }, + + useCurrentDateByDefault() { + return true; + }, + + getDefaultDate() { + // @ts-expect-error + return new Date(null); + }, + + popupShowingHandler() { + this.dateBox._dimensionChanged(); + }, + + _renderWidget() { + this.callBase(); + this._refreshItems(); + }, + + _getWidgetName() { + return List; + }, + + _getWidgetOptions() { + return { + itemTemplate: this._timeListItemTemplate.bind(this), + onItemClick: this._listItemClickHandler.bind(this), + tabIndex: -1, + onFocusedItemChanged: this._refreshActiveDescendant.bind(this), + selectionMode: 'single', + }; + }, + + _refreshActiveDescendant(e) { + this.dateBox.setAria('activedescendant', ''); + this.dateBox.setAria('activedescendant', e.actionValue); + }, + + _refreshItems() { + this._widgetItems = this._getTimeListItems(); + this._widget.option('items', this._widgetItems); + }, + + renderOpenedState() { + if (!this._widget) { + return; + } + + this._widget.option('focusedElement', null); + + this._setSelectedItemsByValue(); + if (this._widget.option('templatesRenderAsynchronously')) { + this._asyncScrollTimeout = setTimeout(this._scrollToSelectedItem.bind(this)); + } else { + this._scrollToSelectedItem(); } + }, + + dispose() { + this.callBase(); + clearTimeout(this._asyncScrollTimeout); + }, + + _updateValue() { + if (!this._widget) { + return; + } + + this._refreshItems(); + + this._setSelectedItemsByValue(); + this._scrollToSelectedItem(); + }, + + _setSelectedItemsByValue() { + const value = this.dateBoxValue(); + const dateIndex = this._getDateIndex(value); + + if (dateIndex === -1) { + this._widget.option('selectedItems', []); + } else { + this._widget.option('selectedIndex', dateIndex); + } + }, + + _scrollToSelectedItem() { + this._widget.scrollToItem(this._widget.option('selectedIndex')); + }, + + _getDateIndex(date) { + let result = -1; + + for (let i = 0, n = this._widgetItems.length; i < n; i++) { + if (this._areDatesEqual(date, this._widgetItems[i])) { + result = i; + break; + } + } + + return result; + }, + + _areDatesEqual(first, second) { + return isDate(first) && isDate(second) + && first.getHours() === second.getHours() + && first.getMinutes() === second.getMinutes(); + }, + + _getTimeListItems() { + let min = this.dateBox.dateOption('min') || this._getBoundaryDate('min'); + const max = this.dateBox.dateOption('max') || this._getBoundaryDate('max'); + const value = this.dateBox.dateOption('value') || null; + let delta = max - min; + const minutes = min.getMinutes() % this.dateBox.option('interval'); + + if (delta < 0) { + return []; + } + + if (delta > dateUtils.ONE_DAY) { + delta = dateUtils.ONE_DAY; + } + + if (value - min < dateUtils.ONE_DAY) { + return this._getRangeItems(min, new Date(min), delta); + } + + min = this._getBoundaryDate('min'); + min.setMinutes(minutes); + + if (value && Math.abs(value - max) < dateUtils.ONE_DAY) { + delta = (max.getHours() * 60 + Math.abs(max.getMinutes() - minutes)) * dateUtils.ONE_MINUTE; + } + + return this._getRangeItems(min, new Date(min), delta); + }, + + _getRangeItems(startValue, currentValue, rangeDuration) { + const rangeItems = []; + const interval = this.dateBox.option('interval'); + + while (currentValue - startValue <= rangeDuration) { + // @ts-expect-error + rangeItems.push(new Date(currentValue)); + currentValue.setMinutes(currentValue.getMinutes() + interval); + } + + return rangeItems; + }, + + _getBoundaryDate(boundary) { + const boundaryValue = BOUNDARY_VALUES[boundary]; + const currentValue = new Date(ensureDefined(this.dateBox.dateOption('value'), 0)); + + return new Date( + currentValue.getFullYear(), + currentValue.getMonth(), + currentValue.getDate(), + boundaryValue.getHours(), + boundaryValue.getMinutes(), + ); + }, + + _timeListItemTemplate(itemData) { + const displayFormat = this.dateBox.option('displayFormat'); + return dateLocalization.format(itemData, this.getDisplayFormat(displayFormat)); + }, + + _listItemClickHandler(e) { + if (this.dateBox.option('applyValueMode') === 'useButtons') { + return; + } + + const date = this._getDateByItemData(e.itemData); + + this.dateBox.option('opened', false); + this.dateBoxValue(date, e.event); + }, + + _getDateByItemData(itemData) { + let date = this.dateBox.option('value'); + const hours = itemData.getHours(); + const minutes = itemData.getMinutes(); + const seconds = itemData.getSeconds(); + const year = itemData.getFullYear(); + const month = itemData.getMonth(); + const day = itemData.getDate(); + + if (date) { + if (this.dateBox.option('dateSerializationFormat')) { + date = dateSerialization.deserializeDate(date); + } else { + date = new Date(date); + } + + date.setHours(hours); + date.setMinutes(minutes); + date.setSeconds(seconds); + date.setFullYear(year); + date.setMonth(month); + date.setDate(day); + } else { + date = new Date(year, month, day, hours, minutes, 0, 0); + } + + return date; + }, + + getKeyboardListener() { + return this._widget; + }, + + _updatePopupHeight() { + const dropDownOptionsHeight = getSizeValue(this.dateBox.option('dropDownOptions.height')); + if (dropDownOptionsHeight === undefined || dropDownOptionsHeight === 'auto') { + this.dateBox._setPopupOption('height', 'auto'); + const popupHeight = getOuterHeight(this._widget.$element()); + const maxHeight = getHeight(window) * 0.45; + this.dateBox._setPopupOption('height', Math.min(popupHeight, maxHeight)); + } + + this.dateBox._timeList && this.dateBox._timeList.updateDimensions(); + }, + + getParsedText(text, format) { + let value = this.callBase(text, format); + + if (value) { + // @ts-expect-error + value = dateUtils.mergeDates(value, new Date(null), DATE_FORMAT); + } + + return value; + }, }); export default ListStrategy; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.native.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.native.ts index f5f3872ad480..e657d3ec231e 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.native.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.native.ts @@ -1,76 +1,77 @@ -import { noop } from '../../core/utils/common'; -import DateBoxStrategy from './ui.date_box.strategy'; -import { inputType } from '../../core/utils/support'; -import dateUtils from './ui.date_utils'; -import dateSerialization from '../../core/utils/date_serialization'; -import { extend } from '../../core/utils/extend'; -import devices from '../../core/devices'; +import devices from '@js/core/devices'; +import { noop } from '@js/core/utils/common'; +import dateSerialization from '@js/core/utils/date_serialization'; +import { extend } from '@js/core/utils/extend'; +import { inputType } from '@js/core/utils/support'; -const NativeStrategy = DateBoxStrategy.inherit({ - - NAME: 'Native', +import DateBoxStrategy from './m_date_box.strategy'; +import dateUtils from './m_date_utils'; - popupConfig: function(popupConfig) { - return extend({}, popupConfig, { width: 'auto' }); - }, - - getParsedText: function(text) { - if(!text) { - return null; - } +const NativeStrategy = DateBoxStrategy.inherit({ - // NOTE: Required for correct date parsing when native picker is used (T418155) - if(this.dateBox.option('type') === 'datetime') { - return new Date(text.replace(/-/g, '/').replace('T', ' ').split('.')[0]); - } + NAME: 'Native', - return dateUtils.fromStandardDateFormat(text); - }, + popupConfig(popupConfig) { + return extend({}, popupConfig, { width: 'auto' }); + }, - renderPopupContent: noop, + getParsedText(text) { + if (!text) { + return null; + } - _getWidgetName: noop, + // NOTE: Required for correct date parsing when native picker is used (T418155) + if (this.dateBox.option('type') === 'datetime') { + return new Date(text.replace(/-/g, '/').replace('T', ' ').split('.')[0]); + } - _getWidgetOptions: noop, + return dateUtils.fromStandardDateFormat(text); + }, - _getDateBoxType: function() { - let type = this.dateBox.option('type'); + renderPopupContent: noop, - if(!dateUtils.SUPPORTED_FORMATS.includes(type)) { - type = 'date'; - } else if(type === 'datetime' && !inputType(type)) { - type = 'datetime-local'; - } + _getWidgetName: noop, - return type; - }, + _getWidgetOptions: noop, - customizeButtons: function() { - const dropDownButton = this.dateBox.getButton('dropDown'); - if(devices.real().android && dropDownButton) { - dropDownButton.on('click', function() { - this.dateBox._input().get(0).click(); - }.bind(this)); - } - }, + _getDateBoxType() { + let type = this.dateBox.option('type'); - getDefaultOptions: function() { - return { - mode: this._getDateBoxType() - }; - }, + if (!dateUtils.SUPPORTED_FORMATS.includes(type)) { + type = 'date'; + } else if (type === 'datetime' && !inputType(type)) { + type = 'datetime-local'; + } - getDisplayFormat: function(displayFormat) { - const type = this._getDateBoxType(); - return displayFormat || dateUtils.FORMATS_MAP[type]; - }, + return type; + }, - renderInputMinMax: function($input) { - $input.attr({ - min: dateSerialization.serializeDate(this.dateBox.dateOption('min'), 'yyyy-MM-dd'), - max: dateSerialization.serializeDate(this.dateBox.dateOption('max'), 'yyyy-MM-dd') - }); + customizeButtons() { + const dropDownButton = this.dateBox.getButton('dropDown'); + if (devices.real().android && dropDownButton) { + dropDownButton.on('click', () => { + this.dateBox._input().get(0).click(); + }); } + }, + + getDefaultOptions() { + return { + mode: this._getDateBoxType(), + }; + }, + + getDisplayFormat(displayFormat) { + const type = this._getDateBoxType(); + return displayFormat || dateUtils.FORMATS_MAP[type]; + }, + + renderInputMinMax($input) { + $input.attr({ + min: dateSerialization.serializeDate(this.dateBox.dateOption('min'), 'yyyy-MM-dd'), + max: dateSerialization.serializeDate(this.dateBox.dateOption('max'), 'yyyy-MM-dd'), + }); + }, }); export default NativeStrategy; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.ts index b659723b00d5..2dc3574f0859 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.strategy.ts @@ -1,132 +1,133 @@ -import $ from '../../core/renderer'; -import eventsEngine from '../../events/core/events_engine'; -import { noop } from '../../core/utils/common'; -import Class from '../../core/class'; -import dateLocalization from '../../localization/date'; +import Class from '@js/core/class'; +import $ from '@js/core/renderer'; +import { noop } from '@js/core/utils/common'; +import eventsEngine from '@js/events/core/events_engine'; +import dateLocalization from '@js/localization/date'; -const abstract = Class.abstract; +const { abstract } = Class; const DateBoxStrategy = Class.inherit({ - ctor: function(dateBox) { - this.dateBox = dateBox; - }, + ctor(dateBox) { + this.dateBox = dateBox; + }, - widgetOption: function() { - return this._widget && this._widget.option.apply(this._widget, arguments); - }, + widgetOption() { + return this._widget && this._widget.option.apply(this._widget, arguments); + }, - _renderWidget: function(element) { - element = element || $('
'); - this._widget = this._createWidget(element); - this._widget.$element().appendTo(this._getWidgetContainer()); - }, + _renderWidget(element) { + element = element || $('
'); + this._widget = this._createWidget(element); + this._widget.$element().appendTo(this._getWidgetContainer()); + }, - _createWidget: function(element) { - const widgetName = this._getWidgetName(); - const widgetOptions = this._getWidgetOptions(); + _createWidget(element) { + const widgetName = this._getWidgetName(); + const widgetOptions = this._getWidgetOptions(); - return this.dateBox._createComponent(element, widgetName, widgetOptions); - }, + return this.dateBox._createComponent(element, widgetName, widgetOptions); + }, - _getWidgetOptions: abstract, + _getWidgetOptions: abstract, - _getWidgetName: abstract, + _getWidgetName: abstract, - getDefaultOptions: function() { - return { mode: 'text' }; - }, + getDefaultOptions() { + return { mode: 'text' }; + }, - getDisplayFormat: abstract, + getDisplayFormat: abstract, - supportedKeys: noop, + supportedKeys: noop, - getKeyboardListener: noop, + getKeyboardListener: noop, - customizeButtons: noop, + customizeButtons: noop, - getParsedText: function(text, format) { - const value = dateLocalization.parse(text, format); - return value ? value : dateLocalization.parse(text); - }, + getParsedText(text, format) { + // @ts-expect-error + const value = dateLocalization.parse(text, format); + // @ts-expect-error + return value || dateLocalization.parse(text); + }, - renderInputMinMax: noop, + renderInputMinMax: noop, - renderOpenedState: function() { - this._updateValue(); - }, + renderOpenedState() { + this._updateValue(); + }, - popupConfig: abstract, + popupConfig: abstract, - _dimensionChanged: function() { - this._getPopup()?.repaint(); - }, + _dimensionChanged() { + this._getPopup()?.repaint(); + }, - renderPopupContent: function() { - const popup = this._getPopup(); - this._renderWidget(); + renderPopupContent() { + const popup = this._getPopup(); + this._renderWidget(); - const $popupContent = popup.$content().parent(); - eventsEngine.off($popupContent, 'mousedown'); - eventsEngine.on($popupContent, 'mousedown', this._preventFocusOnPopup.bind(this)); - }, + const $popupContent = popup.$content().parent(); + eventsEngine.off($popupContent, 'mousedown'); + eventsEngine.on($popupContent, 'mousedown', this._preventFocusOnPopup.bind(this)); + }, - _preventFocusOnPopup: function(e) { - e.preventDefault(); - }, + _preventFocusOnPopup(e) { + e.preventDefault(); + }, - _getWidgetContainer: function() { - return this._getPopup().$content(); - }, + _getWidgetContainer() { + return this._getPopup().$content(); + }, - _getPopup: function() { - return this.dateBox._popup; - }, + _getPopup() { + return this.dateBox._popup; + }, - popupShowingHandler: noop, + popupShowingHandler: noop, - popupHiddenHandler: noop, + popupHiddenHandler: noop, - _updateValue: function() { - this._widget && this._widget.option('value', this.dateBoxValue()); - }, + _updateValue() { + this._widget && this._widget.option('value', this.dateBoxValue()); + }, - useCurrentDateByDefault: noop, + useCurrentDateByDefault: noop, - getDefaultDate: function() { - return new Date(); - }, + getDefaultDate() { + return new Date(); + }, - textChangedHandler: noop, + textChangedHandler: noop, - renderValue: function() { - if(this.dateBox.option('opened')) { - this._updateValue(); - } - }, + renderValue() { + if (this.dateBox.option('opened')) { + this._updateValue(); + } + }, - getValue: function() { - return this._widget.option('value'); - }, + getValue() { + return this._widget.option('value'); + }, - isAdaptivityChanged: function() { - return false; - }, + isAdaptivityChanged() { + return false; + }, - dispose: function() { - const popup = this._getPopup(); + dispose() { + const popup = this._getPopup(); - if(popup) { - popup.$content().empty(); - } - }, + if (popup) { + popup.$content().empty(); + } + }, - dateBoxValue: function() { - if(arguments.length) { - return this.dateBox.dateValue.apply(this.dateBox, arguments); - } else { - return this.dateBox.dateOption.apply(this.dateBox, ['value']); - } + dateBoxValue() { + if (arguments.length) { + return this.dateBox.dateValue.apply(this.dateBox, arguments); } + return this.dateBox.dateOption.apply(this.dateBox, ['value']); + }, }); export default DateBoxStrategy; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_box.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_box.ts index 8163a1737914..7ceb5e76c08f 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_box.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_box.ts @@ -1,7 +1,6 @@ -import registerComponent from '../../core/component_registrator'; -import DateBoxMask from './ui.date_box.mask'; +import registerComponent from '@js/core/component_registrator'; -// STYLE dateBox +import DateBoxMask from './m_date_box.mask'; registerComponent('dxDateBox', DateBoxMask); diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_utils.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_utils.ts index b108a8a0ad69..3e2c68199587 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_utils.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_utils.ts @@ -1,8 +1,8 @@ -import $ from '../../core/renderer'; -import dateSerialization from '../../core/utils/date_serialization'; -import { isDate } from '../../core/utils/type'; -import { each } from '../../core/utils/iterator'; -import dateLocalization from '../../localization/date'; +import $ from '@js/core/renderer'; +import dateSerialization from '@js/core/utils/date_serialization'; +import { each } from '@js/core/utils/iterator'; +import { isDate } from '@js/core/utils/type'; +import dateLocalization from '@js/localization/date'; const DATE_COMPONENTS = ['year', 'day', 'month', 'day']; const TIME_COMPONENTS = ['hours', 'minutes', 'seconds', 'milliseconds']; @@ -11,249 +11,252 @@ const ONE_MINUTE = 1000 * 60; const ONE_DAY = ONE_MINUTE * 60 * 24; const ONE_YEAR = ONE_DAY * 365; -const getStringFormat = function(format) { - const formatType = typeof format; +const getStringFormat = function (format) { + const formatType = typeof format; - if(formatType === 'string') { - return 'format'; - } + if (formatType === 'string') { + return 'format'; + } - if(formatType === 'object' && format.type !== undefined) { - return format.type; - } + if (formatType === 'object' && format.type !== undefined) { + return format.type; + } - return null; + return null; }; const dateUtils = { - SUPPORTED_FORMATS: ['date', 'time', 'datetime'], - - ONE_MINUTE, - ONE_DAY, - ONE_YEAR, - - MIN_DATEVIEW_DEFAULT_DATE: new Date(1900, 0, 1), - MAX_DATEVIEW_DEFAULT_DATE: function() { - const newDate = new Date(); - return new Date(newDate.getFullYear() + 50, newDate.getMonth(), newDate.getDate(), 23, 59, 59); - }(), - - FORMATS_INFO: { - 'date': { - getStandardPattern: function() { - return 'yyyy-MM-dd'; - }, - components: DATE_COMPONENTS - }, - 'time': { - getStandardPattern: function() { - return 'HH:mm'; - }, - components: TIME_COMPONENTS - }, - 'datetime': { - getStandardPattern: function() { - let standardPattern; - - (function androidFormatDetection() { - const androidFormatPattern = 'yyyy-MM-ddTHH:mmZ'; - const testDateString = '2000-01-01T01:01Z'; - - const $input = $('').attr('type', 'datetime'); - $input.val(testDateString); - - if($input.val()) { - standardPattern = androidFormatPattern; - } - })(); - - if(!standardPattern) { - standardPattern = 'yyyy-MM-ddTHH:mm:ssZ'; - } - - dateUtils.FORMATS_INFO['datetime'].getStandardPattern = function() { - return standardPattern; - }; - - return standardPattern; - }, - components: [...DATE_COMPONENTS, ...TIME_COMPONENTS] - }, - 'datetime-local': { - getStandardPattern: function() { - return 'yyyy-MM-ddTHH:mm:ss'; - }, - components: [...DATE_COMPONENTS, 'hours', 'minutes', 'seconds'] - } + SUPPORTED_FORMATS: ['date', 'time', 'datetime'], + + ONE_MINUTE, + ONE_DAY, + ONE_YEAR, + + MIN_DATEVIEW_DEFAULT_DATE: new Date(1900, 0, 1), + MAX_DATEVIEW_DEFAULT_DATE: (function () { + const newDate = new Date(); + return new Date(newDate.getFullYear() + 50, newDate.getMonth(), newDate.getDate(), 23, 59, 59); + }()), + + FORMATS_INFO: { + date: { + getStandardPattern() { + return 'yyyy-MM-dd'; + }, + components: DATE_COMPONENTS, }, - - FORMATS_MAP: { - 'date': 'shortdate', - 'time': 'shorttime', - 'datetime': 'shortdateshorttime' + time: { + getStandardPattern() { + return 'HH:mm'; + }, + components: TIME_COMPONENTS, }, + datetime: { + getStandardPattern() { + let standardPattern; - SUBMIT_FORMATS_MAP: { - 'date': 'date', - 'time': 'time', - 'datetime': 'datetime-local' - }, + (function androidFormatDetection() { + const androidFormatPattern = 'yyyy-MM-ddTHH:mmZ'; + const testDateString = '2000-01-01T01:01Z'; - toStandardDateFormat: function(date, type) { - const pattern = dateUtils.FORMATS_INFO[type].getStandardPattern(); + const $input = $('').attr('type', 'datetime'); + $input.val(testDateString); - return dateSerialization.serializeDate(date, pattern); - }, - - fromStandardDateFormat: function(text) { - const date = dateSerialization.dateParser(text); - return isDate(date) ? date : undefined; - }, + if ($input.val()) { + standardPattern = androidFormatPattern; + } + }()); - getMaxMonthDay: function(year, month) { - return new Date(year, month + 1, 0).getDate(); - }, - - mergeDates: function(oldValue, newValue, format) { - if(!newValue) { - return newValue || null; - } - if(!oldValue || isNaN(oldValue.getTime())) { - const now = new Date(null); - oldValue = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + if (!standardPattern) { + standardPattern = 'yyyy-MM-ddTHH:mm:ssZ'; } - const result = new Date(oldValue.valueOf()); - const formatInfo = dateUtils.FORMATS_INFO[format]; - - each(formatInfo.components, function() { - const componentInfo = dateUtils.DATE_COMPONENTS_INFO[this]; - result[componentInfo.setter](newValue[componentInfo.getter]()); - }); - - return result; - }, + dateUtils.FORMATS_INFO.datetime.getStandardPattern = function () { + return standardPattern; + }; - getLongestCaptionIndex: function(captionArray) { - let longestIndex = 0; - let longestCaptionLength = 0; - let i; - for(i = 0; i < captionArray.length; ++i) { - if(captionArray[i].length > longestCaptionLength) { - longestIndex = i; - longestCaptionLength = captionArray[i].length; - } - } - return longestIndex; + return standardPattern; + }, + components: [...DATE_COMPONENTS, ...TIME_COMPONENTS], }, - - formatUsesMonthName: function(format) { - return dateLocalization.formatUsesMonthName(format); + 'datetime-local': { + getStandardPattern() { + return 'yyyy-MM-ddTHH:mm:ss'; + }, + components: [...DATE_COMPONENTS, 'hours', 'minutes', 'seconds'], }, + }, + + FORMATS_MAP: { + date: 'shortdate', + time: 'shorttime', + datetime: 'shortdateshorttime', + }, + + SUBMIT_FORMATS_MAP: { + date: 'date', + time: 'time', + datetime: 'datetime-local', + }, + + toStandardDateFormat(date, type) { + const pattern = dateUtils.FORMATS_INFO[type].getStandardPattern(); + + return dateSerialization.serializeDate(date, pattern); + }, + + fromStandardDateFormat(text) { + const date = dateSerialization.dateParser(text); + return isDate(date) ? date : undefined; + }, + + getMaxMonthDay(year, month) { + return new Date(year, month + 1, 0).getDate(); + }, + + mergeDates(oldValue, newValue, format) { + if (!newValue) { + return newValue || null; + } + if (!oldValue || isNaN(oldValue.getTime())) { + // @ts-expect-error + const now = new Date(null); + oldValue = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + } - formatUsesDayName: function(format) { - return dateLocalization.formatUsesDayName(format); - }, + const result = new Date(oldValue.valueOf()); + const formatInfo = dateUtils.FORMATS_INFO[format]; + + each(formatInfo.components, function () { + // @ts-expect-error + const componentInfo = dateUtils.DATE_COMPONENTS_INFO[this]; + result[componentInfo.setter](newValue[componentInfo.getter]()); + }); + + return result; + }, + + getLongestCaptionIndex(captionArray) { + let longestIndex = 0; + let longestCaptionLength = 0; + let i; + for (i = 0; i < captionArray.length; ++i) { + if (captionArray[i].length > longestCaptionLength) { + longestIndex = i; + longestCaptionLength = captionArray[i].length; + } + } + return longestIndex; + }, - getLongestDate: function(format, monthNames, dayNames) { - const stringFormat = getStringFormat(format); - let month = 9; + formatUsesMonthName(format) { + // @ts-expect-error + return dateLocalization.formatUsesMonthName(format); + }, - if(!stringFormat || dateUtils.formatUsesMonthName(stringFormat)) { - month = dateUtils.getLongestCaptionIndex(monthNames); - } + formatUsesDayName(format) { + // @ts-expect-error + return dateLocalization.formatUsesDayName(format); + }, - const longestDate = new Date(1888, month, 21, 23, 59, 59, 999); + getLongestDate(format, monthNames, dayNames) { + const stringFormat = getStringFormat(format); + let month = 9; - if(!stringFormat || dateUtils.formatUsesDayName(stringFormat)) { - const date = longestDate.getDate() - longestDate.getDay() + dateUtils.getLongestCaptionIndex(dayNames); - longestDate.setDate(date); - } + if (!stringFormat || dateUtils.formatUsesMonthName(stringFormat)) { + month = dateUtils.getLongestCaptionIndex(monthNames); + } - return longestDate; - }, + const longestDate = new Date(1888, month, 21, 23, 59, 59, 999); - normalizeTime: function(date) { - date.setSeconds(0); - date.setMilliseconds(0); + if (!stringFormat || dateUtils.formatUsesDayName(stringFormat)) { + const date = longestDate.getDate() - longestDate.getDay() + dateUtils.getLongestCaptionIndex(dayNames); + longestDate.setDate(date); } -}; + return longestDate; + }, + normalizeTime(date) { + date.setSeconds(0); + date.setMilliseconds(0); + }, +}; +// @ts-expect-error dateUtils.DATE_COMPONENTS_INFO = { - 'year': { - getter: 'getFullYear', - setter: 'setFullYear', - formatter: function(value, date) { - const formatDate = new Date(date.getTime()); - formatDate.setFullYear(value); - return dateLocalization.format(formatDate, 'yyyy'); - }, - startValue: undefined, - endValue: undefined + year: { + getter: 'getFullYear', + setter: 'setFullYear', + formatter(value, date) { + const formatDate = new Date(date.getTime()); + formatDate.setFullYear(value); + return dateLocalization.format(formatDate, 'yyyy'); }, - - 'day': { - getter: 'getDate', - setter: 'setDate', - formatter: function(value, date) { - const formatDate = new Date(date.getTime()); - formatDate.setDate(value); - return dateLocalization.format(formatDate, 'd'); - }, - startValue: 1, - endValue: undefined + startValue: undefined, + endValue: undefined, + }, + + day: { + getter: 'getDate', + setter: 'setDate', + formatter(value, date) { + const formatDate = new Date(date.getTime()); + formatDate.setDate(value); + return dateLocalization.format(formatDate, 'd'); }, - - 'month': { - getter: 'getMonth', - setter: 'setMonth', - formatter: function(value) { - return dateLocalization.getMonthNames()[value]; - }, - startValue: 0, - endValue: 11 + startValue: 1, + endValue: undefined, + }, + + month: { + getter: 'getMonth', + setter: 'setMonth', + formatter(value) { + return dateLocalization.getMonthNames()[value]; }, - - 'hours': { - getter: 'getHours', - setter: 'setHours', - formatter: function(value) { - return dateLocalization.format(new Date(0, 0, 0, value), 'hour'); - }, - startValue: 0, - endValue: 23 + startValue: 0, + endValue: 11, + }, + + hours: { + getter: 'getHours', + setter: 'setHours', + formatter(value) { + return dateLocalization.format(new Date(0, 0, 0, value), 'hour'); }, - - 'minutes': { - getter: 'getMinutes', - setter: 'setMinutes', - formatter: function(value) { - return dateLocalization.format(new Date(0, 0, 0, 0, value), 'minute'); - }, - startValue: 0, - endValue: 59 + startValue: 0, + endValue: 23, + }, + + minutes: { + getter: 'getMinutes', + setter: 'setMinutes', + formatter(value) { + return dateLocalization.format(new Date(0, 0, 0, 0, value), 'minute'); }, - - 'seconds': { - getter: 'getSeconds', - setter: 'setSeconds', - formatter: function(value) { - return dateLocalization.format(new Date(0, 0, 0, 0, 0, value), 'second'); - }, - startValue: 0, - endValue: 59 + startValue: 0, + endValue: 59, + }, + + seconds: { + getter: 'getSeconds', + setter: 'setSeconds', + formatter(value) { + return dateLocalization.format(new Date(0, 0, 0, 0, 0, value), 'second'); }, - - 'milliseconds': { - getter: 'getMilliseconds', - setter: 'setMilliseconds', - formatter: function(value) { - return dateLocalization.format(new Date(0, 0, 0, 0, 0, 0, value), 'millisecond'); - }, - startValue: 0, - endValue: 999 - } + startValue: 0, + endValue: 59, + }, + + milliseconds: { + getter: 'getMilliseconds', + setter: 'setMilliseconds', + formatter(value) { + return dateLocalization.format(new Date(0, 0, 0, 0, 0, 0, value), 'millisecond'); + }, + startValue: 0, + endValue: 999, + }, }; export default dateUtils; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_view.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_view.ts index ec74a2b1ded5..f67d639bb999 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_view.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_view.ts @@ -1,12 +1,13 @@ -import $ from '../../core/renderer'; -import Editor from '../editor/editor'; -import DateViewRoller from './ui.date_view_roller'; -import dateUtils from '../../core/utils/date'; -import { each } from '../../core/utils/iterator'; -import { extend } from '../../core/utils/extend'; -import uiDateUtils from './ui.date_utils'; -import registerComponent from '../../core/component_registrator'; -import dateLocalization from '../../localization/date'; +import registerComponent from '@js/core/component_registrator'; +import $ from '@js/core/renderer'; +import dateUtils from '@js/core/utils/date'; +import { extend } from '@js/core/utils/extend'; +import { each } from '@js/core/utils/iterator'; +import dateLocalization from '@js/localization/date'; +import Editor from '@js/ui/editor/editor'; + +import uiDateUtils from './m_date_utils'; +import DateViewRoller from './m_date_view_roller'; const DATEVIEW_CLASS = 'dx-dateview'; const DATEVIEW_COMPACT_CLASS = 'dx-dateview-compact'; @@ -15,334 +16,337 @@ const DATEVIEW_ROLLER_CONTAINER_CLASS = 'dx-dateview-rollers'; const DATEVIEW_ROLLER_CLASS = 'dx-dateviewroller'; const TYPE = { - date: 'date', - datetime: 'datetime', - time: 'time' + date: 'date', + datetime: 'datetime', + time: 'time', }; const ROLLER_TYPE = { - year: 'year', - month: 'month', - day: 'day', - hours: 'hours' + year: 'year', + month: 'month', + day: 'day', + hours: 'hours', }; -const DateView = Editor.inherit({ - _valueOption: function() { - const value = this.option('value'); - const date = new Date(value); - - return !value || isNaN(date) ? this._getDefaultDate() : date; - }, - - _getDefaultDate: function() { - const date = new Date(); - - if(this.option('type') === TYPE.date) { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()); - } - - return date; - }, - - _getDefaultOptions: function() { - return extend(this.callBase(), { - minDate: uiDateUtils.MIN_DATEVIEW_DEFAULT_DATE, - maxDate: uiDateUtils.MAX_DATEVIEW_DEFAULT_DATE, - type: TYPE.date, - value: new Date(), - applyCompactClass: false - }); - }, - - _defaultOptionsRules: function() { - return this.callBase().concat([ - { - device: function(device) { - return device.deviceType !== 'desktop'; - }, - options: { - applyCompactClass: true - } - } - ]); - }, - - _render: function() { - this.callBase(); - this.$element().addClass(DATEVIEW_CLASS); - this._toggleFormatClasses(this.option('type')); - this._toggleCompactClass(); - }, - - _toggleFormatClasses: function(currentFormat, previousFormat) { - this.$element().addClass(DATEVIEW_CLASS + '-' + currentFormat); - - previousFormat && this.$element().removeClass(DATEVIEW_CLASS + '-' + previousFormat); - }, - - _toggleCompactClass: function() { - this.$element().toggleClass(DATEVIEW_COMPACT_CLASS, this.option('applyCompactClass')); - }, - - _wrapper: function() { - return this._$wrapper; - }, - - _renderContentImpl: function() { - this._$wrapper = $('
').addClass(DATEVIEW_WRAPPER_CLASS); - this._renderRollers(); - this._$wrapper.appendTo(this.$element()); - }, +const DateView = (Editor as any).inherit({ + _valueOption() { + const value = this.option('value'); + const date = new Date(value); + // @ts-expect-error + return !value || isNaN(date) ? this._getDefaultDate() : date; + }, - _renderRollers: function() { - if(!this._$rollersContainer) { - this._$rollersContainer = $('
').addClass(DATEVIEW_ROLLER_CONTAINER_CLASS); - } + _getDefaultDate() { + const date = new Date(); - this._$rollersContainer.empty(); - this._createRollerConfigs(); - - this._rollers = {}; - - const that = this; - - each(that._rollerConfigs, function(name) { - const $roller = $('
').appendTo(that._$rollersContainer) - .addClass(DATEVIEW_ROLLER_CLASS + '-' + that._rollerConfigs[name].type); - - that._rollers[that._rollerConfigs[name].type] = that._createComponent($roller, DateViewRoller, { - items: that._rollerConfigs[name].displayItems, - selectedIndex: that._rollerConfigs[name].selectedIndex, - showScrollbar: 'never', - scrollByContent: true, - onStart: function(e) { - const roller = e.component; - roller._toggleActive(true); - that._setActiveRoller(that._rollerConfigs[name], roller.option('selectedIndex')); - }, - onEnd: function(e) { - const roller = e.component; - roller._toggleActive(false); - }, - onClick: function(e) { - const roller = e.component; - roller._toggleActive(true); - that._setActiveRoller(that._rollerConfigs[name], roller.option('selectedIndex')); - that._setRollerState(that._rollerConfigs[name], roller.option('selectedIndex')); - roller._toggleActive(false); - }, - onSelectedIndexChanged: function(e) { - const roller = e.component; - that._setRollerState(that._rollerConfigs[name], roller.option('selectedIndex')); - } - }); - }); - that._$rollersContainer.appendTo(that._wrapper()); - }, - - _createRollerConfigs: function(type) { - const that = this; - type = type || that.option('type'); - that._rollerConfigs = {}; - - dateLocalization.getFormatParts(uiDateUtils.FORMATS_MAP[type]).forEach(function(partName) { - that._createRollerConfig(partName); - }); - }, - - _createRollerConfig: function(componentName) { - const componentInfo = uiDateUtils.DATE_COMPONENTS_INFO[componentName]; - - const valueRange = this._calculateRollerConfigValueRange(componentName); - const startValue = valueRange.startValue; - const endValue = valueRange.endValue; - - const formatter = componentInfo.formatter; - - const curDate = this._getCurrentDate(); - - const config = { - type: componentName, - setValue: componentInfo.setter, - valueItems: [], - displayItems: [], - getIndex: function(value) { - return value[componentInfo.getter]() - startValue; - } - }; - - for(let i = startValue; i <= endValue; i++) { - config.valueItems.push(i); - config.displayItems.push(formatter(i, curDate)); - } - - config.selectedIndex = config.getIndex(curDate); + if (this.option('type') === TYPE.date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); + } - this._rollerConfigs[componentName] = config; - }, + return date; + }, + + _getDefaultOptions() { + return extend(this.callBase(), { + minDate: uiDateUtils.MIN_DATEVIEW_DEFAULT_DATE, + maxDate: uiDateUtils.MAX_DATEVIEW_DEFAULT_DATE, + type: TYPE.date, + value: new Date(), + applyCompactClass: false, + }); + }, + + _defaultOptionsRules() { + return this.callBase().concat([ + { + device(device) { + return device.deviceType !== 'desktop'; + }, + options: { + applyCompactClass: true, + }, + }, + ]); + }, + + _render() { + this.callBase(); + this.$element().addClass(DATEVIEW_CLASS); + this._toggleFormatClasses(this.option('type')); + this._toggleCompactClass(); + }, + + _toggleFormatClasses(currentFormat, previousFormat) { + this.$element().addClass(`${DATEVIEW_CLASS}-${currentFormat}`); + + previousFormat && this.$element().removeClass(`${DATEVIEW_CLASS}-${previousFormat}`); + }, + + _toggleCompactClass() { + this.$element().toggleClass(DATEVIEW_COMPACT_CLASS, this.option('applyCompactClass')); + }, + + _wrapper() { + return this._$wrapper; + }, + + _renderContentImpl() { + this._$wrapper = $('
').addClass(DATEVIEW_WRAPPER_CLASS); + this._renderRollers(); + this._$wrapper.appendTo(this.$element()); + }, + + _renderRollers() { + if (!this._$rollersContainer) { + this._$rollersContainer = $('
').addClass(DATEVIEW_ROLLER_CONTAINER_CLASS); + } - _setActiveRoller: function(currentRoller) { - const activeRoller = currentRoller && this._rollers[currentRoller.type]; + this._$rollersContainer.empty(); + this._createRollerConfigs(); + + this._rollers = {}; + + const that = this; + + each(that._rollerConfigs, (name) => { + const $roller = $('
').appendTo(that._$rollersContainer) + .addClass(`${DATEVIEW_ROLLER_CLASS}-${that._rollerConfigs[name].type}`); + + that._rollers[that._rollerConfigs[name].type] = that._createComponent($roller, DateViewRoller, { + items: that._rollerConfigs[name].displayItems, + selectedIndex: that._rollerConfigs[name].selectedIndex, + showScrollbar: 'never', + scrollByContent: true, + onStart(e) { + const roller = e.component; + roller._toggleActive(true); + that._setActiveRoller(that._rollerConfigs[name], roller.option('selectedIndex')); + }, + onEnd(e) { + const roller = e.component; + roller._toggleActive(false); + }, + onClick(e) { + const roller = e.component; + roller._toggleActive(true); + that._setActiveRoller(that._rollerConfigs[name], roller.option('selectedIndex')); + that._setRollerState(that._rollerConfigs[name], roller.option('selectedIndex')); + roller._toggleActive(false); + }, + onSelectedIndexChanged(e) { + const roller = e.component; + that._setRollerState(that._rollerConfigs[name], roller.option('selectedIndex')); + }, + }); + }); + that._$rollersContainer.appendTo(that._wrapper()); + }, + + _createRollerConfigs(type) { + const that = this; + type = type || that.option('type'); + that._rollerConfigs = {}; + // @ts-expect-error + dateLocalization.getFormatParts(uiDateUtils.FORMATS_MAP[type]).forEach((partName) => { + that._createRollerConfig(partName); + }); + }, + + _createRollerConfig(componentName) { + // @ts-expect-error + const componentInfo = uiDateUtils.DATE_COMPONENTS_INFO[componentName]; + + const valueRange = this._calculateRollerConfigValueRange(componentName); + const { startValue } = valueRange; + const { endValue } = valueRange; + + const { formatter } = componentInfo; + + const curDate = this._getCurrentDate(); + + const config = { + type: componentName, + setValue: componentInfo.setter, + valueItems: [], + displayItems: [], + getIndex(value) { + return value[componentInfo.getter]() - startValue; + }, + }; + + for (let i = startValue; i <= endValue; i++) { + // @ts-expect-error + config.valueItems.push(i); + // @ts-expect-error + config.displayItems.push(formatter(i, curDate)); + } + // @ts-expect-error + config.selectedIndex = config.getIndex(curDate); + + this._rollerConfigs[componentName] = config; + }, + + _setActiveRoller(currentRoller) { + const activeRoller = currentRoller && this._rollers[currentRoller.type]; + + each(this._rollers, function () { + this.toggleActiveState(this === activeRoller); + }); + }, + + _updateRollersPosition() { + const that = this; + each(this._rollers, function (type) { + const correctIndex = that._rollerConfigs[type].getIndex(that._getCurrentDate()); + this.option('selectedIndex', correctIndex); + }); + }, + + _setRollerState(roller, selectedIndex) { + if (selectedIndex !== roller.selectedIndex) { + const rollerValue = roller.valueItems[selectedIndex]; + const { setValue } = roller; + let currentValue = new Date(this._getCurrentDate()); + let currentDate = currentValue.getDate(); + const minDate = this.option('minDate'); + const maxDate = this.option('maxDate'); + + if (roller.type === ROLLER_TYPE.month) { + currentDate = Math.min(currentDate, uiDateUtils.getMaxMonthDay(currentValue.getFullYear(), rollerValue)); + } else if (roller.type === ROLLER_TYPE.year) { + currentDate = Math.min(currentDate, uiDateUtils.getMaxMonthDay(rollerValue, currentValue.getMonth())); + } + + currentValue.setDate(currentDate); + currentValue[setValue](rollerValue); + + const normalizedDate = dateUtils.normalizeDate(currentValue, minDate, maxDate); + currentValue = uiDateUtils.mergeDates(normalizedDate, currentValue, 'time'); + currentValue = dateUtils.normalizeDate(currentValue, minDate, maxDate); + + this.option('value', currentValue); + + roller.selectedIndex = selectedIndex; + } - each(this._rollers, function() { - this.toggleActiveState(this === activeRoller); - }); - }, + if (roller.type === ROLLER_TYPE.year) { + this._refreshRollers(); + } - _updateRollersPosition: function() { - const that = this; - each(this._rollers, function(type) { - const correctIndex = that._rollerConfigs[type].getIndex(that._getCurrentDate()); - this.option('selectedIndex', correctIndex); + if (roller.type === ROLLER_TYPE.month) { + this._refreshRoller(ROLLER_TYPE.day); + this._refreshRoller(ROLLER_TYPE.hours); + } + }, + + _refreshRoller(rollerType) { + const roller = this._rollers[rollerType]; + + if (roller) { + this._createRollerConfig(rollerType); + const rollerConfig = this._rollerConfigs[rollerType]; + if (rollerType === ROLLER_TYPE.day || rollerConfig.displayItems.toString() !== roller.option('items').toString()) { + roller.option({ + items: rollerConfig.displayItems, + selectedIndex: rollerConfig.selectedIndex, }); - }, - - _setRollerState: function(roller, selectedIndex) { - if(selectedIndex !== roller.selectedIndex) { - const rollerValue = roller.valueItems[selectedIndex]; - const setValue = roller.setValue; - let currentValue = new Date(this._getCurrentDate()); - let currentDate = currentValue.getDate(); - const minDate = this.option('minDate'); - const maxDate = this.option('maxDate'); - - if(roller.type === ROLLER_TYPE.month) { - currentDate = Math.min(currentDate, uiDateUtils.getMaxMonthDay(currentValue.getFullYear(), rollerValue)); - } else if(roller.type === ROLLER_TYPE.year) { - currentDate = Math.min(currentDate, uiDateUtils.getMaxMonthDay(rollerValue, currentValue.getMonth())); - } - - currentValue.setDate(currentDate); - currentValue[setValue](rollerValue); - - const normalizedDate = dateUtils.normalizeDate(currentValue, minDate, maxDate); - currentValue = uiDateUtils.mergeDates(normalizedDate, currentValue, 'time'); - currentValue = dateUtils.normalizeDate(currentValue, minDate, maxDate); - - this.option('value', currentValue); - - roller.selectedIndex = selectedIndex; - } - - if(roller.type === ROLLER_TYPE.year) { - this._refreshRollers(); - } - - if(roller.type === ROLLER_TYPE.month) { - this._refreshRoller(ROLLER_TYPE.day); - this._refreshRoller(ROLLER_TYPE.hours); - } - }, - - _refreshRoller: function(rollerType) { - const roller = this._rollers[rollerType]; - - if(roller) { - this._createRollerConfig(rollerType); - const rollerConfig = this._rollerConfigs[rollerType]; - if(rollerType === ROLLER_TYPE.day || rollerConfig.displayItems.toString() !== roller.option('items').toString()) { - roller.option({ - items: rollerConfig.displayItems, - selectedIndex: rollerConfig.selectedIndex - }); - } - } - }, - - _getCurrentDate: function() { - const curDate = this._valueOption(); - const minDate = this.option('minDate'); - const maxDate = this.option('maxDate'); - - return dateUtils.normalizeDate(curDate, minDate, maxDate); - }, - - _calculateRollerConfigValueRange: function(componentName) { - const curDate = this._getCurrentDate(); - const minDate = this.option('minDate'); - const maxDate = this.option('maxDate'); - - const minYear = dateUtils.sameYear(curDate, minDate); - const minMonth = minYear && curDate.getMonth() === minDate.getMonth(); - const maxYear = dateUtils.sameYear(curDate, maxDate); - const maxMonth = maxYear && curDate.getMonth() === maxDate.getMonth(); - const minHour = minMonth && curDate.getDate() === minDate.getDate(); - const maxHour = maxMonth && curDate.getDate() === maxDate.getDate(); - - const componentInfo = uiDateUtils.DATE_COMPONENTS_INFO[componentName]; - let startValue = componentInfo.startValue; - let endValue = componentInfo.endValue; - - if(componentName === ROLLER_TYPE.year) { - startValue = minDate.getFullYear(); - endValue = maxDate.getFullYear(); - } + } + } + }, + + _getCurrentDate() { + const curDate = this._valueOption(); + const minDate = this.option('minDate'); + const maxDate = this.option('maxDate'); + + return dateUtils.normalizeDate(curDate, minDate, maxDate); + }, + + _calculateRollerConfigValueRange(componentName) { + const curDate = this._getCurrentDate(); + const minDate = this.option('minDate'); + const maxDate = this.option('maxDate'); + + const minYear = dateUtils.sameYear(curDate, minDate); + const minMonth = minYear && curDate.getMonth() === minDate.getMonth(); + const maxYear = dateUtils.sameYear(curDate, maxDate); + const maxMonth = maxYear && curDate.getMonth() === maxDate.getMonth(); + const minHour = minMonth && curDate.getDate() === minDate.getDate(); + const maxHour = maxMonth && curDate.getDate() === maxDate.getDate(); + // @ts-expect-error + const componentInfo = uiDateUtils.DATE_COMPONENTS_INFO[componentName]; + let { startValue } = componentInfo; + let { endValue } = componentInfo; + + if (componentName === ROLLER_TYPE.year) { + startValue = minDate.getFullYear(); + endValue = maxDate.getFullYear(); + } - if(componentName === ROLLER_TYPE.month) { - if(minYear) { - startValue = minDate.getMonth(); - } - if(maxYear) { - endValue = maxDate.getMonth(); - } - } + if (componentName === ROLLER_TYPE.month) { + if (minYear) { + startValue = minDate.getMonth(); + } + if (maxYear) { + endValue = maxDate.getMonth(); + } + } - if(componentName === ROLLER_TYPE.day) { - endValue = uiDateUtils.getMaxMonthDay(curDate.getFullYear(), curDate.getMonth()); - if(minYear && minMonth) { - startValue = minDate.getDate(); - } - if(maxYear && maxMonth) { - endValue = maxDate.getDate(); - } - } + if (componentName === ROLLER_TYPE.day) { + endValue = uiDateUtils.getMaxMonthDay(curDate.getFullYear(), curDate.getMonth()); + if (minYear && minMonth) { + startValue = minDate.getDate(); + } + if (maxYear && maxMonth) { + endValue = maxDate.getDate(); + } + } - if(componentName === ROLLER_TYPE.hours) { - startValue = minHour ? minDate.getHours() : startValue; - endValue = maxHour ? maxDate.getHours() : endValue; - } + if (componentName === ROLLER_TYPE.hours) { + startValue = minHour ? minDate.getHours() : startValue; + endValue = maxHour ? maxDate.getHours() : endValue; + } - return { - startValue: startValue, - endValue: endValue - }; - }, - - _refreshRollers: function() { - this._refreshRoller(ROLLER_TYPE.month); - this._refreshRoller(ROLLER_TYPE.day); - this._refreshRoller(ROLLER_TYPE.hours); - }, - - _optionChanged: function(args) { - switch(args.name) { - case 'minDate': - case 'maxDate': - case 'type': - this._renderRollers(); - this._toggleFormatClasses(args.value, args.previousValue); - break; - case 'visible': - this.callBase(args); - if(args.value) { - this._renderRollers(); - } - break; - case 'value': - this.option('value', this._valueOption()); - this._refreshRollers(); - this._updateRollersPosition(); - break; - default: - this.callBase(args); + return { + startValue, + endValue, + }; + }, + + _refreshRollers() { + this._refreshRoller(ROLLER_TYPE.month); + this._refreshRoller(ROLLER_TYPE.day); + this._refreshRoller(ROLLER_TYPE.hours); + }, + + _optionChanged(args) { + switch (args.name) { + case 'minDate': + case 'maxDate': + case 'type': + this._renderRollers(); + this._toggleFormatClasses(args.value, args.previousValue); + break; + case 'visible': + this.callBase(args); + if (args.value) { + this._renderRollers(); } - }, - - _clean: function() { - this.callBase(); - delete this._$rollersContainer; + break; + case 'value': + this.option('value', this._valueOption()); + this._refreshRollers(); + this._updateRollersPosition(); + break; + default: + this.callBase(args); } + }, + + _clean() { + this.callBase(); + delete this._$rollersContainer; + }, }); registerComponent('dxDateView', DateView); diff --git a/packages/devextreme/js/__internal/ui/date_box/m_date_view_roller.ts b/packages/devextreme/js/__internal/ui/date_box/m_date_view_roller.ts index f61c7ba1b508..58f2e97dfb03 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_date_view_roller.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_date_view_roller.ts @@ -1,16 +1,17 @@ -import { getHeight } from '../../core/utils/size'; -import $ from '../../core/renderer'; -import eventsEngine from '../../events/core/events_engine'; -import registerComponent from '../../core/component_registrator'; -import { extend } from '../../core/utils/extend'; -import { each } from '../../core/utils/iterator'; -import { addNamespace } from '../../events/utils/index'; -import { name as clickEventName } from '../../events/click'; -import Scrollable from '../scroll_view/ui.scrollable'; -import devices from '../../core/devices'; -import fx from '../../animation/fx'; -import { resetPosition } from '../../animation/translator'; -import { convertToLocation } from '../../renovation/ui/scroll_view/utils/convert_location'; +import fx from '@js/animation/fx'; +import { resetPosition } from '@js/animation/translator'; +import registerComponent from '@js/core/component_registrator'; +import devices from '@js/core/devices'; +import $ from '@js/core/renderer'; +import { extend } from '@js/core/utils/extend'; +import { each } from '@js/core/utils/iterator'; +import { getHeight } from '@js/core/utils/size'; +import { name as clickEventName } from '@js/events/click'; +import eventsEngine from '@js/events/core/events_engine'; +import { addNamespace } from '@js/events/utils/index'; +import { convertToLocation } from '@js/renovation/ui/scroll_view/utils/convert_location'; + +import Scrollable from '../scroll_view/m_scrollable'; const DATEVIEW_ROLLER_CLASS = 'dx-dateviewroller'; const DATEVIEW_ROLLER_ACTIVE_CLASS = 'dx-state-active'; @@ -22,322 +23,330 @@ const DATEVIEW_ROLLER_ITEM_SELECTED_FRAME_CLASS = 'dx-dateview-item-selected-fra const DATEVIEW_ROLLER_ITEM_SELECTED_BORDER_CLASS = 'dx-dateview-item-selected-border'; class DateViewRoller extends Scrollable { - - _getDefaultOptions() { - return extend(super._getDefaultOptions(), { - showScrollbar: 'never', - useNative: false, - selectedIndex: 0, - bounceEnabled: false, - items: [], - showOnClick: false, - onClick: null, - onSelectedIndexChanged: null, - scrollByContent: true - }); - } - - _init() { - super._init(); - - this.option('onVisibilityChange', this._visibilityChangedHandler.bind(this)); - this.option('onEnd', this._endActionHandler.bind(this)); - } - - _render() { - super._render(); - - this._renderSelectedItemFrame(); - - this.$element().addClass(DATEVIEW_ROLLER_CLASS); - - this._renderContainerClick(); - this._renderItems(); - this._renderSelectedValue(); - this._renderItemsClick(); - this._renderWheelEvent(); - - this._renderSelectedIndexChanged(); - } - - _renderSelectedIndexChanged() { - this._selectedIndexChanged = this._createActionByOption('onSelectedIndexChanged'); - } - - _renderWheelEvent() { - eventsEngine.on($(this.container()), 'dxmousewheel', (e) => { - this._isWheelScrolled = true; - }); - } - - _renderContainerClick() { - if(!this.option('showOnClick')) { - return; - } - - const eventName = addNamespace(clickEventName, this.NAME); - - const clickAction = this._createActionByOption('onClick'); - - eventsEngine.off($(this.container()), eventName); - eventsEngine.on($(this.container()), eventName, function(e) { - clickAction({ event: e }); - }); - } - - _renderItems() { - const items = this.option('items') || []; - let $items = $(); - - $(this.content()).empty(); - // NOTE: rendering ~166+30+12+24+60
s >> 50mc - items.forEach(function(item) { - $items = $items.add( - $('
') - .addClass(DATEVIEW_ROLLER_ITEM_CLASS) - .append(item) - ); - }); - - $(this.content()).append($items); - this._$items = $items; - this.update(); - } - - _renderSelectedItemFrame() { + _getDefaultOptions() { + return extend(super._getDefaultOptions(), { + showScrollbar: 'never', + useNative: false, + selectedIndex: 0, + bounceEnabled: false, + items: [], + showOnClick: false, + onClick: null, + onSelectedIndexChanged: null, + scrollByContent: true, + }); + } + + _init(): void { + super._init(); + + this.option('onVisibilityChange', this._visibilityChangedHandler.bind(this)); + this.option('onEnd', this._endActionHandler.bind(this)); + } + + _render(): void { + super._render(); + + this._renderSelectedItemFrame(); + + this.$element().addClass(DATEVIEW_ROLLER_CLASS); + + this._renderContainerClick(); + this._renderItems(); + // @ts-expect-error + this._renderSelectedValue(); + this._renderItemsClick(); + this._renderWheelEvent(); + + this._renderSelectedIndexChanged(); + } + + _renderSelectedIndexChanged(): void { + this._selectedIndexChanged = this._createActionByOption('onSelectedIndexChanged'); + } + + _renderWheelEvent(): void { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + eventsEngine.on($(this.container()), 'dxmousewheel', (e) => { + this._isWheelScrolled = true; + }); + } + + _renderContainerClick(): void { + if (!this.option('showOnClick')) { + return; + } + + const eventName = addNamespace(clickEventName, this.NAME); + + const clickAction = this._createActionByOption('onClick'); + + eventsEngine.off($(this.container()), eventName); + eventsEngine.on($(this.container()), eventName, (e) => { + clickAction({ event: e }); + }); + } + + _renderItems() { + const items = this.option('items') || []; + // @ts-expect-error + let $items = $(); + + $(this.content()).empty(); + // NOTE: rendering ~166+30+12+24+60
s >> 50mc + items.forEach((item) => { + $items = $items.add( + // @ts-expect-error $('
') - .addClass(DATEVIEW_ROLLER_ITEM_SELECTED_FRAME_CLASS) - .append($('
').addClass(DATEVIEW_ROLLER_ITEM_SELECTED_BORDER_CLASS)) - .appendTo($(this.container())); - } - - _renderSelectedValue(selectedIndex) { - const index = this._fitIndex(selectedIndex ?? this.option('selectedIndex')); - - this._moveTo({ top: this._getItemPosition(index) }); - this._renderActiveStateItem(); - } - - _fitIndex(index) { - const items = this.option('items') || []; - const itemCount = items.length; - - if(index >= itemCount) { - return itemCount - 1; - } - - if(index < 0) { - return 0; - } - - return index; + .addClass(DATEVIEW_ROLLER_ITEM_CLASS) + .append(item), + ); + }); + + $(this.content()).append($items); + this._$items = $items; + this.update(); + } + + _renderSelectedItemFrame() { + $('
') + .addClass(DATEVIEW_ROLLER_ITEM_SELECTED_FRAME_CLASS) + .append($('
').addClass(DATEVIEW_ROLLER_ITEM_SELECTED_BORDER_CLASS)) + .appendTo($(this.container())); + } + + _renderSelectedValue(selectedIndex) { + const index = this._fitIndex(selectedIndex ?? this.option('selectedIndex')); + + this._moveTo({ top: this._getItemPosition(index) }); + this._renderActiveStateItem(); + } + + _fitIndex(index) { + const items = this.option('items') || []; + const itemCount = items.length; + + if (index >= itemCount) { + return itemCount - 1; + } + + if (index < 0) { + return 0; + } + + return index; + } + + _getItemPosition(index) { + return Math.round(this._itemHeight() * index); + } + + _renderItemsClick() { + const itemSelector = this._getItemSelector(); + const eventName = addNamespace(clickEventName, this.NAME); + + eventsEngine.off(this.$element(), eventName, itemSelector); + eventsEngine.on(this.$element(), eventName, itemSelector, this._itemClickHandler.bind(this)); + } + + _getItemSelector() { + return `.${DATEVIEW_ROLLER_ITEM_CLASS}`; + } + + _itemClickHandler(e) { + this.option('selectedIndex', this._itemElementIndex(e.currentTarget)); + } + + _itemElementIndex(itemElement) { + return this._itemElements().index(itemElement); + } + + _itemElements() { + return this.$element().find(this._getItemSelector()); + } + + _renderActiveStateItem() { + const selectedIndex = this.option('selectedIndex'); + each(this._$items, function (index) { + $(this).toggleClass(DATEVIEW_ROLLER_ITEM_SELECTED_CLASS, selectedIndex === index); + }); + } + + _shouldScrollToNeighborItem() { + return devices.real().deviceType === 'desktop' + && this._isWheelScrolled; + } + + _moveTo(targetLocation): void { + // @ts-expect-error + const { top, left } = convertToLocation(targetLocation); + + const location = this.scrollOffset(); + const delta = { + // @ts-expect-error + x: location.left - left, + // @ts-expect-error + y: location.top - top, + }; + + if (this._isVisible() && (delta.x || delta.y)) { + this._prepareDirections(true); + + if (this._animation && !this._shouldScrollToNeighborItem()) { + const that = this; + + // @ts-expect-error + fx.stop($(this.content())); + // @ts-expect-error + fx.animate($(this.content()), { + duration: 200, + type: 'slide', + to: { top: Math.floor(delta.y) }, + complete() { + resetPosition($(that.content())); + that.handleMove({ delta }); + }, + }); + delete this._animation; + } else { + this.handleMove({ delta }); + } } + } - _getItemPosition(index) { - return Math.round(this._itemHeight() * index); - } + _validate(e) { + return this._moveIsAllowed(e); + } - _renderItemsClick() { - const itemSelector = this._getItemSelector(); - const eventName = addNamespace(clickEventName, this.NAME); + _fitSelectedIndexInRange(index) { + const itemsCount = this.option('items').length; + return Math.max(Math.min(index, itemsCount - 1), 0); + } - eventsEngine.off(this.$element(), eventName, itemSelector); - eventsEngine.on(this.$element(), eventName, itemSelector, this._itemClickHandler.bind(this)); - } + _isInNullNeighborhood(x) { + const EPS = 0.1; + return -EPS <= x && x <= EPS; + } - _getItemSelector() { - return '.' + DATEVIEW_ROLLER_ITEM_CLASS; - } + _getSelectedIndexAfterScroll(currentSelectedIndex): number { + const locationTop = this.scrollOffset().top; - _itemClickHandler(e) { - this.option('selectedIndex', this._itemElementIndex(e.currentTarget)); - } + const currentSelectedIndexPosition = currentSelectedIndex * this._itemHeight(); + const dy = locationTop - currentSelectedIndexPosition; - _itemElementIndex(itemElement) { - return this._itemElements().index(itemElement); + if (this._isInNullNeighborhood(dy)) { + return currentSelectedIndex; } - _itemElements() { - return this.$element().find(this._getItemSelector()); - } + const direction = dy > 0 ? 1 : -1; + const newSelectedIndex = this._fitSelectedIndexInRange(currentSelectedIndex + direction); - _renderActiveStateItem() { - const selectedIndex = this.option('selectedIndex'); - each(this._$items, function(index) { - $(this).toggleClass(DATEVIEW_ROLLER_ITEM_SELECTED_CLASS, selectedIndex === index); - }); - } + return newSelectedIndex; + } - _shouldScrollToNeighborItem() { - return devices.real().deviceType === 'desktop' - && this._isWheelScrolled; + _getNewSelectedIndex(currentSelectedIndex): number { + if (this._shouldScrollToNeighborItem()) { + return this._getSelectedIndexAfterScroll(currentSelectedIndex); } - _moveTo(targetLocation) { - const { top, left } = convertToLocation(targetLocation); - - const location = this.scrollOffset(); - const delta = { - x: location.left - left, - y: location.top - top - }; - - if(this._isVisible() && (delta.x || delta.y)) { - this._prepareDirections(true); - - if(this._animation && !this._shouldScrollToNeighborItem()) { - const that = this; - - fx.stop($(this.content())); - fx.animate($(this.content()), { - duration: 200, - type: 'slide', - to: { top: Math.floor(delta.y) }, - complete() { - resetPosition($(that.content())); - that.handleMove({ delta }); - } - }); - delete this._animation; - } else { - this.handleMove({ delta }); - } - } - } + this._animation = true; + const ratio = this.scrollOffset().top / this._itemHeight(); + return Math.round(ratio); + } - _validate(e) { - return this._moveIsAllowed(e); - } + _endActionHandler(): void { + const currentSelectedIndex = this.option('selectedIndex'); + const newSelectedIndex = this._getNewSelectedIndex(currentSelectedIndex); - _fitSelectedIndexInRange(index) { - const itemsCount = this.option('items').length; - return Math.max(Math.min(index, itemsCount - 1), 0); + if (newSelectedIndex === currentSelectedIndex) { + this._renderSelectedValue(newSelectedIndex); + } else { + this.option('selectedIndex', newSelectedIndex); } - _isInNullNeighborhood(x) { - const EPS = 0.1; - return -EPS <= x && x <= EPS; - } + this._isWheelScrolled = false; + } - _getSelectedIndexAfterScroll(currentSelectedIndex) { - const locationTop = this.scrollOffset().top; + _itemHeight() { + const $item = this._$items.first(); - const currentSelectedIndexPosition = currentSelectedIndex * this._itemHeight(); - const dy = locationTop - currentSelectedIndexPosition; + return getHeight($item); + } - if(this._isInNullNeighborhood(dy)) { - return currentSelectedIndex; - } + _toggleActive(state: boolean): void { + this.$element().toggleClass(DATEVIEW_ROLLER_ACTIVE_CLASS, state); + } - const direction = dy > 0 ? 1 : -1; - const newSelectedIndex = this._fitSelectedIndexInRange(currentSelectedIndex + direction); + _isVisible(): boolean { + return $(this.container()).is(':visible'); + } - return newSelectedIndex; - } + _fireSelectedIndexChanged(value, previousValue): void { + this._selectedIndexChanged({ + value, + previousValue, + event: undefined, + }); + } - _getNewSelectedIndex(currentSelectedIndex) { - if(this._shouldScrollToNeighborItem()) { - return this._getSelectedIndexAfterScroll(currentSelectedIndex); - } + _visibilityChanged(visible): void { + super._visibilityChanged(visible); + this._visibilityChangedHandler(visible); + } - this._animation = true; - const ratio = this.scrollOffset().top / this._itemHeight(); - return Math.round(ratio); + _visibilityChangedHandler(visible): void { + if (visible) { + // uses for purposes of renovated scrollable widget + this._visibilityTimer = setTimeout(() => { + this._renderSelectedValue(this.option('selectedIndex')); + }); } + this.toggleActiveState(false); + } - _endActionHandler() { - const currentSelectedIndex = this.option('selectedIndex'); - const newSelectedIndex = this._getNewSelectedIndex(currentSelectedIndex); + toggleActiveState(state: boolean): void { + this.$element().toggleClass(DATEVIEW_ROLLER_CURRENT_CLASS, state); + } - if(newSelectedIndex === currentSelectedIndex) { - this._renderSelectedValue(newSelectedIndex); - } else { - this.option('selectedIndex', newSelectedIndex); - } + _refreshSelectedIndex(): void { + const selectedIndex = this.option('selectedIndex'); + const fitIndex = this._fitIndex(selectedIndex); - this._isWheelScrolled = false; + if (fitIndex === selectedIndex) { + this._renderActiveStateItem(); + } else { + this.option('selectedIndex', fitIndex); } + } - _itemHeight() { - const $item = this._$items.first(); - - return getHeight($item); - } - - _toggleActive(state) { - this.$element().toggleClass(DATEVIEW_ROLLER_ACTIVE_CLASS, state); - } - - _isVisible() { - return $(this.container()).is(':visible'); - } - - _fireSelectedIndexChanged(value, previousValue) { - this._selectedIndexChanged({ - value: value, - previousValue: previousValue, - event: undefined - }); - } - - _visibilityChanged(visible) { - super._visibilityChanged(visible); - this._visibilityChangedHandler(visible); - } - - _visibilityChangedHandler(visible) { - if(visible) { - // uses for purposes of renovated scrollable widget - this._visibilityTimer = setTimeout(() => { - this._renderSelectedValue(this.option('selectedIndex')); - }); - } - this.toggleActiveState(false); - } - - toggleActiveState(state) { - this.$element().toggleClass(DATEVIEW_ROLLER_CURRENT_CLASS, state); - } - - _refreshSelectedIndex() { - const selectedIndex = this.option('selectedIndex'); - const fitIndex = this._fitIndex(selectedIndex); - - if(fitIndex === selectedIndex) { - this._renderActiveStateItem(); - } else { - this.option('selectedIndex', fitIndex); - } - } - - _optionChanged(args) { - switch(args.name) { - case 'selectedIndex': - this._fireSelectedIndexChanged(args.value, args.previousValue); - this._renderSelectedValue(args.value); - break; - case 'items': - this._renderItems(); - this._refreshSelectedIndex(); - break; - case 'onClick': - case 'showOnClick': - this._renderContainerClick(); - break; - case 'onSelectedIndexChanged': - this._renderSelectedIndexChanged(); - break; - default: - super._optionChanged(args); - } + _optionChanged(args: Record): void { + switch (args.name) { + case 'selectedIndex': + this._fireSelectedIndexChanged(args.value, args.previousValue); + this._renderSelectedValue(args.value); + break; + case 'items': + this._renderItems(); + this._refreshSelectedIndex(); + break; + case 'onClick': + case 'showOnClick': + this._renderContainerClick(); + break; + case 'onSelectedIndexChanged': + this._renderSelectedIndexChanged(); + break; + default: + super._optionChanged(args); } + } - _dispose() { - clearTimeout(this._visibilityTimer); - super._dispose(); - } + _dispose(): void { + clearTimeout(this._visibilityTimer); + super._dispose(); + } } - +// @ts-expect-error ts-error registerComponent('dxDateViewRoller', DateViewRoller); export default DateViewRoller; diff --git a/packages/devextreme/js/__internal/ui/date_box/m_time_view.ts b/packages/devextreme/js/__internal/ui/date_box/m_time_view.ts index d988934cb9f1..3f3205139499 100644 --- a/packages/devextreme/js/__internal/ui/date_box/m_time_view.ts +++ b/packages/devextreme/js/__internal/ui/date_box/m_time_view.ts @@ -1,12 +1,13 @@ -import $ from '../../core/renderer'; -import Editor from '../editor/editor'; -import NumberBox from '../number_box'; -import SelectBox from '../select_box'; -import Box from '../box'; -import { extend } from '../../core/utils/extend'; -import registerComponent from '../../core/component_registrator'; -import dateLocalization from '../../localization/date'; -import dateUtils from './ui.date_utils'; +import registerComponent from '@js/core/component_registrator'; +import $ from '@js/core/renderer'; +import { extend } from '@js/core/utils/extend'; +import dateLocalization from '@js/localization/date'; +import Box from '@js/ui/box'; +import Editor from '@js/ui/editor/editor'; +import NumberBox from '@js/ui/number_box'; +import SelectBox from '@js/ui/select_box'; + +import dateUtils from './m_date_utils'; const TIMEVIEW_CLASS = 'dx-timeview'; const TIMEVIEW_CLOCK_CLASS = 'dx-timeview-clock'; @@ -18,296 +19,299 @@ const TIMEVIEW_FORMAT12_AM = -1; const TIMEVIEW_FORMAT12_PM = 1; const TIMEVIEW_MINUTEARROW_CLASS = 'dx-timeview-minutearrow'; -const rotateArrow = function($arrow, angle, offset) { - cssRotate($arrow, angle, offset); +const rotateArrow = function ($arrow, angle, offset) { + cssRotate($arrow, angle, offset); }; -const cssRotate = function($arrow, angle, offset) { - $arrow.css('transform', 'rotate(' + angle + 'deg)' + ' translate(0,' + offset + 'px)'); +const cssRotate = function ($arrow, angle, offset) { + // eslint-disable-next-line no-useless-concat + $arrow.css('transform', `rotate(${angle}deg)` + ` translate(0,${offset}px)`); }; +const TimeView = (Editor as any).inherit({ + + _getDefaultOptions() { + return extend(this.callBase(), { + value: new Date(Date.now()), + use24HourFormat: true, + _showClock: true, + _arrowOffset: 5, + stylingMode: undefined, + }); + }, + + _getValue() { + return this.option('value') || new Date(); + }, + + _init() { + this.callBase(); + + this.$element().addClass(TIMEVIEW_CLASS); + }, + + _render() { + this.callBase(); + + this._renderBox(); + this._updateTime(); + }, + + _renderBox() { + const $box = $('
').appendTo(this.$element()); + const items = []; + + if (this.option('_showClock')) { + // @ts-expect-error + items.push({ + ratio: 1, + shrink: 0, + baseSize: 'auto', + template: this._renderClock.bind(this), + }); + } + // @ts-expect-error + items.push({ + ratio: 0, + shrink: 0, + baseSize: 'auto', + template: this._renderField.bind(this), + }); + + this._createComponent($box, Box, { + height: '100%', + width: '100%', + direction: 'col', + items, + }); + }, + + _renderClock(_, __, container) { + this._$hourArrow = $('
').addClass(TIMEVIEW_HOURARROW_CLASS); + this._$minuteArrow = $('
').addClass(TIMEVIEW_MINUTEARROW_CLASS); + + const $container = $(container); + $container.addClass(TIMEVIEW_CLOCK_CLASS) + .append(this._$hourArrow) + .append(this._$minuteArrow); + + this.setAria('role', 'presentation', $container); + }, + + _updateClock() { + const time = this._getValue(); + const hourArrowAngle = time.getHours() / 12 * 360 + time.getMinutes() / 60 * 30; + const minuteArrowAngle = time.getMinutes() / 60 * 360; + + rotateArrow(this._$hourArrow, hourArrowAngle, this.option('_arrowOffset')); + rotateArrow(this._$minuteArrow, minuteArrowAngle, this.option('_arrowOffset')); + }, + + _getBoxItems(is12HourFormat) { + const items = [{ + ratio: 0, + shrink: 0, + baseSize: 'auto', + template: () => this._hourBox.$element(), + }, { + ratio: 0, + shrink: 0, + baseSize: 'auto', + // @ts-expect-error + template: $('
').addClass(TIMEVIEW_TIME_SEPARATOR_CLASS).text(dateLocalization.getTimeSeparator()), + }, { + ratio: 0, + shrink: 0, + baseSize: 'auto', + template: () => this._minuteBox.$element(), + }]; + + if (is12HourFormat) { + items.push({ + ratio: 0, + shrink: 0, + baseSize: 'auto', + template: () => this._format12.$element(), + }); + } + + return items; + }, + + _renderField() { + const is12HourFormat = !this.option('use24HourFormat'); -const TimeView = Editor.inherit({ + this._createHourBox(is12HourFormat); + this._createMinuteBox(); - _getDefaultOptions: function() { - return extend(this.callBase(), { - value: new Date(Date.now()), - use24HourFormat: true, - _showClock: true, - _arrowOffset: 5, - stylingMode: undefined - }); - }, + if (is12HourFormat) { + this._createFormat12Box(); + } - _getValue: function() { - return this.option('value') || new Date(); - }, + return this._createComponent($('
').addClass(TIMEVIEW_FIELD_CLASS), Box, { + direction: 'row', + align: 'center', + crossAlign: 'center', + items: this._getBoxItems(is12HourFormat), + }).$element(); + }, + + _createHourBox(is12HourFormat) { + const editor = this._hourBox = this._createComponent($('
'), NumberBox, extend({ + min: -1, + max: is12HourFormat ? 13 : 24, + value: this._getValue().getHours(), + onValueChanged: this._onHourBoxValueChanged.bind(this), + onKeyboardHandled: (opts) => this._keyboardHandler(opts), + }, this._getNumberBoxConfig())); + + editor.setAria('label', 'hours'); + }, + + _isPM() { + return !this.option('use24HourFormat') && this._format12.option('value') === 1; + }, + + _onHourBoxValueChanged({ value, component }) { + const currentValue = this._getValue(); + const newValue = new Date(currentValue); + let newHours = this._convertMaxHourToMin(value); + + component.option('value', newHours); + + if (this._isPM()) { + newHours += 12; + } - _init: function() { - this.callBase(); + newValue.setHours(newHours); + dateUtils.normalizeTime(newValue); + this.option('value', newValue); + }, + + _convertMaxHourToMin(hours) { + const maxHoursValue = this.option('use24HourFormat') ? 24 : 12; + return (maxHoursValue + hours) % maxHoursValue; + }, + + _createMinuteBox() { + const editor = this._minuteBox = this._createComponent($('
'), NumberBox, extend({ + min: -1, + max: 60, + value: this._getValue().getMinutes(), + onKeyboardHandled: (opts) => this._keyboardHandler(opts), + onValueChanged: ({ value, component }) => { + const newMinutes = (60 + value) % 60; + component.option('value', newMinutes); + + const time = new Date(this._getValue()); + time.setMinutes(newMinutes); + dateUtils.normalizeTime(time); + this.option('value', time); + }, + }, this._getNumberBoxConfig())); + + editor.setAria('label', 'minutes'); + }, + + _createFormat12Box() { + // @ts-expect-error + const periodNames = dateLocalization.getPeriodNames(); + const editor = this._format12 = this._createComponent($('
').addClass(TIMEVIEW_FORMAT12_CLASS), SelectBox, { + items: [ + { value: TIMEVIEW_FORMAT12_AM, text: periodNames[0] }, + { value: TIMEVIEW_FORMAT12_PM, text: periodNames[1] }, + ], + valueExpr: 'value', + displayExpr: 'text', + onKeyboardHandled: (opts) => this._keyboardHandler(opts), + onValueChanged: ({ value }) => { + const hours = this._getValue().getHours(); + const time = new Date(this._getValue()); + const newHours = (hours + value * 12) % 24; + + time.setHours(newHours); + this.option('value', time); + }, + value: this._getValue().getHours() >= 12 ? TIMEVIEW_FORMAT12_PM : TIMEVIEW_FORMAT12_AM, + stylingMode: this.option('stylingMode'), + }); + + editor.setAria('label', 'type'); + }, + + _refreshFormat12() { + if (this.option('use24HourFormat')) return; + + const value = this._getValue(); + const hours = value.getHours(); + const isPM = hours >= 12; + const newValue = isPM ? TIMEVIEW_FORMAT12_PM : TIMEVIEW_FORMAT12_AM; + + this._silentEditorValueUpdate(this._format12, newValue); + }, + + _silentEditorValueUpdate(editor, value) { + if (editor) { + editor._suppressValueChangeAction(); + editor.option('value', value); + editor._resumeValueChangeAction(); + } + }, + + _getNumberBoxConfig() { + return { + showSpinButtons: true, + displayValueFormatter(value) { + return (value < 10 ? '0' : '') + value; + }, + stylingMode: this.option('stylingMode'), + }; + }, + + _normalizeHours(hours) { + return this.option('use24HourFormat') ? hours : hours % 12 || 12; + }, + + _updateField() { + const hours = this._normalizeHours(this._getValue().getHours()); + + this._silentEditorValueUpdate(this._hourBox, hours); + this._silentEditorValueUpdate(this._minuteBox, this._getValue().getMinutes()); + + this._refreshFormat12(); + }, + + _updateTime() { + if (this.option('_showClock')) { + this._updateClock(); + } - this.$element().addClass(TIMEVIEW_CLASS); - }, + this._updateField(); + }, - _render: function() { - this.callBase(); + _visibilityChanged(visible) { + if (visible) { + this._updateTime(); + } + }, - this._renderBox(); + _optionChanged(args) { + switch (args.name) { + case 'value': this._updateTime(); - }, - - _renderBox: function() { - const $box = $('
').appendTo(this.$element()); - const items = []; - - if(this.option('_showClock')) { - items.push({ - ratio: 1, - shrink: 0, - baseSize: 'auto', - template: this._renderClock.bind(this) - }); - } - - items.push({ - ratio: 0, - shrink: 0, - baseSize: 'auto', - template: this._renderField.bind(this) - }); - - this._createComponent($box, Box, { - height: '100%', - width: '100%', - direction: 'col', - items: items - }); - }, - - _renderClock: function(_, __, container) { - this._$hourArrow = $('
').addClass(TIMEVIEW_HOURARROW_CLASS); - this._$minuteArrow = $('
').addClass(TIMEVIEW_MINUTEARROW_CLASS); - - const $container = $(container); - $container.addClass(TIMEVIEW_CLOCK_CLASS) - .append(this._$hourArrow) - .append(this._$minuteArrow); - - this.setAria('role', 'presentation', $container); - }, - - _updateClock: function() { - const time = this._getValue(); - const hourArrowAngle = time.getHours() / 12 * 360 + time.getMinutes() / 60 * 30; - const minuteArrowAngle = time.getMinutes() / 60 * 360; - - rotateArrow(this._$hourArrow, hourArrowAngle, this.option('_arrowOffset')); - rotateArrow(this._$minuteArrow, minuteArrowAngle, this.option('_arrowOffset')); - }, - - _getBoxItems: function(is12HourFormat) { - const items = [{ - ratio: 0, - shrink: 0, - baseSize: 'auto', - template: () => this._hourBox.$element() - }, { - ratio: 0, - shrink: 0, - baseSize: 'auto', - template: $('
').addClass(TIMEVIEW_TIME_SEPARATOR_CLASS).text(dateLocalization.getTimeSeparator()) - }, { - ratio: 0, - shrink: 0, - baseSize: 'auto', - template: () => this._minuteBox.$element() - }]; - - if(is12HourFormat) { - items.push({ - ratio: 0, - shrink: 0, - baseSize: 'auto', - template: () => this._format12.$element() - }); - } - - return items; - }, - - _renderField: function() { - const is12HourFormat = !this.option('use24HourFormat'); - - this._createHourBox(is12HourFormat); - this._createMinuteBox(); - - if(is12HourFormat) { - this._createFormat12Box(); - } - - return this._createComponent($('
').addClass(TIMEVIEW_FIELD_CLASS), Box, { - direction: 'row', - align: 'center', - crossAlign: 'center', - items: this._getBoxItems(is12HourFormat) - }).$element(); - }, - - _createHourBox: function(is12HourFormat) { - const editor = this._hourBox = this._createComponent($('
'), NumberBox, extend({ - min: -1, - max: is12HourFormat ? 13 : 24, - value: this._getValue().getHours(), - onValueChanged: this._onHourBoxValueChanged.bind(this), - onKeyboardHandled: opts => this._keyboardHandler(opts) - }, this._getNumberBoxConfig())); - - editor.setAria('label', 'hours'); - }, - - _isPM: function() { - return !this.option('use24HourFormat') && this._format12.option('value') === 1; - }, - - _onHourBoxValueChanged: function({ value, component }) { - const currentValue = this._getValue(); - const newValue = new Date(currentValue); - let newHours = this._convertMaxHourToMin(value); - - component.option('value', newHours); - - if(this._isPM()) { - newHours += 12; - } - - newValue.setHours(newHours); - dateUtils.normalizeTime(newValue); - this.option('value', newValue); - }, - - _convertMaxHourToMin: function(hours) { - const maxHoursValue = this.option('use24HourFormat') ? 24 : 12; - return (maxHoursValue + hours) % maxHoursValue; - }, - - _createMinuteBox: function() { - const editor = this._minuteBox = this._createComponent($('
'), NumberBox, extend({ - min: -1, - max: 60, - value: this._getValue().getMinutes(), - onKeyboardHandled: opts => this._keyboardHandler(opts), - onValueChanged: ({ value, component }) => { - const newMinutes = (60 + value) % 60; - component.option('value', newMinutes); - - const time = new Date(this._getValue()); - time.setMinutes(newMinutes); - dateUtils.normalizeTime(time); - this.option('value', time); - } - }, this._getNumberBoxConfig())); - - editor.setAria('label', 'minutes'); - }, - - _createFormat12Box: function() { - const periodNames = dateLocalization.getPeriodNames(); - const editor = this._format12 = this._createComponent($('
').addClass(TIMEVIEW_FORMAT12_CLASS), SelectBox, { - items: [ - { value: TIMEVIEW_FORMAT12_AM, text: periodNames[0] }, - { value: TIMEVIEW_FORMAT12_PM, text: periodNames[1] } - ], - valueExpr: 'value', - displayExpr: 'text', - onKeyboardHandled: opts => this._keyboardHandler(opts), - onValueChanged: ({ value }) => { - const hours = this._getValue().getHours(); - const time = new Date(this._getValue()); - const newHours = (hours + value * 12) % 24; - - time.setHours(newHours); - this.option('value', time); - }, - value: this._getValue().getHours() >= 12 ? TIMEVIEW_FORMAT12_PM : TIMEVIEW_FORMAT12_AM, - stylingMode: this.option('stylingMode') - }); - - editor.setAria('label', 'type'); - }, - - _refreshFormat12: function() { - if(this.option('use24HourFormat')) return; - - const value = this._getValue(); - const hours = value.getHours(); - const isPM = hours >= 12; - const newValue = isPM ? TIMEVIEW_FORMAT12_PM : TIMEVIEW_FORMAT12_AM; - - this._silentEditorValueUpdate(this._format12, newValue); - }, - - _silentEditorValueUpdate: function(editor, value) { - if(editor) { - editor._suppressValueChangeAction(); - editor.option('value', value); - editor._resumeValueChangeAction(); - } - }, - - _getNumberBoxConfig: function() { - return { - showSpinButtons: true, - displayValueFormatter: function(value) { - return (value < 10 ? '0' : '') + value; - }, - stylingMode: this.option('stylingMode') - }; - }, - - _normalizeHours: function(hours) { - return this.option('use24HourFormat') ? hours : hours % 12 || 12; - }, - - _updateField: function() { - const hours = this._normalizeHours(this._getValue().getHours()); - - this._silentEditorValueUpdate(this._hourBox, hours); - this._silentEditorValueUpdate(this._minuteBox, this._getValue().getMinutes()); - - this._refreshFormat12(); - }, - - _updateTime: function() { - if(this.option('_showClock')) { - this._updateClock(); - } - - this._updateField(); - }, - - _visibilityChanged: function(visible) { - if(visible) { - this._updateTime(); - } - }, - - _optionChanged: function(args) { - switch(args.name) { - case 'value': - this._updateTime(); - this.callBase(args); - break; - case '_arrowOffset': - break; - case 'use24HourFormat': - case '_showClock': - case 'stylingMode': - this._invalidate(); - break; - default: - this.callBase(args); - } + this.callBase(args); + break; + case '_arrowOffset': + break; + case 'use24HourFormat': + case '_showClock': + case 'stylingMode': + this._invalidate(); + break; + default: + this.callBase(args); } + }, }); registerComponent('dxTimeView', TimeView); diff --git a/packages/devextreme/js/__internal/ui/date_range_box/strategy/m_rangeCalendar.ts b/packages/devextreme/js/__internal/ui/date_range_box/strategy/m_rangeCalendar.ts index 19a92568c0df..dc7fd18559b5 100644 --- a/packages/devextreme/js/__internal/ui/date_range_box/strategy/m_rangeCalendar.ts +++ b/packages/devextreme/js/__internal/ui/date_range_box/strategy/m_rangeCalendar.ts @@ -3,8 +3,8 @@ import { isFunction } from '@js/core/utils/type'; import eventsEngine from '@js/events/core/events_engine'; import type Calendar from '@js/ui/calendar'; import type DateBox from '@js/ui/date_box'; -import CalendarStrategy from '@js/ui/date_box/ui.date_box.strategy.calendar'; +import CalendarStrategy from '../../date_box/m_date_box.strategy.calendar'; import { getDeserializedDate, isSameDateArrays, isSameDates } from '../m_date_range.utils'; import type DateRangeBox from '../m_date_range_box'; import type MultiselectDateBox from '../m_multiselect_date_box'; diff --git a/packages/devextreme/js/ui/date_box.js b/packages/devextreme/js/ui/date_box.js index 5e7806e15199..f3b9f7c8aff4 100644 --- a/packages/devextreme/js/ui/date_box.js +++ b/packages/devextreme/js/ui/date_box.js @@ -1,2 +1,4 @@ -import DateBox from './date_box/ui.date_box'; +import DateBox from '../__internal/ui/date_box/m_date_box'; export default DateBox; + +// STYLE dateBox diff --git a/packages/devextreme/js/ui/date_box/ui.date_view.js b/packages/devextreme/js/ui/date_box/ui.date_view.js new file mode 100644 index 000000000000..698649132b36 --- /dev/null +++ b/packages/devextreme/js/ui/date_box/ui.date_view.js @@ -0,0 +1,3 @@ +import DateView from '../../__internal/ui/date_box/m_time_view'; + +export default DateView; diff --git a/packages/devextreme/js/ui/date_box/ui.date_view_roller.js b/packages/devextreme/js/ui/date_box/ui.date_view_roller.js new file mode 100644 index 000000000000..01fc36dd3306 --- /dev/null +++ b/packages/devextreme/js/ui/date_box/ui.date_view_roller.js @@ -0,0 +1,3 @@ +import DateViewRoller from '../../__internal/ui/date_box/m_time_view'; + +export default DateViewRoller; diff --git a/packages/devextreme/js/ui/date_box/ui.time_view.js b/packages/devextreme/js/ui/date_box/ui.time_view.js new file mode 100644 index 000000000000..57aa736114c2 --- /dev/null +++ b/packages/devextreme/js/ui/date_box/ui.time_view.js @@ -0,0 +1,3 @@ +import TimeView from '../../__internal/ui/date_box/m_time_view'; + +export default TimeView; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/datebox.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/datebox.tests.js index 824ffd7a383b..2b4fadee0c59 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/datebox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/datebox.tests.js @@ -4,7 +4,7 @@ import $ from 'jquery'; import fx from 'animation/fx'; import support from 'core/utils/support'; import devices from 'core/devices'; -import uiDateUtils from 'ui/date_box/ui.date_utils'; +import uiDateUtils from '__internal/ui/date_box/m_date_utils'; import dateLocalization from 'localization/date'; import ko from 'knockout'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateView.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateView.tests.js index 555dc587a621..9742bd7c41ca 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateView.tests.js @@ -5,8 +5,8 @@ import { triggerShownEvent } from 'events/visibility_change'; import 'generic_light.css!'; import $ from 'jquery'; import dateLocalization from 'localization/date'; -import 'ui/date_box/ui.date_view'; -import 'ui/date_box/ui.date_view_roller'; +import '__internal/ui/date_box/m_date_view'; +import '__internal/ui/date_box/m_date_view_roller'; import executeAsyncMock from '../../helpers/executeAsyncMock.js'; import pointerMock from '../../helpers/pointerMock.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.markup.tests.js index 547516ab1d10..6ce8b2084c95 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.markup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.markup.tests.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import support from 'core/utils/support'; -import uiDateUtils from 'ui/date_box/ui.date_utils'; +import uiDateUtils from '__internal/ui/date_box/m_date_utils'; import DateBox from 'ui/date_box'; import dateLocalization from 'localization/date'; import keyboardMock from '../../helpers/keyboardMock.js'; 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 d9cfba29ab7c..a0ffea2d6a74 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 @@ -1,5 +1,5 @@ import $ from 'jquery'; -import { renderDateParts, getDatePartIndexByPosition } from 'ui/date_box/ui.date_box.mask.parts'; +import { renderDateParts, getDatePartIndexByPosition } from '__internal/ui/date_box/m_date_box.mask.parts'; import dateParser from 'localization/ldml/date.parser'; import dateLocalization from 'localization/date'; import { noop } from 'core/utils/common'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.tests.js index 7d13351622b3..13546ed8027a 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.tests.js @@ -17,7 +17,7 @@ import ja from 'localization/messages/ja.json!'; import pointerMock from '../../helpers/pointerMock.js'; import support from 'core/utils/support'; import typeUtils from 'core/utils/type'; -import uiDateUtils from 'ui/date_box/ui.date_utils'; +import uiDateUtils from '__internal/ui/date_box/m_date_utils'; import { noop } from 'core/utils/common'; import { logger } from 'core/utils/console'; import { normalizeKeyName } from 'events/utils/index';