diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/scrollTo.ts b/e2e/testcafe-devextreme/tests/scheduler/common/scrollTo.ts index 03be9d4df440..e405b63bcacc 100644 --- a/e2e/testcafe-devextreme/tests/scheduler/common/scrollTo.ts +++ b/e2e/testcafe-devextreme/tests/scheduler/common/scrollTo.ts @@ -230,3 +230,91 @@ test('ScrollTo works correctly in timeline RTL (native, sync header/workspace)', height: 580, rtlEnabled: true, })); + +[ + // startDayHour: 6:00, endDayHour: 18:00 + { + offset: 0, + targetDate: new Date(2021, 1, 3, 4, 0), + expectedDate: new Date(2021, 1, 3, 6, 0), + }, + { + offset: 0, + targetDate: new Date(2021, 1, 3, 12, 0), + expectedDate: new Date(2021, 1, 3, 12, 0), + }, + { + offset: 0, + targetDate: new Date(2021, 1, 3, 20, 0), + expectedDate: new Date(2021, 1, 3, 18, 0), + }, + + // startDayHour: 18:00, endDayHour: next day 6:00 + { + offset: 720, + targetDate: new Date(2021, 1, 3, 10, 0), + expectedDate: new Date(2021, 1, 3, 6, 0), + }, + { + offset: 720, + targetDate: new Date(2021, 1, 3, 20, 0), + expectedDate: new Date(2021, 1, 3, 20, 0), + }, + { + offset: 720, + targetDate: new Date(2021, 1, 4, 1, 0), + expectedDate: new Date(2021, 1, 4, 1, 0), + }, + { + offset: 720, + targetDate: new Date(2021, 1, 4, 7, 0), + expectedDate: new Date(2021, 1, 4, 6, 0), + }, + + // startDayHour: prev day 18:00, endDayHour: 6:00 + { + offset: -720, + targetDate: new Date(2021, 1, 3, 16, 0), + expectedDate: new Date(2021, 1, 3, 18, 0), + }, + { + offset: -720, + targetDate: new Date(2021, 1, 3, 21, 0), + expectedDate: new Date(2021, 1, 3, 21, 0), + }, + { + offset: -720, + targetDate: new Date(2021, 1, 4, 3, 0), + expectedDate: new Date(2021, 1, 4, 3, 0), + }, + { + offset: -720, + targetDate: new Date(2021, 1, 3, 7, 0), + expectedDate: new Date(2021, 1, 3, 6, 0), + }, +].forEach(({ offset, targetDate, expectedDate }) => { + test(`scrollTo should scroll to date with offset=${offset}, targetDate=${targetDate.toString()} (T1310544)`, async (t) => { + const scheduler = new Scheduler('#container'); + + await scheduler.scrollTo(targetDate); + + const cellData = await scheduler.getCellDataAtViewportCenter(); + + await t + .expect(expectedDate.getTime()).gte(cellData.startDate.getTime()) + // eslint-disable-next-line spellcheck/spell-checker + .expect(expectedDate.getTime()).lte(cellData.endDate.getTime()); + }).before(async () => createScheduler({ + dataSource: [], + views: [{ + type: 'timelineWeek', + offset, + cellDuration: 60, + }], + currentView: 'timelineWeek', + currentDate: new Date(2021, 1, 2), + startDayHour: 6, + endDayHour: 18, + height: 580, + })); +}); diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_timeline.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_timeline.ts index 849115ee7a13..89679e7d539c 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_timeline.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_timeline.ts @@ -280,7 +280,10 @@ class SchedulerTimeline extends SchedulerWorkSpace { } scrollToTime(hours, minutes, date) { - const coordinates = this._getScrollCoordinates(hours, minutes, date); + date = date || new Date(this.option('currentDate')); + date.setHours(hours, minutes, 0, 0); + + const coordinates = this._getScrollCoordinates(date); const scrollable = this.getScrollable(); const offset = this.option('rtlEnabled') ? getBoundingRect(this.getScrollableContainer().get(0)).width : 0; diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts index 8f7490714114..0a4353a5afe5 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts @@ -1404,26 +1404,20 @@ class SchedulerWorkSpace extends Widget { return (this.$element() as any).find(`.${GROUP_HEADER_CLASS}`); } - _getScrollCoordinates(hours, minutes, date, groupIndex?: any, allDay?: any) { + _getScrollCoordinates(date, groupIndex?: any, allDay?: any) { const currentDate = date || new Date(this.option('currentDate')); - const startDayHour = this.option('startDayHour'); - const endDayHour = this.option('endDayHour'); - if (hours < startDayHour) { - hours = startDayHour; - } + const cell = this.viewDataProvider.findGlobalCellPosition(currentDate, groupIndex, allDay, true); - if (hours >= endDayHour) { - hours = endDayHour - 1; + if (!cell) { + return undefined; } - currentDate.setHours(hours, minutes, 0, 0); - - const cell = this.viewDataProvider.findGlobalCellPosition(currentDate, groupIndex, allDay); + currentDate.setHours(cell.cellData.startDate.getHours(), currentDate.getMinutes(), 0, 0); return this.virtualScrollingDispatcher.calculateCoordinatesByDataAndPosition( - cell?.cellData, - cell?.position, + cell.cellData, + cell.position, currentDate, isDateAndTimeView(this.type as any), this.viewDirection === 'vertical', @@ -1825,7 +1819,9 @@ class SchedulerWorkSpace extends Widget { return; } - const coordinates = this._getScrollCoordinates(hours, minutes, date); + date = date || new Date(this.option('currentDate')); + date.setHours(hours, minutes, 0, 0); + const coordinates = this._getScrollCoordinates(date); const scrollable = this.getScrollable(); @@ -1845,7 +1841,11 @@ class SchedulerWorkSpace extends Widget { : 0; const isScrollToAllDay = allDay && this.isAllDayPanelVisible; - const coordinates = this._getScrollCoordinates(date.getHours(), date.getMinutes(), date, groupIndex, isScrollToAllDay); + const coordinates = this._getScrollCoordinates(date, groupIndex, isScrollToAllDay); + + if (!coordinates) { + return; + } const scrollable = this.getScrollable(); const $scrollable = scrollable.$element(); @@ -1877,8 +1877,9 @@ class SchedulerWorkSpace extends Widget { } _isValidScrollDate(date, throwWarning = true) { - const min = this.getStartViewDate(); - const max = this.getEndViewDate(); + const viewOffset = this.option('viewOffset') as number; + const min = new Date(this.getStartViewDate().getTime() + viewOffset); + const max = new Date(this.getEndViewDate().getTime() + viewOffset); if (date < min || date > max) { throwWarning && errors.log('W1008', date); diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/view_model/m_view_data_provider.ts b/packages/devextreme/js/__internal/scheduler/workspaces/view_model/m_view_data_provider.ts index 49b903a2802e..c1bb4a3b77ae 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/view_model/m_view_data_provider.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/view_model/m_view_data_provider.ts @@ -282,51 +282,67 @@ export default class ViewDataProvider { return startDate < groupEndDate && endDate > groupStartDate; } - findGlobalCellPosition(date, groupIndex = 0, allDay = false) { + findGlobalCellPosition(date, groupIndex = 0, allDay = false, findClosest = false) { const { completeViewDataMap } = this; const showAllDayPanel = this._options.isAllDayPanelVisible; + let resultDiff = Number.MAX_VALUE; + let resultCellData: ViewCellData | undefined; + let resultCellColumnIndex = -1; + let resultCellRowIndex = -1; + + const getCellPosition = (columnIndex: number, rowIndex: number) => ({ + columnIndex, + rowIndex: showAllDayPanel && !this._options.isVerticalGrouping + ? rowIndex - 1 + : rowIndex, + }); + for (let rowIndex = 0; rowIndex < completeViewDataMap.length; rowIndex += 1) { const currentRow = completeViewDataMap[rowIndex]; for (let columnIndex = 0; columnIndex < currentRow.length; columnIndex += 1) { const cellData = currentRow[columnIndex]; const { - startDate: currentStartDate, - endDate: currentEndDate, - groupIndex: currentGroupIndex, - allDay: currentAllDay, + startDate: cellStartDate, + endDate: cellEndDate, + groupIndex: cellGroupIndex, + allDay: cellAllDay, } = cellData; - if (groupIndex === currentGroupIndex - && allDay === Boolean(currentAllDay) - && this._compareDatesAndAllDay(date, currentStartDate, currentEndDate, allDay)) { + if (groupIndex !== cellGroupIndex || allDay !== Boolean(cellAllDay)) { + continue; + } + + const isDateInCell = allDay + ? dateUtils.sameDate(date, cellStartDate) + : date >= cellStartDate && date < cellEndDate; + + if (isDateInCell) { return { - position: { - columnIndex, - rowIndex: showAllDayPanel && !this._options.isVerticalGrouping - ? rowIndex - 1 - : rowIndex, - }, + position: getCellPosition(columnIndex, rowIndex), cellData, }; } + + const diff = Math.abs(date.getTime() - cellStartDate.getTime()); + + if (findClosest && diff < resultDiff) { + resultDiff = diff; + resultCellData = cellData; + resultCellColumnIndex = columnIndex; + resultCellRowIndex = rowIndex; + } } } - return undefined; - } - - private _compareDatesAndAllDay( - date: Date, - cellStartDate: Date, - cellEndDate: Date, - allDay: boolean, - ): boolean { - return allDay - ? dateUtils.sameDate(date, cellStartDate) - : date >= cellStartDate && date < cellEndDate; + return resultCellData + ? { + position: getCellPosition(resultCellColumnIndex, resultCellRowIndex), + cellData: resultCellData, + } + : undefined; } getSkippedDaysCount(groupIndex, startDate, endDate, daysCount) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/scrollToTime.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/scrollToTime.tests.js index f62962b562a9..3d02e73d8477 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/scrollToTime.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/scrollToTime.tests.js @@ -142,7 +142,7 @@ QUnit.module('Scrolling to time', () => { assert.roughEqual( scrollBy.getCall(0).args[0].left, - scheduler.instance._workSpace._getScrollCoordinates(9, 5, new Date(2015, 1, 9)).left, + scheduler.instance._workSpace._getScrollCoordinates(new Date(2015, 1, 9, 9, 5)).left, 1.001, 'scrollBy was called with right distance', ); @@ -170,7 +170,7 @@ QUnit.module('Scrolling to time', () => { assert.roughEqual( scrollBy.getCall(0).args[0].left, - scheduler.instance._workSpace._getScrollCoordinates(9, 5, new Date(2015, 1, 9)).left - scrollLeft - offset, + scheduler.instance._workSpace._getScrollCoordinates(new Date(2015, 1, 9, 9, 5)).left - scrollLeft - offset, 1.001, 'scrollBy was called with right distance', ); @@ -196,7 +196,7 @@ QUnit.module('Scrolling to time', () => { assert.roughEqual( scrollBy.getCall(0).args[0].left, - scheduler.instance._workSpace._getScrollCoordinates(9, 5, new Date(2015, 1, 11)).left, + scheduler.instance._workSpace._getScrollCoordinates(new Date(2015, 1, 11, 9, 5)).left, 1.001, 'scrollBy was called with right distance', ); @@ -225,7 +225,7 @@ QUnit.module('Scrolling to time', () => { assert.roughEqual( scrollBy.getCall(0).args[0].left, - scheduler.instance._workSpace._getScrollCoordinates(9, 5, new Date(2015, 1, 11)).left - scrollLeft - offset, + scheduler.instance._workSpace._getScrollCoordinates(new Date(2015, 1, 11, 9, 5)).left - scrollLeft - offset, 1.001, 'scrollBy was called with right distance', ); diff --git a/packages/testcafe-models/scheduler/index.ts b/packages/testcafe-models/scheduler/index.ts index 208518cf7248..a8fd2502b6cd 100644 --- a/packages/testcafe-models/scheduler/index.ts +++ b/packages/testcafe-models/scheduler/index.ts @@ -159,6 +159,28 @@ export default class Scheduler extends Widget { return cells.filter(`.${CLASS.focusedCell}`); } + getCellDataAtViewportCenter(): any { + const { getInstance } = this; + + return ClientFunction( + () => { + const instance = getInstance() as any; + const workSpace = instance.getWorkSpace(); + const scrollable = workSpace.getScrollable(); + const scrollLeft = scrollable.scrollLeft(); + const scrollTop = scrollable.scrollTop(); + const centerX = scrollLeft + scrollable.$element().width() / 2; + const centerY = scrollTop + scrollable.$element().height() / 2; + + const cellElement = workSpace.getCellByCoordinates({ top: centerY, left: centerX }, false); + const cellData = workSpace.getCellData(cellElement); + + return cellData; + }, + { dependencies: { getInstance } } + )(); + } + getSelectedCells(isAllDay = false): Selector { const cells = isAllDay ? this.allDayTableCells : this.dateTableCells; @@ -193,13 +215,15 @@ export default class Scheduler extends Widget { scrollTo(date: Date, group?: Record, allDay?: boolean): Promise { const { getInstance } = this; - const scrollTo = (): any => (getInstance() as any).scrollTo(date, group, allDay); - return ClientFunction(scrollTo, { - dependencies: { - date, group, allDay, getInstance, - }, - })(); + return ClientFunction( + () => { + const instance = getInstance() as any; + instance.scrollTo(date, group, allDay); + }, { + dependencies: { date, group, allDay, getInstance }, + } + )(); } hideAppointmentTooltip(): Promise {