diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end (fluent.blue.light).png new file mode 100644 index 000000000000..c0d53fa47e16 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end-with-current-time-indicator (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end-with-current-time-indicator (fluent.blue.light).png new file mode 100644 index 000000000000..cbedbc14a593 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-end-with-current-time-indicator (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start (fluent.blue.light).png new file mode 100644 index 000000000000..4ba67ea48a26 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start-with-current-time-indicator (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start-with-current-time-indicator (fluent.blue.light).png new file mode 100644 index 000000000000..ef5ceaeb7028 Binary files /dev/null and b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/etalons/shader-virtual-scrolling-week-start-with-current-time-indicator (fluent.blue.light).png differ diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/shaderVirtualScrolling.ts b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/shaderVirtualScrolling.ts new file mode 100644 index 000000000000..7df681256759 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/scheduler/common/layout/timeIndication/shaderVirtualScrolling.ts @@ -0,0 +1,106 @@ +import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; +import Scheduler from 'devextreme-testcafe-models/scheduler'; +import { insertStylesheetRulesToPage } from '../../../../../helpers/domUtils'; +import { createWidget } from '../../../../../helpers/createWidget'; +import url from '../../../../../helpers/getPageUrl'; +import { testScreenshot } from '../../../../../helpers/themeUtils'; + +fixture.disablePageReloads`Scheduler: Current Time Indication: Shader with Virtual Scrolling` + .page(url(__dirname, '../../../../container.html')) + .beforeEach(async (t) => { + await t.resizeWindow(2560, 600); + }); + +const style = ` +.dx-scheduler-date-time-shader-top::before, +.dx-scheduler-date-time-shader-bottom::before, +.dx-scheduler-timeline .dx-scheduler-date-time-shader::before, +.dx-scheduler-date-time-shader-all-day { + background-color: red !important; +}`; + +const resources = [ + { text: 'Room 1', id: 1, color: '#cb6bb2' }, + { text: 'Room 2', id: 2, color: '#56ca85' }, + { text: 'Room 3', id: 3, color: '#1e90ff' }, + { text: 'Room 4', id: 4, color: '#ff9747' }, + { text: 'Room 5', id: 5, color: '#ff6a00' }, + { text: 'Room 6', id: 6, color: '#ffc0cb' }, +]; + +test('Should render shader correct with virtual scrolling without current time indicator', async (t) => { + const scheduler = new Scheduler('#container'); + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + + await testScreenshot( + t, + takeScreenshot, + 'shader-virtual-scrolling-week-start.png', + ); + + await scheduler.scrollTo(new Date(2025, 9, 15, 17, 30), { roomId: 6 }); + + await testScreenshot( + t, + takeScreenshot, + 'shader-virtual-scrolling-week-end.png', + ); + + await t.expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => { + await insertStylesheetRulesToPage(style); + + await createWidget('dxScheduler', { + dataSource: [], + currentView: 'week', + views: ['week'], + groups: ['roomId'], + resources: [{ fieldExpr: 'roomId', dataSource: resources, label: 'Room' }], + startDayHour: 8, + endDayHour: 18, + currentDate: new Date(2025, 9, 15), + height: 400, + shadeUntilCurrentTime: true, + scrolling: { mode: 'virtual' }, + }); +}); + +test('Should render shader correctly with virtual scrolling and current time indicator', async (t) => { + const scheduler = new Scheduler('#container'); + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + + await testScreenshot( + t, + takeScreenshot, + 'shader-virtual-scrolling-week-start-with-current-time-indicator.png', + ); + + await scheduler.scrollTo(new Date(2025, 9, 15, 17, 30), { roomId: 6 }); + + await testScreenshot( + t, + takeScreenshot, + 'shader-virtual-scrolling-week-end-with-current-time-indicator.png', + ); + + await t.expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => { + await insertStylesheetRulesToPage(style); + + await createWidget('dxScheduler', { + dataSource: [], + currentView: 'week', + views: ['week'], + groups: ['roomId'], + resources: [{ fieldExpr: 'roomId', dataSource: resources, label: 'Room' }], + startDayHour: 8, + endDayHour: 18, + currentDate: new Date(2025, 9, 15), + indicatorTime: new Date(2025, 9, 15, 17, 30), + height: 400, + shadeUntilCurrentTime: true, + scrolling: { mode: 'virtual' }, + }); +}); diff --git a/packages/devextreme/js/__internal/scheduler/shaders/m_current_time_shader_vertical.ts b/packages/devextreme/js/__internal/scheduler/shaders/m_current_time_shader_vertical.ts index 956306d8a26d..cca4af93001b 100644 --- a/packages/devextreme/js/__internal/scheduler/shaders/m_current_time_shader_vertical.ts +++ b/packages/devextreme/js/__internal/scheduler/shaders/m_current_time_shader_vertical.ts @@ -35,7 +35,7 @@ class VerticalCurrentTimeShader extends CurrentTimeShader { _renderShaderParts(groupCount, shaderHeight, maxHeight, isSolidShader) { for (let i = 0; i < groupCount; i++) { - const shaderWidth = this._getShaderWidth(i); + const shaderWidth = this._getShaderWidth(); this._renderTopShader(this._$shader, shaderHeight, shaderWidth, i); !isSolidShader && this._renderBottomShader(this._$shader, maxHeight, shaderHeight, shaderWidth, i); @@ -45,7 +45,7 @@ class VerticalCurrentTimeShader extends CurrentTimeShader { } _renderGroupedByDateShaderParts(groupCount, shaderHeight, maxHeight, isSolidShader) { - const shaderWidth = this._getShaderWidth(0); + const shaderWidth = this._getShaderWidth(); let bottomShaderWidth = shaderWidth - this._workSpace.getCellWidth(); if (shaderHeight < 0) { @@ -116,8 +116,8 @@ class VerticalCurrentTimeShader extends CurrentTimeShader { return this._workSpace.getGroupedStrategy().getShaderMaxHeight(); } - _getShaderWidth(i) { - return this._workSpace.getGroupedStrategy().getShaderWidth(i); + _getShaderWidth() { + return this._workSpace.getGroupedStrategy().getShaderWidth(); } clean() { 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..bd5b7925de72 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts @@ -1560,31 +1560,6 @@ class SchedulerWorkSpace extends Widget { return DATE_TABLE_MIN_CELL_WIDTH; } - getRoundedCellWidth(groupIndex, startIndex, cellCount) { - if (groupIndex < 0 || !hasWindow()) { - return 0; - } - - const $row = (this.$element() as any).find(`.${DATE_TABLE_ROW_CLASS}`).eq(0); - let width = 0; - const $cells = $row.find(`.${DATE_TABLE_CELL_CLASS}`); - const totalCellCount = this._getCellCount() * groupIndex; - - cellCount = cellCount || this._getCellCount(); - - if (!isDefined(startIndex)) { - startIndex = totalCellCount; - } - - for (let i = startIndex; i < totalCellCount + cellCount; i++) { - const element = $($cells).eq(i).get(0); - const elementWidth = element ? getBoundingRect(element).width : 0; - width += elementWidth; - } - - return width / (totalCellCount + cellCount - startIndex); - } - // Mappings getCellWidth() { return getCellWidth(this.getDOMElementsMetaData()); diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_grouped_strategy_horizontal.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_grouped_strategy_horizontal.ts index b5cea019dcc2..121ac7ce3712 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_grouped_strategy_horizontal.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_grouped_strategy_horizontal.ts @@ -142,17 +142,17 @@ class HorizontalGroupedStrategy { _calculateOffset(groupIndex) { const indicatorStartPosition = this._workSpace.getIndicatorOffset(groupIndex); - const offset = this._workSpace._getCellCount() * this._workSpace.getRoundedCellWidth(groupIndex - 1, 0) * groupIndex; + const offset = this._workSpace._getCellCount() * this._workSpace.getCellWidth() * groupIndex; return indicatorStartPosition + offset; } _calculateGroupByDateOffset(groupIndex) { - return this._workSpace.getIndicatorOffset(0) * this._workSpace._getGroupCount() + this._workSpace.getRoundedCellWidth(groupIndex - 1, 0) * groupIndex; + return this._workSpace.getIndicatorOffset(0) * this._workSpace._getGroupCount() + this._workSpace.getCellWidth() * groupIndex; } getShaderOffset(i, width) { - const offset = this._workSpace._getCellCount() * this._workSpace.getRoundedCellWidth(i - 1) * i; + const offset = this._workSpace._getCellCount() * this._workSpace.getCellWidth() * i; return this._workSpace.option('rtlEnabled') ? getBoundingRect(this._workSpace._dateTableScrollable.$content().get(0)).width - offset - this._workSpace.getTimePanelWidth() - width : offset; } @@ -170,8 +170,8 @@ class HorizontalGroupedStrategy { return getBoundingRect(this._workSpace._dateTableScrollable.$content().get(0)).height; } - getShaderWidth(i) { - return this._workSpace.getIndicationWidth(i); + getShaderWidth() { + return this._workSpace.getIndicationWidth(); } getScrollableScrollTop(allDay) { diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_grouped_strategy_vertical.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_grouped_strategy_vertical.ts index 1c3e6597bdcb..8edcbf3a854b 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_grouped_strategy_vertical.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_grouped_strategy_vertical.ts @@ -159,7 +159,7 @@ class VerticalGroupedStrategy { } getShaderWidth() { - return this._workSpace.getIndicationWidth(0); + return this._workSpace.getIndicationWidth(); } getScrollableScrollTop() { diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_indicator.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_indicator.ts index a23fd9e7b445..d7c00cba9354 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_indicator.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space_indicator.ts @@ -107,31 +107,30 @@ class SchedulerWorkSpaceIndicator extends SchedulerWorkSpace { return true; } - getIndicationWidth(groupIndex) { - const maxWidth = this.getCellWidth() * this._getCellCount(); + getIndicationWidth() { + const cellCount = this._getCellCount(); + const cellSpan = Math.min(this._getIndicatorDaysSpan(), cellCount); + const width = cellSpan * this.getCellWidth(); + const maxWidth = this.getCellWidth() * cellCount; - let difference = this._getIndicatorDuration(); - if (difference > this._getCellCount()) { - difference = this._getCellCount(); - } - const width = difference * this.getRoundedCellWidth(groupIndex, groupIndex * this._getCellCount(), difference); - - return maxWidth < width ? maxWidth : width; + return Math.min(width, maxWidth); } - getIndicatorOffset(groupIndex) { - const difference = this._getIndicatorDuration() - 1; - const offset = difference * this.getRoundedCellWidth(groupIndex, groupIndex * this._getCellCount(), difference); + getIndicatorOffset() { + const cellSpan = this._getIndicatorDaysSpan() - 1; + const offset = cellSpan * this.getCellWidth(); return offset; } - _getIndicatorDuration() { + _getIndicatorDaysSpan(): number { const today = this._getToday(); - const firstViewDate = new Date(this.getStartViewDate()); - let timeDiff = today.getTime() - firstViewDate.getTime(); + const viewStartTime = this.getStartViewDate().getTime(); + let timeDiff = today.getTime() - viewStartTime; + if (this.option('type') === 'workWeek') { - timeDiff -= this._getWeekendsCount(Math.round(timeDiff / toMs('day'))) * toMs('day'); + const weekendDays = this._getWeekendsCount(Math.round(timeDiff / toMs('day'))) * toMs('day'); + timeDiff -= weekendDays; } return Math.ceil((timeDiff + 1) / toMs('day')); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/currentTimeIndicator.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/currentTimeIndicator.tests.js index 96a6fb31730d..bb69c6c167aa 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/currentTimeIndicator.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/currentTimeIndicator.tests.js @@ -151,7 +151,7 @@ QUnit.module('DateTime indicator on Day View', () => { assert.equal($indicators.length, 2, 'Indicator count is correct'); assert.equal($indicators.eq(0).position().left, 0); assert.equal($indicators.eq(0).position().top, 9.5 * cellHeight); - assert.equal($indicators.eq(1).position().left, instance.getRoundedCellWidth(1)); + assert.equal($indicators.eq(1).position().left, instance.getCellWidth()); assert.equal($indicators.eq(1).position().top, 9.5 * cellHeight); }); @@ -174,7 +174,7 @@ QUnit.module('DateTime indicator on Day View', () => { assert.equal($indicators.length, 2, 'Indicator count is correct'); assert.equal($indicators.eq(0).position().left, 0); assert.equal($indicators.eq(0).position().top, 9.5 * cellHeight); - assert.equal($indicators.eq(1).position().left, instance.getRoundedCellWidth(1)); + assert.equal($indicators.eq(1).position().left, instance.getCellWidth()); assert.equal($indicators.eq(1).position().top, 9.5 * cellHeight); }); @@ -357,7 +357,7 @@ QUnit.module('DateTime indicator on Day View', () => { const $shader = $element.find('.' + SCHEDULER_DATE_TIME_SHADER_CLASS); const $cell = instance.$element().find('.dx-scheduler-date-table-cell').eq(0); const cellHeight = $cell.get(0).getBoundingClientRect().height; - const cellWidth = instance.getRoundedCellWidth(1); + const cellWidth = instance.getCellWidth(); assert.roughEqual(getOuterHeight($shader), 9.5 * cellHeight, 1, 'Shader has correct height'); assert.roughEqual(getOuterWidth($shader), 9 * cellWidth, 5, 'Shader has correct width'); @@ -574,7 +574,7 @@ QUnit.module('DateTime indicator on Day View, vertical grouping', () => { const $shader = $element.find('.' + SCHEDULER_DATE_TIME_SHADER_CLASS); const $cell = instance.$element().find('.dx-scheduler-date-table-cell').eq(0); const cellHeight = getOuterHeight($cell); - const cellWidth = instance.getRoundedCellWidth(0); + const cellWidth = instance.getCellWidth(); assert.roughEqual(getOuterHeight($shader), 10.5 * cellHeight, 1, 'Shader has correct height'); assert.roughEqual(getOuterWidth($shader), 3 * cellWidth + 100, 5, 'Shader has correct width');