diff --git a/packages/devextreme/js/__internal/common/core/animation/fx.ts b/packages/devextreme/js/__internal/common/core/animation/fx.ts index f0085202a523..e667c91508f7 100644 --- a/packages/devextreme/js/__internal/common/core/animation/fx.ts +++ b/packages/devextreme/js/__internal/common/core/animation/fx.ts @@ -337,8 +337,8 @@ const FrameAnimationStrategy = { each(transformString.match(/\w+\d*\w*\([^)]*\)\s*/g), (i, part) => { const translateData = parseTranslate(part); - const scaleData = part.match(/scale\((.+?)\)/); - const rotateData = part.match(/(rotate.)\((.+)deg\)/); + const scaleData = /scale\((.+?)\)/.exec(part); + const rotateData = /(rotate.)\((.+)deg\)/.exec(part); if (translateData) { result.translate = translateData; diff --git a/packages/devextreme/js/__internal/common/core/animation/m_position.ts b/packages/devextreme/js/__internal/common/core/animation/m_position.ts index ce6207053ab0..95e79e9c8908 100644 --- a/packages/devextreme/js/__internal/common/core/animation/m_position.ts +++ b/packages/devextreme/js/__internal/common/core/animation/m_position.ts @@ -206,10 +206,24 @@ const defaultPositionResult = { }, }; +interface AxisPositionResult { + location: number; + flip: boolean; + fit: boolean; + oversize: number; + collisionSide?: string; +} + +interface PositionResult { + h: AxisPositionResult; + v: AxisPositionResult; + precise?: boolean; +} + const calculatePosition = function (what, options) { const $what = $(what); const currentOffset = $what.offset(); - const result = extend(true, {}, defaultPositionResult, { + const result: PositionResult = extend(true, {}, defaultPositionResult, { // @ts-expect-error h: { location: currentOffset.left }, // @ts-expect-error diff --git a/packages/devextreme/js/__internal/core/action.ts b/packages/devextreme/js/__internal/core/action.ts index 5c2295dd1338..d5af3073463e 100644 --- a/packages/devextreme/js/__internal/core/action.ts +++ b/packages/devextreme/js/__internal/core/action.ts @@ -17,7 +17,7 @@ export type ActionArguments< validatingTargetName?: string; }; -interface ActionEvent { +export interface ActionEvent { action: unknown; args: unknown[]; context: unknown; diff --git a/packages/devextreme/js/__internal/core/devices.ts b/packages/devextreme/js/__internal/core/devices.ts index 5798f2bd6cfa..ff8beb10a93d 100644 --- a/packages/devextreme/js/__internal/core/devices.ts +++ b/packages/devextreme/js/__internal/core/devices.ts @@ -255,7 +255,7 @@ class Devices { return undefined; } /// #ENDDEBUG - return extend({}, this._realDevice) as ResolvedDevice; + return extend({}, this._realDevice); } orientation(): Orientation | undefined { diff --git a/packages/devextreme/js/__internal/core/r1/dom_component_wrapper.tsx b/packages/devextreme/js/__internal/core/r1/dom_component_wrapper.tsx index 07f4cf376113..261a8367fc8c 100644 --- a/packages/devextreme/js/__internal/core/r1/dom_component_wrapper.tsx +++ b/packages/devextreme/js/__internal/core/r1/dom_component_wrapper.tsx @@ -138,7 +138,7 @@ export class DomComponentWrapper extends InfernoComponent = extend({ rtlEnabled: this.getConfig()?.rtlEnabled, isRenovated: true, }, normalizedProps); diff --git a/packages/devextreme/js/__internal/core/utils/__tests__/m_date_serialization.test.ts b/packages/devextreme/js/__internal/core/utils/__tests__/m_date_serialization.test.ts new file mode 100644 index 000000000000..79df16940f07 --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/__tests__/m_date_serialization.test.ts @@ -0,0 +1,224 @@ +import { + afterEach, describe, expect, it, +} from '@jest/globals'; +import config from '@js/core/config'; +import { dateSerialization } from '@ts/core/utils/m_date_serialization'; + +const { + createLocalDateFromUTCTimestamp, + dateParser, + deserializeDate, + serializeDate, + getDateSerializationFormat, +} = dateSerialization; + +const time = (value: unknown): number => (value as Date).getTime(); + +describe('Date serialization utils', () => { + afterEach(() => { + config({ forceIsoDateParsing: true }); + }); + + describe('createLocalDateFromUTCTimestamp', () => { + it('should create the local midnight of the UTC date', () => { + const result = createLocalDateFromUTCTimestamp(Date.UTC(2020, 4, 17, 23, 59, 59)); + + expect(time(result)).toBe(time(new Date(2020, 4, 17))); + }); + }); + + describe('deserializeDate', () => { + it('should create a date from a number', () => { + const result = deserializeDate(1234567890000); + + expect(result).toBeInstanceOf(Date); + expect(time(result)).toBe(1234567890000); + }); + + it('should parse an ISO string without a time zone as a local date', () => { + expect(time(deserializeDate('2020-05-17'))).toBe(time(new Date(2020, 4, 17))); + expect(time(deserializeDate('2020-05-17T10:20:30'))).toBe(time(new Date(2020, 4, 17, 10, 20, 30))); + expect(time(deserializeDate('2020-05-17T10:20'))).toBe(time(new Date(2020, 4, 17, 10, 20))); + expect(time(deserializeDate('2020-05-17T10'))).toBe(time(new Date(2020, 4, 17, 10))); + }); + + it('should parse an ISO string with the Z designator as UTC', () => { + expect(time(deserializeDate('2020-05-17T10:20:30Z'))).toBe(Date.UTC(2020, 4, 17, 10, 20, 30)); + }); + + it('should apply the time zone offset of an ISO string', () => { + expect(time(deserializeDate('2020-05-17T10:20:30+03:00'))).toBe(Date.UTC(2020, 4, 17, 7, 20, 30)); + expect(time(deserializeDate('2020-05-17T10:20:30-02:30'))).toBe(Date.UTC(2020, 4, 17, 12, 50, 30)); + expect(time(deserializeDate('2020-05-17T10:20:30+0300'))).toBe(Date.UTC(2020, 4, 17, 7, 20, 30)); + expect(time(deserializeDate('2020-05-17T10:20:30+03'))).toBe(Date.UTC(2020, 4, 17, 7, 20, 30)); + }); + + it('should parse the milliseconds of an ISO string', () => { + expect(time(deserializeDate('2020-05-17T10:20:30.5'))).toBe(time(new Date(2020, 4, 17, 10, 20, 30, 500))); + expect(time(deserializeDate('2020-05-17T10:20:30.12'))).toBe(time(new Date(2020, 4, 17, 10, 20, 30, 120))); + expect(time(deserializeDate('2020-05-17T10:20:30.123456'))).toBe(time(new Date(2020, 4, 17, 10, 20, 30, 123))); + }); + + it('should parse an ISO string without separators', () => { + expect(time(deserializeDate('20200517T102030'))).toBe(time(new Date(2020, 4, 17, 10, 20, 30))); + expect(time(deserializeDate('20200517'))).toBe(time(new Date(2020, 4, 17))); + }); + + it('should parse a time string as a time of the zero date', () => { + expect(time(deserializeDate('10:20:30'))).toBe(time(new Date(0, 0, 0, 10, 20, 30))); + expect(time(deserializeDate('10:20'))).toBe(time(new Date(0, 0, 0, 10, 20, 0))); + }); + + it('should keep the year of an ISO string below 100', () => { + const result = deserializeDate('0050-01-02T03:04:05') as Date; + + expect(result.getFullYear()).toBe(50); + expect(result.getMonth()).toBe(0); + expect(result.getDate()).toBe(2); + expect((deserializeDate('0050-01-02T03:04:05Z') as Date).getUTCFullYear()).toBe(50); + }); + + it('should parse a partial ISO date as a local date', () => { + expect(time(deserializeDate('2020-05'))).toBe(time(new Date(2020, 4, 1))); + expect(time(deserializeDate('2020'))).toBe(time(new Date(2020, 0, 1))); + }); + + it('should parse the default serialization formats', () => { + expect(time(deserializeDate('2020/05/17'))).toBe(time(new Date(2020, 4, 17))); + expect(time(deserializeDate('2020/05/17 10:20:30'))).toBe(time(new Date(2020, 4, 17, 10, 20, 30))); + }); + + it('should build a date of the default serialization format that the native parser rejects', () => { + expect(time(deserializeDate('2020/13/45'))).toBe(time(new Date(2021, 1, 14))); + }); + + it('should return a string that is not a date as is', () => { + expect(deserializeDate('not a date')).toBe('not a date'); + expect(deserializeDate('')).toBe(''); + }); + + it('should return the values that are not strings and numbers as is', () => { + const date = new Date(5); + const object = {}; + + expect(deserializeDate(date)).toBe(date); + expect(deserializeDate(object)).toBe(object); + expect(deserializeDate(null)).toBeNull(); + expect(deserializeDate(undefined)).toBeUndefined(); + expect(deserializeDate(true)).toBe(true); + }); + + it('should parse an ISO date with the native parser when the ISO parsing is not forced', () => { + config({ forceIsoDateParsing: false }); + + expect(time(deserializeDate('2020-05-17'))).toBe(Date.UTC(2020, 4, 17)); + expect(time(deserializeDate('2020-05-17T10:20:30Z'))).toBe(Date.UTC(2020, 4, 17, 10, 20, 30)); + expect(time(deserializeDate('2020/05/17'))).toBe(time(new Date(2020, 4, 17))); + }); + }); + + describe('dateParser', () => { + it('should parse ISO strings by default', () => { + expect(time(dateParser('2020-05-17T10:20:30'))).toBe(time(new Date(2020, 4, 17, 10, 20, 30))); + expect(time(dateParser('2020-05-17T10:20:30Z'))).toBe(Date.UTC(2020, 4, 17, 10, 20, 30)); + }); + + it('should skip the ISO parsing on demand', () => { + expect(time(dateParser('2020-05-17', true))).toBe(Date.UTC(2020, 4, 17)); + }); + + it('should return a value that is not a date as is', () => { + expect(dateParser('abc')).toBe('abc'); + expect(dateParser(null)).toBeNull(); + }); + }); + + describe('serializeDate', () => { + const date = new Date(2020, 4, 17, 10, 20, 30); + + it('should return the value as is without a serialization format', () => { + const object = {}; + + expect(serializeDate(date)).toBe(date); + expect(serializeDate(date, '')).toBe(date); + expect(serializeDate(date, null)).toBe(date); + expect(serializeDate('text')).toBe('text'); + expect(serializeDate(object, undefined)).toBe(object); + expect(serializeDate(5)).toBe(5); + }); + + it('should return null for a value that is not a date', () => { + expect(serializeDate('2020-05-17', 'yyyy-MM-dd')).toBeNull(); + expect(serializeDate(5, 'number')).toBeNull(); + expect(serializeDate(null, 'yyyy')).toBeNull(); + expect(serializeDate(undefined, 'yyyy')).toBeNull(); + }); + + it('should serialize a date to the number of milliseconds', () => { + expect(serializeDate(new Date(5), 'number')).toBe(5); + }); + + it('should format a date with the serialization format', () => { + expect(serializeDate(date, 'yyyy-MM-dd')).toBe('2020-05-17'); + expect(serializeDate(date, 'yyyy/MM/dd HH:mm:ss')).toBe('2020/05/17 10:20:30'); + expect(serializeDate(date, 'HH:mm')).toBe('10:20'); + expect(serializeDate(date, 'yyyyMMdd')).toBe('20200517'); + }); + }); + + describe('getDateSerializationFormat', () => { + it('should return the number format for a number', () => { + expect(getDateSerializationFormat(0)).toBe('number'); + expect(getDateSerializationFormat(1234567890000)).toBe('number'); + }); + + it('should return the format of an ISO string', () => { + expect(getDateSerializationFormat('2020-05-17')).toBe('yyyy-MM-dd'); + expect(getDateSerializationFormat('20200517')).toBe('yyyyMMdd'); + expect(getDateSerializationFormat('2020-05-17T10')).toBe('yyyy-MM-ddTHH'); + expect(getDateSerializationFormat('2020-05-17T10:20')).toBe('yyyy-MM-ddTHH:mm'); + expect(getDateSerializationFormat('2020-05-17T10:20:30')).toBe('yyyy-MM-ddTHH:mm:ss'); + expect(getDateSerializationFormat('2020-05-17T10:20:30.123')).toBe('yyyy-MM-ddTHH:mm:ss.SSS'); + expect(getDateSerializationFormat('20200517T102030')).toBe('yyyyMMddTHHmmss'); + }); + + it('should return the format of an ISO string with a time zone', () => { + expect(getDateSerializationFormat('2020-05-17T10:20:30Z')).toBe('yyyy-MM-ddTHH:mm:ss\'Z\''); + expect(getDateSerializationFormat('2020-05-17T10:20:30+03:00')).toBe('yyyy-MM-ddTHH:mm:ssxxx'); + expect(getDateSerializationFormat('2020-05-17T10:20:30+0300')).toBe('yyyy-MM-ddTHH:mm:ssxx'); + expect(getDateSerializationFormat('2020-05-17T10:20:30+03')).toBe('yyyy-MM-ddTHH:mm:ssx'); + }); + + it('should return the format of a time string', () => { + expect(getDateSerializationFormat('10:20')).toBe('HH:mm'); + expect(getDateSerializationFormat('10:20:30')).toBe('HH:mm:ss'); + }); + + it('should return the default formats for the other strings', () => { + expect(getDateSerializationFormat('2020/05/17')).toBe('yyyy/MM/dd'); + expect(getDateSerializationFormat('text')).toBe('yyyy/MM/dd'); + expect(getDateSerializationFormat('')).toBe('yyyy/MM/dd'); + expect(getDateSerializationFormat('2020/05/17 10:20:30')).toBe('yyyy/MM/dd HH:mm:ss'); + expect(getDateSerializationFormat('May 17, 2020 10:20')).toBe('yyyy/MM/dd HH:mm:ss'); + }); + + it('should not detect an ISO format when the ISO parsing is not forced', () => { + config({ forceIsoDateParsing: false }); + + expect(getDateSerializationFormat('2020-05-17')).toBe('yyyy/MM/dd'); + expect(getDateSerializationFormat('2020-05-17T10:20:30')).toBe('yyyy/MM/dd HH:mm:ss'); + }); + + it('should return null for the other truthy values', () => { + expect(getDateSerializationFormat(new Date())).toBeNull(); + expect(getDateSerializationFormat({})).toBeNull(); + expect(getDateSerializationFormat(true)).toBeNull(); + }); + + it('should return undefined for the falsy values that are not numbers or strings', () => { + expect(getDateSerializationFormat(null)).toBeUndefined(); + expect(getDateSerializationFormat(undefined)).toBeUndefined(); + expect(getDateSerializationFormat(false)).toBeUndefined(); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/core/utils/__tests__/m_error.test.ts b/packages/devextreme/js/__internal/core/utils/__tests__/m_error.test.ts new file mode 100644 index 000000000000..a91aa73da817 --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/__tests__/m_error.test.ts @@ -0,0 +1,150 @@ +import { + beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import consoleUtils from '@ts/core/utils/m_console'; +import errorFactory from '@ts/core/utils/m_error'; + +jest.mock('@ts/core/utils/m_console', () => { + const logger = { + log: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(), + }; + + return { __esModule: true, logger, default: { logger } }; +}); + +const { logger } = consoleUtils; + +const BASE_MESSAGES = { + E0001: 'Message {0} and {1}.', + E0002: 'Trailing dots... ', + W0003: 'Warning {0}', + X0004: 'Other {0}', +}; + +describe('Error utils', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('ERROR_MESSAGES', () => { + it('should contain the base messages', () => { + expect(errorFactory(BASE_MESSAGES).ERROR_MESSAGES).toEqual(BASE_MESSAGES); + }); + + it('should extend the passed errors object with the base messages', () => { + const errors = { E0001: 'Overridden', E0100: 'Extra' }; + + const { ERROR_MESSAGES } = errorFactory(BASE_MESSAGES, errors); + + expect(ERROR_MESSAGES).toBe(errors); + expect(ERROR_MESSAGES).toEqual({ ...BASE_MESSAGES, E0100: 'Extra' }); + }); + + it('should not share messages between factories', () => { + const first = errorFactory({ E1: 'first {0}' }); + const second = errorFactory({ E1: 'second {0}' }); + + expect(first.Error('E1', 'x').message).toContain('first x'); + expect(second.Error('E1', 'x').message).toContain('second x'); + }); + }); + + describe('Error', () => { + const { Error: makeError } = errorFactory(BASE_MESSAGES); + + it('should create an error with the formatted details and the link to the article', () => { + const error = makeError('E0001', 'a', 'b'); + + expect(error).toBeInstanceOf(Error); + expect(error.__id).toBe('E0001'); + expect(error.__details).toBe('Message a and b'); + expect(error.url).toMatch(/^https:\/\/js\.devexpress\.com\/error\/\d+_\d+\/E0001$/); + expect(error.message).toBe( + `E0001 - Message a and b.\n\nFor additional information on this error message, see: ${error.url}`, + ); + }); + + it('should remove the trailing dots and spaces of the details', () => { + expect(makeError('E0002').__details).toBe('Trailing dots'); + }); + + it('should mention a warning for the ids that start with W', () => { + expect(makeError('W0003', 'x').message).toContain('on this warning message'); + expect(makeError('X0004', 'x').message).toContain('on this error message'); + }); + + it('should keep the placeholders without values and ignore the extra values', () => { + expect(makeError('E0001').__details).toBe('Message {0} and {1}'); + expect(makeError('E0001', 'a', 'b', 'c').__details).toBe('Message a and b'); + }); + + it('should insert the values without interpreting the replacement patterns', () => { + expect(makeError('E0001', '$&', '$1').__details).toBe('Message $& and $1'); + }); + + it('should be callable with new', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const error = new (makeError as any)('E0001', 'a', 'b') as Error & { __id: string }; + + expect(error).toBeInstanceOf(Error); + expect(error.__id).toBe('E0001'); + expect(error.message).toContain('Message a and b'); + }); + + it('should not depend on the call context', () => { + const { Error: detached } = errorFactory(BASE_MESSAGES); + + expect(detached('E0001', 'a', 'b').__details).toBe('Message a and b'); + }); + }); + + describe('log', () => { + const { Error: makeError, log } = errorFactory(BASE_MESSAGES); + + it('should log the message of an error id as an error', () => { + log('E0001', 'a', 'b'); + + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith(makeError('E0001', 'a', 'b').message); + expect(logger.warn).not.toHaveBeenCalled(); + expect(logger.log).not.toHaveBeenCalled(); + }); + + it('should log the message of a warning id as a warning', () => { + log('W0003', 'x'); + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith(makeError('W0003', 'x').message); + expect(logger.error).not.toHaveBeenCalled(); + expect(logger.log).not.toHaveBeenCalled(); + }); + + it('should log only the id when it is neither an error nor a warning id', () => { + log('X0004', 'x'); + log('plain'); + + expect(logger.log).toHaveBeenCalledTimes(2); + expect(logger.log).toHaveBeenNthCalledWith(1, 'X0004'); + expect(logger.log).toHaveBeenNthCalledWith(2, 'plain'); + expect(logger.error).not.toHaveBeenCalled(); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + it('should require the id to consist of the letter and digits only', () => { + log('E12x'); + log('E'); + log('W'); + log('W1 '); + + expect(logger.log).toHaveBeenCalledTimes(4); + }); + + it('should not depend on the call context', () => { + const { log: detached } = errorFactory(BASE_MESSAGES); + + detached('E0001', 'a', 'b'); + + expect(logger.error).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/core/utils/__tests__/m_extend.test.ts b/packages/devextreme/js/__internal/core/utils/__tests__/m_extend.test.ts new file mode 100644 index 000000000000..83fe90b2d4e5 --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/__tests__/m_extend.test.ts @@ -0,0 +1,247 @@ +import { describe, expect, it } from '@jest/globals'; +import { extend, extendFromObject } from '@ts/core/utils/m_extend'; + +describe('Extend utils', () => { + describe('extend', () => { + it('should copy the properties of a source to the target and return the target', () => { + const target = { a: 1 }; + + const result = extend(target, { b: 2 }); + + expect(result).toBe(target); + expect(target).toEqual({ a: 1, b: 2 }); + }); + + it('should apply the sources in the passed order', () => { + expect(extend({ a: 1 }, { a: 2, b: 2 }, { b: 3 })).toEqual({ a: 2, b: 3 }); + }); + + it('should skip undefined values and copy the other falsy ones', () => { + const result = extend({ + a: 1, b: 1, c: 1, d: 1, e: 1, + }, { + a: undefined, b: null, c: 0, d: '', e: false, + }); + + expect(result).toEqual({ + a: 1, b: null, c: 0, d: '', e: false, + }); + }); + + it('should copy the inherited enumerable properties of a source', () => { + const source = Object.create({ inherited: 1 }) as Record; + source.own = 2; + + expect(extend({}, source)).toEqual({ own: 2, inherited: 1 }); + }); + + it('should skip null and undefined sources', () => { + expect(extend({ a: 1 }, null, undefined, { b: 2 })).toEqual({ a: 1, b: 2 }); + }); + + it('should copy nested objects and arrays by reference', () => { + const source = { nested: { a: 1 }, list: [1] }; + + const result = extend({}, source); + + expect(result.nested).toBe(source.nested); + expect(result.list).toBe(source.list); + }); + + it('should create a new object when the target is missing or falsy', () => { + expect(extend()).toEqual({}); + expect(extend(undefined, { a: 1 })).toEqual({ a: 1 }); + expect(extend(null, { a: 1 })).toEqual({ a: 1 }); + expect(extend(0, { a: 1 })).toEqual({ a: 1 }); + expect(extend('', { a: 1 })).toEqual({ a: 1 }); + }); + + it('should treat false as a missing target and merge the other arguments as sources', () => { + const first = { a: 1 }; + + const result = extend(false, first, { b: 2 }); + + expect(result).toEqual({ a: 1, b: 2 }); + expect(result).not.toBe(first); + expect(first).toEqual({ a: 1 }); + }); + + it('should skip the __proto__ and constructor keys', () => { + const source = JSON.parse('{"__proto__": {"polluted": true}, "constructor": 1, "a": 1}') as object; + + const result = extend({}, source); + + expect(result).toEqual({ a: 1 }); + expect(Object.getPrototypeOf(result)).toBe(Object.prototype); + expect(({} as Record).polluted).toBeUndefined(); + }); + + it('should skip a property whose value is the target itself', () => { + const target: Record = { a: 1 }; + + extend(target, { self: target, b: 2 }); + + expect(target).toEqual({ a: 1, b: 2 }); + }); + + describe('deep', () => { + it('should merge plain objects recursively', () => { + const result = extend(true, { nested: { a: 1, b: 1 } }, { nested: { b: 2, c: 3 } }); + + expect(result).toEqual({ nested: { a: 1, b: 2, c: 3 } }); + }); + + it('should clone the nested plain objects and arrays of a source', () => { + const source = { nested: { a: 1 }, list: [1, 2] }; + + const result = extend(true, {}, source); + + expect(result).toEqual(source); + expect(result.nested).not.toBe(source.nested); + expect(result.list).not.toBe(source.list); + }); + + it('should keep and extend the nested plain object and array of the target', () => { + const nested = { a: 1 }; + const list = [1, 2, 3]; + const target = { nested, list }; + + extend(true, target, { nested: { b: 2 }, list: [9] }); + + expect(target.nested).toBe(nested); + expect(nested).toEqual({ a: 1, b: 2 }); + expect(target.list).toBe(list); + expect(list).toEqual([9, 2, 3]); + }); + + it('should replace a target value of another kind with a new array or object', () => { + const result = extend(true, { list: { a: 1 }, nested: [1], number: 5 }, { + list: [1], nested: { b: 2 }, number: { c: 3 }, + }); + + expect(result).toEqual({ list: [1], nested: { b: 2 }, number: { c: 3 } }); + expect(Array.isArray(result.list)).toBe(true); + expect(Array.isArray(result.nested)).toBe(false); + }); + + it('should copy the values that are not plain objects or arrays by reference', () => { + class Custom { + public value = 1; + } + const date = new Date(1); + const custom = new Custom(); + const callback = (): void => {}; + + const result = extend(true, {}, { + date, custom, callback, text: 'text', + }); + + expect(result.date).toBe(date); + expect(result.custom).toBe(custom); + expect(result.callback).toBe(callback); + expect(result.text).toBe('text'); + }); + + it('should skip undefined values and copy null', () => { + const result = extend(true, { a: { b: 1 }, c: 1 }, { a: undefined, c: null }); + + expect(result).toEqual({ a: { b: 1 }, c: null }); + }); + + it('should create a new object when the target is missing', () => { + expect(extend(true)).toEqual({}); + + const source = { nested: { a: 1 } }; + const result = extend(true, undefined, source); + + expect(result).toEqual(source); + expect(result.nested).not.toBe(source.nested); + }); + + it('should skip the __proto__ key of nested objects', () => { + const source = JSON.parse('{"nested": {"__proto__": {"polluted": true}, "a": 1}}') as object; + + const result = extend(true, {}, source); + + expect(result).toEqual({ nested: { a: 1 } }); + expect(({} as Record).polluted).toBeUndefined(); + }); + }); + + describe('the type of the result', () => { + it('should keep the properties of a source that can be skipped optional', () => { + const skipped = undefined as { value: string } | undefined; + + const merged = extend({}, skipped); + const isOptional: Record extends Pick + ? true + : false = true; + + expect(isOptional).toBe(true); + expect(merged).toEqual({}); + }); + + it('should keep the properties of a source that is always present required', () => { + const merged = extend({}, { value: 'a' }); + const isRequired: Record extends Pick + ? false + : true = true; + + expect(isRequired).toBe(true); + expect(merged).toEqual({ value: 'a' }); + }); + }); + }); + + describe('extendFromObject', () => { + it('should copy the own properties of a source and return the target', () => { + const target = { a: 1 }; + + const result = extendFromObject(target, { b: 2 }); + + expect(result).toBe(target); + expect(target).toEqual({ a: 1, b: 2 }); + }); + + it('should not copy the inherited properties of a source', () => { + const source = Object.create({ inherited: 1 }) as Record; + source.own = 2; + + expect(extendFromObject({}, source)).toEqual({ own: 2 }); + }); + + it('should keep the existing properties of the target unless they are overridden', () => { + expect(extendFromObject({ a: 1 }, { a: 2, b: 2 })).toEqual({ a: 1, b: 2 }); + expect(extendFromObject({ a: 1 }, { a: 2, b: 2 }, false)).toEqual({ a: 1, b: 2 }); + expect(extendFromObject({ a: 1 }, { a: 2, b: 2 }, true)).toEqual({ a: 2, b: 2 }); + }); + + it('should consider the inherited properties of the target as existing', () => { + const target = Object.create({ inherited: 1 }) as Record; + + extendFromObject(target, { inherited: 2 }); + expect(Object.keys(target)).toEqual([]); + + extendFromObject(target, { inherited: 2 }, true); + expect(Object.keys(target)).toEqual(['inherited']); + expect(target.inherited).toBe(2); + }); + + it('should copy undefined values', () => { + expect(extendFromObject({}, { a: undefined })).toEqual({ a: undefined }); + expect(Object.keys(extendFromObject({}, { a: undefined }))).toEqual(['a']); + }); + + it('should create a new object when the target is missing', () => { + expect(extendFromObject(null, { a: 1 })).toEqual({ a: 1 }); + expect(extendFromObject(undefined, { a: 1 })).toEqual({ a: 1 }); + }); + + it('should return the target when the source is missing', () => { + const target = { a: 1 }; + + expect(extendFromObject(target, null)).toBe(target); + expect(extendFromObject(target, undefined)).toBe(target); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/core/utils/__tests__/m_iterator.test.ts b/packages/devextreme/js/__internal/core/utils/__tests__/m_iterator.test.ts new file mode 100644 index 000000000000..f71cf54ea5dc --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/__tests__/m_iterator.test.ts @@ -0,0 +1,224 @@ +import { + describe, expect, it, jest, +} from '@jest/globals'; +import { each, map, reverseEach } from '@ts/core/utils/m_iterator'; + +describe('Iterator utils', () => { + describe('each', () => { + it('should pass the index and the item of an array to the callback', () => { + const callback = jest.fn(); + + each(['a', 'b'], callback); + + expect(callback.mock.calls).toEqual([[0, 'a'], [1, 'b']]); + }); + + it('should pass the key and the value of an object to the callback', () => { + const callback = jest.fn(); + + each({ a: 1, b: 2 }, callback); + + expect(callback.mock.calls).toEqual([['a', 1], ['b', 2]]); + }); + + it('should iterate the inherited enumerable keys of an object after its own keys', () => { + const values = Object.create({ inherited: 2 }) as Record; + values.own = 1; + const callback = jest.fn(); + + each(values, callback); + + expect(callback.mock.calls).toEqual([['own', 1], ['inherited', 2]]); + }); + + it('should iterate an object with the length property as an array-like', () => { + const callback = jest.fn(); + + each({ + length: 2, 0: 'a', 1: 'b', other: 'c', + }, callback); + + expect(callback.mock.calls).toEqual([[0, 'a'], [1, 'b']]); + }); + + it('should call the callback with the item as this', () => { + const item = {}; + const contexts: unknown[] = []; + const collect = function collect(this: unknown): void { + contexts.push(this); + }; + + each([item, 5], collect); + each({ key: item }, collect); + + expect(contexts).toHaveLength(3); + expect(contexts[0]).toBe(item); + expect(contexts[1]).toBe(5); + expect(contexts[2]).toBe(item); + }); + + it('should return the iterated array or object itself', () => { + const array = [1]; + const object = { a: 1 }; + + expect(each(array, jest.fn())).toBe(array); + expect(each(object, jest.fn())).toBe(object); + }); + + it('should return undefined and skip the callback for falsy values', () => { + const callback = jest.fn(); + + expect(each(undefined, callback)).toBeUndefined(); + expect(each(null, callback)).toBeUndefined(); + expect(each(0, callback)).toBeUndefined(); + expect(each('', callback)).toBeUndefined(); + expect(each(false, callback)).toBeUndefined(); + + expect(callback).not.toHaveBeenCalled(); + }); + + it('should stop iterating an array when the callback returns false', () => { + const callback = jest.fn((index: number) => index !== 1); + + each([1, 2, 3, 4], callback); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('should stop iterating an object when the callback returns false', () => { + const callback = jest.fn((key: string) => key !== 'b'); + + each({ a: 1, b: 2, c: 3 }, callback); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('should continue iterating when the callback returns a falsy value other than false', () => { + [undefined, null, 0, '', NaN].forEach((result) => { + const callback = jest.fn(() => result); + + each([1, 2, 3], callback); + + expect(callback).toHaveBeenCalledTimes(3); + }); + }); + + it('should return the iterated value after the iteration is stopped', () => { + const array = [1, 2]; + + expect(each(array, () => false)).toBe(array); + }); + + it('should read the length of an array on every step', () => { + const array = [1, 2]; + const visited: number[] = []; + + each(array, (index: number, item: number) => { + visited.push(item); + + if (index === 0) { + array.push(3); + } + }); + + expect(visited).toEqual([1, 2, 3]); + }); + }); + + describe('map', () => { + it('should map an array and pass the item, the index and the array to the callback', () => { + const array = ['a', 'b']; + const callback = jest.fn((item: string) => item.toUpperCase()); + + expect(map(array, callback)).toEqual(['A', 'B']); + expect(callback.mock.calls).toEqual([['a', 0, array], ['b', 1, array]]); + }); + + it('should map an object and pass only the value and the key to the callback', () => { + const callback = jest.fn((value: number, key: string) => `${key}${value}`); + + expect(map({ a: 1, b: 2 }, callback)).toEqual(['a1', 'b2']); + expect(callback.mock.calls).toEqual([[1, 'a'], [2, 'b']]); + }); + + it('should map the inherited enumerable keys of an object', () => { + const values = Object.create({ inherited: 2 }) as Record; + values.own = 1; + + expect(map(values, (value: number, key: string) => [key, value])).toEqual([ + ['own', 1], + ['inherited', 2], + ]); + }); + + it('should keep the undefined results', () => { + expect(map([1, 2], () => undefined)).toEqual([undefined, undefined]); + expect(map({ a: 1 }, () => undefined)).toEqual([undefined]); + }); + + it('should iterate the keys of an array-like object instead of its indexes', () => { + const callback = jest.fn((value: unknown, key: string) => key); + + expect(map({ length: 2, 0: 'a', 1: 'b' }, callback)).toEqual(['0', '1', 'length']); + }); + + it('should return an empty array for null and undefined', () => { + expect(map(null, jest.fn())).toEqual([]); + expect(map(undefined, jest.fn())).toEqual([]); + }); + }); + + describe('reverseEach', () => { + it('should iterate an array from the last item to the first one', () => { + const callback = jest.fn(); + + reverseEach(['a', 'b', 'c'], callback); + + expect(callback.mock.calls).toEqual([[2, 'c'], [1, 'b'], [0, 'a']]); + }); + + it('should iterate an array-like object', () => { + const callback = jest.fn(); + + reverseEach({ length: 2, 0: 'a', 1: 'b' }, callback); + + expect(callback.mock.calls).toEqual([[1, 'b'], [0, 'a']]); + }); + + it('should call the callback with the item as this', () => { + const item = {}; + const contexts: unknown[] = []; + + reverseEach([item, 5], function collect(this: unknown): void { + contexts.push(this); + }); + + expect(contexts[0]).toBe(5); + expect(contexts[1]).toBe(item); + }); + + it('should stop iterating when the callback returns false', () => { + const callback = jest.fn((index: number) => index !== 1); + + reverseEach([1, 2, 3], callback); + + expect(callback).toHaveBeenCalledTimes(2); + }); + + it('should skip the callback for empty and not indexed values', () => { + const callback = jest.fn(); + + reverseEach([], callback); + reverseEach({ a: 1 }, callback); + reverseEach({ length: 0 }, callback); + reverseEach(null, callback); + reverseEach(undefined, callback); + + expect(callback).not.toHaveBeenCalled(); + }); + + it('should return undefined', () => { + expect(reverseEach([1], jest.fn())).toBeUndefined(); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/core/utils/__tests__/m_selection_filter.test.ts b/packages/devextreme/js/__internal/core/utils/__tests__/m_selection_filter.test.ts new file mode 100644 index 000000000000..3e5f0205fc87 --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/__tests__/m_selection_filter.test.ts @@ -0,0 +1,229 @@ +import { describe, expect, it } from '@jest/globals'; +import { SelectionFilterCreator } from '@ts/core/utils/m_selection_filter'; + +interface SelectionFilter { + getExpr: (keyExpr?: unknown) => unknown; + getCombinedFilter: (keyExpr?: unknown, dataSourceFilter?: unknown, force?: boolean) => unknown; + getLocalFilter: ( + keyGetter: (item: unknown) => unknown, + equalKeys?: (key1: unknown, key2: unknown) => boolean, + equalByReference?: boolean, + keyExpr?: unknown, + ) => (item: unknown) => boolean; +} + +const createFilter = (keys: unknown[], isSelectAll?: boolean): SelectionFilter => new ( + SelectionFilterCreator as unknown as new (...args: unknown[]) => SelectionFilter +)(keys, isSelectAll); + +describe('Selection filter utils', () => { + describe('getExpr', () => { + it('should return undefined without a key expression', () => { + const filter = createFilter([1, 2]); + + expect(filter.getExpr(undefined)).toBeUndefined(); + expect(filter.getExpr(null)).toBeUndefined(); + expect(filter.getExpr('')).toBeUndefined(); + }); + + it('should return undefined without keys', () => { + expect(createFilter([]).getExpr('id')).toBeUndefined(); + }); + + it('should return a single condition for a single key', () => { + expect(createFilter([1]).getExpr('id')).toEqual(['id', '=', 1]); + }); + + it('should join the conditions of several keys with or', () => { + expect(createFilter([1, 2, 3]).getExpr('id')).toEqual([ + ['id', '=', 1], 'or', ['id', '=', 2], 'or', ['id', '=', 3], + ]); + }); + + it('should invert the conditions and join them with and when all items are selected', () => { + expect(createFilter([1], true).getExpr('id')).toEqual(['id', '<>', 1]); + expect(createFilter([1, 2], true).getExpr('id')).toEqual([ + ['id', '<>', 1], 'and', ['id', '<>', 2], + ]); + }); + + it('should use a function key expression as is', () => { + const keyExpr = (item: unknown): unknown => item; + + expect(createFilter([5]).getExpr(keyExpr)).toEqual([keyExpr, '=', 5]); + }); + + it('should build a condition for each field of a composite key', () => { + const filter = createFilter([{ a: 1, b: 2 }, { a: 3, b: 4 }]); + + expect(filter.getExpr(['a', 'b'])).toEqual([ + [['a', '=', 1], 'and', ['b', '=', 2]], + 'or', + [['a', '=', 3], 'and', ['b', '=', 4]], + ]); + }); + + it('should join the fields of a composite key with or when all items are selected', () => { + expect(createFilter([{ a: 1, b: 2 }], true).getExpr(['a', 'b'])).toEqual([ + ['a', '<>', 1], 'or', ['b', '<>', 2], + ]); + }); + + it('should stop at the first field of a composite key without a value', () => { + expect(createFilter([{ a: 1 }]).getExpr(['a', 'b'])).toEqual([['a', '=', 1]]); + expect(createFilter([{ b: 1 }]).getExpr(['a', 'b'])).toEqual([]); + }); + + it('should keep a gap in place of a plain key that is undefined', () => { + expect(createFilter([undefined]).getExpr('id')).toBeUndefined(); + expect(createFilter([1, undefined]).getExpr('id')).toEqual([['id', '=', 1], 'or', undefined]); + }); + }); + + describe('getCombinedFilter', () => { + const dataSourceFilter = ['field', '>', 1]; + + it('should return the key expression filter by default', () => { + expect(createFilter([1]).getCombinedFilter('id', dataSourceFilter)).toEqual(['id', '=', 1]); + }); + + it('should combine the filters when the combination is forced', () => { + expect(createFilter([1]).getCombinedFilter('id', dataSourceFilter, true)).toEqual([ + ['id', '=', 1], dataSourceFilter, + ]); + }); + + it('should combine the filters when all items are selected', () => { + expect(createFilter([1], true).getCombinedFilter('id', dataSourceFilter)).toEqual([ + ['id', '<>', 1], dataSourceFilter, + ]); + }); + + it('should return the data source filter when there is no key expression filter', () => { + expect(createFilter([], true).getCombinedFilter('id', dataSourceFilter)).toBe(dataSourceFilter); + expect(createFilter([1]).getCombinedFilter(undefined, dataSourceFilter, true)) + .toBe(dataSourceFilter); + }); + + it('should ignore an empty data source filter', () => { + expect(createFilter([1], true).getCombinedFilter('id', null)).toEqual(['id', '<>', 1]); + expect(createFilter([1]).getCombinedFilter('id', undefined, true)).toEqual(['id', '=', 1]); + }); + + it('should return undefined when there is nothing to combine', () => { + expect(createFilter([]).getCombinedFilter('id', dataSourceFilter, true)).toBe(dataSourceFilter); + expect(createFilter([]).getCombinedFilter('id', undefined, true)).toBeUndefined(); + }); + }); + + describe('getLocalFilter', () => { + const getId = (item: unknown): unknown => (item as { id: unknown }).id; + + it('should accept the selected items', () => { + const filter = createFilter([1, 3]).getLocalFilter(getId); + + expect(filter({ id: 1 })).toBe(true); + expect(filter({ id: 2 })).toBe(false); + expect(filter({ id: 3 })).toBe(true); + }); + + it('should accept the items that are not excluded when all items are selected', () => { + const filter = createFilter([1, 3], true).getLocalFilter(getId); + + expect(filter({ id: 1 })).toBe(false); + expect(filter({ id: 2 })).toBe(true); + expect(filter({ id: 3 })).toBe(false); + }); + + it('should compare object keys by value', () => { + const filter = createFilter([{ a: 1 }]).getLocalFilter(getId); + + expect(filter({ id: { a: 1 } })).toBe(true); + expect(filter({ id: { a: 2 } })).toBe(false); + }); + + it('should compare the keys one by one with the passed function when they are compared by reference', () => { + const selected = { a: 1 }; + const equalKeys = (key1: unknown, key2: unknown): boolean => key1 === key2; + const filter = createFilter([selected]).getLocalFilter(getId, equalKeys, true); + + expect(filter({ id: selected })).toBe(true); + expect(filter({ id: { a: 1 } })).toBe(false); + }); + + it('should compare the keys by value by default even when they are compared by reference', () => { + const filter = createFilter([{ a: 1 }]).getLocalFilter(getId, undefined, true); + + expect(filter({ id: { a: 1 } })).toBe(true); + expect(filter({ id: { a: 2 } })).toBe(false); + }); + + it('should not use the passed function to compare the keys with a lookup', () => { + let calls = 0; + const equalKeys = (): boolean => { + calls += 1; + + return false; + }; + const filter = createFilter([1]).getLocalFilter(getId, equalKeys); + + expect(filter({ id: 1 })).toBe(true); + expect(calls).toBe(0); + }); + + it('should normalize the keys of a composite key expression with the key getter', () => { + const filter = createFilter([{ a: 1, b: 2 }]).getLocalFilter( + (item: unknown) => ({ ...(item as object) }), + undefined, + false, + ['a', 'b'], + ); + + expect(filter({ a: 1, b: 2 })).toBe(true); + expect(filter({ a: 1, b: 3 })).toBe(false); + }); + + it('should normalize the selected keys of a composite key expression with the key getter', () => { + const filter = createFilter([{ b: 2, a: 1 }]).getLocalFilter( + (item: unknown) => ({ a: (item as { a: number }).a, b: (item as { b: number }).b }), + undefined, + false, + ['a', 'b'], + ); + + expect(filter({ a: 1, b: 2 })).toBe(true); + }); + + it('should skip the selected items when all items are selected and the keys are compared one by one', () => { + const selected = { a: 1 }; + const equalKeys = (key1: unknown, key2: unknown): boolean => key1 === key2; + const filter = createFilter([selected], true).getLocalFilter(getId, equalKeys, true); + + expect(filter({ id: selected })).toBe(false); + expect(filter({ id: { a: 1 } })).toBe(true); + }); + + it('should compare the keys one by one when their hash is not a string', () => { + const filter = createFilter([{ a: undefined }]).getLocalFilter(getId); + + expect(filter({ id: { a: undefined } })).toBe(true); + expect(filter({ id: { b: undefined } })).toBe(false); + }); + + it('should not select anything without keys', () => { + expect(createFilter([]).getLocalFilter(getId)({ id: 1 })).toBe(false); + expect(createFilter([], true).getLocalFilter(getId)({ id: 1 })).toBe(true); + }); + + it('should build the lookup of the selected keys only once', () => { + const keys = [1, 2]; + const filter = createFilter(keys).getLocalFilter(getId); + + expect(filter({ id: 1 })).toBe(true); + + keys.push(3); + + expect(filter({ id: 3 })).toBe(false); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/core/utils/__tests__/m_shadow_dom.test.ts b/packages/devextreme/js/__internal/core/utils/__tests__/m_shadow_dom.test.ts new file mode 100644 index 000000000000..9d0a815033c7 --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/__tests__/m_shadow_dom.test.ts @@ -0,0 +1,454 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import type { dxElementWrapper } from '@js/core/renderer'; +import { computeStyleSheetsHash, getShadowElementsFromPoint } from '@ts/core/utils/m_shadow_dom'; + +const createSheet = (rules: string[] | 'cross-origin'): CSSStyleSheet => ({ + get cssRules() { + if (rules === 'cross-origin') { + throw new Error('cross-origin'); + } + + return rules.map((cssText) => ({ cssText })); + }, +}) as unknown as CSSStyleSheet; + +describe('Shadow DOM utils', () => { + describe('computeStyleSheetsHash', () => { + it('should return the offset basis for no style sheets', () => { + expect(computeStyleSheetsHash([])).toBe(2166136261); + }); + + it('should compute the hash of the rule texts', () => { + expect(computeStyleSheetsHash([createSheet(['.a{color:red}'])])).toBe(2242418873); + expect(computeStyleSheetsHash([createSheet(['x'.repeat(5000), 'ÿ€😀'])])).toBe(939802646); + }); + + it('should return a different hash for different rules', () => { + const hash = computeStyleSheetsHash([createSheet(['.a{color:red}'])]); + + expect(computeStyleSheetsHash([createSheet(['.a{color:blue}'])])).not.toBe(hash); + expect(computeStyleSheetsHash([createSheet(['.a{color:red}', '.b{}'])])).not.toBe(hash); + }); + + it('should return the same hash for the style sheets with the same rules', () => { + expect(computeStyleSheetsHash([createSheet(['.same{}'])])) + .toBe(computeStyleSheetsHash([createSheet(['.same{}'])])); + }); + + it('should not depend on the order of the style sheets', () => { + const first = createSheet(['.a{}']); + const second = createSheet(['.b{}']); + const third = createSheet(['.c{}', '.d{}']); + + expect(computeStyleSheetsHash([first, second, third])).toBe(4210407800); + expect(computeStyleSheetsHash([third, second, first])).toBe(4210407800); + }); + + it('should neutralize a style sheet that is passed twice', () => { + const sheet = createSheet(['.a{}']); + + expect(computeStyleSheetsHash([sheet, sheet])).toBe(2166136261); + }); + + it('should return 0 for a style sheet without rules', () => { + expect(computeStyleSheetsHash([createSheet([])])).toBe(0); + }); + + it('should ignore a style sheet whose rules are not accessible', () => { + expect(computeStyleSheetsHash([createSheet('cross-origin')])).toBe(0); + expect(computeStyleSheetsHash([ + createSheet('cross-origin'), + createSheet(['.a{color:red}']), + ])).toBe(78906748); + }); + + it('should read the rules of a style sheet only once', () => { + let reads = 0; + const countedSheet = { + get cssRules() { + reads += 1; + + return [{ cssText: '.counted{}' }]; + }, + } as unknown as CSSStyleSheet; + + const other = createSheet(['.other{}']); + const first = computeStyleSheetsHash([countedSheet, other]); + const second = computeStyleSheetsHash([other, countedSheet]); + + expect(second).toBe(first); + expect(reads).toBe(1); + }); + + it('should accept any iterable of style sheets', () => { + const sheet = createSheet(['.set{}']); + + expect(computeStyleSheetsHash(new Set([sheet]))).toBe(computeStyleSheetsHash([sheet])); + }); + }); + + describe('getShadowElementsFromPoint', () => { + const roots: HTMLElement[] = []; + + interface Rect { + left: number; + top: number; + right: number; + bottom: number; + } + + const createElement = ( + tag: string, + id: string, + rect?: Rect, + pointerEvents?: string, + ): HTMLElement => { + const element = document.createElement(tag); + + element.id = id; + + if (rect) { + element.getBoundingClientRect = (): DOMRect => rect as DOMRect; + } + if (pointerEvents) { + element.style.pointerEvents = pointerEvents; + } + + return element; + }; + + const rectangle = ( + left: number, + top: number, + width: number, + height: number, + ): Rect => ({ + left, top, right: left + width, bottom: top + height, + }); + + const createRoot = (): HTMLElement => { + const root = document.createElement('div'); + + document.body.appendChild(root); + roots.push(root); + + return root; + }; + + const getIds = (x: number, y: number, root: Node): string[] => ( + getShadowElementsFromPoint(x, y, root).map((element) => (element as HTMLElement).id) + ); + + afterEach(() => { + roots.splice(0).forEach((root) => root.remove()); + }); + + it('should return the elements under the point from the deepest one to the topmost one', () => { + const root = createRoot(); + const first = createElement('div', 'first', rectangle(0, 0, 100, 100)); + const second = createElement('div', 'second', rectangle(10, 10, 50, 50)); + const third = createElement('span', 'third', rectangle(20, 20, 10, 10)); + const other = createElement('div', 'other', rectangle(200, 200, 10, 10)); + + root.appendChild(first); + first.appendChild(second); + second.appendChild(third); + root.appendChild(other); + + expect(getIds(25, 25, root)).toEqual(['third', 'second', 'first']); + expect(getIds(5, 5, root)).toEqual(['first']); + expect(getIds(205, 205, root)).toEqual(['other']); + expect(getIds(500, 500, root)).toEqual([]); + }); + + it('should include the top and left edges of an element and exclude the bottom and right ones', () => { + const root = createRoot(); + + root.appendChild(createElement('div', 'element', rectangle(0, 0, 100, 100))); + + expect(getIds(0, 0, root)).toEqual(['element']); + expect(getIds(99.9, 99.9, root)).toEqual(['element']); + expect(getIds(100, 50, root)).toEqual([]); + expect(getIds(50, 100, root)).toEqual([]); + }); + + it('should list the elements breadth first and reverse the order', () => { + const root = createRoot(); + const area = rectangle(0, 0, 100, 100); + const elements = ['l1a', 'l1b', 'l2a', 'l2b', 'l3'].map((id) => createElement('div', id, area)); + const [l1a, l1b, l2a, l2b, l3] = elements; + + root.appendChild(l1a); + root.appendChild(l1b); + l1a.appendChild(l2a); + l1b.appendChild(l2b); + l2a.appendChild(l3); + + expect(getIds(5, 5, root)).toEqual(['l3', 'l2b', 'l2a', 'l1b', 'l1a']); + }); + + it('should skip the elements that do not catch the pointer together with their children', () => { + const root = createRoot(); + const element = createElement('div', 'element', rectangle(0, 0, 100, 100), 'none'); + + element.appendChild(createElement('div', 'child', rectangle(0, 0, 100, 100))); + root.appendChild(element); + + expect(getIds(10, 10, root)).toEqual([]); + }); + + it('should skip the nodes that are not elements and the elements without a rectangle', () => { + const root = createRoot(); + const withoutRectangle = createElement('div', 'without-rectangle'); + + (withoutRectangle as { getBoundingClientRect?: unknown }).getBoundingClientRect = undefined; + root.appendChild(document.createTextNode('text')); + root.appendChild(document.createComment('comment')); + root.appendChild(withoutRectangle); + root.appendChild(createElement('div', 'element', rectangle(0, 0, 10, 10))); + + expect(getIds(1, 1, root)).toEqual(['element']); + }); + + it('should return an empty array for a root without children', () => { + expect(getShadowElementsFromPoint(0, 0, createRoot())).toEqual([]); + }); + }); + + describe('addShadowDomStyles', () => { + class FakeStyleSheet { + cssRules: { cssText: string }[] = []; + + insertRule(cssText: string, index: number): number { + this.cssRules.splice(index, 0, { cssText }); + + return index; + } + } + + const originalStyleSheet = window.CSSStyleSheet; + + const setStyleSheetConstructor = (value: unknown): void => { + Object.defineProperty(window, 'CSSStyleSheet', { value, configurable: true, writable: true }); + }; + + const createRules = (rules: object[]): { cssText: string }[] => rules.map((rule, index) => ({ + cssText: `rule-${index}`, + ...rule, + })); + + const createStyleSheetOf = (rules: object[] | 'cross-origin'): CSSStyleSheet => ({ + get cssRules() { + if (rules === 'cross-origin') { + throw new Error('cross-origin'); + } + + return createRules(rules); + }, + }) as unknown as CSSStyleSheet; + + interface FakeRoot { + host?: object; + styleSheets: CSSStyleSheet[]; + adoptedStyleSheets?: FakeStyleSheet[]; + } + + const createWrapper = ( + root: unknown, + documentSheets: CSSStyleSheet[] = [], + ): dxElementWrapper => { + const element = { + getRootNode: (): unknown => root, + ownerDocument: { styleSheets: documentSheets }, + }; + + return { get: () => element } as unknown as dxElementWrapper; + }; + + const createRoot = (sheets: CSSStyleSheet[]): FakeRoot => ({ host: {}, styleSheets: sheets }); + + type ShadowDomModule = typeof import('@ts/core/utils/m_shadow_dom'); + + const load = (): ShadowDomModule => { + jest.resetModules(); + + return jest.requireActual('@ts/core/utils/m_shadow_dom'); + }; + + const getTexts = (sheet: FakeStyleSheet): string[] => ( + sheet.cssRules.map((rule) => rule.cssText) + ); + + beforeEach(() => { + setStyleSheetConstructor(FakeStyleSheet); + }); + + afterEach(() => { + setStyleSheetConstructor(originalStyleSheet); + }); + + it('should do nothing when copying styles to the shadow DOM is disabled', () => { + const { addShadowDomStyles } = load(); + const config = jest.requireActual('@js/core/config').default; + const root = createRoot([]); + + config({ copyStylesToShadowDom: false }); + addShadowDomStyles(createWrapper(root)); + + expect(root.adoptedStyleSheets).toBeUndefined(); + }); + + it('should do nothing for an element that is not in a shadow root', () => { + const { addShadowDomStyles } = load(); + const root = createRoot([]); + + delete root.host; + + addShadowDomStyles(createWrapper(root)); + addShadowDomStyles(createWrapper(undefined)); + + expect(root.adoptedStyleSheets).toBeUndefined(); + }); + + it('should adopt the document style sheet and the style sheet of the shadow root', () => { + const { addShadowDomStyles } = load(); + const root = createRoot([]); + + addShadowDomStyles(createWrapper(root)); + + expect(root.adoptedStyleSheets).toHaveLength(2); + expect(root.adoptedStyleSheets?.[0]).toBeInstanceOf(FakeStyleSheet); + expect(root.adoptedStyleSheets?.[1]).toBeInstanceOf(FakeStyleSheet); + expect(root.adoptedStyleSheets?.[0]).not.toBe(root.adoptedStyleSheets?.[1]); + }); + + it('should copy only the DevExtreme rules of the document style sheets', () => { + const { addShadowDomStyles } = load(); + const root = createRoot([]); + const documentSheet = createStyleSheetOf([ + { selectorText: '.dx-button' }, + { selectorText: '.other' }, + { cssRules: [{ selectorText: '.dx-in-media' }] }, + { cssRules: [{ selectorText: '.other-in-media' }] }, + { name: 'dx-keyframes' }, + { name: 'other-keyframes' }, + { style: { fontFamily: 'DXIcons' } }, + { style: { fontFamily: 'Arial' } }, + {}, + ]); + + addShadowDomStyles(createWrapper(root, [documentSheet])); + + expect(getTexts((root.adoptedStyleSheets as FakeStyleSheet[])[0])).toEqual([ + 'rule-0', 'rule-2', 'rule-4', 'rule-6', + ]); + }); + + it('should copy all the rules of the shadow root style sheets', () => { + const { addShadowDomStyles } = load(); + const root = createRoot([ + createStyleSheetOf([{ selectorText: '.local' }, { selectorText: '.dx-local' }]), + createStyleSheetOf([{}]), + ]); + + addShadowDomStyles(createWrapper(root)); + + expect(getTexts((root.adoptedStyleSheets as FakeStyleSheet[])[1])).toEqual([ + 'rule-0', 'rule-1', 'rule-0', + ]); + }); + + it('should skip the style sheets with not accessible rules', () => { + const { addShadowDomStyles } = load(); + const root = createRoot([createStyleSheetOf('cross-origin'), createStyleSheetOf([{}])]); + const documentSheets = [ + createStyleSheetOf('cross-origin'), + createStyleSheetOf([{ selectorText: '.dx-button' }]), + ]; + + addShadowDomStyles(createWrapper(root, documentSheets)); + + expect(getTexts((root.adoptedStyleSheets as FakeStyleSheet[])[0])).toEqual(['rule-0']); + expect(getTexts((root.adoptedStyleSheets as FakeStyleSheet[])[1])).toEqual(['rule-0']); + }); + + it('should not process the shadow root again while its style sheets are not changed', () => { + const { addShadowDomStyles } = load(); + const root = createRoot([createStyleSheetOf([{}])]); + const wrapper = createWrapper(root); + + addShadowDomStyles(wrapper); + const adopted = root.adoptedStyleSheets; + addShadowDomStyles(wrapper); + + expect(root.adoptedStyleSheets).toBe(adopted); + }); + + it('should process the shadow root again when its style sheets are changed', () => { + const { addShadowDomStyles } = load(); + const root = createRoot([createStyleSheetOf([{}])]); + const wrapper = createWrapper(root); + + addShadowDomStyles(wrapper); + const adopted = root.adoptedStyleSheets as FakeStyleSheet[]; + root.styleSheets.push(createStyleSheetOf([{}, {}])); + addShadowDomStyles(wrapper); + + expect(root.adoptedStyleSheets).not.toBe(adopted); + expect(root.adoptedStyleSheets?.[0]).toBe(adopted[0]); + expect(root.adoptedStyleSheets?.[1]).not.toBe(adopted[1]); + expect(getTexts((root.adoptedStyleSheets as FakeStyleSheet[])[1])).toEqual([ + 'rule-0', 'rule-0', 'rule-1', + ]); + }); + + it('should build the document style sheet only once for all the shadow roots', () => { + const { addShadowDomStyles } = load(); + const first = createRoot([]); + const second = createRoot([createStyleSheetOf([{}])]); + const documentSheets = [createStyleSheetOf([{ selectorText: '.dx-button' }])]; + + addShadowDomStyles(createWrapper(first, documentSheets)); + addShadowDomStyles(createWrapper(second, [])); + + expect(second.adoptedStyleSheets?.[0]).toBe(first.adoptedStyleSheets?.[0]); + expect(getTexts((second.adoptedStyleSheets as FakeStyleSheet[])[0])).toEqual(['rule-0']); + }); + + it('should create a style element when the constructed style sheets are not supported', () => { + setStyleSheetConstructor(() => { + throw new Error('not supported'); + }); + + const { addShadowDomStyles } = load(); + const sheets: FakeStyleSheet[] = []; + const appended: unknown[] = []; + const root = { + host: {}, + styleSheets: [] as CSSStyleSheet[], + adoptedStyleSheets: undefined as unknown, + ownerDocument: { + createElement: (tag: string): { tag: string; sheet: FakeStyleSheet } => { + const sheet = new FakeStyleSheet(); + + sheets.push(sheet); + + return { tag, sheet }; + }, + }, + appendChild: (node: unknown): number => appended.push(node), + }; + + addShadowDomStyles(createWrapper(root)); + + expect(appended).toHaveLength(2); + expect(appended).toEqual([ + { tag: 'style', sheet: sheets[0] }, + { tag: 'style', sheet: sheets[1] }, + ]); + expect(root.adoptedStyleSheets).toEqual(sheets); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/core/utils/m_common.ts b/packages/devextreme/js/__internal/core/utils/m_common.ts index b2b248cf367f..ebb7ba771241 100644 --- a/packages/devextreme/js/__internal/core/utils/m_common.ts +++ b/packages/devextreme/js/__internal/core/utils/m_common.ts @@ -119,7 +119,7 @@ export const findBestMatches = ( each(items, (index, itemSrc) => { let matchCount = 0; - const item = mapFn ? mapFn(itemSrc) : itemSrc; + const item = (mapFn ? mapFn(itemSrc) : itemSrc) as Record; each(targetFilter, (paramName, targetValue) => { const value = item[paramName]; diff --git a/packages/devextreme/js/__internal/core/utils/m_data.ts b/packages/devextreme/js/__internal/core/utils/m_data.ts index 3e7754a9ebb3..73344077896c 100644 --- a/packages/devextreme/js/__internal/core/utils/m_data.ts +++ b/packages/devextreme/js/__internal/core/utils/m_data.ts @@ -37,7 +37,6 @@ const readPropValue = function (obj, propName, options) { const assignPropValue = function (obj, propName, value, options) { if (propName === 'this') { - // @ts-expect-error only void function can be called with new throw new errors.Error('E4016'); } diff --git a/packages/devextreme/js/__internal/core/utils/m_date_serialization.ts b/packages/devextreme/js/__internal/core/utils/m_date_serialization.ts index 1568d2ea2dc4..549b743da1a4 100644 --- a/packages/devextreme/js/__internal/core/utils/m_date_serialization.ts +++ b/packages/devextreme/js/__internal/core/utils/m_date_serialization.ts @@ -15,17 +15,7 @@ const DATE_SERIALIZATION_PATTERN = /^(\d{4})\/(\d{2})\/(\d{2})$/; const MILLISECOND_LENGHT = 3; -const dateParser = function (text, skipISO8601Parsing?: boolean) { - let result; - - if (isString(text) && !skipISO8601Parsing) { - result = parseISO8601String(text); - } - - return result || parseDate(text); -}; - -function getTimePart(part) { +function getTimePart(part: string): number { return +part || 0; } @@ -39,14 +29,78 @@ function isISOPartialDateString(text: string): boolean { return ISO_PARTIAL_DATE_PATTERN.test(text); } -function parseDate(text): string | Date { +const getIso8601Format = function getIso8601Format( + text: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + useUtc?: boolean, +): string | undefined { + let parts = ISO8601_PATTERN.exec(text); + let result = ''; + + if (!parts) { + parts = ISO8601_TIME_PATTERN.exec(text); + if (parts) { + return parts[3] ? 'HH:mm:ss' : 'HH:mm'; + } + return undefined; + } + + for (let i = 1; i < ISO8601_PATTERN_PARTS.length; i += 1) { + if (parts[i]) { + result += ISO8601_PATTERN_PARTS[i] || parts[i]; + } + } + + if (parts[12] === 'Z') { + result += '\'Z\''; + } + + if (parts[14]) { + if (parts[15]) { + result += 'xxx'; + } else if (parts[16]) { + result += 'xx'; + } else { + result += 'x'; + } + } + + return result; +}; + +const getDateSerializationFormat = function getDateSerializationFormat( + value: unknown, +): string | null | undefined { + if (typeof value === 'number') { + return NUMBER_SERIALIZATION_FORMAT; + } if (isString(value)) { + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in the if below + let format: string | undefined; + + if (config().forceIsoDateParsing) { + format = getIso8601Format(value); + } + if (format) { + return format; + } if (value.includes(':')) { + return DATETIME_SERIALIZATION_FORMAT; + } + return DATE_SERIALIZATION_FORMAT; + } if (value) { + return null; + } + + return undefined; +}; + +function parseDate(text: T): T | Date { const isDefaultSerializationFormat = getDateSerializationFormat(text) === DATE_SERIALIZATION_FORMAT; - const parsedValue = !isDate(text) && Date.parse(text); + const parsedValue = !isDate(text) && Date.parse(text as string); if (!parsedValue && isDefaultSerializationFormat) { - const parts = text.match(DATE_SERIALIZATION_PATTERN); + const parts = DATE_SERIALIZATION_PATTERN.exec(text as string); if (parts) { const newDate = new Date(getTimePart(parts[1]), getTimePart(parts[2]), getTimePart(parts[3])); @@ -63,30 +117,32 @@ function parseDate(text): string | Date { return text; } - return isISOPartialDateString(text) + return isISOPartialDateString(text as string) ? createLocalDateFromUTCTimestamp(parsedValue) : new Date(parsedValue); } -function parseISO8601String(text) { - let parts = text.match(ISO8601_PATTERN); +function parseISO8601String(text: string): Date | undefined { + let parts = ISO8601_PATTERN.exec(text); if (!parts) { - parts = text.match(ISO8601_TIME_PATTERN); + parts = ISO8601_TIME_PATTERN.exec(text); if (parts) { return new Date(0, 0, 0, getTimePart(parts[1]), getTimePart(parts[2]), getTimePart(parts[4])); } - return; + return undefined; } const year = getTimePart(parts[1]); - const month = --parts[3]; - const day = parts[5]; + const month = Number(parts[3]) - 1; + const day = Number(parts[5]); let timeZoneHour = 0; let timeZoneMinute = 0; - const correctYear = (d) => { - year < 100 && d.setFullYear(year); + const correctYear = (d: Date): Date => { + if (year < 100) { + d.setFullYear(year); + } return d; }; @@ -101,8 +157,7 @@ function parseISO8601String(text) { const hour = getTimePart(parts[6]) - timeZoneHour; const minute = getTimePart(parts[8]) - timeZoneMinute; const second = getTimePart(parts[10]); - const parseMilliseconds = function (part) { - part = part || ''; + const parseMilliseconds = function parseMilliseconds(part = ''): number { return getTimePart(part) * 10 ** (MILLISECOND_LENGHT - part.length); }; const millisecond = parseMilliseconds(parts[11]); @@ -114,51 +169,29 @@ function parseISO8601String(text) { return correctYear(new Date(year, month, day, hour, minute, second, millisecond)); } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const getIso8601Format = function (text, useUtc?) { - let parts = text.match(ISO8601_PATTERN); - let result = ''; - - if (!parts) { - parts = text.match(ISO8601_TIME_PATTERN); - if (parts) { - return parts[3] ? 'HH:mm:ss' : 'HH:mm'; - } - return; - } - - for (let i = 1; i < ISO8601_PATTERN_PARTS.length; i++) { - if (parts[i]) { - result += ISO8601_PATTERN_PARTS[i] || parts[i]; - } - } +const dateParser = function dateParser(text: T, skipISO8601Parsing?: boolean): T | Date { + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in the if below + let result: Date | undefined; - if (parts[12] === 'Z') { - result += '\'Z\''; - } - - if (parts[14]) { - if (parts[15]) { - result += 'xxx'; - } else if (parts[16]) { - result += 'xx'; - } else { - result += 'x'; - } + if (isString(text) && !skipISO8601Parsing) { + result = parseISO8601String(text); } - return result; + return result ?? parseDate(text); }; -const deserializeDate = function (value) { +const deserializeDate = function deserializeDate(value: T): Date | Exclude { if (typeof value === 'number') { return new Date(value); } - return dateParser(value, !config().forceIsoDateParsing); + return dateParser(value, !config().forceIsoDateParsing) as Date | Exclude; }; -const serializeDate = function (value, serializationFormat) { +const serializeDate = function serializeDate( + value: T, + serializationFormat?: string | null, +): T | number | string | null { if (!serializationFormat) { return value; } @@ -168,32 +201,12 @@ const serializeDate = function (value, serializationFormat) { } if (serializationFormat === NUMBER_SERIALIZATION_FORMAT) { - return value && value.valueOf ? value.valueOf() : null; + return value?.valueOf ? value.valueOf() : null; } return getLDMLFormatter(serializationFormat, defaultDateNames)(value); }; -const getDateSerializationFormat = function (value) { - if (typeof value === 'number') { - return NUMBER_SERIALIZATION_FORMAT; - } if (isString(value)) { - let format; - - if (config().forceIsoDateParsing) { - format = getIso8601Format(value); - } - if (format) { - return format; - } if (value.includes(':')) { - return DATETIME_SERIALIZATION_FORMAT; - } - return DATE_SERIALIZATION_FORMAT; - } if (value) { - return null; - } -}; - const dateSerialization = { createLocalDateFromUTCTimestamp, dateParser, diff --git a/packages/devextreme/js/__internal/core/utils/m_error.ts b/packages/devextreme/js/__internal/core/utils/m_error.ts index a9b0b5867145..f4776e518a53 100644 --- a/packages/devextreme/js/__internal/core/utils/m_error.ts +++ b/packages/devextreme/js/__internal/core/utils/m_error.ts @@ -12,49 +12,44 @@ export interface DxError extends Error { url: string; } -function error(baseErrors, errors?) { - const exports = { +type ErrorMessages = Record; - ERROR_MESSAGES: extend(errors, baseErrors), - - Error: function (...args) { - return makeError(args); - }, - - log(...args) { - const id = args[0]; - let method = 'log'; - - if (/^E\d+$/.test(id)) { - method = 'error'; - } else if (/^W\d+$/.test(id)) { - method = 'warn'; - } +interface ErrorFactory { + (id: string, ...details: unknown[]): DxError; + new (id: string, ...details: unknown[]): DxError; +} - consoleUtils.logger[method](method === 'log' ? id : combineMessage(args)); - }, - }; +interface ErrorUtils { + ERROR_MESSAGES: ErrorMessages; + Error: ErrorFactory; + log: (id: string, ...details: unknown[]) => void; +} - function combineMessage(args) { - const id = args[0]; - args = args.slice(1); - return formatMessage(id, formatDetails(id, args)); +function error(baseErrors: ErrorMessages, errors?: ErrorMessages): ErrorUtils { + function getErrorUrl(id: string): string { + return ERROR_URL + id; } - function formatDetails(id, args) { - args = [exports.ERROR_MESSAGES[id]].concat(args); - return format.apply(this, args).replace(/\.*\s*?$/, ''); + function formatDetails(id: string, args: unknown[]): string { + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- exports comes below + const formatArgs: [unknown, ...unknown[]] = [exports.ERROR_MESSAGES[id], ...args]; + return format.apply(this, formatArgs).replace(/\.*\s*?$/, ''); } - function formatMessage(id, details) { + function formatMessage(id: string, details: string): string { const kind = id?.startsWith('W') ? 'warning' : 'error'; return format.apply(this, ['{0} - {1}.\n\nFor additional information on this {2} message, see: {3}', id, details, kind, getErrorUrl(id)]); } - function makeError(args) { - const id = args[0]; - args = args.slice(1); - const details = formatDetails(id, args); + function combineMessage(args: unknown[]): string { + const id = args[0] as string; + const details = args.slice(1); + return formatMessage(id, formatDetails(id, details)); + } + + function makeError(args: unknown[]): DxError { + const id = args[0] as string; + const details = formatDetails(id, args.slice(1)); const url = getErrorUrl(id); const message = formatMessage(id, details); @@ -65,9 +60,27 @@ function error(baseErrors, errors?) { }); } - function getErrorUrl(id) { - return ERROR_URL + id; - } + const exports: ErrorUtils = { + + ERROR_MESSAGES: extend(errors, baseErrors), + + Error: function Error(...args: unknown[]): DxError { + return makeError(args); + } as unknown as ErrorFactory, + + log(...args: unknown[]): void { + const id = args[0] as string; + let method: 'log' | 'error' | 'warn' = 'log'; + + if (/^E\d+$/.test(id)) { + method = 'error'; + } else if (/^W\d+$/.test(id)) { + method = 'warn'; + } + + consoleUtils.logger[method](method === 'log' ? id : combineMessage(args)); + }, + }; return exports; } diff --git a/packages/devextreme/js/__internal/core/utils/m_extend.ts b/packages/devextreme/js/__internal/core/utils/m_extend.ts index cfbfe04debc7..225d2c020073 100644 --- a/packages/devextreme/js/__internal/core/utils/m_extend.ts +++ b/packages/devextreme/js/__internal/core/utils/m_extend.ts @@ -1,49 +1,102 @@ import { isPlainObject } from '@js/core/utils/type'; -export const extendFromObject = function (target, source, overrideExistingValues) { - target = target || {}; +type Dictionary = Record; + +export const extendFromObject = function extendFromObject( + target: Dictionary | null | undefined, + source: Dictionary | null | undefined, + overrideExistingValues?: boolean, +): Dictionary { + const result = target || {}; + // eslint-disable-next-line no-restricted-syntax -- the own keys are checked in the loop for (const prop in source) { if (Object.prototype.hasOwnProperty.call(source, prop)) { const value = source[prop]; - if (!(prop in target) || overrideExistingValues) { - target[prop] = value; + if (!(prop in result) || overrideExistingValues) { + result[prop] = value; } } } - return target; + return result; }; -export const extend: any = function (target) { - target = target || {}; +// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- a void source is skipped +type EmptySource = null | undefined | void; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- an any source gives an any result +type MergedSource = 0 extends 1 & T ? any : [T] extends [EmptySource] + ? unknown + : [Extract] extends [never] + ? NonNullable + : Partial>; + +type Merged = TSources extends readonly [ + infer THead, + ...infer TTail, +] + ? MergedSource & Merged + : unknown; + +interface Extend { + ( + target: TTarget, + ...sources: TSources + ): TTarget & Merged; + ( + deep: true, + target: TTarget, + ...sources: TSources + ): TTarget & Merged; + ( + deep: false | null | undefined, + ...sources: TSources + ): Merged; + ( + deep: boolean, + target: null | undefined, + ...sources: TSources + ): Merged; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the callers are not typed + (...args: any[]): any; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- the overloads type the result +export const extend: Extend = function extend(...args: unknown[]): any { + let target = (args[0] || {}) as Dictionary | boolean; let i = 1; let deep = false; if (typeof target === 'boolean') { deep = target; - target = arguments[1] || {}; - i++; + target = (args[1] || {}) as Dictionary; + i += 1; } - for (; i < arguments.length; i++) { - const source = arguments[i]; + for (; i < args.length; i += 1) { + const source = args[i] as Dictionary | null | undefined; if (source == null) { + // eslint-disable-next-line no-continue -- the sources that are not set are skipped continue; } + // eslint-disable-next-line no-restricted-syntax, guard-for-in -- inherited keys are copied too for (const key in source) { const targetValue = target[key]; const sourceValue = source[key]; let sourceValueIsArray = false; - let clone; + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in the branches + let clone: unknown; if (key === '__proto__' || key === 'constructor' || target === sourceValue) { + // eslint-disable-next-line no-continue -- the keys that are not copied are skipped continue; } if (deep && sourceValue && (isPlainObject(sourceValue) // eslint-disable-next-line no-cond-assign || (sourceValueIsArray = Array.isArray(sourceValue)))) { + // eslint-disable-next-line max-depth -- the branch of the deep copy if (sourceValueIsArray) { clone = targetValue && Array.isArray(targetValue) ? targetValue : []; } else { diff --git a/packages/devextreme/js/__internal/core/utils/m_iterator.ts b/packages/devextreme/js/__internal/core/utils/m_iterator.ts index 6b6b17d5e989..f8fb0e69ebfb 100644 --- a/packages/devextreme/js/__internal/core/utils/m_iterator.ts +++ b/packages/devextreme/js/__internal/core/utils/m_iterator.ts @@ -1,10 +1,50 @@ -const map = (values, callback): any[] => { +type EachCallback = (this: TThis, key: TKey, value: TValue) => unknown; + +interface Each { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- an empty literal is never[] + (values: never[], callback: EachCallback): never[]; + (values: T[], callback: EachCallback): T[]; + (values: readonly T[], callback: EachCallback): readonly T[]; + (values: T[] | null | undefined, callback: EachCallback): T[] | undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the type of {} has no T + (values: Record, callback: EachCallback): Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the callers are not typed + (values: any, callback: EachCallback): any; +} + +interface ReverseEach { + (array: ArrayLike | null | undefined, callback: EachCallback): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the callers are not typed + (array: any, callback: EachCallback): void; +} + +interface MapValues { + ( + values: never[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- an empty literal is never[] + callback: (value: any, index: number, array: never[]) => TResult, + ): TResult[]; + ( + values: readonly T[], + callback: (value: T, index: number, array: readonly T[]) => TResult, + ): TResult[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the type of {} has no T + ( + values: Record, + callback: (value: T, key: string) => TResult, + ): TResult[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- the callers are not typed + (values: any, callback: (value: any, key: any, array?: any) => any): any[]; +} + +const map: MapValues = (values, callback) => { if (Array.isArray(values)) { return values.map(callback); } const result: unknown[] = []; + // eslint-disable-next-line no-restricted-syntax, guard-for-in -- inherited keys are mapped too for (const key in values) { result.push(callback(values[key], key)); } @@ -12,17 +52,11 @@ const map = (values, callback): any[] => { return result; }; -/** - * @type {{ - * (values: readonly T[], callback: (this: T, index: number, value: T) => void | boolean): T[], - * (values: T, callback: (this: T[keyof T], index: keyof T, value: T[keyof T]) => void | boolean): T, - * }} - */ -const each = (values, callback) => { - if (!values) return; +const each: Each = (values, callback) => { + if (!values) return undefined; if ('length' in values) { - for (let i = 0; i < values.length; i++) { + for (let i = 0; i < values.length; i += 1) { if (callback.call(values[i], i, values[i]) === false) { break; } @@ -36,13 +70,14 @@ const each = (values, callback) => { } } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- the callers are not typed return values; }; -const reverseEach = (array, callback) => { +const reverseEach: ReverseEach = (array, callback) => { if (!array || !('length' in array) || array.length === 0) return; - for (let i = array.length - 1; i >= 0; i--) { + for (let i = array.length - 1; i >= 0; i -= 1) { if (callback.call(array[i], i, array[i]) === false) { break; } diff --git a/packages/devextreme/js/__internal/core/utils/m_selection_filter.ts b/packages/devextreme/js/__internal/core/utils/m_selection_filter.ts index 3f799971d57f..8be7b13d2b52 100644 --- a/packages/devextreme/js/__internal/core/utils/m_selection_filter.ts +++ b/packages/devextreme/js/__internal/core/utils/m_selection_filter.ts @@ -2,86 +2,67 @@ import { equalByValue, getKeyHash } from '@js/core/utils/common'; import { compileGetter } from '@js/core/utils/data'; import { isFunction, isObject, isString } from '@js/core/utils/type'; -export const SelectionFilterCreator = function (selectedItemKeys, isSelectAll?: boolean) { - this.getLocalFilter = function (keyGetter, equalKeys, equalByReference, keyExpr) { - equalKeys = equalKeys === undefined ? equalByValue : equalKeys; - return functionFilter.bind(this, equalKeys, keyGetter, equalByReference, keyExpr); - }; - - this.getExpr = function (keyExpr) { - if (!keyExpr) { - return; - } - - let filterExpr; - - selectedItemKeys.forEach(function (key, index) { - filterExpr = filterExpr || []; - - let filterExprPart; - - if (index > 0) { - filterExpr.push(isSelectAll ? 'and' : 'or'); - } - - if (isString(keyExpr) || isFunction(keyExpr)) { - filterExprPart = getFilterForPlainKey(keyExpr, key); - } else { - filterExprPart = getFilterForCompositeKey(keyExpr, key); - } - - filterExpr.push(filterExprPart); - }); - - if (filterExpr && filterExpr.length === 1) { - // eslint-disable-next-line prefer-destructuring - filterExpr = filterExpr[0]; - } - - return filterExpr; - }; - - this.getCombinedFilter = function (keyExpr, dataSourceFilter, forceCombinedFilter = false) { - const filterExpr = this.getExpr(keyExpr); - let combinedFilter = filterExpr; - - if ((forceCombinedFilter || isSelectAll) && dataSourceFilter) { - if (filterExpr) { - combinedFilter = []; - combinedFilter.push(filterExpr); - combinedFilter.push(dataSourceFilter); - } else { - combinedFilter = dataSourceFilter; - } - } - - return combinedFilter; +type KeyGetter = (item: unknown) => unknown; +type EqualKeys = (key1: unknown, key2: unknown) => boolean; +type PlainKeyExpression = string | KeyGetter; +type KeyExpression = PlainKeyExpression | string[]; + +interface SelectionFilter { + getLocalFilter: ( + keyGetter: KeyGetter, + equalKeys?: EqualKeys, + equalByReference?: boolean, + keyExpr?: KeyExpression, + ) => (item: unknown) => boolean; + getExpr: (keyExpr?: KeyExpression) => unknown[] | undefined; + getCombinedFilter: ( + keyExpr?: KeyExpression, + dataSourceFilter?: unknown, + forceCombinedFilter?: boolean, + ) => unknown; +} + +export const SelectionFilterCreator = function SelectionFilterCreator( + this: SelectionFilter, + selectedItemKeys: readonly unknown[], + isSelectAll?: boolean, +): void { + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned when first needed + let selectedItemKeyHashesMap: Record | undefined; + + const normalizeKeys = function normalizeKeys( + keys: readonly unknown[], + keyOf: KeyGetter, + keyExpr?: KeyExpression, + ): readonly unknown[] { + return Array.isArray(keyExpr) ? keys.map((key) => keyOf(key)) : keys; }; - let selectedItemKeyHashesMap; - - const getSelectedItemKeyHashesMap = function (keyOf, keyExpr) { + const getSelectedItemKeyHashesMap = function getSelectedItemKeyHashesMap( + keyOf: KeyGetter, + keyExpr?: KeyExpression, + ): Record { if (!selectedItemKeyHashesMap) { selectedItemKeyHashesMap = {}; const normalizedKeys = normalizeKeys(selectedItemKeys, keyOf, keyExpr); - for (let i = 0; i < normalizedKeys.length; i++) { - selectedItemKeyHashesMap[getKeyHash(normalizedKeys[i])] = true; + for (const normalizedKey of normalizedKeys) { + selectedItemKeyHashesMap[getKeyHash(normalizedKey)] = true; } } return selectedItemKeyHashesMap; }; - const normalizeKeys = function (keys, keyOf, keyExpr) { - return Array.isArray(keyExpr) ? keys.map((key) => keyOf(key)) : keys; - }; - - function functionFilter(equalKeys, keyOf, equalByReference, keyExpr, item) { + function functionFilter( + equalKeys: EqualKeys, + keyOf: KeyGetter, + equalByReference: boolean | undefined, + keyExpr: KeyExpression | undefined, + item: unknown, + ): boolean { const key = keyOf(item); - let keyHash; - let i; if (!equalByReference) { - keyHash = getKeyHash(key); + const keyHash = getKeyHash(key); if (!isObject(keyHash)) { const selectedKeyHashesMap = getSelectedItemKeyHashesMap(keyOf, keyExpr); if (selectedKeyHashesMap[keyHash]) { @@ -91,25 +72,28 @@ export const SelectionFilterCreator = function (selectedItemKeys, isSelectAll?: } } - for (i = 0; i < selectedItemKeys.length; i++) { - if (equalKeys(selectedItemKeys[i], key)) { + for (const selectedItemKey of selectedItemKeys) { + if (equalKeys(selectedItemKey, key)) { return !isSelectAll; } } return !!isSelectAll; } - function getFilterForPlainKey(keyExpr, keyValue) { + function getFilterForPlainKey( + keyExpr: PlainKeyExpression, + keyValue: unknown, + ): unknown[] | undefined { if (keyValue === undefined) { - return; + return undefined; } return [keyExpr, isSelectAll ? '<>' : '=', keyValue]; } - function getFilterForCompositeKey(keyExpr, itemKeyValue) { - const filterExpr: any[] = []; + function getFilterForCompositeKey(keyExpr: string[], itemKeyValue: unknown): unknown[] { + const filterExpr: unknown[] = []; - for (let i = 0, { length } = keyExpr; i < length; i++) { + for (let i = 0, { length } = keyExpr; i < length; i += 1) { const currentKeyExpr = keyExpr[i]; const keyValueGetter = compileGetter(currentKeyExpr); // @ts-expect-error keyValueGetter is unknown @@ -129,4 +113,66 @@ export const SelectionFilterCreator = function (selectedItemKeys, isSelectAll?: return filterExpr; } + + this.getLocalFilter = function getLocalFilter( + keyGetter: KeyGetter, + equalKeys?: EqualKeys, + equalByReference?: boolean, + keyExpr?: KeyExpression, + ): (item: unknown) => boolean { + const equalKeysFunction = equalKeys === undefined ? equalByValue : equalKeys; + return functionFilter.bind(this, equalKeysFunction, keyGetter, equalByReference, keyExpr); + }; + + this.getExpr = function getExpr(keyExpr?: KeyExpression): unknown[] | undefined { + if (!keyExpr) { + return undefined; + } + + let filterExpr = undefined as unknown[] | undefined; + + selectedItemKeys.forEach((key, index) => { + filterExpr = filterExpr || []; + + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned in the branches + let filterExprPart: unknown[] | undefined; + + if (index > 0) { + filterExpr.push(isSelectAll ? 'and' : 'or'); + } + + if (isString(keyExpr) || isFunction(keyExpr)) { + filterExprPart = getFilterForPlainKey(keyExpr, key); + } else { + filterExprPart = getFilterForCompositeKey(keyExpr, key); + } + + filterExpr.push(filterExprPart); + }); + + if (filterExpr?.length === 1) { + filterExpr = filterExpr[0] as unknown[] | undefined; + } + + return filterExpr; + }; + + this.getCombinedFilter = function getCombinedFilter( + keyExpr?: KeyExpression, + dataSourceFilter?: unknown, + forceCombinedFilter = false, + ): unknown { + const filterExpr = this.getExpr(keyExpr); + let combinedFilter: unknown = filterExpr; + + if ((forceCombinedFilter || isSelectAll) && dataSourceFilter) { + if (filterExpr) { + combinedFilter = [filterExpr, dataSourceFilter]; + } else { + combinedFilter = dataSourceFilter; + } + } + + return combinedFilter; + }; }; diff --git a/packages/devextreme/js/__internal/core/utils/m_shadow_dom.ts b/packages/devextreme/js/__internal/core/utils/m_shadow_dom.ts index df2d3a43dc71..ad4f88a68386 100644 --- a/packages/devextreme/js/__internal/core/utils/m_shadow_dom.ts +++ b/packages/devextreme/js/__internal/core/utils/m_shadow_dom.ts @@ -1,10 +1,26 @@ import config from '@js/core/config'; +import type { dxElementWrapper } from '@js/core/renderer'; const DX_RULE_PREFIX = 'dx-'; -let ownerDocumentStyleSheet = null; +interface RuleLike { + cssText: string; + selectorText?: string; + cssRules?: ArrayLike; + name?: string; + style?: { fontFamily?: string }; +} + +interface Queue { + push: (this: Queue, item: T) => Queue; + shift: () => T; + readonly length: number; + readonly items: T[]; +} -function createConstructedStyleSheet(rootNode) { +let ownerDocumentStyleSheet: CSSStyleSheet | null = null; + +function createConstructedStyleSheet(rootNode: ShadowRoot): CSSStyleSheet { try { return new CSSStyleSheet(); } catch (err) { @@ -12,24 +28,16 @@ function createConstructedStyleSheet(rootNode) { rootNode.appendChild(styleElement); + // @ts-expect-error the sheet is null when the root is not connected return styleElement.sheet; } } -function processRules(targetStyleSheet, styleSheets, needApplyAllStyles) { - for (let i = 0; i < styleSheets.length; i++) { - const sheet = styleSheets[i]; - try { - for (let j = 0; j < sheet.cssRules.length; j++) { - insertRule(targetStyleSheet, sheet.cssRules[j], needApplyAllStyles); - } - } catch (err) { - // NOTE: need try/catch block for not-supported cross-domain css - } - } -} - -function insertRule(targetStyleSheet, rule, needApplyAllStyles) { +function insertRule( + targetStyleSheet: CSSStyleSheet, + rule: RuleLike, + needApplyAllStyles: boolean, +): void { const isDxRule = needApplyAllStyles || rule.selectorText?.includes(DX_RULE_PREFIX) || rule.cssRules?.[0]?.selectorText?.includes(DX_RULE_PREFIX) @@ -44,14 +52,33 @@ function insertRule(targetStyleSheet, rule, needApplyAllStyles) { } } +function processRules( + targetStyleSheet: CSSStyleSheet, + styleSheets: Iterable, + needApplyAllStyles: boolean, +): void { + for (const sheet of styleSheets) { + try { + for (const rule of sheet.cssRules) { + insertRule(targetStyleSheet, rule, needApplyAllStyles); + } + } catch (err) { + // NOTE: need try/catch block for not-supported cross-domain css + } + } +} + const FNV_OFFSET_BASIS = 2166136261; -const sheetHashes = new WeakMap(); -export function computeStyleSheetsHash(styleSheets) { +const sheetHashes = new WeakMap(); +export function computeStyleSheetsHash(styleSheets: Iterable): number { let hash = FNV_OFFSET_BASIS; for (const sheet of styleSheets) { - if (sheetHashes.has(sheet)) { - hash ^= sheetHashes.get(sheet); + const cachedHash = sheetHashes.get(sheet); + if (cachedHash !== undefined) { + // eslint-disable-next-line no-bitwise -- FNV hash + hash ^= cachedHash; + // eslint-disable-next-line no-continue -- the cached sheet is done continue; } @@ -59,32 +86,40 @@ export function computeStyleSheetsHash(styleSheets) { try { for (const rule of sheet.cssRules) { const text = rule.cssText; - for (let i = 0; i < text.length; i++) { + // eslint-disable-next-line max-depth -- the loop over the characters of a rule + for (let i = 0; i < text.length; i += 1) { + // eslint-disable-next-line no-bitwise -- FNV hash localHash ^= text.charCodeAt(i); - localHash += (localHash << 1) + (localHash << 4) + (localHash << 7) + (localHash << 8) + (localHash << 24); + // eslint-disable-next-line no-bitwise -- FNV hash + localHash += (localHash << 1) + (localHash << 4) + (localHash << 7) + (localHash << 8) + // eslint-disable-next-line no-bitwise -- FNV hash + + (localHash << 24); } } } catch (_) { // ignore } + // eslint-disable-next-line no-bitwise -- FNV hash localHash >>>= 0; sheetHashes.set(sheet, localHash); + // eslint-disable-next-line no-bitwise -- FNV hash hash ^= localHash; } + // eslint-disable-next-line no-bitwise -- FNV hash return hash >>> 0; } -const styleSheetHashes = new WeakMap(); +const styleSheetHashes = new WeakMap(); -export function addShadowDomStyles($element) { +export function addShadowDomStyles($element: dxElementWrapper): void { if (!config().copyStylesToShadowDom) { return; } const el = $element.get(0); - const root = el.getRootNode?.(); + const root = el.getRootNode?.() as ShadowRoot | undefined; if (!root?.host) return; if (!ownerDocumentStyleSheet) { @@ -103,49 +138,48 @@ export function addShadowDomStyles($element) { root.adoptedStyleSheets = [ownerDocumentStyleSheet, currentShadowDomStyleSheet]; } -function isPositionInElementRectangle(element, x, y) { +function isPositionInElementRectangle(element: Element, x: number, y: number): boolean { const rect = element.getBoundingClientRect?.(); return rect && x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom; } -function createQueue() { +function createQueue(): Queue { let shiftIndex = 0; - const items: any[] = []; + const items: T[] = []; return { - push(item) { + push(this: Queue, item: T): Queue { items.push(item); return this; }, - shift() { - shiftIndex++; + shift(): T { + shiftIndex += 1; return items[shiftIndex - 1]; }, - get length() { + get length(): number { return items.length - shiftIndex; }, - get items() { + get items(): T[] { return items; }, }; } -export function getShadowElementsFromPoint(x, y, root) { - const elementQueue = createQueue().push(root); +export function getShadowElementsFromPoint(x: number, y: number, root: Node): Node[] { + const elementQueue = createQueue().push(root); while (elementQueue.length) { const el = elementQueue.shift(); - for (let i = 0; i < el.childNodes.length; i++) { - const childNode = el.childNodes[i]; + for (const childNode of el.childNodes) { if (childNode.nodeType === Node.ELEMENT_NODE - && isPositionInElementRectangle(childNode, x, y) + && isPositionInElementRectangle(childNode as Element, x, y) - && getComputedStyle(childNode).pointerEvents !== 'none' + && getComputedStyle(childNode as Element).pointerEvents !== 'none' ) { elementQueue.push(childNode); } diff --git a/packages/devextreme/js/__internal/core/widget/component.ts b/packages/devextreme/js/__internal/core/widget/component.ts index 49cc587a60b2..1125609a365d 100644 --- a/packages/devextreme/js/__internal/core/widget/component.ts +++ b/packages/devextreme/js/__internal/core/widget/component.ts @@ -21,6 +21,7 @@ import { isDefined, isFunction, isPlainObject, isString, } from '@js/core/utils/type'; import type { DxEvent, EventInfo, InitializedEventInfo } from '@js/events'; +import type { ActionEvent } from '@ts/core/action'; import type { DeprecatedOptionInfo } from '@ts/core/options/index'; import type { IntegrationOptions } from '@ts/core/template_manager'; import type { OptionChanged } from '@ts/core/widget/types'; @@ -34,8 +35,8 @@ const isInnerOption = ( ): boolean => optionName.indexOf('_', 0) === 0; export interface ActionConfig { - beforeExecute?: (e: Record) => void; - afterExecute?: (e: Record) => void; + beforeExecute?: (e: ActionEvent) => void; + afterExecute?: (e: ActionEvent) => void; excludeValidators?: ('disabled' | 'readOnly')[]; element?: Element; validatingTargetName?: string; diff --git a/packages/devextreme/js/__internal/core/widget/dom_component.ts b/packages/devextreme/js/__internal/core/widget/dom_component.ts index 9b12bf48847f..962df04ca7d0 100644 --- a/packages/devextreme/js/__internal/core/widget/dom_component.ts +++ b/packages/devextreme/js/__internal/core/widget/dom_component.ts @@ -77,7 +77,6 @@ class DOMComponent< } _getDefaultOptions(): TProperties { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return extend(super._getDefaultOptions(), { width: undefined, height: undefined, @@ -208,10 +207,9 @@ class DOMComponent< const classNames = attributes.class; delete attributes.class; - // @ts-expect-error this.$element() + // @ts-expect-error attr has no overload for an object of attributes .attr(attributes) - // @ts-expect-error .removeClass(this._customClass) .addClass(classNames); @@ -350,14 +348,14 @@ class DOMComponent< const configuration = componentConfiguration ?? {}; const synchronizableOptions = this._getSynchronizableOptionsForCreateComponent() - .filter((value) => !(value in configuration)) as (keyof TProperties)[]; + .filter((value) => !(value in configuration)) as (keyof TProperties & string)[]; const { integrationOptions } = this.option(); let { nestedComponentOptions } = this.option(); nestedComponentOptions = nestedComponentOptions ?? noop; - const nestedComponentConfig = extend( + const nestedComponentConfig: Record = extend( { integrationOptions }, nestedComponentOptions(this as unknown as TComponent), ); diff --git a/packages/devextreme/js/__internal/data/data_source/data_source.ts b/packages/devextreme/js/__internal/data/data_source/data_source.ts index 1f1cc8ee8fec..70dadd15b5e5 100644 --- a/packages/devextreme/js/__internal/data/data_source/data_source.ts +++ b/packages/devextreme/js/__internal/data/data_source/data_source.ts @@ -688,6 +688,7 @@ export class DataSource { } // Process result + // @ts-expect-error extra is not always an object, _processStoreLoadResult makes it one const loadResult: LoadResult = extend(normalizeLoadResult(data, extra), loadOptions); this._eventsStrategy.fireEvent('customizeLoadResult', [loadResult]); diff --git a/packages/devextreme/js/__internal/data/errors.ts b/packages/devextreme/js/__internal/data/errors.ts index 076821979898..4dd00371dd14 100644 --- a/packages/devextreme/js/__internal/data/errors.ts +++ b/packages/devextreme/js/__internal/data/errors.ts @@ -69,7 +69,7 @@ export let errorHandler: DataErrorHandler | null = null; export const handleError = function (error: unknown): void { /// #DEBUG const id = isObject(error) && '__id' in error ? error.__id : 'E4000'; - errors.log(id, error); + errors.log(id as string, error); /// #ENDDEBUG errorHandler?.(error); }; diff --git a/packages/devextreme/js/__internal/exporter/exceljs/export_format.ts b/packages/devextreme/js/__internal/exporter/exceljs/export_format.ts index 5eacc2b88b54..9c5ebfb088f5 100644 --- a/packages/devextreme/js/__internal/exporter/exceljs/export_format.ts +++ b/packages/devextreme/js/__internal/exporter/exceljs/export_format.ts @@ -166,8 +166,15 @@ function _includesCSVExpression(value) { return !isNumeric(value); } +interface ConvertedFormat { + format: unknown; + precision: unknown; + dataType: unknown; + currency?: unknown; +} + export const ExportFormat = { - formatObjectConverter(format, dataType) { + formatObjectConverter(format, dataType): ConvertedFormat { const result = { format, precision: format?.precision, diff --git a/packages/devextreme/js/__internal/filter_builder/filter_builder.ts b/packages/devextreme/js/__internal/filter_builder/filter_builder.ts index 55db80f88f78..1c5f42ee766b 100644 --- a/packages/devextreme/js/__internal/filter_builder/filter_builder.ts +++ b/packages/devextreme/js/__internal/filter_builder/filter_builder.ts @@ -18,6 +18,7 @@ import TreeView, { type ContentReadyEvent, type ItemClickEvent, type ItemRenderedEvent, type Properties as TreeViewProperties, } from '@js/ui/tree_view'; +import type { ActionConfig } from '@ts/core/widget/component'; import type { OptionChanged } from '@ts/core/widget/types'; import Widget from '@ts/core/widget/widget'; import { getElementMaxHeightByWindow } from '@ts/ui/overlay/utils'; @@ -78,7 +79,7 @@ const TAB_KEY = 'tab'; const ENTER_KEY = 'enter'; const ESCAPE_KEY = 'escape'; -const ACTIONS = [{ +const ACTIONS: { name: string; config: ActionConfig }[] = [{ name: 'onEditorPreparing', config: { excludeValidators: ['disabled', 'readOnly'], category: 'rendering' }, }, { diff --git a/packages/devextreme/js/__internal/filter_builder/utils.ts b/packages/devextreme/js/__internal/filter_builder/utils.ts index 0dc038cf9eb8..a8d3efe33fad 100644 --- a/packages/devextreme/js/__internal/filter_builder/utils.ts +++ b/packages/devextreme/js/__internal/filter_builder/utils.ts @@ -306,7 +306,6 @@ export function getOperationFromAvailable( return availableOperation; } } - // @ts-expect-error wrong usage of new throw new errors.Error('E1048', operation); } @@ -553,7 +552,6 @@ export function getField(dataField: string, fields: FilterBuilderField[]): Filte if (extendedFields.length > 0) { return extendedFields[0]; } - // @ts-expect-error wrong usage of new throw new errors.Error('E1047', dataField); } diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts index 76b59be13499..3be8516b7974 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts @@ -387,7 +387,7 @@ export const GroupingHeaderPanelExtender = ( return this._columnsController.getGroupColumns(); } - protected getBoundingRect() { + public getBoundingRect() { const that = this; const $element = that.element(); @@ -458,7 +458,7 @@ export const GroupingHeaderPanelExtender = ( }; export const GroupingRowsViewExtender = (Base: ModuleType) => class GroupingRowsViewExtender extends Base { - private getContextMenuItems(options) { + public getContextMenuItems(options) { const that = this; const contextMenuEnabled = that.option('grouping.contextMenuEnabled'); let items; diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts index f092e42405af..ab837ddf6760 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts @@ -151,7 +151,7 @@ const sortGroupsBySummary = function (data, group, summary) { }; export class FooterView extends ColumnsView { - protected _getRows() { + public _getRows() { // @ts-expect-error return this._dataController.footerItems(); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_rows_view.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_rows_view.ts index f2de75420dc9..fda221bd5293 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_rows_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_rows_view.ts @@ -105,7 +105,7 @@ export const adaptivityRowsViewExtender = ( return super._getCellElement.apply(this, arguments as any); } - private getContextMenuItems(options) { + public getContextMenuItems(options) { if (options.row?.rowType === 'detailAdaptive') { const view = this._columnHeadersView; const formItem = $(options.targetElement).closest('.dx-field-item-label').next().data('dx-form-item'); @@ -113,7 +113,6 @@ export const adaptivityRowsViewExtender = ( options.column = formItem ? formItem.column : options.column; return view.getContextMenuItems?.(options); } - // @ts-expect-error return super.getContextMenuItems?.(options); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts index da95dbcf510b..b19fa2107497 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/m_adaptivity.ts @@ -13,7 +13,7 @@ import { getWidth } from '@js/core/utils/size'; import { isDefined, isString } from '@js/core/utils/type'; import Form from '@js/ui/form'; import { isMaterial } from '@js/ui/themes'; -import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { Column, ProcessedColumn } from '@ts/grids/grid_core/columns_controller/types'; import { AI_COLUMN_NAME } from '../ai_column/const'; import type { ColumnsController } from '../columns_controller/columns_controller'; @@ -380,7 +380,7 @@ export class AdaptiveColumnsController extends modules.ViewController { const columns = that._columnsController.getVisibleColumns(); let colWidth = 0; - each(columns, (index, column) => { + each(columns as ProcessedColumn[], (index, column) => { if (column.index < 0 || column.command) { colWidth += that._columnsController.columnOption(getColumnId(that, column), 'bestFitWidth') || 0; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.ai_data.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.ai_data.test.ts index 198fac354460..bc3035b575cb 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.ai_data.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.ai_data.test.ts @@ -31,7 +31,7 @@ interface RequestResult { const beforeTest = (): void => { baseBeforeTest(); jest.spyOn(errors, 'log').mockImplementation(jest.fn()); - jest.spyOn(errors, 'Error').mockImplementation(() => ({})); + jest.spyOn(errors, 'Error').mockImplementation(() => ({}) as ReturnType); }; describe('AI data', () => { beforeEach(beforeTest); diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.api_handlers.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.api_handlers.test.ts index 3e945866de08..ffd060f2eeb6 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.api_handlers.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.api_handlers.test.ts @@ -21,7 +21,7 @@ interface RequestResult { const beforeTest = (): void => { baseBeforeTest(); jest.spyOn(errors, 'log').mockImplementation(jest.fn()); - jest.spyOn(errors, 'Error').mockImplementation(() => ({})); + jest.spyOn(errors, 'Error').mockImplementation(() => ({}) as ReturnType); }; describe('API Handlers', () => { const columnSendRequestStarted = jest.fn(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.cache.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.cache.test.ts index 53f225666b84..63272d17383a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.cache.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.cache.test.ts @@ -19,7 +19,7 @@ interface RequestResult { const beforeTest = (): void => { baseBeforeTest(); jest.spyOn(errors, 'log').mockImplementation(jest.fn()); - jest.spyOn(errors, 'Error').mockImplementation(() => ({})); + jest.spyOn(errors, 'Error').mockImplementation(() => ({}) as ReturnType); }; describe('Cache', () => { const sendRequestSpy = jest.fn(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.data_source_change.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.data_source_change.test.ts index 7832ee2ed9be..a2e15dc21e13 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.data_source_change.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.data_source_change.test.ts @@ -38,7 +38,7 @@ const createNextItems = (): Record[] => [ const beforeTest = (): void => { baseBeforeTest(); jest.spyOn(errors, 'log').mockImplementation(jest.fn()); - jest.spyOn(errors, 'Error').mockImplementation(() => ({})); + jest.spyOn(errors, 'Error').mockImplementation(() => ({}) as ReturnType); }; const getAdapter = (instance: DataGridInstance): { changed: unknown } => instance diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.integration.test.ts index 9bec6c3efd15..c2da6160f80d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/__tests__/ai_column.integration.test.ts @@ -28,7 +28,7 @@ interface RequestResult { const beforeTest = (): void => { baseBeforeTest(); jest.spyOn(errors, 'log').mockImplementation(jest.fn()); - jest.spyOn(errors, 'Error').mockImplementation(() => ({})); + jest.spyOn(errors, 'Error').mockImplementation(() => ({}) as ReturnType); }; describe('Options', () => { diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts index e8832e6cfea0..d1a605fc1e83 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/__tests__/column_chooser.integration.test.ts @@ -13,7 +13,7 @@ describe('Bugs', () => { beforeEach(() => { beforeTest(); jest.spyOn(errors, 'log').mockImplementation(jest.fn()); - jest.spyOn(errors, 'Error').mockImplementation(() => ({})); + jest.spyOn(errors, 'Error').mockImplementation(() => ({}) as ReturnType); }); afterEach(afterTest); diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts index ddd03b4dab9f..94e151a54690 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_chooser/m_column_chooser.ts @@ -462,7 +462,7 @@ export class ColumnChooserView extends ColumnsView { return this.isColumnChooserVisible() && isParentColumnVisible && isColumnHidden; } - protected getBoundingRect() { + public getBoundingRect() { const that = this; const container = that._popupContainer?.$overlayContent(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts index e2142fc3dfbd..2c4d7a4337eb 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/column_headers/m_column_headers.ts @@ -385,7 +385,7 @@ export class ColumnHeadersView extends ColumnContextMenuMixin(ColumnsView) { /** * @extended: filter_row */ - protected _getRows() { + public _getRows() { const result: any[] = []; const rowCount = this.getRowCount(); @@ -577,7 +577,7 @@ export class ColumnHeadersView extends ColumnContextMenuMixin(ColumnsView) { return this.isColumnReorderingEnabled(column) && columns.length > 1; } - protected getBoundingRect() { + public getBoundingRect() { const that = this; const $columnElements = that.getColumnElements(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts index e065e1bc1808..3cb72239ac64 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.integration.test.ts @@ -292,7 +292,7 @@ describe('Bugs', () => { beforeEach(() => { beforeTest(); jest.spyOn(errors, 'log').mockImplementation(jest.fn()); - jest.spyOn(errors, 'Error').mockImplementation(() => ({})); + jest.spyOn(errors, 'Error').mockImplementation(() => ({}) as ReturnType); }); afterEach(afterTest); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/columns_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/columns_controller.ts index f514318929d3..18ce4a6f0988 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/columns_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/columns_controller.ts @@ -21,7 +21,7 @@ import type { Grouping, GroupPanel } from '@js/ui/data_grid'; import filterUtils from '@js/ui/shared/filtering'; import errors from '@js/ui/widget/ui.errors'; import inflector from '@ts/core/utils/m_inflector'; -import type { SortingInfo } from '@ts/data/utils'; +import type { SortingInfo, SortingSelector } from '@ts/data/utils'; import type { BandColumnsCache, Column, @@ -42,6 +42,8 @@ import type { FilterField, GroupColumn, IndexedColumns, + ProcessedColumn, + ProcessedLookup, SavedColumnState, } from '@ts/grids/grid_core/columns_controller/types'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; @@ -108,6 +110,8 @@ import { } from './const'; import { UserStateApplier } from './user_state_applier'; +type ColumnOptionsList = (Column | SortingSelector | undefined)[]; + export class ColumnsController extends modules.Controller { public _skipProcessingColumnsChange!: string | boolean; @@ -498,7 +502,6 @@ export class ColumnsController extends modules.Controller { const groupingOptions: Grouping = this.option('grouping') ?? {}; const groupPanelOptions: GroupPanel = this.option('groupPanel') ?? {}; - // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- extend has an untyped result return extend({ allowFixing: this.option('columnFixing.enabled'), // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- false -> undefined @@ -728,7 +731,6 @@ export class ColumnsController extends modules.Controller { const rtlEnabled = this.option('rtlEnabled'); const expandColumn = expandColumns.length ? this.columnOption('command:expand') : undefined; - // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- extend has an untyped result expandColumns = map(expandColumns, (column: Column): Column => extend( {}, { @@ -1358,7 +1360,7 @@ export class ColumnsController extends modules.Controller { const inferDataTypes = (): void => { for (const item of firstItems) { - const value = column.calculateCellValue(item); + const value = (column as ProcessedColumn).calculateCellValue(item); if (!column.dataType) { const valueDataType = getValueDataType(value); @@ -1392,7 +1394,7 @@ export class ColumnsController extends modules.Controller { const inferSerializationFormats = (): void => { for (const item of firstItems) { - const value = column.calculateCellValue(item, true); + const value = (column as ProcessedColumn).calculateCellValue(item, true); if (column.serializationFormat === undefined) { column.serializationFormat = getSerializationFormat(column.dataType, value); @@ -1401,7 +1403,7 @@ export class ColumnsController extends modules.Controller { if (lookup && lookup.serializationFormat === undefined) { lookup.serializationFormat = getSerializationFormat( lookup.dataType, - lookup.calculateCellValue(value, true), + (lookup as ProcessedLookup).calculateCellValue(value, true), ); } } @@ -1595,11 +1597,11 @@ export class ColumnsController extends modules.Controller { if (!this._columns.length) { each(groupParameters, (_: number, group) => { - this._columns.push(group.selector); + (this._columns as ColumnOptionsList).push(group.selector); }); each(sortParameters, (_: number, sort) => { if (!isFunction(sort.selector)) { - this._columns.push(sort.selector); + (this._columns as ColumnOptionsList).push(sort.selector); } }); assignColumns(this, createColumnsFromOptions(this, this._columns)); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts index b7185414f064..96712e1178c7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts @@ -114,6 +114,16 @@ export interface InternalColumnOptions extends ValueSerializers { export type Column = ColumnBase & InternalColumnOptions; +export type ProcessedLookup = InternalColumnLookup & { + calculateCellValue: (value: unknown, skipDeserialization?: boolean) => unknown; +}; + +export type ProcessedColumn = Column & { + index: number; + calculateCellValue: (rowData: RawItemData, skipDeserialization?: boolean) => unknown; + lookup?: ProcessedLookup; +}; + export type ColumnOptionsUpdate = Partial | Record; export interface ColumnDataSourceParameter { diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/utils.ts index 107d37be51ab..1eda8a9ed6c5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/utils.ts @@ -1,8 +1,9 @@ import { each } from '@ts/core/utils/m_iterator'; -import type { View } from '@ts/grids/grid_core/modules/modules'; + +import type { DraggingPanel } from './types'; interface DraggingPanelBoundingRect { - draggingPanel: View; + draggingPanel: DraggingPanel; boundingRect: { top?: number; left?: number; @@ -12,7 +13,7 @@ interface DraggingPanelBoundingRect { } export const getDraggingPanelBoundingRects = ( - draggingPanels: View[], + draggingPanels: DraggingPanel[], ): DraggingPanelBoundingRect[] | null => { const boundingRects: DraggingPanelBoundingRect[] = []; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 6712cfc346e9..ab0c438553c0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -615,7 +615,7 @@ export class DataController extends modules.Controller { this.dataErrorOccurred.fire(e); } - public fireError(...args: unknown[]): void { + public fireError(...args: [id: string, ...details: unknown[]]): void { this.dataErrorOccurred.fire(errors.Error(...args)); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter/__tests__/filter_controller.normalize_filter_selectors.test.ts b/packages/devextreme/js/__internal/grids/grid_core/filter/__tests__/filter_controller.normalize_filter_selectors.test.ts index 134acb4f5b59..1ce7a731840e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter/__tests__/filter_controller.normalize_filter_selectors.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter/__tests__/filter_controller.normalize_filter_selectors.test.ts @@ -101,7 +101,10 @@ describe('FilterController.normalizeFilterSelectors', () => { describe('when the filter array carries its own properties', () => { it('should carry them over to the result', async () => { const { instance } = await createGrid(); - const filter = extend([], [['name', '=', 'Alex'], 'and', ['age', '=', 15]]); + const filter: unknown[] & TaggedSelector = extend( + [], + [['name', '=', 'Alex'], 'and', ['age', '=', 15]], + ); filter.columnIndex = 7; filter.filterValue = 'ZZ'; @@ -117,7 +120,7 @@ describe('FilterController.normalizeFilterSelectors', () => { it('should pass columnIndex and filterValue down but not selectedFilterOperation', async () => { const { instance } = await createGrid(); const customSelector: ColumnSelector = (): number => 1; - const filter = extend([], [[customSelector, '=', 'Alex']]); + const filter: unknown[] & TaggedSelector = extend([], [[customSelector, '=', 'Alex']]); filter.columnIndex = 3; filter.filterValue = 'inherited'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts index cf55b35e0c7e..1e1fddbc5885 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts @@ -447,7 +447,7 @@ const columnHeadersView = (Base: ModuleType) => class ColumnH return $row; } - protected _getRows() { + public _getRows() { const result = super._getRows(); if (this.isFilterRowVisible()) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/m_filter_custom_operations.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/m_filter_custom_operations.ts index d49e034f2d44..259cc6176458 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/m_filter_custom_operations.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/m_filter_custom_operations.ts @@ -1,3 +1,4 @@ +import type { DataType } from '@js/common'; import messageLocalization from '@js/common/core/localization/message'; import { DataSource } from '@js/common/data/data_source/data_source'; import $ from '@js/core/renderer'; @@ -10,7 +11,7 @@ import { } from '@ts/filter_builder/utils'; function baseOperation(grid) { - const calculateFilterExpression = function (filterValue, field, fields) { + const calculateFilterExpression = function (filterValue, field, fields?) { const result: string[] = []; const lastIndex = filterValue.length - 1; filterValue && filterValue.forEach((value, index) => { @@ -49,7 +50,7 @@ function baseOperation(grid) { const headerFilterController = grid && grid.getController('headerFilter'); // Override in the private API WA [T1232532] - const customizeText = function (fieldInfo, options) { + const customizeText = function (fieldInfo, options?) { options = options || {}; const { value } = fieldInfo; let column = grid.columnOption(fieldInfo.field.dataField); @@ -101,7 +102,7 @@ function baseOperation(grid) { return text; }; return { - dataTypes: ['string', 'date', 'datetime', 'number', 'boolean', 'object'], + dataTypes: ['string', 'date', 'datetime', 'number', 'boolean', 'object'] as DataType[], calculateFilterExpression, editorTemplate(conditionInfo, container) { const div = $('
') @@ -148,7 +149,7 @@ export function anyOf(grid): CustomOperation { export function noneOf(grid): CustomOperation { const baseOp = baseOperation(grid); return extend({}, baseOp, { - calculateFilterExpression(filterValue, field, fields) { + calculateFilterExpression(filterValue, field, fields?) { const baseFilter = baseOp.calculateFilterExpression(filterValue, field, fields); if (!baseFilter || baseFilter.length === 0) return null; diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/utils.ts index 7d9f6d3dab37..2fc3c5856b0c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/utils.ts @@ -57,7 +57,6 @@ const canSyncHeaderFilterWithFilterRow = (column: FilterSyncColumn): boolean => export const checkForErrors = (columns: FilterSyncColumn[]): void => { columns.forEach((column) => { if (!isDefined(getColumnIdentifier(column)) && column.allowFiltering) { - // @ts-expect-error `errors.Error` is not declared as a constructor throw new errors.Error('E1049', column.caption); } }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts index 701808658748..bbabc9589f69 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter_core.ts @@ -408,8 +408,9 @@ export class HeaderFilterView extends Modules.View { that._listComponent = that._createComponent( $('
').appendTo($content), TreeView, + // @ts-expect-error the onOptionChanged handler takes the internal TreeView extend(widgetOptions, { - showCheckBoxesMode: needShowSelectAllCheckbox ? 'selectAll' : 'normal', + showCheckBoxesMode: needShowSelectAllCheckbox ? 'selectAll' as const : 'normal' as const, onOptionChanged: onTreeViewOptionChanged, keyExpr: 'id', }), @@ -420,9 +421,9 @@ export class HeaderFilterView extends Modules.View { List, extend(widgetOptions, { searchExpr: that._getSearchExpr(options, headerFilterOptions), - pageLoadMode: 'scrollBottom', + pageLoadMode: 'scrollBottom' as const, showSelectionControls: true, - selectionMode: needShowSelectAllCheckbox ? 'all' : 'multiple', + selectionMode: needShowSelectAllCheckbox ? 'all' as const : 'multiple' as const, onOptionChanged: onListOptionChanged, onSelectionChanged(event) { const { component: listComponent } = event; diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts index 1bab5d07cb19..c608eb1090ca 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts @@ -27,6 +27,7 @@ import Validator from '@js/ui/validator'; import errors from '@js/ui/widget/ui.errors'; import { focused } from '@ts/core/utils/m_selectors'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/columns_controller'; +import type { ProcessedColumn } from '@ts/grids/grid_core/columns_controller/types'; import type { EditorFactory } from '@ts/grids/grid_core/editor_factory/m_editor_factory'; import type { ErrorHandlingViewController } from '@ts/grids/grid_core/error_handling/error_handling_view_controller'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; @@ -843,7 +844,7 @@ export const validatingEditingExtender = (Base: ModuleType) = invisibleColumns.push(...groupColumns); if (!FORM_BASED_MODES.includes(this.getEditMode())) { - each(columns, (_, column) => { + each(columns as ProcessedColumn[], (_, column) => { changes.forEach((change) => { let data; if (isCellVisible(column, change.key)) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts index 09f7aa1ccda3..69cbc239bcfd 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts @@ -173,6 +173,15 @@ export const normalizeWidth = (width: string | number | undefined): string | und return width; }; +interface ScrollableOptions { + direction: string; + bounceEnabled: boolean; + useKeyboard: boolean; + useNative?: boolean | 'auto'; + useSimulatedScrollbar?: boolean; + onScroll?: (e) => void; +} + export class ColumnsView extends ColumnStateMixin(modules.View) { protected _tableElement: any; @@ -270,7 +279,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { const scrollingOptions = that.option('scrolling'); let useNativeScrolling = that.option('scrolling.useNative'); - const options = extend({}, scrollingOptions, { + const options: ScrollableOptions = extend({}, scrollingOptions, { direction: 'both', bounceEnabled: false, useKeyboard: false, @@ -922,7 +931,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { } - protected _getRows(change?) { + public _getRows(change?) { return [] as any[]; } @@ -1478,7 +1487,9 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { return this._getRowElements().index($row); } - protected getBoundingRect() { } + public getBoundingRect() { } + + public getContextMenuItems?(options): unknown; public getName() { } diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts index 066270078a47..dcce5138ea96 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts @@ -418,7 +418,9 @@ export class ResizingController extends modules.ViewController { const { width } = column; if (width !== 'auto') { if (isDefined(width)) { - resultWidths[index] = isNumeric(width) || isPixelWidth(width) ? parseFloat(width) : width; + resultWidths[index] = isNumeric(width) || isPixelWidth(width) + ? parseFloat(width as string) + : width; } else if (!columnAutoWidth) { resultWidths[index] = undefined; } @@ -856,6 +858,7 @@ export class ResizingController extends modules.ViewController { each(VIEW_NAMES, (index, viewName) => { // TODO getView + // @ts-expect-error errorView is in the list, but no such view is registered const view = that.getView(viewName); if (view) { view.resize(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts index 40ca6e6d8e14..4e54eedc4d4a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts @@ -914,7 +914,7 @@ export class RowsView extends ColumnsView { return deferred; } - protected _getRows(change) { + public _getRows(change?) { return change && change.items || this._dataController.items(); } diff --git a/packages/devextreme/js/__internal/grids/new/card_view/widget.ts b/packages/devextreme/js/__internal/grids/new/card_view/widget.ts index a71ce2af7eca..fc61bc0e6c47 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/widget.ts +++ b/packages/devextreme/js/__internal/grids/new/card_view/widget.ts @@ -55,7 +55,7 @@ export class CardViewBase extends GridCoreNew { protected _getDefaultOptions() { return { ...super._getDefaultOptions(), - ...extend(true, {}, defaultOptions) as typeof defaultOptions, + ...extend(true, {}, defaultOptions), }; } } diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_controller.ts b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_controller.ts index c13467a47ce9..c760298b3ab3 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_controller.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_controller.ts @@ -77,14 +77,14 @@ export class FilterController { getHeaderFilterController: (): unknown => this.headerFilterCompatibilityController, }; - const builtInCustomOperation = [ + const builtInCustomOperation: unknown[] = [ anyOf(config), noneOf(config), ]; return builtInCustomOperation .concat(this.filterBuilderCustomOperations.value) - .filter((o) => o) as unknown[]; + .filter((o) => o); }); public readonly displayFilter = computed( diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/component.mock.ts b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/component.mock.ts index a67d3ed93f2c..117966783591 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/component.mock.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/component.mock.ts @@ -32,4 +32,4 @@ export const createComponentMock = < } return template; } -}(extend(true, {}, options)); +}(extend(true, {}, options) as TProps); diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts index 7d0802e2a6c7..ac027264e740 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/options_controller/options_controller_base.ts @@ -63,7 +63,7 @@ export class OptionsController< this.defaults = component._getDefaultOptions?.() ?? {}; this.internalOptions = signal({ - options: extend(true, {}, component.option()), + options: extend(true, {}, component.option()) as TProps, changes: null, }); diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/options_validation/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/options_validation/utils.ts index d05d8d2020db..26ed77ccc931 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/options_validation/utils.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/options_validation/utils.ts @@ -1,5 +1,5 @@ import errors from '@js/ui/widget/ui.errors'; -export const throwError = (errorCode?: string, message?: string): void => { +export const throwError = (errorCode: string, message?: string): void => { throw errors.Error(errorCode, message); }; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts index 6abeba274c50..fb29d7cb0892 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/toolbar/utils.ts @@ -42,7 +42,7 @@ function normalizeToolbarItem( } } - return extend(true, {}, defaultProps, button) as ToolbarItem; + return extend(true, {}, defaultProps, button); } export function getSortedToolbarItems( diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts index bd25524f3b3a..d1a06a58e6ba 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts @@ -155,7 +155,7 @@ export class GridCoreNewBase< return { // @ts-expect-error ...super._getDefaultOptions() as {}, - ...extend(true, {}, defaultOptions) as typeof defaultOptions, + ...extend(true, {}, defaultOptions), }; } diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/m_widget.ts b/packages/devextreme/js/__internal/grids/pivot_grid/m_widget.ts index 136a8c65ea45..b4ed63c59ca5 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/m_widget.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/m_widget.ts @@ -125,6 +125,8 @@ function clickedOnFieldsArea($targetElement) { return $targetElement.closest(`.${FIELDS_CLASS}`).length || $targetElement.find(`.${FIELDS_CLASS}`).length; } +type CellEventArgs = ReturnType & { cancel?: boolean }; + class PivotGrid extends Widget { _dataController: any; @@ -893,7 +895,8 @@ class PivotGrid extends Widget { _handleCellClick(e) { const that = this; - const args = that._createEventArgs(e.currentTarget, e); + // @ts-expect-error the event args are built for a cell or for a field + const args: CellEventArgs = that._createEventArgs(e.currentTarget, e); const { cell } = args; if (!cell || (!args.area && (args.rowIndex || args.columnIndex))) { @@ -1081,7 +1084,8 @@ class PivotGrid extends Widget { if (e.key !== 'Enter' && e.key !== ' ') { return; } - const args = this._createEventArgs(e.currentTarget, e); + // @ts-expect-error the event args are built for a cell or for a field + const args: CellEventArgs = this._createEventArgs(e.currentTarget, e); const { cell } = args; if (!cell || !isDefined(cell.expanded)) { return; diff --git a/packages/devextreme/js/__internal/grids/tree_list/core.ts b/packages/devextreme/js/__internal/grids/tree_list/core.ts index 9775d378c5a2..8df7f68215ff 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/core.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/core.ts @@ -4,7 +4,7 @@ import modules from '@ts/grids/grid_core/modules/modules'; export default extend({}, modules, { modules: [], - foreachNodes(nodes, callBack, ignoreHasChildren) { + foreachNodes(nodes, callBack, ignoreHasChildren?) { // eslint-disable-next-line @typescript-eslint/prefer-for-of -- callBack may mutate nodes for (let i = 0; i < nodes.length; i += 1) { if (callBack(nodes[i]) !== false && (ignoreHasChildren || nodes[i].hasChildren) diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts b/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts index cc33042f922a..9a35e411bd55 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts @@ -98,8 +98,10 @@ class TreeList extends GridCoreWidget { super._init(); + // @ts-expect-error the widget has no index signature of the component instance treeListCore.processModules(that, treeListCore); + // @ts-expect-error the widget's _controllers and _views are private treeListCore.callModuleItemsMethod(this, 'init'); } diff --git a/packages/devextreme/js/__internal/scheduler/appointment_popup/form.ts b/packages/devextreme/js/__internal/scheduler/appointment_popup/form.ts index 93b19f072130..d29d7bd0e08c 100644 --- a/packages/devextreme/js/__internal/scheduler/appointment_popup/form.ts +++ b/packages/devextreme/js/__internal/scheduler/appointment_popup/form.ts @@ -4,7 +4,7 @@ import '@js/ui/tag_box'; import '@js/ui/switch'; import '@js/ui/select_box'; -import type { DayOfWeek, TextEditorButton } from '@js/common'; +import type { DateLike, DayOfWeek, TextEditorButton } from '@js/common'; import messageLocalization from '@js/common/core/localization/message'; import type { DataSourceOptions } from '@js/common/data'; import { DataSource } from '@js/common/data'; @@ -208,14 +208,14 @@ export class AppointmentForm { get startDate(): Date | null { const { startDateExpr } = this.config.dataAccessors.expr; - const value = this.getFormDataField(startDateExpr); + const value = this.getFormDataField(startDateExpr) as DateLike | undefined; return value ? new Date(dateSerialization.deserializeDate(value)) : null; } get endDate(): Date | null { const { endDateExpr } = this.config.dataAccessors.expr; - const value = this.getFormDataField(endDateExpr); + const value = this.getFormDataField(endDateExpr) as DateLike | undefined; return value ? new Date(dateSerialization.deserializeDate(value)) : null; } diff --git a/packages/devextreme/js/__internal/scheduler/scheduler.ts b/packages/devextreme/js/__internal/scheduler/scheduler.ts index df1c88727206..bb53a6249715 100644 --- a/packages/devextreme/js/__internal/scheduler/scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/scheduler.ts @@ -1697,6 +1697,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { || horizontalVirtualScrollingAllowed || isTimelineView(currentViewOptions.type); + // @ts-expect-error the raw options are merged here and converted below const workSpaceOptions = extend({ resources: this.option('resources'), getResourceManager: () => this.resourceManager, @@ -2371,7 +2372,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { {}, rawAppointment, newRawTargetedAppointment, - ) as SafeAppointment; + ); const isCreateAppointment = createNewAppointment ?? isEmptyObject(rawAppointment); diff --git a/packages/devextreme/js/__internal/scheduler/utils/data_accessor/appointment_data_accessor.ts b/packages/devextreme/js/__internal/scheduler/utils/data_accessor/appointment_data_accessor.ts index 24af62bf558e..d679d5162f5c 100644 --- a/packages/devextreme/js/__internal/scheduler/utils/data_accessor/appointment_data_accessor.ts +++ b/packages/devextreme/js/__internal/scheduler/utils/data_accessor/appointment_data_accessor.ts @@ -1,3 +1,4 @@ +import type { DateLike } from '@js/common'; import { compileGetter, compileSetter } from '@js/core/utils/data'; import dateSerialization from '@js/core/utils/date_serialization'; import type { Appointment } from '@js/ui/scheduler'; @@ -46,12 +47,14 @@ export class AppointmentDataAccessor extends DataAccessor { const date = this.forceIsoDateParsing - ? dateSerialization.deserializeDate(commonGetter(object)) - : commonGetter(object); + ? dateSerialization.deserializeDate( + commonGetter(object) as Exclude | undefined, + ) + : commonGetter(object) as Exclude | undefined; return date === undefined ? date : new Date(date); }; diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts index 0876d66245e6..85844a7e8c1c 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts @@ -650,7 +650,7 @@ class SchedulerWorkSpace extends Widget { }, }; - return extend(super._supportedKeys(), supportedKeys) as SupportedKeys; + return extend(super._supportedKeys(), supportedKeys); } private isRTL(): boolean { @@ -1715,7 +1715,7 @@ class SchedulerWorkSpace extends Widget { allDay: cellData.allDay, }; - return extend(true, {}, normalizedCellData) as NormalizedCellData; + return extend(true, {}, normalizedCellData); } private getSelectedCellsData(): NormalizedCellData[] { diff --git a/packages/devextreme/js/__internal/sortable.ts b/packages/devextreme/js/__internal/sortable.ts index a95655d7fc4a..9bfaeb5a7bed 100644 --- a/packages/devextreme/js/__internal/sortable.ts +++ b/packages/devextreme/js/__internal/sortable.ts @@ -1099,7 +1099,7 @@ class Sortable extends Draggable { } else if (prevPosition !== position || (fullUpdate && isDefined(position))) { animate(itemElement, extend({}, animationConfig, { to: { [positionPropName]: !isVerticalOrientation && rtlEnabled ? -position : position }, - })); + }) as AnimateConfig); } } } diff --git a/packages/devextreme/js/__internal/ui/action_sheet.ts b/packages/devextreme/js/__internal/ui/action_sheet.ts index 1875740a78e8..2e155c6b102e 100644 --- a/packages/devextreme/js/__internal/ui/action_sheet.ts +++ b/packages/devextreme/js/__internal/ui/action_sheet.ts @@ -156,7 +156,7 @@ class ActionSheet extends CollectionWidget { super._clean(); } - _overlayConfig(): PopoverProperties | PopupProperties { + _overlayConfig(): PopoverProperties & PopupProperties { const { title } = this.option(); return { disabled: false, diff --git a/packages/devextreme/js/__internal/ui/calendar/calendar.base_view.ts b/packages/devextreme/js/__internal/ui/calendar/calendar.base_view.ts index 98060dfac0c9..181da272d6a5 100644 --- a/packages/devextreme/js/__internal/ui/calendar/calendar.base_view.ts +++ b/packages/devextreme/js/__internal/ui/calendar/calendar.base_view.ts @@ -338,7 +338,7 @@ class BaseView< cell.className = this._getClassNameByDate(cellDate, cellIndex); - cell.setAttribute('data-value', dateSerialization.serializeDate(cellDate, coreDateUtils.getShortDateFormat())); + cell.setAttribute('data-value', dateSerialization.serializeDate(cellDate, coreDateUtils.getShortDateFormat()) as string); elementData(cell, CALENDAR_DATE_VALUE_KEY, cellDate); this.setAria({ diff --git a/packages/devextreme/js/__internal/ui/calendar/calendar.ts b/packages/devextreme/js/__internal/ui/calendar/calendar.ts index d0087f00a824..eb99bd19877b 100644 --- a/packages/devextreme/js/__internal/ui/calendar/calendar.ts +++ b/packages/devextreme/js/__internal/ui/calendar/calendar.ts @@ -318,13 +318,11 @@ class Calendar< return undefined; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return dateSerialization.getDateSerializationFormat(value); } _convertToDate(value: DateLike | undefined): Date | null { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return dateSerialization.deserializeDate(value); + return dateSerialization.deserializeDate(value) as Date | null; } _dateValue(value: Date | (Date | null)[] | null, event: DxEvent): void { @@ -348,7 +346,6 @@ class Calendar< ): void { const serializationFormat = this._getSerializationFormat(optionName); const serializedValue = this._isArrayValue(optionName, optionValue) - // eslint-disable-next-line @typescript-eslint/no-unsafe-return ? optionValue.map((value) => dateSerialization.serializeDate(value, serializationFormat)) : dateSerialization.serializeDate(optionValue, serializationFormat); @@ -1349,7 +1346,7 @@ class Calendar< const dateValue = this._convertToDate(value); this._getSubmitElement() - .val(dateSerialization.serializeDate(dateValue, CALENDAR_INPUT_STANDARD_PATTERN)); + .val(dateSerialization.serializeDate(dateValue, CALENDAR_INPUT_STANDARD_PATTERN) as string); } _getSubmitElement(): dxElementWrapper { diff --git a/packages/devextreme/js/__internal/ui/chat/messagelist.ts b/packages/devextreme/js/__internal/ui/chat/messagelist.ts index 675fde1351f6..54d54343f067 100644 --- a/packages/devextreme/js/__internal/ui/chat/messagelist.ts +++ b/packages/devextreme/js/__internal/ui/chat/messagelist.ts @@ -750,7 +750,8 @@ class MessageList extends Widget { const lastMessageTimestampInMs = dateSerialization.deserializeDate(lastMessageTimestamp); const newMessageTimestampInMs = dateSerialization.deserializeDate(newMessageTimestamp); - const result = newMessageTimestampInMs - lastMessageTimestampInMs > MESSAGEGROUP_TIMEOUT; + const result = Number(newMessageTimestampInMs) - Number(lastMessageTimestampInMs) + > MESSAGEGROUP_TIMEOUT; return result; } diff --git a/packages/devextreme/js/__internal/ui/collection/collection_widget.base.ts b/packages/devextreme/js/__internal/ui/collection/collection_widget.base.ts index 9a37f9ba37c4..887db8261822 100644 --- a/packages/devextreme/js/__internal/ui/collection/collection_widget.base.ts +++ b/packages/devextreme/js/__internal/ui/collection/collection_widget.base.ts @@ -224,7 +224,6 @@ class CollectionWidget< currentTarget: $target.get(0), }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return params; } @@ -1510,7 +1509,7 @@ class CollectionWidget< _itemEventHandlerImpl( initiator: dxElementWrapper | Element, - action: (event?: Record) => void, + action: (event?: object) => void, actionArgs: ActionArgs, ): void { const $itemElement = this._closestItemElement($(initiator)); diff --git a/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts b/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts index b7898ee1cbee..9879d5c4e6dd 100644 --- a/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts +++ b/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts @@ -129,6 +129,7 @@ class CollectionWidgetLiveUpdate< if (change.type === 'insert') { result = true; each(items, (_, item) => { + // @ts-expect-error the data of a grouped widget are groups with a key if (change.data.key !== undefined && change.data.key === item.key) { result = false; return false; diff --git a/packages/devextreme/js/__internal/ui/color_box/color_view.ts b/packages/devextreme/js/__internal/ui/color_box/color_view.ts index b7cec5c92167..ec16525c56fa 100644 --- a/packages/devextreme/js/__internal/ui/color_box/color_view.ts +++ b/packages/devextreme/js/__internal/ui/color_box/color_view.ts @@ -736,7 +736,7 @@ class ColorView extends Editor { const { editorType: EditorConstructor } = options; const { stylingMode } = this.option(); - const editorOptions = extend({ + const editorOptions: Record = extend({ value: options.value, onValueChanged: options.onValueChanged, onKeyboardHandled: (opts) => this._keyboardHandler(opts), diff --git a/packages/devextreme/js/__internal/ui/context_menu/menu_base.edit.strategy.ts b/packages/devextreme/js/__internal/ui/context_menu/menu_base.edit.strategy.ts index 1989db93ceb2..5eb781cad59e 100644 --- a/packages/devextreme/js/__internal/ui/context_menu/menu_base.edit.strategy.ts +++ b/packages/devextreme/js/__internal/ui/context_menu/menu_base.edit.strategy.ts @@ -8,10 +8,11 @@ class MenuBaseEditStrategy extends PlainEditStrategy { const items = this._getItems(); const result = map(items, function getMenuItems(item: Item): Item | Item[] { + // @ts-expect-error map returns the nested items, so the items of a third level stay nested return item.items ? [item].concat(map(item.items, getMenuItems)) : item; }); - return result.flat() as Item[]; + return result.flat(); } static _stringifyItem(item: Item): string { diff --git a/packages/devextreme/js/__internal/ui/date_box/date_box.base.ts b/packages/devextreme/js/__internal/ui/date_box/date_box.base.ts index 006782434242..9ae276035f16 100644 --- a/packages/devextreme/js/__internal/ui/date_box/date_box.base.ts +++ b/packages/devextreme/js/__internal/ui/date_box/date_box.base.ts @@ -560,7 +560,7 @@ class DateBox< const { type = 'date', dateSerializationFormat } = this.option(); const submitFormat = uiDateUtils.SUBMIT_FORMATS_MAP[type]; const submitValue = dateSerializationFormat - ? dateSerialization.serializeDate(value, dateSerializationFormat) + ? dateSerialization.serializeDate(value, dateSerializationFormat) as string : uiDateUtils.toStandardDateFormat(value, submitFormat); this._getSubmitElement().val(submitValue); @@ -913,7 +913,7 @@ class DateBox< getDateOption(optionName: 'value' | 'min' | 'max'): Date | null { const { [optionName]: optionValue } = this.option(); - const deserializedDate: Date | null = dateSerialization.deserializeDate(optionValue); + const deserializedDate = dateSerialization.deserializeDate(optionValue) as Date | null; return deserializedDate; } @@ -924,13 +924,10 @@ class DateBox< this.option(optionName, serializedDate); } - _serializeDate(date?: DateLike): Date | string | null { + _serializeDate(date?: DateLike): DateLike | undefined { const serializationFormat = this._getSerializationFormat(); - const serializedDate: Date | string | null = dateSerialization.serializeDate( - date, - serializationFormat, - ); + const serializedDate = dateSerialization.serializeDate(date, serializationFormat); return serializedDate; } diff --git a/packages/devextreme/js/__internal/ui/date_range_box/date_range_box.ts b/packages/devextreme/js/__internal/ui/date_range_box/date_range_box.ts index 07c5752a8b08..2da97fa7cf5d 100644 --- a/packages/devextreme/js/__internal/ui/date_range_box/date_range_box.ts +++ b/packages/devextreme/js/__internal/ui/date_range_box/date_range_box.ts @@ -708,7 +708,7 @@ class DateRangeBox extends Editor { return validationMessagePosition; } - _getSerializedDates([startDate, endDate]: (DateLike | undefined)[]): DateLike[] { + _getSerializedDates([startDate, endDate]: (DateLike | undefined)[]): (DateLike | undefined)[] { return [ this.getStartDateBox()._serializeDate(getDeserializedDate(startDate)), this.getStartDateBox()._serializeDate(getDeserializedDate(endDate)), diff --git a/packages/devextreme/js/__internal/ui/diagram/ui.diagram.toolbar.ts b/packages/devextreme/js/__internal/ui/diagram/ui.diagram.toolbar.ts index 5898f33d941d..c7d86fa6ae7e 100644 --- a/packages/devextreme/js/__internal/ui/diagram/ui.diagram.toolbar.ts +++ b/packages/devextreme/js/__internal/ui/diagram/ui.diagram.toolbar.ts @@ -810,12 +810,11 @@ class DiagramToolbar extends DiagramPanel { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type _getDefaultOptions() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return extend(super._getDefaultOptions(), { isMobileView: false, export: { fileName: 'Diagram', - }, + } as { fileName?: string }, locateInMenu: 'auto', buttonStylingMode: 'text', buttonType: 'normal', diff --git a/packages/devextreme/js/__internal/ui/diagram/ui.diagram.ts b/packages/devextreme/js/__internal/ui/diagram/ui.diagram.ts index 49c3c6a7057b..f02aa00cd90c 100644 --- a/packages/devextreme/js/__internal/ui/diagram/ui.diagram.ts +++ b/packages/devextreme/js/__internal/ui/diagram/ui.diagram.ts @@ -508,6 +508,7 @@ class Diagram extends Widget { isVisible: this._isToolboxVisible(), container: this.$element(), height: bounds.height, + // @ts-expect-error the toolbox options are not in its default options offsetParent: $parent, offsetX: bounds.offsetX, offsetY: bounds.offsetY, @@ -758,6 +759,7 @@ class Diagram extends Widget { isMobileView: this.isMobileScreenSize(), isVisible: this._isPropertiesPanelVisible(), container: this.$element(), + // @ts-expect-error the properties panel options are not in its default options offsetParent: $parent, offsetX, offsetY, @@ -1889,7 +1891,6 @@ class Diagram extends Widget { _updateFormatUnitsMethod(): void { const { DiagramLocalizationService } = getDiagram(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return DiagramLocalizationService.formatUnit = (value): string => numberLocalization.format(value); } @@ -2202,7 +2203,6 @@ class Diagram extends Widget { } _getDefaultOptions(): Properties { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return extend(super._getDefaultOptions(), { readOnly: false, zoomLevel: DIAGRAM_DEFAULT_ZOOMLEVEL, diff --git a/packages/devextreme/js/__internal/ui/file_manager/file_items_controller.ts b/packages/devextreme/js/__internal/ui/file_manager/file_items_controller.ts index 1e323ad51f38..ffa8a96d3e11 100644 --- a/packages/devextreme/js/__internal/ui/file_manager/file_items_controller.ts +++ b/packages/devextreme/js/__internal/ui/file_manager/file_items_controller.ts @@ -114,6 +114,10 @@ interface FileItemsControllerOptions { editingEvents?: FileManagerActions['editing']; } +type RootDirectoryInfo = ReturnType & { + displayName?: string; +}; + export class FileItemsController { _options: FileItemsControllerOptions; @@ -1193,13 +1197,11 @@ export class FileItemsController { _createDirInfoByName(name, parentDirectoryInfo) { const dirPathInfo = this._getPathInfo(parentDirectoryInfo); const fileItem = new FileSystemItem(dirPathInfo, name, true); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return this._createDirectoryInfo(fileItem, parentDirectoryInfo); } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type _createDirectoryInfo(fileItem, parentDirectoryInfo) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return extend(this._createFileInfo(fileItem, parentDirectoryInfo), { icon: 'folder', expanded: fileItem.isRoot(), @@ -1267,9 +1269,8 @@ export class FileItemsController { _createRootDirectoryInfo(text) { const rootDirectory = new FileSystemItem(null, '', true); - const result = this._createDirectoryInfo(rootDirectory, null); + const result: RootDirectoryInfo = this._createDirectoryInfo(rootDirectory, null); result.displayName = text || DEFAULT_ROOT_FILE_SYSTEM_ITEM_NAME; - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return result; } diff --git a/packages/devextreme/js/__internal/ui/form/form.items_runtime_info.ts b/packages/devextreme/js/__internal/ui/form/form.items_runtime_info.ts index c0089467ce50..646c1e80dd1e 100644 --- a/packages/devextreme/js/__internal/ui/form/form.items_runtime_info.ts +++ b/packages/devextreme/js/__internal/ui/form/form.items_runtime_info.ts @@ -61,7 +61,7 @@ export default class FormItemsRunTimeInfo { each(this._map, (_guid: Guid, { widgetInstance, item }): boolean => { if (condition(item)) { - result = widgetInstance; + result = widgetInstance as T | undefined; return false; } diff --git a/packages/devextreme/js/__internal/ui/form/form.layout_manager.utils.ts b/packages/devextreme/js/__internal/ui/form/form.layout_manager.utils.ts index b954a92fc8c0..62f2977bdd23 100644 --- a/packages/devextreme/js/__internal/ui/form/form.layout_manager.utils.ts +++ b/packages/devextreme/js/__internal/ui/form/form.layout_manager.utils.ts @@ -193,7 +193,7 @@ function _convertToLabelOptions({ const isEditorWithoutLabels = EDITORS_WITHOUT_LABELS.includes( item.editorType, ); - const labelOptions: LabelOptions = extend( + const labelOptions = extend( { showColon: showColonAfterLabel, location: labelLocation, @@ -209,7 +209,7 @@ function _convertToLabelOptions({ labelTemplate, onLabelTemplateRendered, }, - ); + ) as LabelOptions; const editorsRequiringIdForLabel: FormItemComponent[] = [ 'dxRadioGroup', diff --git a/packages/devextreme/js/__internal/ui/gantt/ui.gantt.cache.ts b/packages/devextreme/js/__internal/ui/gantt/ui.gantt.cache.ts index a3b78f5c3de3..c4464e3e5e06 100644 --- a/packages/devextreme/js/__internal/ui/gantt/ui.gantt.cache.ts +++ b/packages/devextreme/js/__internal/ui/gantt/ui.gantt.cache.ts @@ -27,7 +27,6 @@ export class GanttDataCache { pullDataFromCache(key, target): void { const data = this._getCache(key); if (data) { - // @ts-expect-error ts-error extendFromObject(target, data); } this._onKeyExpired(key); diff --git a/packages/devextreme/js/__internal/ui/popup/popup.ts b/packages/devextreme/js/__internal/ui/popup/popup.ts index d04d111ffc63..1d0c14bfbe21 100644 --- a/packages/devextreme/js/__internal/ui/popup/popup.ts +++ b/packages/devextreme/js/__internal/ui/popup/popup.ts @@ -1,5 +1,6 @@ import '@js/ui/toolbar/ui.toolbar.base'; +import type { ToolbarItemLocation } from '@js/common'; import { triggerResizeEvent } from '@js/common/core/events/visibility_change'; import messageLocalization from '@js/common/core/localization/message'; import type { DeepPartial } from '@js/core'; @@ -32,7 +33,9 @@ import { import { isDefined, isObject } from '@js/core/utils/type'; import type { Properties as ButtonProperties } from '@js/ui/button'; import Button from '@js/ui/button'; -import type { dxPopupAnimation, Properties, ToolbarItem } from '@js/ui/popup'; +import type { + dxPopupAnimation, Properties, ToolbarItem, ToolbarLocation, +} from '@js/ui/popup'; import type { ResizeEndEvent, ResizeEvent, ResizeStartEvent } from '@js/ui/resizable'; import Resizable from '@js/ui/resizable'; import { @@ -123,11 +126,15 @@ interface HeightCssStyles { maxHeight: number | string; } -const getButtonPlace = (name: string): { toolbar: string; location: string } => { +type PopupToolbarItem = ToolbarItem & { shortcut?: string }; + +const getButtonPlace = ( + name: string, +): { toolbar: ToolbarLocation; location: ToolbarItemLocation } => { const device = devices.current(); const { platform } = device; - let toolbar = 'bottom'; - let location = 'before'; + let toolbar: ToolbarLocation = 'bottom'; + let location: ToolbarItemLocation = 'before'; if (platform === 'ios') { // eslint-disable-next-line default-case @@ -870,9 +877,9 @@ class Popup< let index = 0; - each(toolbarItems, (_, data) => { + each(toolbarItems as PopupToolbarItem[] | undefined, (_, data) => { const isShortcut = isDefined(data.shortcut); - const item = isShortcut ? getButtonPlace(data.shortcut) : data; + const item: ToolbarItem = isShortcut ? getButtonPlace(data.shortcut as string) : data; if (isShortcut && currentPlatform === 'ios' && index < 2) { item.toolbar = 'top'; diff --git a/packages/devextreme/js/__internal/ui/responsive_box.ts b/packages/devextreme/js/__internal/ui/responsive_box.ts index bf35b9bc5421..690ac6a2098c 100644 --- a/packages/devextreme/js/__internal/ui/responsive_box.ts +++ b/packages/devextreme/js/__internal/ui/responsive_box.ts @@ -51,6 +51,8 @@ interface BlockOptions { prevBlockOptions?: BlockOptions; } +type ScreenItem = ResponsiveBoxItem & { location: LocationItem }; + export interface GridCell { item: ResponsiveBoxItem; location: { @@ -81,7 +83,7 @@ class ResponsiveBox extends CollectionWidget { _layoutChangedAction?: () => void; - _screenItems?: ResponsiveBoxItem[]; + _screenItems?: ScreenItem[]; _$root?: dxElementWrapper; @@ -250,10 +252,8 @@ class ResponsiveBox extends CollectionWidget { } _sizesByScreen(sizeConfigs: ResponsiveBoxItem[] | undefined): ResponsiveBoxItem[] { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return map( this._filterByScreen(sizeConfigs), - // eslint-disable-next-line @typescript-eslint/no-unsafe-return (sizeConfig) => extend(this._defaultSizeConfig(), sizeConfig), ); } @@ -320,6 +320,7 @@ class ResponsiveBox extends CollectionWidget { const location = itemInfo.location || {}; const itemCol = location.col; const itemRow = location.row; + // @ts-expect-error a location can have no row, then there is no cell const row = this._grid[itemRow]; const itemCell = row?.[itemCol]; @@ -327,10 +328,10 @@ class ResponsiveBox extends CollectionWidget { }); } - _itemsByScreen(): Item[] { + _itemsByScreen(): ScreenItem[] { const { items = [] } = this.option(); - return items.reduce((result: Item[], item: ResponsiveBoxItem) => { + return items.reduce((result, item: ResponsiveBoxItem) => { let locations = (item.location ?? {}) as ResponsiveBoxItem[]; locations = isPlainObject(locations) @@ -339,7 +340,6 @@ class ResponsiveBox extends CollectionWidget { this._filterByScreen(locations).forEach((location) => { result.push({ - // @ts-expect-error ts-error item, location: { rowspan: 1, @@ -463,7 +463,6 @@ class ResponsiveBox extends CollectionWidget { _prepareBoxConfig(config: BoxOptions): BoxOptions { const { onItemStateChanged } = this.option(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return extend(config || {}, { crossAlign: 'stretch', onItemStateChanged, @@ -662,8 +661,7 @@ class ResponsiveBox extends CollectionWidget { _clearItemNodeTemplates(): void { const { items } = this.option(); - each(items, function clearTemplates() { - // eslint-disable-next-line @typescript-eslint/no-invalid-this + each(items, function clearTemplates(this: Item & { node?: unknown }) { delete this.node; }); } diff --git a/packages/devextreme/js/__internal/ui/scroll_view/scroll_view.native.pull_down.ts b/packages/devextreme/js/__internal/ui/scroll_view/scroll_view.native.pull_down.ts index 2ad0703aa1e7..b858f96bdab1 100644 --- a/packages/devextreme/js/__internal/ui/scroll_view/scroll_view.native.pull_down.ts +++ b/packages/devextreme/js/__internal/ui/scroll_view/scroll_view.native.pull_down.ts @@ -36,7 +36,7 @@ class PullDownNativeScrollViewStrategy< _$pullDown!: dxElementWrapper; - _$refreshingText?: dxElementWrapper; + _$refreshingText!: dxElementWrapper; _$scrollViewContent!: dxElementWrapper; diff --git a/packages/devextreme/js/__internal/ui/scroll_view/scroll_view.simulated.ts b/packages/devextreme/js/__internal/ui/scroll_view/scroll_view.simulated.ts index 39ee28547491..5d9964b2a469 100644 --- a/packages/devextreme/js/__internal/ui/scroll_view/scroll_view.simulated.ts +++ b/packages/devextreme/js/__internal/ui/scroll_view/scroll_view.simulated.ts @@ -38,11 +38,11 @@ export class ScrollViewScroller extends Scroller { _$pullDown!: dxElementWrapper; - _$pullingDownText?: dxElementWrapper; + _$pullingDownText!: dxElementWrapper; - _$pulledDownText?: dxElementWrapper; + _$pulledDownText!: dxElementWrapper; - _$refreshingText?: dxElementWrapper; + _$refreshingText!: dxElementWrapper; _$bottomPocket!: dxElementWrapper; diff --git a/packages/devextreme/js/__internal/ui/scroll_view/scrollable.native.ts b/packages/devextreme/js/__internal/ui/scroll_view/scrollable.native.ts index 5fcb6fe45112..5e38f3056340 100644 --- a/packages/devextreme/js/__internal/ui/scroll_view/scrollable.native.ts +++ b/packages/devextreme/js/__internal/ui/scroll_view/scrollable.native.ts @@ -177,7 +177,7 @@ class NativeStrategy< _eachScrollbar(callback: (scrollbar: Scrollbar, direction: Orientation) => void): void { each(this._scrollbars || {}, (direction, scrollbar) => { - callback(scrollbar, direction); + callback(scrollbar, direction as Orientation); }); } diff --git a/packages/devextreme/js/__internal/ui/scroll_view/scrollable.simulated.ts b/packages/devextreme/js/__internal/ui/scroll_view/scrollable.simulated.ts index 1399aacceb82..c9e540a52609 100644 --- a/packages/devextreme/js/__internal/ui/scroll_view/scrollable.simulated.ts +++ b/packages/devextreme/js/__internal/ui/scroll_view/scrollable.simulated.ts @@ -738,7 +738,7 @@ export class SimulatedStrategy< _createActionByOption!: ( optionName: string, config?: ActionConfig, - ) => (event?: Record) => void; + ) => (event?: unknown) => void; _scrollAction?: () => void; diff --git a/packages/devextreme/js/__internal/ui/selection/selection.ts b/packages/devextreme/js/__internal/ui/selection/selection.ts index 20a62ae1a6a2..9eb2f00c2b80 100644 --- a/packages/devextreme/js/__internal/ui/selection/selection.ts +++ b/packages/devextreme/js/__internal/ui/selection/selection.ts @@ -31,7 +31,7 @@ export default class Selection< constructor(options: Partial>) { this.options = extend(this._getDefaultOptions(), options, { selectedItemKeys: options.selectedKeys ?? [], - }); + }) as SelectionOptions; this._selectionStrategy = (this.options.deferred ? new DeferredStrategy(this.options) diff --git a/packages/devextreme/js/__internal/ui/speed_dial_action/speed_dial_main_item.ts b/packages/devextreme/js/__internal/ui/speed_dial_action/speed_dial_main_item.ts index 8fe2f4a5ed6f..5c88cf663b07 100644 --- a/packages/devextreme/js/__internal/ui/speed_dial_action/speed_dial_main_item.ts +++ b/packages/devextreme/js/__internal/ui/speed_dial_action/speed_dial_main_item.ts @@ -60,7 +60,7 @@ const modifyActionOptions = (action: SpeedDialAction): SpeedDialItemProperties = zIndex, } = action.option(); - const actionOptions: SpeedDialItemProperties = extend({}, { + const actionOptions = extend({}, { animation, actionComponent, actionVisible, @@ -83,7 +83,7 @@ const modifyActionOptions = (action: SpeedDialAction): SpeedDialItemProperties = }, { onInitialized: null, onDisposing: null, - }); + }) as SpeedDialItemProperties; return actionOptions; }; diff --git a/packages/devextreme/js/__internal/ui/text_box/text_editor.base.ts b/packages/devextreme/js/__internal/ui/text_box/text_editor.base.ts index 6fe15ab75575..ba933ecc9fc6 100644 --- a/packages/devextreme/js/__internal/ui/text_box/text_editor.base.ts +++ b/packages/devextreme/js/__internal/ui/text_box/text_editor.base.ts @@ -429,6 +429,7 @@ class TextEditorBase< ): void { const inputAttributes = extend(this._getDefaultAttributes(), customAttributes); + // @ts-expect-error attr has no overload for an object of attributes $input.attr(inputAttributes); $input.addClass(TEXTEDITOR_INPUT_CLASS); diff --git a/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts b/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts index d81f03fd1cf8..a4bae99be258 100644 --- a/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts +++ b/packages/devextreme/js/__internal/ui/toolbar/toolbar.base.ts @@ -63,6 +63,8 @@ const ANIMATION_TIMEOUT = 15; type ItemLike = string | Item; +type ToolbarGroup = Item & { items?: Item[] }; + export interface ToolbarBaseProperties< TItem extends ItemLike = Item, TKey extends CollectionItemKey = CollectionItemKey, @@ -609,7 +611,7 @@ class ToolbarBase< _renderGroupedItems(): void { const { items: groups = [] } = this.option(); - each(groups, (groupIndex, group) => { + each(groups as ToolbarGroup[], (groupIndex, group) => { const groupItems = group.items; const $container = $('
').addClass(TOOLBAR_GROUP_CLASS); const location = group.location ?? 'center'; diff --git a/packages/devextreme/js/__internal/ui/tree_view/tree_view.base.ts b/packages/devextreme/js/__internal/ui/tree_view/tree_view.base.ts index 35cf024510f0..817ef87f26c2 100644 --- a/packages/devextreme/js/__internal/ui/tree_view/tree_view.base.ts +++ b/packages/devextreme/js/__internal/ui/tree_view/tree_view.base.ts @@ -297,7 +297,7 @@ class TreeViewBase extends HierarchicalCollectionWidget) => void, + action: (event?: object) => void, actionArgs: ActionArgs, ): void { const $itemElement = $(initiator).closest(`.${NODE_CLASS}`).children(`.${ITEM_CLASS}`); diff --git a/packages/devextreme/js/__internal/viz/core/base_widget.ts b/packages/devextreme/js/__internal/viz/core/base_widget.ts index 3e6230d3a1c1..adf280d4c615 100644 --- a/packages/devextreme/js/__internal/viz/core/base_widget.ts +++ b/packages/devextreme/js/__internal/viz/core/base_widget.ts @@ -68,7 +68,7 @@ function getFalse(): boolean { function defaultOnIncidentOccurred(e): void { if (!e.component._eventsStrategy.hasEvent('incidentOccurred')) { - log.apply(null, [e.target.id].concat(e.target.args || [])); + log.apply(null, [e.target.id].concat(e.target.args || []) as [string, ...unknown[]]); } } diff --git a/packages/devextreme/js/__internal/viz/series/financial_series.ts b/packages/devextreme/js/__internal/viz/series/financial_series.ts index 501a7d63febe..1897689b4edd 100644 --- a/packages/devextreme/js/__internal/viz/series/financial_series.ts +++ b/packages/devextreme/js/__internal/viz/series/financial_series.ts @@ -295,7 +295,8 @@ export const candlestick = _extend({}, stock, { _parsePointStyle(style, defaultColor, innerColor) { const color = extractColor(style.color, true) || innerColor; - const base = stock._parsePointStyle.call(this, style, defaultColor, color); + const base: ReturnType & { hatching?: unknown } = stock + ._parsePointStyle.call(this, style, defaultColor, color); base.fill = color; base.hatching = style.hatching; return base; diff --git a/packages/devextreme/js/__internal/viz/vector_map/map_layer.ts b/packages/devextreme/js/__internal/viz/vector_map/map_layer.ts index 120e9418544f..53ee223d04f8 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/map_layer.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/map_layer.ts @@ -309,6 +309,12 @@ const emptyStrategy = { getDefaultColor: _noop, }; +type LayerStrategy = typeof emptyStrategy & { + type?: string; + fullType?: string; + elementType?: string; +}; + const strategiesByType = {}; const strategiesByGeometry = {}; const strategiesByElementType = {}; @@ -319,7 +325,7 @@ let selectStrategy = function (options, data) { let type = _normalizeEnum(options.type); let elementType = _normalizeEnum(options.elementType); let sample; - const strategy = _extend({}, emptyStrategy); + const strategy: LayerStrategy = _extend({}, emptyStrategy); if (data.count() > 0) { sample = data.geometry(data.item(0)); type = strategiesByType[type] ? type : guessTypeByData(sample);