diff --git a/packages/devextreme/js/__internal/common/core/animation/m_position.test.ts b/packages/devextreme/js/__internal/common/core/animation/position.test.ts similarity index 96% rename from packages/devextreme/js/__internal/common/core/animation/m_position.test.ts rename to packages/devextreme/js/__internal/common/core/animation/position.test.ts index 20da271eba0b..059123bd259e 100644 --- a/packages/devextreme/js/__internal/common/core/animation/m_position.test.ts +++ b/packages/devextreme/js/__internal/common/core/animation/position.test.ts @@ -3,7 +3,7 @@ import { } from '@jest/globals'; import $ from '@js/core/renderer'; -import positionUtils from './m_position'; +import positionUtils from './position'; describe('position (setup)', () => { describe('when called with an element that does not exist in the DOM', () => { diff --git a/packages/devextreme/js/__internal/common/core/animation/m_position.ts b/packages/devextreme/js/__internal/common/core/animation/position.ts similarity index 62% rename from packages/devextreme/js/__internal/common/core/animation/m_position.ts rename to packages/devextreme/js/__internal/common/core/animation/position.ts index e26067fe4d54..d7c64787b549 100644 --- a/packages/devextreme/js/__internal/common/core/animation/m_position.ts +++ b/packages/devextreme/js/__internal/common/core/animation/position.ts @@ -1,6 +1,7 @@ import { move, resetPosition } from '@js/common/core/animation/translator'; import devices from '@js/core/devices'; import domAdapter from '@js/core/dom_adapter'; +import type { Coordinates, dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; @@ -14,6 +15,15 @@ import { isDefined, isWindow } from '@js/core/utils/type'; import { getWindow } from '@js/core/utils/window'; import { pairToObject, splitPair } from '@ts/core/utils/common'; +import type { + AxisBounds, + AxisPositionData, + CollisionSide, + PositionOptions, + PositionResult, + PositionTarget, +} from './types'; + const window = getWindow(); const horzRe = /left|right/; @@ -21,7 +31,7 @@ const vertRe = /top|bottom/; const collisionRe = /fit|flip|none/; const scaleRe = /scale\(.+?\)/; -const normalizeAlign = function (raw) { +const normalizeAlign = function normalizeAlign(raw: unknown): { h: string; v: string } { const result = { h: 'center', v: 'center', @@ -30,7 +40,7 @@ const normalizeAlign = function (raw) { const pair = splitPair(raw); if (pair) { - each(pair, function () { + each(pair, function normalizeAlignPart(this: unknown) { const w = String(this).toLowerCase(); if (horzRe.test(w)) { result.h = w; @@ -43,11 +53,14 @@ const normalizeAlign = function (raw) { return result; }; -const normalizeOffset = function (raw, preventRound) { +const normalizeOffset = function normalizeOffset( + raw: unknown, + preventRound?: boolean, +): { h: number; v: number } { return pairToObject(raw, preventRound); }; -const normalizeCollision = function (raw) { +const normalizeCollision = function normalizeCollision(raw: unknown): { h: string; v: string } { const pair = splitPair(raw); let h = String(pair && pair[0]).toLowerCase(); let v = String(pair && pair[1]).toLowerCase(); @@ -59,10 +72,10 @@ const normalizeCollision = function (raw) { v = h; } - return { h: h, v: v }; + return { h, v }; }; -const getAlignFactor = function (align) { +const getAlignFactor = function getAlignFactor(align: string): number { switch (align) { case 'center': return 0.5; @@ -74,7 +87,7 @@ const getAlignFactor = function (align) { } }; -const inverseAlign = function (align) { +const inverseAlign = function inverseAlign(align: string): string { switch (align) { case 'left': return 'right'; @@ -89,7 +102,10 @@ const inverseAlign = function (align) { } }; -const calculateOversize = function (data, bounds) { +const calculateOversize = function calculateOversize( + data: AxisPositionData, + bounds: AxisBounds, +): number { let oversize = 0; if (data.myLocation < bounds.min) { @@ -103,7 +119,11 @@ const calculateOversize = function (data, bounds) { return oversize; }; -const collisionSide = function (direction, data, bounds) { +const collisionSide = function collisionSide( + direction: 'h' | 'v', + data: AxisPositionData, + bounds: AxisBounds, +): CollisionSide { if (data.myLocation < bounds.min) { return direction === 'h' ? 'left' : 'top'; } @@ -114,16 +134,24 @@ const collisionSide = function (direction, data, bounds) { }; // TODO: rename? -const initMyLocation = function (data) { +const initMyLocation = function initMyLocation(data: AxisPositionData): void { data.myLocation = data.atLocation + getAlignFactor(data.atAlign) * data.atSize - getAlignFactor(data.myAlign) * data.mySize + data.offset; }; -const collisionResolvers = { +interface CollisionResolvers { + fit: (data: AxisPositionData, bounds: AxisBounds) => void; + flip: (data: AxisPositionData, bounds: AxisBounds) => void; + // eslint-disable-next-line spellcheck/spell-checker + flipfit: (data: AxisPositionData, bounds: AxisBounds) => void; + none: (data: AxisPositionData) => void; +} - fit: function (data, bounds) { +const collisionResolvers: CollisionResolvers = { + + fit(data, bounds) { let result = false; if (data.myLocation > bounds.max) { data.myLocation = bounds.max; @@ -137,7 +165,7 @@ const collisionResolvers = { data.fit = result; }, - flip: function (data, bounds) { + flip(data, bounds) { data.flip = false; if (data.myAlign === 'center' && data.atAlign === 'center') { @@ -154,7 +182,10 @@ const collisionResolvers = { initMyLocation(inverseData); inverseData.oversize = calculateOversize(inverseData, bounds); - if ((inverseData.myLocation >= bounds.min && inverseData.myLocation <= bounds.max) || data.oversize > inverseData.oversize) { + if ( + (inverseData.myLocation >= bounds.min && inverseData.myLocation <= bounds.max) + || data.oversize > inverseData.oversize + ) { data.myLocation = inverseData.myLocation; data.oversize = inverseData.oversize; data.flip = true; @@ -163,19 +194,20 @@ const collisionResolvers = { }, // eslint-disable-next-line spellcheck/spell-checker - flipfit: function (data, bounds) { + flipfit(data, bounds) { this.flip(data, bounds); this.fit(data, bounds); }, - none: function (data) { + none(data) { data.oversize = 0; }, }; -let scrollbarWidth; +// eslint-disable-next-line @typescript-eslint/init-declarations -- set by calculateScrollbarWidth +let scrollbarWidth: number | undefined; -const calculateScrollbarWidth = function () { +const calculateScrollbarWidth = function calculateScrollbarWidth(): void { const $scrollDiv = $('
').css({ width: 100, height: 100, @@ -183,7 +215,7 @@ const calculateScrollbarWidth = function () { position: 'absolute', top: -9999, }).appendTo($('body')); - // @ts-expect-error + // @ts-expect-error get(0) is typed as Element const result = $scrollDiv.get(0).offsetWidth - $scrollDiv.get(0).clientWidth; $scrollDiv.remove(); @@ -206,27 +238,63 @@ const defaultPositionResult = { }, }; -interface AxisPositionResult { - location: number; - flip: boolean; - fit: boolean; - oversize: number; - collisionSide?: string; -} +// NOTE: Setting the 'element.style' requires creating attributeNode when both of the conditions +// met: +// - a form contains an input with the name property set to "style"; +// - a form contains a dx-validator (or other popup widget). +// T941581 +const setScaleProperty = function setScaleProperty( + element: Element & { style?: unknown }, + scale: string, + styleAttr: string, + isEmpty: boolean, +): void { + const stylePropIsValid = isDefined(element.style) && !domAdapter.isNode(element.style); + const newStyleValue = isEmpty ? styleAttr.replace(scale, '') : styleAttr; -interface PositionResult { - h: AxisPositionResult; - v: AxisPositionResult; - precise?: boolean; -} + if (stylePropIsValid) { + setStyle(element, newStyleValue, false); + } else { + const styleAttributeNode = domAdapter.createAttribute('style'); + styleAttributeNode.value = newStyleValue; + element.setAttributeNode(styleAttributeNode); + } +}; + +const getOffsetWithoutScale = function getOffsetWithoutScale( + $startElement: dxElementWrapper, + $currentElement: dxElementWrapper = $startElement, +): Coordinates | undefined { + const currentElement = $currentElement.get(0); + if (!currentElement) { + return $startElement.offset(); + } + + const style = currentElement.getAttribute?.('style') ?? ''; + const scale = scaleRe.exec(style)?.[0]; + // eslint-disable-next-line @typescript-eslint/init-declarations -- set in both branches + let offset: Coordinates | undefined; + + if (scale) { + setScaleProperty(currentElement, scale, style, true); + offset = getOffsetWithoutScale($startElement, $currentElement.parent()); + setScaleProperty(currentElement, scale, style, false); + } else { + offset = getOffsetWithoutScale($startElement, $currentElement.parent()); + } + + return offset; +}; -const calculatePosition = function (what, options) { +const calculatePosition = function calculatePosition( + what: PositionTarget, + options?: PositionOptions, +): PositionResult { const $what = $(what); - const currentOffset = $what.offset(); + // @ts-expect-error offset() is typed as possibly undefined + const currentOffset: Coordinates = $what.offset(); const result: PositionResult = extend(true, {}, defaultPositionResult, { - // @ts-expect-error h: { location: currentOffset.left }, - // @ts-expect-error v: { location: currentOffset.top }, }); @@ -236,13 +304,16 @@ const calculatePosition = function (what, options) { const my = normalizeAlign(options.my); const at = normalizeAlign(options.at); - let of = ($(options.of).length && options.of) || window; + // @ts-expect-error renderer() is typed without event objects + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- element, selector, window, event + let of: any = ($(options.of).length && options.of) || window; const offset = normalizeOffset(options.offset, options.precise); const collision = normalizeCollision(options.collision); const { boundary } = options; const boundaryOffset = normalizeOffset(options.boundaryOffset, options.precise); - const h: any = { + const h: AxisPositionData = { + // @ts-expect-error getOuterWidth() can be undefined; the location fields are set below mySize: getOuterWidth($what), myAlign: my.h, atAlign: at.h, @@ -251,7 +322,8 @@ const calculatePosition = function (what, options) { boundaryOffset: boundaryOffset.h, }; - const v: any = { + const v: AxisPositionData = { + // @ts-expect-error getOuterHeight() can be undefined; the location fields are set below mySize: getOuterHeight($what), myAlign: my.v, atAlign: at.v, @@ -290,7 +362,8 @@ const calculatePosition = function (what, options) { v.atSize = getHeight(of); } else { const ofRect = getBoundingRect(of.get(0)); - const o = getOffsetWithoutScale(of); + // @ts-expect-error offset() is typed as possibly undefined + const o: Coordinates = getOffsetWithoutScale(of); h.atLocation = o.left; v.atLocation = o.top; h.atSize = Math.max(ofRect.width, getOuterWidth(of)); @@ -301,12 +374,14 @@ const calculatePosition = function (what, options) { initMyLocation(h); initMyLocation(v); - const bounds = (function () { + const bounds = (function calculateBounds(): { h: AxisBounds; v: AxisBounds } { const win = $(window); const windowWidth = getWidth(win.get(0)); const windowHeight = getHeight(win.get(0)); - let left = win.scrollLeft(); - let top = win.scrollTop(); + // @ts-expect-error scrollLeft() is typed as a setter only + let left: number = win.scrollLeft(); + // @ts-expect-error scrollTop() is typed as a setter only + let top: number = win.scrollTop(); const documentElement = domAdapter.getDocumentElement(); const hZoomLevel = touch ? documentElement.clientWidth / windowWidth : 1; const vZoomLevel = touch ? documentElement.clientHeight / windowHeight : 1; @@ -320,10 +395,9 @@ const calculatePosition = function (what, options) { if (boundary && !isWindow(boundary)) { const $boundary = $(boundary); - const boundaryPosition = $boundary.offset(); - // @ts-expect-error + // @ts-expect-error offset() is typed as possibly undefined + const boundaryPosition: Coordinates = $boundary.offset(); left = boundaryPosition.left; - // @ts-expect-error top = boundaryPosition.top; boundaryWidth = Number(getWidth($boundary)); @@ -333,16 +407,14 @@ const calculatePosition = function (what, options) { return { h: { min: left + h.boundaryOffset, - // @ts-expect-error max: left + boundaryWidth / hZoomLevel - h.mySize - h.boundaryOffset, }, v: { min: top + v.boundaryOffset, - // @ts-expect-error max: top + boundaryHeight / vZoomLevel - v.mySize - v.boundaryOffset, }, }; - })(); + }()); h.oversize = calculateOversize(h, bounds.h); v.oversize = calculateOversize(v, bounds.v); @@ -358,61 +430,44 @@ const calculatePosition = function (what, options) { collisionResolvers[v.collision](v, bounds.v); } - const preciser = function (number) { + const preciser = function preciser(number: number): number { return options.precise ? number : Math.round(number); }; extend(true, result, { h: { - location: preciser(h.myLocation), oversize: preciser(h.oversize), fit: h.fit, flip: h.flip, collisionSide: h.collisionSide, + location: preciser(h.myLocation), + oversize: preciser(h.oversize), + fit: h.fit, + flip: h.flip, + collisionSide: h.collisionSide, }, v: { - location: preciser(v.myLocation), oversize: preciser(v.oversize), fit: v.fit, flip: v.flip, collisionSide: v.collisionSide, + location: preciser(v.myLocation), + oversize: preciser(v.oversize), + fit: v.fit, + flip: v.flip, + collisionSide: v.collisionSide, }, precise: options.precise, }); return result; }; -// NOTE: Setting the 'element.style' requires creating attributeNode when both of the conditions met: -// - a form contains an input with the name property set to "style"; -// - a form contains a dx-validator (or other popup widget). -// T941581 -const setScaleProperty = function (element, scale, styleAttr, isEmpty) { - const stylePropIsValid = isDefined(element.style) && !domAdapter.isNode(element.style); - const newStyleValue = isEmpty ? styleAttr.replace(scale, '') : styleAttr; - - if (stylePropIsValid) { - setStyle(element, newStyleValue, false); - } else { - const styleAttributeNode = domAdapter.createAttribute('style'); - styleAttributeNode.value = newStyleValue; - element.setAttributeNode(styleAttributeNode); - } -}; - -const getOffsetWithoutScale = function ($startElement, $currentElement = $startElement) { - const currentElement = $currentElement.get(0); - if (!currentElement) { - return $startElement.offset(); - } - - const style = currentElement.getAttribute?.('style') || ''; - const scale = style.match(scaleRe)?.[0]; - let offset; - - if (scale) { - setScaleProperty(currentElement, scale, style, true); - offset = getOffsetWithoutScale($startElement, $currentElement.parent()); - setScaleProperty(currentElement, scale, style, false); - } else { - offset = getOffsetWithoutScale($startElement, $currentElement.parent()); - } - - return offset; -}; -const position = function (what, options?) { +function position(what: PositionTarget): Coordinates | undefined; +function position( + what: PositionTarget, + options: PositionOptions | PositionResult, +): PositionResult | undefined; +function position( + what: PositionTarget, + options?: PositionOptions | PositionResult, +): Coordinates | PositionResult | undefined; +function position( + what: PositionTarget, + options?: PositionOptions | PositionResult, +): Coordinates | PositionResult | undefined { const $what = $(what); if (!$what.length) { @@ -425,10 +480,13 @@ const position = function (what, options?) { resetPosition($what, true); - const offset = getOffsetWithoutScale($what); - const targetPosition = options.h && options.v ? options : calculatePosition($what, options); + // @ts-expect-error offset() is typed as possibly undefined + const offset: Coordinates = getOffsetWithoutScale($what); + const targetPosition = options.h && options.v + ? options + : calculatePosition($what, options); - const preciser = function (number) { + const preciser = function preciser(number: number): number { return options.precise ? number : Math.round(number); }; @@ -438,30 +496,34 @@ const position = function (what, options?) { }); return targetPosition; -} as any; +} -const offset = function (element) { - element = $(element).get(0); - if (isWindow(element)) { +const offset = function offset(element: PositionOptions['of']): Coordinates | null | undefined { + // @ts-expect-error renderer() is typed without event objects + const domElement = $(element).get(0); + if (isWindow(domElement)) { return null; - } if (element && 'pageY' in element && 'pageX' in element) { - return { top: element.pageY, left: element.pageX }; + } if (domElement && 'pageY' in domElement && 'pageX' in domElement) { + // @ts-expect-error get(0) is typed as Element, but an event has pageX and pageY + return { top: domElement.pageY, left: domElement.pageX }; } - return $(element).offset(); + return $(domElement).offset(); }; +// @ts-expect-error inverseAlign is assigned right below if (!position.inverseAlign) { position.inverseAlign = inverseAlign; } +// @ts-expect-error normalizeAlign is assigned right below if (!position.normalizeAlign) { position.normalizeAlign = normalizeAlign; } export default { - calculateScrollbarWidth: calculateScrollbarWidth, + calculateScrollbarWidth, calculate: calculatePosition, setup: position, - offset: offset, + offset, }; diff --git a/packages/devextreme/js/__internal/common/core/animation/types.ts b/packages/devextreme/js/__internal/common/core/animation/types.ts new file mode 100644 index 000000000000..67ac6a8e6fa5 --- /dev/null +++ b/packages/devextreme/js/__internal/common/core/animation/types.ts @@ -0,0 +1,54 @@ +import type { dxElementWrapper } from '@js/core/renderer'; +import type { DxEvent } from '@js/events'; + +export type PositionTarget = string | EventTarget | dxElementWrapper | null | undefined; + +export type CollisionSide = 'left' | 'right' | 'top' | 'bottom' | 'none'; + +export interface AxisPositionResult { + location: number; + flip: boolean; + fit: boolean; + oversize: number; + collisionSide?: CollisionSide; +} + +export interface PositionResult { + h: AxisPositionResult; + v: AxisPositionResult; + precise?: boolean; +} + +export interface PositionOptions { + my?: unknown; + at?: unknown; + of?: PositionTarget | DxEvent; + offset?: unknown; + collision?: unknown; + boundary?: PositionTarget | 0; + boundaryOffset?: unknown; + precise?: boolean; + h?: undefined; + v?: undefined; +} + +export interface AxisPositionData { + mySize: number; + myAlign: string; + atAlign: string; + offset: number; + collision: string; + boundaryOffset: number; + atLocation: number; + atSize: number; + myLocation: number; + oversize: number; + collisionSide?: CollisionSide; + fit?: boolean; + flip?: boolean; +} + +export interface AxisBounds { + min: number; + max: number; +} diff --git a/packages/devextreme/js/__internal/data/m_data_helper.ts b/packages/devextreme/js/__internal/data/m_data_helper.ts index b9499ff9641a..156fc0842aaf 100644 --- a/packages/devextreme/js/__internal/data/m_data_helper.ts +++ b/packages/devextreme/js/__internal/data/m_data_helper.ts @@ -1,7 +1,7 @@ import { DataSource } from '@js/common/data/data_source/data_source'; import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; import { extend } from '@js/core/utils/extend'; -import DataController from '@ts/ui/collection/m_data_controller'; +import DataController from '@ts/ui/collection/data_controller'; const DATA_SOURCE_OPTIONS_METHOD = '_dataSourceOptions'; const DATA_SOURCE_CHANGED_METHOD = '_dataSourceChangedHandler'; diff --git a/packages/devextreme/js/__internal/integration/jquery/hold_ready.ts b/packages/devextreme/js/__internal/integration/jquery/hold_ready.ts index ec54aac3f71a..09951ffb8c14 100644 --- a/packages/devextreme/js/__internal/integration/jquery/hold_ready.ts +++ b/packages/devextreme/js/__internal/integration/jquery/hold_ready.ts @@ -1,5 +1,5 @@ import readyCallbacks from '@ts/core/utils/ready_callbacks'; -import { themeReadyCallback } from '@ts/ui/m_themes_callback'; +import { themeReadyCallback } from '@ts/ui/themes_callback'; // eslint-disable-next-line import/no-extraneous-dependencies import jQuery from 'jquery'; diff --git a/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts b/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts index 85bcffe6447f..a65ab0907746 100644 --- a/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts +++ b/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts @@ -28,7 +28,7 @@ import type { } from '@ts/ui/collection/collection_widget.base'; import BaseCollectionWidget from '@ts/ui/collection/collection_widget.base'; import PlainEditStrategy from '@ts/ui/collection/collection_widget.edit.strategy.plain'; -import type DataController from '@ts/ui/collection/m_data_controller'; +import type DataController from '@ts/ui/collection/data_controller'; import Selection from '@ts/ui/selection/selection'; import type { CollectionItemIndex } from './collection_widget.edit.strategy'; @@ -192,7 +192,6 @@ class CollectionWidget< } _getCombinedFilter(): unknown { - // @ts-expect-error arguments return this._dataController.filter(); } @@ -272,7 +271,7 @@ class CollectionWidget< options.userData = dataController.userData(); if (dataController.store()) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return + // @ts-expect-error the store is typed for StoreLoadOptions and unknown items return dataController.loadFromStore(options).done((loadResult) => { if (that._disposed) { return; @@ -284,8 +283,7 @@ class CollectionWidget< } return Deferred().resolve(this.plainItems()); }, - // eslint-disable-next-line @stylistic/max-len - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/explicit-function-return-type + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type dataFields: () => this._dataController.select(), plainItems: itemsGetter.bind(this._editStrategy), }); @@ -816,7 +814,6 @@ class CollectionWidget< _refreshLastPage(): DeferredObj { this._expectLastItemLoading(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return this._dataController.load(); } diff --git a/packages/devextreme/js/__internal/ui/collection/data_controller.ts b/packages/devextreme/js/__internal/ui/collection/data_controller.ts new file mode 100644 index 000000000000..ae0cfcd0c201 --- /dev/null +++ b/packages/devextreme/js/__internal/ui/collection/data_controller.ts @@ -0,0 +1,218 @@ +import { noop } from '@js/core/utils/common'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { Deferred } from '@js/core/utils/deferred'; +import type Store from '@ts/data/abstract_store'; +import type { StoreKey } from '@ts/data/abstract_store'; +import type { DataSource } from '@ts/data/data_source/data_source'; +import type { DataSourceEventName, StoreLoadOptions } from '@ts/data/data_source/types'; + +const DataControllerMock = { + load: (): DeferredObj => Deferred().reject(), + loadSingle: (): DeferredObj => Deferred().reject(), + loadFromStore: (): DeferredObj => Deferred().reject(), + loadNextPage: (): DeferredObj => Deferred().reject(), + loadOptions: noop, + userData: noop, + cancel: noop, + cancelAll: noop, + filter: noop, + addSearchFilter: noop, + group: noop, + paginate: noop, + pageSize: noop, + pageIndex: noop, + resetDataSourcePageIndex: noop, + totalCount: noop, + isLastPage: noop, + isLoading: noop, + isLoaded: noop, + searchValue: noop, + searchOperation: noop, + searchExpr: noop, + select: noop, + key: noop, + keyOf: noop, + store: noop, + items: noop, + applyMapFunction: noop, + getDataSource: noop, + reload: noop, + on: noop, + off: noop, +}; + +class DataController { + private readonly _dataSource!: DataSource; + + constructor(dataSource: DataSource | null | undefined) { + if (!dataSource) { + // @ts-expect-error the mock has the same methods, all of them no-ops + // eslint-disable-next-line no-constructor-return + return DataControllerMock; + } + this._dataSource = dataSource; + } + + load(): DeferredObj { + return this._dataSource.load(); + } + + loadSingle(propName: unknown, propValue?: unknown): DeferredObj { + if (arguments.length < 2) { + return this._dataSource.loadSingle(this.key(), propName); + } + + return this._dataSource.loadSingle(propName, propValue); + } + + loadFromStore(loadOptions?: StoreLoadOptions): DeferredObj { + return this.store().load(loadOptions); + } + + loadNextPage(): DeferredObj { + this.pageIndex(1 + this.pageIndex()); + + return this.load(); + } + + loadOptions(): StoreLoadOptions { + return this._dataSource.loadOptions(); + } + + userData(): Record { + return this._dataSource._userData; + } + + cancel(operationId: number): void { + this._dataSource.cancel(operationId); + } + + cancelAll(): void { + this._dataSource.cancelAll(); + } + + filter(filter?: undefined): StoreLoadOptions['filter']; + filter(filter: NonNullable): void; + filter(filter?: StoreLoadOptions['filter']): StoreLoadOptions['filter'] { + return this._dataSource.filter(filter); + } + + addSearchFilter(storeLoadOptions: StoreLoadOptions): void { + this._dataSource._addSearchFilter(storeLoadOptions); + } + + group(group?: undefined): StoreLoadOptions['group']; + group(group: NonNullable): void; + group(group?: StoreLoadOptions['group']): StoreLoadOptions['group'] { + // @ts-expect-error DataSource accessors are typed without the undefined getter argument + return this._dataSource.group(group); + } + + paginate(): boolean | undefined { + return this._dataSource.paginate(); + } + + pageSize(): number { + return this._dataSource._pageSize; + } + + pageIndex(): number; + pageIndex(pageIndex: number): void; + pageIndex(pageIndex?: number): number | undefined { + // @ts-expect-error DataSource accessors are typed without the undefined getter argument + return this._dataSource.pageIndex(pageIndex); + } + + resetDataSourcePageIndex(): void { + if (this.pageIndex()) { + this.pageIndex(0); + this.load(); + } + } + + totalCount(): number { + return this._dataSource.totalCount(); + } + + isLastPage(): boolean { + return this._dataSource.isLastPage() || !this._dataSource._pageSize; + } + + isLoading(): boolean { + return this._dataSource.isLoading(); + } + + isLoaded(): boolean { + return this._dataSource.isLoaded(); + } + + searchValue(): StoreLoadOptions['searchValue']; + searchValue(value: StoreLoadOptions['searchValue']): void; + searchValue(value?: StoreLoadOptions['searchValue']): StoreLoadOptions['searchValue'] { + if (!arguments.length) { + return this._dataSource.searchValue(); + } + + return this._dataSource.searchValue(value); + } + + searchOperation(operation?: unknown): StoreLoadOptions['searchOperation'] { + // @ts-expect-error DataSource.searchOperation() treats a non-string argument as the getter + return this._dataSource.searchOperation(operation); + } + + searchExpr(): StoreLoadOptions['searchExpr']; + searchExpr(expr: NonNullable): void; + searchExpr(expr?: StoreLoadOptions['searchExpr']): StoreLoadOptions['searchExpr'] { + if (!arguments.length) { + return this._dataSource.searchExpr(); + } + + // @ts-expect-error DataSource.searchExpr() is typed without an array or undefined argument + return this._dataSource.searchExpr(expr); + } + + select(): StoreLoadOptions['select']; + select(...selectExpr: NonNullable[]): void; + select(...selectExpr: NonNullable[]): unknown { + return this._dataSource.select(...selectExpr); + } + + key(): StoreKey | undefined { + return this._dataSource.key(); + } + + keyOf(item: unknown): unknown { + return this.store().keyOf(item); + } + + store(): Store { + return this._dataSource.store(); + } + + items(): unknown[] { + return this._dataSource.items(); + } + + applyMapFunction(data: unknown[]): unknown[] { + return this._dataSource._applyMapFunction(data); + } + + getDataSource(): DataSource | null { + return this._dataSource || null; + } + + reload(): DeferredObj { + return this._dataSource.reload(); + } + + on(event: DataSourceEventName, handler: Function): void { + this._dataSource.on(event, handler); + } + + off(event: DataSourceEventName, handler?: Function): void { + this._dataSource.off(event, handler); + } +} + +export default DataController; diff --git a/packages/devextreme/js/__internal/ui/collection/m_data_controller.ts b/packages/devextreme/js/__internal/ui/collection/m_data_controller.ts deleted file mode 100644 index 64d0247cb2d1..000000000000 --- a/packages/devextreme/js/__internal/ui/collection/m_data_controller.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { noop } from '@js/core/utils/common'; -import { Deferred } from '@js/core/utils/deferred'; - -const DataControllerMock = { - load: () => Deferred().reject(), - loadSingle: () => Deferred().reject(), - loadFromStore: () => Deferred().reject(), - loadNextPage: () => Deferred().reject(), - loadOptions: noop, - userData: noop, - cancel: noop, - cancelAll: noop, - filter: noop, - addSearchFilter: noop, - group: noop, - paginate: noop, - pageSize: noop, - pageIndex: noop, - resetDataSourcePageIndex: noop, - totalCount: noop, - isLastPage: noop, - isLoading: noop, - isLoaded: noop, - searchValue: noop, - searchOperation: noop, - searchExpr: noop, - select: noop, - key: noop, - keyOf: noop, - store: noop, - items: noop, - applyMapFunction: noop, - getDataSource: noop, - reload: noop, - on: noop, - off: noop, -}; - -class DataController { - _dataSource: any; - - constructor(dataSource) { - if (!dataSource) { - // @ts-expect-error - // eslint-disable-next-line no-constructor-return - return DataControllerMock; - } - this._dataSource = dataSource; - } - - load() { - return this._dataSource.load(); - } - - loadSingle(propName, propValue) { - if (arguments.length < 2) { - propValue = propName; - propName = this.key(); - } - - return this._dataSource.loadSingle(propName, propValue); - } - - loadFromStore(loadOptions) { - return this.store().load(loadOptions); - } - - loadNextPage() { - this.pageIndex(1 + this.pageIndex()); - - return this.load(); - } - - loadOptions() { - return this._dataSource.loadOptions(); - } - - userData() { - return this._dataSource._userData; - } - - cancel(operationId) { - this._dataSource.cancel(operationId); - } - - cancelAll() { - this._dataSource.cancelAll(); - } - - filter(filter) { - return this._dataSource.filter(filter); - } - - addSearchFilter(storeLoadOptions) { - this._dataSource._addSearchFilter(storeLoadOptions); - } - - group(group?) { - return this._dataSource.group(group); - } - - paginate() { - return this._dataSource.paginate(); - } - - pageSize() { - return this._dataSource._pageSize; - } - - pageIndex(pageIndex?: number) { - return this._dataSource.pageIndex(pageIndex); - } - - resetDataSourcePageIndex() { - if (this.pageIndex()) { - this.pageIndex(0); - this.load(); - } - } - - totalCount() { - return this._dataSource.totalCount(); - } - - isLastPage() { - return this._dataSource.isLastPage() || !this._dataSource._pageSize; - } - - isLoading() { - return this._dataSource.isLoading(); - } - - isLoaded() { - return this._dataSource.isLoaded(); - } - - searchValue(value) { - if (!arguments.length) { - return this._dataSource.searchValue(); - } - - return this._dataSource.searchValue(value); - } - - searchOperation(operation) { - return this._dataSource.searchOperation(operation); - } - - searchExpr(expr) { - if (!arguments.length) { - return this._dataSource.searchExpr(); - } - - return this._dataSource.searchExpr(expr); - } - - select() { - return this._dataSource.select(...arguments); - } - - key() { - return this._dataSource.key(); - } - - keyOf(item) { - return this.store().keyOf(item); - } - - store() { - return this._dataSource.store(); - } - - items() { - return this._dataSource.items(); - } - - applyMapFunction(data) { - return this._dataSource._applyMapFunction(data); - } - - getDataSource() { - return this._dataSource || null; - } - - reload() { - return this._dataSource.reload(); - } - - on(event, handler): void { - this._dataSource.on(event, handler); - } - - off(event, handler): void { - this._dataSource.off(event, handler); - } -} - -export default DataController; diff --git a/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_editor.ts b/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_editor.ts index 3f9853d9d5f4..4a1b8114336b 100644 --- a/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_editor.ts +++ b/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_editor.ts @@ -1001,12 +1001,15 @@ class DropDownEditor< let positionSide = 'bottom'; if (this._popup?.option('visible')) { - const { top: myTop } = animationPosition.setup(this.$element()); + const myOffset = animationPosition.setup(this.$element()); - const { top: popupTop } = animationPosition.setup(this._popup.$content()); - const { popupPosition } = this.option(); - // @ts-expect-error Should be updated on PositionConfig level - positionSide = (myTop + popupPosition.offset.v) > popupTop ? 'bottom' : 'top'; + const popupOffset = animationPosition.setup(this._popup.$content()); + + if (myOffset && popupOffset) { + const { popupPosition } = this.option(); + // @ts-expect-error Should be updated on PositionConfig level + positionSide = (myOffset.top + popupPosition.offset.v) > popupOffset.top ? 'bottom' : 'top'; + } } return positionSide; diff --git a/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_list.ts b/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_list.ts index 81e34094c987..56a8d03e76c5 100644 --- a/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_list.ts +++ b/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_list.ts @@ -29,7 +29,7 @@ import type { Item, ItemClickEvent } from '@js/ui/list'; import errors from '@js/ui/widget/ui.errors'; import type { OptionChanged } from '@ts/core/widget/types'; import { getDataSourceOptions } from '@ts/data/data_converter/grouped'; -import type DataController from '@ts/ui/collection/m_data_controller'; +import type DataController from '@ts/ui/collection/data_controller'; import type { DropDownEditorInternalProperties } from '@ts/ui/drop_down_editor/drop_down_editor'; import DropDownEditor from '@ts/ui/drop_down_editor/drop_down_editor'; import type { ListBaseProperties } from '@ts/ui/list/list.base'; @@ -740,7 +740,7 @@ class DropDownList< const isMinSearchLengthExceeded = this._isMinSearchLengthExceeded(); const { showDataBeforeSearch, minSearchLength } = this.option(); - return this._dataController.isLoaded() as boolean + return this._dataController.isLoaded() && Boolean(showDataBeforeSearch) && Boolean(minSearchLength) && !isMinSearchLengthExceeded @@ -877,7 +877,6 @@ class DropDownList< _clearFilter(): void { const dataController = this._dataController; - // @ts-expect-error fix argument type in m_data_controller.ts if (dataController.searchValue()) { dataController.searchValue(null); } @@ -963,7 +962,7 @@ class DropDownList< const dataController = this._dataController; const currentPageIndex = dataController.pageIndex(); const needRepaint = (isDefined(this._pageIndex) && currentPageIndex <= this._pageIndex) - || (dataController.isLastPage() as boolean && !this._list?._scrollViewIsFull()); + || (dataController.isLastPage() && !this._list?._scrollViewIsFull()); this._pageIndex = currentPageIndex; diff --git a/packages/devextreme/js/__internal/ui/list/list.edit.search.ts b/packages/devextreme/js/__internal/ui/list/list.edit.search.ts index f590d87d9e7f..7298b11e2668 100644 --- a/packages/devextreme/js/__internal/ui/list/list.edit.search.ts +++ b/packages/devextreme/js/__internal/ui/list/list.edit.search.ts @@ -24,7 +24,6 @@ class ListSearch extends ListEdit { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types _getCombinedFilter() { const dataController = this._dataController; - // @ts-expect-error ts-error const storeLoadOptions = { filter: dataController.filter() }; dataController.addSearchFilter(storeLoadOptions); const { filter } = storeLoadOptions; diff --git a/packages/devextreme/js/__internal/ui/overlay/overlay_position_controller.ts b/packages/devextreme/js/__internal/ui/overlay/overlay_position_controller.ts index 6c243eecaf13..8e67a464e5b3 100644 --- a/packages/devextreme/js/__internal/ui/overlay/overlay_position_controller.ts +++ b/packages/devextreme/js/__internal/ui/overlay/overlay_position_controller.ts @@ -14,6 +14,7 @@ import type { DxEvent, PointerInteractionEvent, } from '@js/events'; +import type { PositionResult } from '@ts/common/core/animation/types'; import swatch from '@ts/core/utils/swatch_container'; import windowUtils from '@ts/core/utils/window'; import type { @@ -142,7 +143,7 @@ export class OverlayPositionController< _visualPosition?: Coordinates; - _initialPosition?: OverlayPosition; + _initialPosition?: Coordinates | PositionResult; _previousVisualPosition?: Coordinates; diff --git a/packages/devextreme/js/__internal/ui/popover/popover.ts b/packages/devextreme/js/__internal/ui/popover/popover.ts index e503e74fbb9e..4b78f98a3218 100644 --- a/packages/devextreme/js/__internal/ui/popover/popover.ts +++ b/packages/devextreme/js/__internal/ui/popover/popover.ts @@ -511,6 +511,10 @@ class Popover< const contentPosition = this._getContainerPosition(); const resultLocation = positionUtils.setup(this.$overlayContent(), contentPosition); + if (!resultLocation) { + return; + } + const positionSide = this._getSideByLocation(resultLocation); this._togglePositionClass(`dx-position-${positionSide}`); @@ -626,7 +630,7 @@ class Popover< const contentOffset = positionUtils.offset(this.$overlayContent()); const arrowSize = arrowRect[sizeProperty]; - const contentLocation = contentOffset?.[axis]; + const contentLocation = contentOffset?.[axis] ?? NaN; const contentSize = getBoundingRect(this.$overlayContent().get(0))[sizeProperty]; const targetLocation = targetOffset[axis]; const targetElement = $target.get(0); diff --git a/packages/devextreme/js/__internal/ui/select_box.ts b/packages/devextreme/js/__internal/ui/select_box.ts index a9c947e5abab..19928fe24d86 100644 --- a/packages/devextreme/js/__internal/ui/select_box.ts +++ b/packages/devextreme/js/__internal/ui/select_box.ts @@ -488,7 +488,6 @@ class SelectBox< } _getActualSearchValue(): string | null { - // @ts-expect-error fix argument type in m_data_controller.ts return this._dataController.searchValue() as string | null; } diff --git a/packages/devextreme/js/__internal/ui/tag_box.ts b/packages/devextreme/js/__internal/ui/tag_box.ts index 3008a40180ee..6a985a145be7 100644 --- a/packages/devextreme/js/__internal/ui/tag_box.ts +++ b/packages/devextreme/js/__internal/ui/tag_box.ts @@ -897,7 +897,6 @@ class TagBox< } _getFilter(creator: FilterCreator): unknown[] | undefined { - // @ts-expect-error fix argument type in m_data_controller.ts const dataSourceFilter: unknown[] | null = this._dataController.filter(); const { valueExpr, maxFilterQueryLength } = this.option(); const filterExpr = creator.getCombinedFilter(valueExpr, dataSourceFilter); @@ -955,6 +954,7 @@ class TagBox< const mappedItems = dataController.applyMapFunction(items); d.resolve(mappedItems.filter(clientFilterFunction as (item: unknown) => boolean)); }) + // eslint-disable-next-line @typescript-eslint/no-misused-promises -- ignores the result .fail(d.reject); this._loadFilteredItemsPromise = d; diff --git a/packages/devextreme/js/__internal/ui/themes.ts b/packages/devextreme/js/__internal/ui/themes.ts index 8fb38ea3e50f..561631d0435f 100644 --- a/packages/devextreme/js/__internal/ui/themes.ts +++ b/packages/devextreme/js/__internal/ui/themes.ts @@ -11,7 +11,7 @@ import { changeCallback, originalViewPort, value as viewPortValue } from '@js/co import { getWindow, hasWindow } from '@js/core/utils/window'; import errors from '@js/ui/widget/ui.errors'; import { uiLayerInitialized } from '@ts/core/utils/common'; -import { themeReadyCallback } from '@ts/ui/m_themes_callback'; +import { themeReadyCallback } from '@ts/ui/themes_callback'; const window = getWindow(); const ready = readyCallbacks.add; diff --git a/packages/devextreme/js/__internal/ui/m_themes_callback.ts b/packages/devextreme/js/__internal/ui/themes_callback.ts similarity index 100% rename from packages/devextreme/js/__internal/ui/m_themes_callback.ts rename to packages/devextreme/js/__internal/ui/themes_callback.ts diff --git a/packages/devextreme/js/common/core/animation/position.js b/packages/devextreme/js/common/core/animation/position.js index 6546eaafb8ce..0e48d6bfb7ab 100644 --- a/packages/devextreme/js/common/core/animation/position.js +++ b/packages/devextreme/js/common/core/animation/position.js @@ -1,3 +1,3 @@ -import Position from '../../../__internal/common/core/animation/m_position'; +import Position from '../../../__internal/common/core/animation/position'; export default Position; diff --git a/packages/devextreme/js/ui/themes_callback.js b/packages/devextreme/js/ui/themes_callback.js index c5c154f1d788..fb6979116e83 100644 --- a/packages/devextreme/js/ui/themes_callback.js +++ b/packages/devextreme/js/ui/themes_callback.js @@ -1,3 +1,3 @@ -import { themeReadyCallback } from '../__internal/ui/m_themes_callback'; +import { themeReadyCallback } from '../__internal/ui/themes_callback'; export { themeReadyCallback };