From b5c1888466ed3033ae708aac834b85228f7fa2e5 Mon Sep 17 00:00:00 2001 From: Sergei Burkatskii <5622446+sjbur@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:34:24 +0200 Subject: [PATCH 1/2] fix: fix bug --- .../resizeAppointments/verticalGrouping.ts | 12 +++------- .../appointments/appointment/m_appointment.ts | 8 ++++++- .../js/__internal/scheduler/m_subscribes.ts | 8 +++++++ .../scheduler/workspaces/work_space.ts | 22 +++++++++++++++++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/scheduler/common/resizeAppointments/verticalGrouping.ts b/e2e/testcafe-devextreme/tests/scheduler/common/resizeAppointments/verticalGrouping.ts index 79f835adf74b..2a6664d4b291 100644 --- a/e2e/testcafe-devextreme/tests/scheduler/common/resizeAppointments/verticalGrouping.ts +++ b/e2e/testcafe-devextreme/tests/scheduler/common/resizeAppointments/verticalGrouping.ts @@ -12,16 +12,12 @@ test('Should correctly calculate group resizing area (T1025952)', async (t) => { await t .drag(firstAppointment.resizableHandle.bottom, 0, 100) - .expect(firstAppointment.size.height) - .eql('140.594px') .expect(firstAppointment.date.time) - .eql('9:30 AM - 11:21 AM') + .eql('9:30 AM - 11:00 AM') .drag(secondAppointment.resizableHandle.bottom, 0, 100) - .expect(secondAppointment.size.height) - .eql('165.922px') .expect(secondAppointment.date.time) - .eql('9:30 AM - 11:41 AM'); + .eql('9:30 AM - 11:00 AM'); }).before(async () => createScheduler({ dataSource: [ { @@ -74,10 +70,8 @@ test('Should correctly calculate group resizing area after scroll (T1041672)', a await t .drag(appointment.resizableHandle.bottom, 0, 100) - .expect(appointment.size.height) - .eql('165.922px') .expect(appointment.date.time) - .eql('9:30 AM - 11:41 AM'); + .eql('9:30 AM - 11:00 AM'); }).before(async () => createScheduler({ dataSource: [ { diff --git a/packages/devextreme/js/__internal/scheduler/appointments/appointment/m_appointment.ts b/packages/devextreme/js/__internal/scheduler/appointments/appointment/m_appointment.ts index 828f6172d282..f5c9d3dad308 100644 --- a/packages/devextreme/js/__internal/scheduler/appointments/appointment/m_appointment.ts +++ b/packages/devextreme/js/__internal/scheduler/appointments/appointment/m_appointment.ts @@ -282,7 +282,13 @@ export class Appointment extends DOMComponent { _createResizingConfig() { const config: any = this.option('direction') === 'vertical' ? this._getVerticalResizingRule() : this._getHorizontalResizingRule(); - if (!this.invoke('isGroupedByDate')) { + const cellHeight = Math.round(this.invoke('getCellHeight') ?? 0); + const allDayHeight = Math.round(this.invoke('getAllDayHeight') ?? 0); + const allDayBreaksCellGrid = Boolean(this.invoke('isVerticalGroupedWorkSpace')) + && allDayHeight > 0 + && allDayHeight !== cellHeight; + + if (!this.invoke('isGroupedByDate') && !allDayBreaksCellGrid) { config.stepPrecision = 'strict'; } diff --git a/packages/devextreme/js/__internal/scheduler/m_subscribes.ts b/packages/devextreme/js/__internal/scheduler/m_subscribes.ts index 93f5f76ba0ea..7861a106b7c3 100644 --- a/packages/devextreme/js/__internal/scheduler/m_subscribes.ts +++ b/packages/devextreme/js/__internal/scheduler/m_subscribes.ts @@ -200,6 +200,14 @@ const subscribes = { return this.getWorkSpace().getCellHeight(); }, + getAllDayHeight() { + return this.getWorkSpace().getAllDayHeight(); + }, + + isVerticalGroupedWorkSpace() { + return this.getWorkSpace().isVerticalGroupedWorkSpace(); + }, + needCorrectAppointmentDates() { return !['month', 'timelineMonth'].includes(this.currentView.type); }, diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts index bd0149d8a5e3..c84259410e89 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts @@ -1937,6 +1937,28 @@ class SchedulerWorkSpace extends Widget { } getGroupBoundsVertical(groupIndex: number): GroupBoundsOffset | undefined { + const groupedMap = this.viewDataProvider.groupedDataMap.dateTableGroupedMap[groupIndex]; + const firstRowIndex = Number(this.viewDataProvider.hasGroupAllDayPanel(groupIndex)); + const firstCell = groupedMap?.[firstRowIndex]?.[0]; + const lastRow = groupedMap?.[groupedMap.length - 1]; + const lastCell = lastRow?.[lastRow.length - 1]; + + if (firstCell && lastCell) { + const $first = this.domGetDateCell(firstCell.position); + const $last = this.domGetDateCell(lastCell.position); + const firstOffset = $first.offset(); + const lastOffset = $last.offset(); + + if ($first.length && $last.length && firstOffset && lastOffset) { + return { + left: firstOffset.left, + right: lastOffset.left + (getOuterWidth($last) as number), + top: firstOffset.top, + bottom: lastOffset.top + (getOuterHeight($last) as number), + }; + } + } + const $firstAndLastCells = this.getFirstAndLastDataTableCell(); if (this.groupedStrategy instanceof VerticalGroupedStrategy) { return this.groupedStrategy.getGroupBoundsOffset(groupIndex, [ From 25f737bb1da2ef1402651cd7055fdbb5942d7c94 Mon Sep 17 00:00:00 2001 From: Sergei Burkatskii <5622446+sjbur@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:48:11 +0200 Subject: [PATCH 2/2] fix: copilot review --- .../scheduler/workspaces/work_space.ts | 56 ++++++------------- ...rk_space_grouped_strategy_vertical.test.ts | 37 ++++++------ .../work_space_grouped_strategy_vertical.ts | 44 ++++----------- 3 files changed, 44 insertions(+), 93 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts index c84259410e89..795f0143b29c 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts @@ -830,11 +830,6 @@ class SchedulerWorkSpace extends Widget { useKeyboard: false, bounceEnabled: false, updateManually: true, - onScroll: () => { - if (this.groupedStrategy instanceof VerticalGroupedStrategy) { - this.groupedStrategy.cache.clear(); - } - }, // TODO (Scrollable:useKeyboard) -> remove this WA // after ScrollView private option "useKeyboard" will be extended to useNative: true // NOTE: Scrollable container focusable by default @@ -1635,15 +1630,6 @@ class SchedulerWorkSpace extends Widget { return this.$element().find(`.${cellClass}`); } - private getFirstAndLastDataTableCell(): Element[] { - const selector = this.isVirtualScrolling() - ? `.${DATE_TABLE_CELL_CLASS}, .${VIRTUAL_CELL_CLASS}` - : `.${DATE_TABLE_CELL_CLASS}`; - - const $cells = this.$element().find(selector); - return [$cells.get(0), $cells.get(-1)]; - } - private getAllCells(allDay: boolean): dxElementWrapper { if (this.isVerticalGroupedWorkSpace()) { return this.$dateTable.find(`td:not(.${VIRTUAL_CELL_CLASS})`); @@ -1937,35 +1923,25 @@ class SchedulerWorkSpace extends Widget { } getGroupBoundsVertical(groupIndex: number): GroupBoundsOffset | undefined { - const groupedMap = this.viewDataProvider.groupedDataMap.dateTableGroupedMap[groupIndex]; - const firstRowIndex = Number(this.viewDataProvider.hasGroupAllDayPanel(groupIndex)); - const firstCell = groupedMap?.[firstRowIndex]?.[0]; - const lastRow = groupedMap?.[groupedMap.length - 1]; - const lastCell = lastRow?.[lastRow.length - 1]; - - if (firstCell && lastCell) { - const $first = this.domGetDateCell(firstCell.position); - const $last = this.domGetDateCell(lastCell.position); - const firstOffset = $first.offset(); - const lastOffset = $last.offset(); - - if ($first.length && $last.length && firstOffset && lastOffset) { - return { - left: firstOffset.left, - right: lastOffset.left + (getOuterWidth($last) as number), - top: firstOffset.top, - bottom: lastOffset.top + (getOuterHeight($last) as number), - }; - } + if (!(this.groupedStrategy instanceof VerticalGroupedStrategy)) { + return undefined; } - const $firstAndLastCells = this.getFirstAndLastDataTableCell(); - if (this.groupedStrategy instanceof VerticalGroupedStrategy) { - return this.groupedStrategy.getGroupBoundsOffset(groupIndex, [ - $firstAndLastCells[0], $firstAndLastCells[1], - ]); + const $dateTable = this.getDateTable(); + const dateTableOffset = $dateTable.offset(); + + if (!dateTableOffset) { + return undefined; } - return undefined; + + const { top, height } = this.groupedStrategy.getGroupVerticalOffset(groupIndex); + + return { + left: dateTableOffset.left, + right: dateTableOffset.left + (getOuterWidth($dateTable) as number), + top: dateTableOffset.top + top, + bottom: dateTableOffset.top + top + height, + }; } getGroupBoundsHorizontal( diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_vertical.test.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_vertical.test.ts index 2913d192235d..8ca880893b08 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_vertical.test.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_vertical.test.ts @@ -72,16 +72,9 @@ describe('VerticalGroupedStrategy', () => { it('should use uniform group heights when group heights are not specified', () => { const strategy = new VerticalGroupedStrategy(createConfig()); - const result = strategy.getGroupBoundsOffset(2, [ - createElement({ left: 10 }), - createElement({ right: 710 }), - ]); - - expect(result).toEqual({ - left: 10, - right: 710, - top: 2 * 480 + 20 + 5 - 10, - bottom: 2 * 480 + 20 + 5 - 10 + 480, + expect(strategy.getGroupVerticalOffset(2)).toEqual({ + top: 2 * 480, + height: 480, }); }); @@ -90,16 +83,22 @@ describe('VerticalGroupedStrategy', () => { getGroupHeights: (): number[] => [100, 200, 300], })); - const result = strategy.getGroupBoundsOffset(2, [ - createElement({ left: 10 }), - createElement({ right: 710 }), - ]); + expect(strategy.getGroupVerticalOffset(2)).toEqual({ + top: 100 + 200, + height: 300, + }); + }); + + it('should offset group bounds by the all-day row height, not by the cell height', () => { + const strategy = new VerticalGroupedStrategy(createConfig({ + getGroupHeights: (): number[] => [100, 200, 300], + supportAllDayRow: (): boolean => true, + showAllDayPanel: (): boolean => true, + })); - expect(result).toEqual({ - left: 10, - right: 710, - top: 100 + 200 + 20 + 5 - 10, - bottom: 100 + 200 + 20 + 5 - 10 + 300, + expect(strategy.getGroupVerticalOffset(2)).toEqual({ + top: 100 + 200 + 20 * 3, + height: 300, }); }); diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_vertical.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_vertical.ts index 8018efecb51c..bff824ac95ca 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_vertical.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_vertical.ts @@ -1,19 +1,14 @@ import type { dxElementWrapper } from '@js/core/renderer'; import { getBoundingRect } from '@js/core/utils/position'; import { calculateDayDuration, getVerticalGroupCountClass } from '@ts/scheduler/r1/utils/index'; -import type { CellPositionData, GroupBoundsOffset } from '@ts/scheduler/types'; +import type { CellPositionData } from '@ts/scheduler/types'; import { WORK_SPACE_BORDER_PX } from '@ts/scheduler/workspaces/const'; import { FIRST_GROUP_CELL_CLASS, LAST_GROUP_CELL_CLASS } from '../classes'; -import { Cache } from '../global_cache'; import type { ResourceLoader } from '../utils/loader/resource_loader'; import type { GroupedStrategyConfig } from './work_space_grouped_strategy_config'; class VerticalGroupedStrategy { - cache = new Cache(); - - private groupBoundsOffset!: GroupBoundsOffset; - constructor(private readonly config: GroupedStrategyConfig) {} prepareCellIndexes(cellCoordinates: CellPositionData, groupIndex: number, inAllDayRow: boolean) @@ -108,35 +103,16 @@ class VerticalGroupedStrategy { return offset; } - getGroupBoundsOffset(groupIndex: number, [$firstCell, $lastCell]: [Element, Element]) - : GroupBoundsOffset { - const groupHeightsKey = this.config.getGroupHeights?.()?.join('.') ?? ''; - - return this.cache.memo(`groupBoundsOffset${groupIndex}.${groupHeightsKey}`, () => { - const groupHeight = this.getGroupHeight(groupIndex); - const scrollTop = this.getScrollableScrollTop(); - const headerRowHeight = getBoundingRect(this.config.getHeaderPanelContainerElement()).height; - - let topOffset = this.getCumulativeGroupOffset(groupIndex) + headerRowHeight - + this.config.getHeaderHeight() - scrollTop; - - if (this.config.showAllDayPanel() && this.config.supportAllDayRow()) { - topOffset += this.config.getCellHeight() * (groupIndex + 1); - } - - const bottomOffset = topOffset + groupHeight; - - const { left } = $firstCell.getBoundingClientRect(); - const { right } = $lastCell.getBoundingClientRect(); - this.groupBoundsOffset = { - left, - right, - top: topOffset, - bottom: bottomOffset, - }; + getGroupVerticalOffset(groupIndex: number): { top: number; height: number } { + const hasAllDayRows = this.config.showAllDayPanel() && this.config.supportAllDayRow(); + const allDayOffset = hasAllDayRows + ? this.config.getAllDayHeight() * (groupIndex + 1) + : 0; - return this.groupBoundsOffset; - }); + return { + top: this.getCumulativeGroupOffset(groupIndex) + allDayOffset, + height: this.getGroupHeight(groupIndex), + }; } shiftIndicator($indicator: dxElementWrapper, height: number, rtlOffset: number, i: number): void {