diff --git a/packages/devextreme/js/__internal/core/options/m_index.ts b/packages/devextreme/js/__internal/core/options/m_index.ts index bc81f61a5194..872728c8a406 100644 --- a/packages/devextreme/js/__internal/core/options/m_index.ts +++ b/packages/devextreme/js/__internal/core/options/m_index.ts @@ -245,7 +245,7 @@ export class Options { return Object.prototype.hasOwnProperty.call(this._deprecated, name); } - cache(name, options) { + cache(name, options?: Record) { const isGetter = arguments.length < 2; if (isGetter) { diff --git a/packages/devextreme/js/__internal/ui/editor.ts b/packages/devextreme/js/__internal/ui/editor.ts deleted file mode 100644 index db90895c9ca3..000000000000 --- a/packages/devextreme/js/__internal/ui/editor.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Editor from '@js/ui/editor/editor'; -import type ValidationMessage from '@js/ui/validation_message'; -import type { Properties } from '@ts/core/widget/widget'; -import Widget from '@ts/core/widget/widget'; - -declare class ExtendedEditor extends Widget { - public _validationMessage?: ValidationMessage; - - _saveValueChangeEvent(e: unknown): void; - - _renderValidationState(): void; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const TypedEditor: typeof ExtendedEditor = Editor as any; - -export default TypedEditor; diff --git a/packages/devextreme/js/__internal/ui/editor/m_editor.ts b/packages/devextreme/js/__internal/ui/editor/m_editor.ts index ceb25826f671..25d36592c809 100644 --- a/packages/devextreme/js/__internal/ui/editor/m_editor.ts +++ b/packages/devextreme/js/__internal/ui/editor/m_editor.ts @@ -1,15 +1,23 @@ +import type { Position } from '@js/common'; +import type { NativeEventInfo } from '@js/common/core/events'; import EventsEngine from '@js/common/core/events/core/events_engine'; import { addNamespace, normalizeKeyName } from '@js/common/core/events/utils/index'; import { data } from '@js/core/element_data'; import Guid from '@js/core/guid'; +import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import Callbacks from '@js/core/utils/callbacks'; -import { noop } from '@js/core/utils/common'; import { extend } from '@js/core/utils/extend'; import { hasWindow } from '@js/core/utils/window'; +import type PublicEditor from '@js/ui/editor/editor'; +import type { + EditorOptions, + ValueChangedInfo, +} from '@js/ui/editor/editor'; import ValidationEngine from '@js/ui/validation_engine'; import ValidationMessage from '@js/ui/validation_message'; -import Widget from '@js/ui/widget/ui.widget'; +import type { OptionChanged } from '@ts/core/widget/types'; +import Widget from '@ts/core/widget/widget'; import domUtils from '../../core/utils/m_dom'; @@ -30,133 +38,168 @@ const VALIDATION_MESSAGE_KEYS_MAP = { validationMessageOffset: 'offset', validationBoundary: 'boundary', }; -// @ts-expect-error -const Editor = Widget.inherit({ - ctor() { - this.showValidationMessageTimeout = null; + +export type UnresolvedEvents = 'onContentReady' | 'onDisposing' | 'onInitialized' | 'onOptionChanged' | 'onValueChanged'; +type ValueChangedEvent = NativeEventInfo & ValueChangedInfo; + +export interface EditorProperties< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TComponent extends PublicEditor = PublicEditor, +> extends EditorOptions { + validationMessageOffset: { h: number; v: number }; + validationBoundary?: dxElementWrapper; + validationTooltipOptions: Record; + _showValidationMessage: boolean; + name?: string; + _onMarkupRendered?: () => void; +} + +class Editor< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TProperties extends EditorProperties = EditorProperties, +> extends Widget { + _initialValue: unknown; + + public _validationMessage?: ValidationMessage; + + public validationRequest!: ReturnType; + + // eslint-disable-next-line no-restricted-globals + public showValidationMessageTimeout?: ReturnType; + + private _valueChangeAction!: ((event?: Record) => void); + + private _valueChangeActionSuppressed?: boolean; + + private _valueChangeEventInstance?: ValueChangedEvent; + + _$validationMessage?: dxElementWrapper; + + ctor(element: Element, options: TProperties): void { + this.showValidationMessageTimeout = undefined; this.validationRequest = Callbacks(); - this.callBase.apply(this, arguments); - }, + super.ctor(element, options); + } - _createElement(element) { - this.callBase(element); + _createElement(element: Element): void { + super._createElement(element); const $element = this.$element(); if ($element) { data($element[0], VALIDATION_TARGET, this); } - }, + } - _initOptions(options) { - this.callBase.apply(this, arguments); - // @ts-expect-error + _initOptions(options: TProperties): void { + super._initOptions(options); + // @ts-expect-error ts-error this.option(ValidationEngine.initValidationOptions(options)); - }, + } - _init() { + _init(): void { this._initialValue = this.option('value'); - this.callBase(); - this._options.cache('validationTooltipOptions', this.option('validationTooltipOptions')); + super._init(); + + const { validationTooltipOptions } = this.option(); + + this._options.cache('validationTooltipOptions', validationTooltipOptions); const $element = this.$element(); $element.addClass(DX_INVALID_BADGE_CLASS); - }, + } - _getDefaultOptions() { - return extend(this.callBase(), { + _getDefaultOptions(): TProperties { + return { + ...super._getDefaultOptions(), value: null, - name: '', - onValueChanged: null, - readOnly: false, - isValid: true, - validationError: null, - validationErrors: null, - validationStatus: VALIDATION_STATUS_VALID, - validationMessageMode: 'auto', - validationMessagePosition: 'bottom', - validationBoundary: undefined, - validationMessageOffset: { h: 0, v: 0 }, - validationTooltipOptions: {}, - _showValidationMessage: true, - isDirty: false, - }); - }, + } as unknown as TProperties; + } - _attachKeyboardEvents() { + _attachKeyboardEvents(): void { if (!this.option('readOnly')) { - this.callBase(); + super._attachKeyboardEvents(); } - }, + } - _setOptionsByReference() { - this.callBase(); + _setOptionsByReference(): void { + super._setOptionsByReference(); extend(this._optionsByReference, { validationError: true, }); - }, + } - _createValueChangeAction() { + _createValueChangeAction(): void { this._valueChangeAction = this._createActionByOption('onValueChanged', { excludeValidators: ['disabled', 'readOnly'], }); - }, + } - _suppressValueChangeAction() { + _suppressValueChangeAction(): void { this._valueChangeActionSuppressed = true; - }, + } - _resumeValueChangeAction() { + _resumeValueChangeAction(): void { this._valueChangeActionSuppressed = false; - }, + } - _initMarkup() { + _initMarkup(): void { this._toggleReadOnlyState(); - this._setSubmitElementName(this.option('name')); - this.callBase(); + const { name, _onMarkupRendered: markupRendered } = this.option(); + + this._setSubmitElementName(name); + + super._initMarkup(); this._renderValidationState(); - this.option('_onMarkupRendered')?.(); - }, - _raiseValueChangeAction(value, previousValue) { + markupRendered?.(); + } + + _raiseValueChangeAction(value: unknown, previousValue: unknown): void { if (!this._valueChangeAction) { this._createValueChangeAction(); } this._valueChangeAction(this._valueChangeArgs(value, previousValue)); - }, + } - _valueChangeArgs(value, previousValue) { + _valueChangeArgs(value: unknown, previousValue: unknown): { + value: unknown; + previousValue: unknown; + event?: NativeEventInfo & ValueChangedInfo; + } { return { value, previousValue, event: this._valueChangeEventInstance, }; - }, + } - _saveValueChangeEvent(e) { + _saveValueChangeEvent(e: ValueChangedEvent | undefined): void { this._valueChangeEventInstance = e; - }, + } - _focusInHandler(e) { - const isValidationMessageShownOnFocus = this.option('validationMessageMode') === 'auto'; + _focusInHandler(e: NativeEventInfo): void { + const { validationMessageMode } = this.option(); + const isValidationMessageShownOnFocus = validationMessageMode === 'auto'; // NOTE: The click should be processed before the validation message is shown because // it can change the editor's value if (this._canValueBeChangedByClick() && isValidationMessageShownOnFocus) { + // @ts-expect-error ts-error // NOTE: Prevent the validation message from showing const $validationMessageWrapper = this._validationMessage?.$wrapper(); $validationMessageWrapper?.removeClass(INVALID_MESSAGE_AUTO); @@ -164,83 +207,104 @@ const Editor = Widget.inherit({ clearTimeout(this.showValidationMessageTimeout); // NOTE: Show the validation message after a click changes the value - this.showValidationMessageTimeout = setTimeout(() => $validationMessageWrapper?.addClass(INVALID_MESSAGE_AUTO), 150); + // eslint-disable-next-line no-restricted-globals + this.showValidationMessageTimeout = setTimeout( + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + () => $validationMessageWrapper?.addClass(INVALID_MESSAGE_AUTO), + 150, + ); } - return this.callBase(e); - }, + super._focusInHandler(e); + } - _canValueBeChangedByClick() { + // eslint-disable-next-line class-methods-use-this + _canValueBeChangedByClick(): boolean { return false; - }, + } - _getStylingModePrefix() { + // eslint-disable-next-line class-methods-use-this + _getStylingModePrefix(): string { return 'dx-editor-'; - }, + } - _renderStylingMode() { - const optionName = 'stylingMode'; - const optionValue = this.option(optionName); + _renderStylingMode(): void { + const { stylingMode } = this.option(); const prefix = this._getStylingModePrefix(); const allowedStylingClasses = ALLOWED_STYLING_MODES.map((mode) => prefix + mode); allowedStylingClasses.forEach((className) => this.$element().removeClass(className)); - let stylingModeClass = prefix + optionValue; + let stylingModeClass = prefix + String(stylingMode); if (!allowedStylingClasses.includes(stylingModeClass)) { + const optionName = 'stylingMode'; const defaultOptionValue = this._getDefaultOptions()[optionName]; - const platformOptionValue = this._convertRulesToOptions(this._defaultOptionsRules())[optionName]; - stylingModeClass = prefix + (platformOptionValue || defaultOptionValue); + const platformOptionValue = this._convertRulesToOptions( + this._defaultOptionsRules(), + )[optionName]; + + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + stylingModeClass = prefix + (platformOptionValue ?? defaultOptionValue); } this.$element().addClass(stylingModeClass); - }, + } - _getValidationErrors() { - let validationErrors = this.option('validationErrors'); - if (!validationErrors && this.option('validationError')) { - validationErrors = [this.option('validationError')]; + _getValidationErrors(): unknown[] | undefined { + let { validationErrors } = this.option(); + const { validationError } = this.option(); + + if (!validationErrors && validationError) { + validationErrors = [validationError]; } return validationErrors; - }, + } - _disposeValidationMessage() { + _disposeValidationMessage(): void { if (this._$validationMessage) { this._$validationMessage.remove(); this.setAria('describedby', null); this._$validationMessage = undefined; this._validationMessage = undefined; } - }, + } - _toggleValidationClasses(isInvalid) { + _toggleValidationClasses(isInvalid: boolean): void { this.$element().toggleClass(INVALID_CLASS, isInvalid); this.setAria(VALIDATION_STATUS_INVALID, isInvalid || undefined); - }, - - _renderValidationState() { - const isValid = this.option('isValid') && this.option('validationStatus') !== VALIDATION_STATUS_INVALID; + } + + _renderValidationState(): void { + const { + validationStatus, + _showValidationMessage: showValidationMessage, + } = this.option(); + const isValid = this.option('isValid') && validationStatus !== VALIDATION_STATUS_INVALID; const validationErrors = this._getValidationErrors(); const $element = this.$element(); this._toggleValidationClasses(!isValid); - if (!hasWindow() || this.option('_showValidationMessage') === false) { + if (!hasWindow() || !showValidationMessage) { return; } this._disposeValidationMessage(); if (!isValid && validationErrors) { const { - validationMessageMode, validationMessageOffset, validationBoundary, rtlEnabled, + validationMessageMode, + validationMessageOffset, + validationBoundary, + rtlEnabled, } = this.option(); this._$validationMessage = $('
').appendTo($element); const validationMessageContentId = `dx-${new Guid()}`; this.setAria('describedby', validationMessageContentId); + // @ts-expect-error ts-error this._validationMessage = new ValidationMessage(this._$validationMessage, extend({ validationErrors, rtlEnabled, @@ -254,29 +318,31 @@ const Editor = Widget.inherit({ }, this._options.cache('validationTooltipOptions'))); this._bindInnerWidgetOptions(this._validationMessage, 'validationTooltipOptions'); } - }, + } - _getValidationMessagePosition() { - return this.option('validationMessagePosition'); - }, + _getValidationMessagePosition(): Position | undefined { + const { validationMessagePosition } = this.option(); + return validationMessagePosition; + } - _getValidationMessageTarget() { + _getValidationMessageTarget(): dxElementWrapper { return this.$element(); - }, + } - _toggleReadOnlyState() { - const readOnly = this.option('readOnly'); + _toggleReadOnlyState(): void { + const { readOnly } = this.option(); this._toggleBackspaceHandler(readOnly); this.$element().toggleClass(READONLY_STATE_CLASS, !!readOnly); this._setAriaReadonly(readOnly); - }, + } - _setAriaReadonly(readOnly) { + _setAriaReadonly(readOnly: boolean | undefined): void { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing this.setAria('readonly', readOnly || undefined); - }, + } - _toggleBackspaceHandler(isReadOnly) { + _toggleBackspaceHandler(isReadOnly: boolean | undefined): void { const $eventTarget = this._keyboardEventBindingTarget(); const eventName = addNamespace('keydown', READONLY_NAMESPACE); @@ -289,44 +355,55 @@ const Editor = Widget.inherit({ } }); } - }, + } - _dispose() { + _dispose(): void { const element = this.$element()[0]; data(element, VALIDATION_TARGET, null); clearTimeout(this.showValidationMessageTimeout); this._disposeValidationMessage(); - this.callBase(); - }, + super._dispose(); + } - _setSubmitElementName(name) { + _setSubmitElementName(name: string | undefined): void { const $submitElement = this._getSubmitElement(); if (!$submitElement) { return; } - if (name.length > 0) { + if (name && name.length > 0) { $submitElement.attr('name', name); } else { $submitElement.removeAttr('name'); } - }, + } - _getSubmitElement() { + // eslint-disable-next-line class-methods-use-this + _getSubmitElement(): dxElementWrapper | null { return null; - }, - - _setValidationMessageOption({ name, value }) { - const optionKey = VALIDATION_MESSAGE_KEYS_MAP[name] ? VALIDATION_MESSAGE_KEYS_MAP[name] : name; + } + + _setValidationMessageOption({ + name, + value, + }: OptionChanged | Record): void { + const optionKey = VALIDATION_MESSAGE_KEYS_MAP[String(name)] + ? VALIDATION_MESSAGE_KEYS_MAP[String(name)] + : name; this._validationMessage?.option(optionKey, value); - }, + } + + // eslint-disable-next-line class-methods-use-this + _hasActiveElement(): boolean { + return false; + } - _hasActiveElement: noop, + _optionChanged(args: OptionChanged | Record): void { + const { name, value, previousValue } = args; - _optionChanged(args) { - switch (args.name) { + switch (name) { case 'onValueChanged': this._createValueChangeAction(); break; @@ -335,31 +412,32 @@ const Editor = Widget.inherit({ this._refreshFocusState(); break; case 'value': - if (args.value != args.previousValue) { // eslint-disable-line eqeqeq - this.option('isDirty', this._initialValue !== args.value); + if (value != previousValue) { // eslint-disable-line eqeqeq + this.option('isDirty', this._initialValue !== value); this.validationRequest.fire({ - value: args.value, + value, editor: this, }); } if (!this._valueChangeActionSuppressed) { - this._raiseValueChangeAction(args.value, args.previousValue); + this._raiseValueChangeAction(value, previousValue); this._saveValueChangeEvent(undefined); } break; case 'width': - this.callBase(args); + super._optionChanged(args); + // @ts-expect-error ts-error this._validationMessage?.updateMaxWidth(); break; case 'name': - this._setSubmitElementName(args.value); + this._setSubmitElementName(value as EditorProperties[typeof name]); break; case 'isValid': case 'validationError': case 'validationErrors': case 'validationStatus': - // @ts-expect-error + // @ts-expect-error ts-error this.option(ValidationEngine.synchronizeValidationOptions(args, this.option())); this._renderValidationState(); break; @@ -371,7 +449,7 @@ const Editor = Widget.inherit({ break; case 'rtlEnabled': this._setValidationMessageOption(args); - this.callBase(args); + super._optionChanged(args); break; case 'validationTooltipOptions': this._innerWidgetOptionChanged(this._validationMessage, args); @@ -380,26 +458,26 @@ const Editor = Widget.inherit({ case 'isDirty': break; default: - this.callBase(args); + super._optionChanged(args); } - }, + } - _resetToInitialValue() { + _resetToInitialValue(): void { this.option('value', this._initialValue); - }, + } - blur() { + blur(): void { if (this._hasActiveElement()) { domUtils.resetActiveElement(); } - }, + } - clear() { + clear(): void { const defaultOptions = this._getDefaultOptions(); this.option('value', defaultOptions.value); - }, + } - reset(value = undefined) { + reset(value = undefined): void { if (arguments.length) { this._initialValue = value; } @@ -407,8 +485,9 @@ const Editor = Widget.inherit({ this._resetToInitialValue(); this.option('isDirty', false); this.option('isValid', true); - }, -}); + } +} -Editor.isEditor = (instance) => instance instanceof Editor; +// @ts-expect-error ts-error +Editor.isEditor = (instance): boolean => instance instanceof Editor; export default Editor; diff --git a/packages/devextreme/js/__internal/ui/m_file_uploader.ts b/packages/devextreme/js/__internal/ui/m_file_uploader.ts index 8510322d9d00..2c8b6862e14a 100644 --- a/packages/devextreme/js/__internal/ui/m_file_uploader.ts +++ b/packages/devextreme/js/__internal/ui/m_file_uploader.ts @@ -23,7 +23,8 @@ import Button from '@js/ui/button'; import type { Properties as PublicProperties } from '@js/ui/file_uploader'; import ProgressBar from '@js/ui/progress_bar'; import { isFluent, isMaterial } from '@js/ui/themes'; -import Editor from '@ts/ui/editor'; +import type { EditorProperties, UnresolvedEvents } from '@ts/ui/editor/m_editor'; +import Editor from '@ts/ui/editor/m_editor'; const window = getWindow(); @@ -69,7 +70,10 @@ export interface Properties extends PublicProperties { _uploadButtonType?: ButtonType; } -class FileUploader extends Editor { +interface FileUploaderProperties extends Properties, + Omit, UnresolvedEvents | 'value'> {} + +class FileUploader extends Editor { // Temporary solution. Move to component level public NAME!: string; @@ -150,7 +154,7 @@ class FileUploader extends Editor { }); } - _getDefaultOptions(): Properties { + _getDefaultOptions(): FileUploaderProperties { return extend(super._getDefaultOptions(), { chunkSize: 0, value: [], @@ -287,6 +291,7 @@ class FileUploader extends Editor { } _setUploadStrategy() { + // @ts-expect-error if (this.option('chunkSize') > 0) { const uploadChunk = this.option('uploadChunk'); this._uploadStrategy = uploadChunk && isFunction(uploadChunk) @@ -502,14 +507,14 @@ class FileUploader extends Editor { _validateMaxFileSize(file): boolean { const fileSize = file.value.size; const maxFileSize = this.option('maxFileSize'); - + // @ts-expect-error return maxFileSize > 0 ? fileSize <= maxFileSize : true; } _validateMinFileSize(file): boolean { const fileSize = file.value.size; const minFileSize = this.option('minFileSize'); - + // @ts-expect-error return minFileSize > 0 ? fileSize >= minFileSize : true; } diff --git a/packages/devextreme/js/__internal/ui/radio_group/m_radio_group.ts b/packages/devextreme/js/__internal/ui/radio_group/m_radio_group.ts index 10aaa8fde937..93a9518a7311 100644 --- a/packages/devextreme/js/__internal/ui/radio_group/m_radio_group.ts +++ b/packages/devextreme/js/__internal/ui/radio_group/m_radio_group.ts @@ -9,8 +9,9 @@ import { extend } from '@js/core/utils/extend'; import { isDefined } from '@js/core/utils/type'; import DataExpressionMixin from '@js/ui/editor/ui.data_expression'; import type { Properties } from '@js/ui/radio_group'; +import type { EditorProperties, UnresolvedEvents } from '@ts/ui/editor/m_editor'; +import Editor from '@ts/ui/editor/m_editor'; -import Editor from '../editor'; import RadioCollection from './m_radio_collection'; const RADIO_BUTTON_CLASS = 'dx-radiobutton'; @@ -20,7 +21,10 @@ const RADIO_GROUP_CLASS = 'dx-radiogroup'; const RADIO_FEEDBACK_HIDE_TIMEOUT = 100; -class RadioGroup extends Editor { +interface RadioGroupProperties extends Properties, + Omit, UnresolvedEvents> {} + +class RadioGroup extends Editor { private _radios?: RadioCollection; private _areRadiosCreated!: DeferredObj; @@ -31,7 +35,7 @@ class RadioGroup extends Editor { return { paginate: false }; } - _defaultOptionsRules(): DefaultOptionsRule[] { + _defaultOptionsRules(): DefaultOptionsRule[] { const defaultOptionsRules = super._defaultOptionsRules(); return defaultOptionsRules.concat([{