From 9a80ec6831a24919d0b429df3b7dc573743754bf Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 10:48:38 +0200 Subject: [PATCH 01/11] feat: implement feature --- .../widgets/base/scheduler/views/_index.scss | 43 +++------------ .../base/group_panel_vertical_node.test.tsx | 52 +++++++++++++++++++ .../base/group_panel_vertical_node.tsx | 3 ++ ...rk_space_grouped_strategy_vertical.test.ts | 8 +++ .../work_space_grouped_strategy_vertical.ts | 5 +- 5 files changed, 72 insertions(+), 39 deletions(-) diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss index a8250d8f5fef..d461be7dedc5 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss @@ -93,31 +93,6 @@ $scheduler-month-date-text-padding: 6px; } } -.dx-scheduler-group-column-count-one { - .dx-scheduler-header-panel-empty-cell { - width: $scheduler-left-column-width; - } -} - -.dx-scheduler-group-column-count-two { - .dx-scheduler-header-panel-empty-cell { - width: 1.5 * $scheduler-left-column-width; - } - - .dx-scheduler-group-flex-container .dx-scheduler-group-header { - width: 0.75 * $scheduler-left-column-width; - } -} - -.dx-scheduler-group-column-count-three { - .dx-scheduler-header-panel-empty-cell { - width: 2 * $scheduler-left-column-width; - } - - .dx-scheduler-group-flex-container .dx-scheduler-group-header { - width: math.div($scheduler-left-column-width * 2, 3); - } -} .dx-scheduler-work-space-week, .dx-scheduler-work-space-work-week, @@ -157,6 +132,8 @@ $scheduler-month-date-text-padding: 6px; } .dx-scheduler-work-space { + --dx-scheduler-group-header-width: #{$scheduler-left-column-width}; + border: $scheduler-base-border; border-color: $scheduler-base-border-color; background-color: $scheduler-workspace-background-color; @@ -942,23 +919,15 @@ $scheduler-month-date-text-padding: 6px; } .dx-scheduler-small { + .dx-scheduler-work-space { + --dx-scheduler-group-header-width: #{$scheduler-small-size-factor * $scheduler-left-column-width}; + } + .dx-scheduler-work-space-week, .dx-scheduler-work-space-work-week, .dx-scheduler-work-space-day { .dx-scheduler-header-panel-empty-cell { width: $scheduler-small-size-factor * $scheduler-left-column-width; } - - &.dx-scheduler-group-column-count-one .dx-scheduler-header-panel-empty-cell { - width: (1 + $scheduler-small-size-factor) * $scheduler-left-column-width; - } - - &.dx-scheduler-group-column-count-two .dx-scheduler-header-panel-empty-cell { - width: (1.5 + $scheduler-small-size-factor) * $scheduler-left-column-width; - } - - &.dx-scheduler-group-column-count-three .dx-scheduler-header-panel-empty-cell { - width: (2 + $scheduler-small-size-factor) * $scheduler-left-column-width; - } } } diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.test.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.test.tsx index 10d323687336..162eaf28593b 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.test.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.test.tsx @@ -69,4 +69,56 @@ describe('GroupPanelVerticalNode', () => { expect(nestedContainer.className).toBe('dx-scheduler-group-flex-container'); expect(nestedContainer.children).toHaveLength(2); }); + + it('should apply custom property width to non-leaf header cell', () => { + const parent: GroupPanelTreeNode = { + ...leafNode('parent', 'Building A', 2), + children: [leafNode('child1', 'Room A1', 1), leafNode('child2', 'Room A2', 1)], + }; + const component = new GroupPanelVerticalNode({ node: parent, index: 0 }); + const result = component.render() as VirtualNodeLike; + const children = result.children as VirtualNodeLike[]; + const headerCell = children[0]; + + expect(headerCell.props?.style).toEqual({ width: 'var(--dx-scheduler-group-header-width)' }); + }); + + it('should not apply width style to leaf header cell', () => { + const component = new GroupPanelVerticalNode({ node: leafNode('a', 'Room A', 1), index: 0 }); + const result = component.render() as VirtualNodeLike; + const children = result.children as VirtualNodeLike[]; + const headerCell = children[0]; + + expect(headerCell.props?.style).toEqual({}); + }); + + it('should apply custom property width consistently across non-uniform depth branches', () => { + const branch1Depth2: GroupPanelTreeNode = { + ...leafNode('building1', 'Building 1', 2), + children: [leafNode('room1', 'Room 1', 1), leafNode('room2', 'Room 2', 1)], + }; + const branch2Depth3: GroupPanelTreeNode = { + ...leafNode('building2', 'Building 2', 3), + children: [ + { + ...leafNode('floor1', 'Floor 1', 2), + children: [leafNode('room3', 'Room 3', 1), leafNode('room4', 'Room 4', 1)], + }, + leafNode('room5', 'Room 5', 1), + ], + }; + + const checkNonLeafWidth = (node: GroupPanelTreeNode) => { + const component = new GroupPanelVerticalNode({ node, index: 0 }); + const result = component.render() as VirtualNodeLike; + const children = result.children as VirtualNodeLike[]; + const headerCell = children[0]; + + expect(headerCell.className).not.toContain('dx-scheduler-group-header-leaf'); + expect(headerCell.props?.style).toEqual({ width: 'var(--dx-scheduler-group-header-width)' }); + }; + + checkNonLeafWidth(branch1Depth2); + checkNonLeafWidth(branch2Depth3); + }); }); diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.tsx index c85f3eb41dd4..49d4c532c04b 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.tsx @@ -43,6 +43,8 @@ export class GroupPanelVerticalNode extends BaseInfernoComponent
{ cellTemplate 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..9c84e72de55f 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 @@ -69,6 +69,14 @@ const createConfig = ( }); describe('VerticalGroupedStrategy', () => { + it('should return undefined from getGroupCountClass (no workspace-root class needed for hierarchical width)', () => { + const strategy = new VerticalGroupedStrategy(createConfig()); + + const result = strategy.getGroupCountClass([]); + + expect(result).toBeUndefined(); + }); + it('should use uniform group heights when group heights are not specified', () => { const strategy = new VerticalGroupedStrategy(createConfig()); 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..93952f39571f 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,6 +1,6 @@ import type { dxElementWrapper } from '@js/core/renderer'; import { getBoundingRect } from '@js/core/utils/position'; -import { calculateDayDuration, getVerticalGroupCountClass } from '@ts/scheduler/r1/utils/index'; +import { calculateDayDuration } from '@ts/scheduler/r1/utils/index'; import type { CellPositionData, GroupBoundsOffset } from '@ts/scheduler/types'; import { WORK_SPACE_BORDER_PX } from '@ts/scheduler/workspaces/const'; @@ -77,8 +77,9 @@ class VerticalGroupedStrategy { return 0; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars getGroupCountClass(groups: ResourceLoader[]): string | undefined { - return getVerticalGroupCountClass(groups); + return undefined; } getLeftOffset(): number { From a8f02769d49f6059771195f730509926cade34ff Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 11:11:24 +0200 Subject: [PATCH 02/11] fix: fix border for leafs --- .../scss/widgets/base/scheduler/views/_index.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss index d461be7dedc5..a102dd1003bf 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss @@ -336,12 +336,12 @@ $scheduler-month-date-text-padding: 6px; border: none; border-top: $scheduler-base-border; border-top-color: $scheduler-base-border-color; + border-right: $scheduler-base-border; + border-right-color: $scheduler-base-border-color; &.dx-scheduler-group-header-leaf { flex: 1 1 auto; width: auto; - border-right: $scheduler-base-border; - border-right-color: $scheduler-base-border-color; } } From 302feb1d64e8b7e10ca1f0d7a155e16f1576b709 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 13:13:56 +0200 Subject: [PATCH 03/11] fix: fix timeline layout for multilevel + fix styles --- .../widgets/base/scheduler/views/_index.scss | 26 ++----- .../scheduler/views/timelines/_index.scss | 48 ++++++------ .../components/base/group_panel_vertical.tsx | 4 +- .../base/group_panel_vertical_node.test.tsx | 42 +++-------- .../base/group_panel_vertical_node.tsx | 3 - ...rk_space_grouped_strategy_vertical.test.ts | 14 +++- packages/devextreme/playground/jquery.html | 74 ++++++++++++++++++- .../timeline.markup.tests.js | 6 +- 8 files changed, 124 insertions(+), 93 deletions(-) diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss index a102dd1003bf..7dcafc9b6723 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss @@ -101,18 +101,6 @@ $scheduler-month-date-text-padding: 6px; width: $scheduler-left-column-width; } - &.dx-scheduler-group-column-count-one .dx-scheduler-header-panel-empty-cell { - width: 2 * $scheduler-left-column-width; - } - - &.dx-scheduler-group-column-count-two .dx-scheduler-header-panel-empty-cell { - width: 2.5 * $scheduler-left-column-width; - } - - &.dx-scheduler-group-column-count-three .dx-scheduler-header-panel-empty-cell { - width: 3 * $scheduler-left-column-width; - } - &.dx-scheduler-work-space-odd-cells { .dx-scheduler-date-table-row:nth-child(odd) .dx-scheduler-date-table-cell { border-top: $scheduler-accent-border; @@ -327,7 +315,7 @@ $scheduler-month-date-text-padding: 6px; flex: 0 0 auto; padding: 0 5px; height: 100%; - width: $scheduler-left-column-width; + width: var(--dx-scheduler-group-header-width); justify-content: flex-start; align-items: flex-start; text-align: left; @@ -350,6 +338,11 @@ $scheduler-month-date-text-padding: 6px; flex-direction: column; min-height: 0; width: auto; + + .dx-scheduler-group-row .dx-scheduler-group-header { + border-right: $scheduler-base-border; + border-right-color: $scheduler-base-border-color; + } } } @@ -892,11 +885,8 @@ $scheduler-month-date-text-padding: 6px; padding: 0 5px 0 10px; } - &.dx-scheduler-group-column-count-two, - &.dx-scheduler-group-column-count-three { - .dx-scheduler-group-header:last-child { - text-align: right; - } + .dx-scheduler-group-row .dx-scheduler-group-header:last-child:not(:only-child) { + text-align: right; } } } diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss index d5226a597875..0745c2589570 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss @@ -16,26 +16,6 @@ $scheduler-timeline-min-height: 100px; border-bottom: none; } - &.dx-scheduler-group-column-count-two { - .dx-scheduler-header-panel-empty-cell { - width: 160px; - } - - .dx-scheduler-group-flex-container .dx-scheduler-group-header { - width: 80px; - } - } - - &.dx-scheduler-group-column-count-three { - .dx-scheduler-header-panel-empty-cell { - width: 240px; - } - - .dx-scheduler-group-flex-container .dx-scheduler-group-header { - width: 80px; - } - } - // This is a WA for existing behaviour. Refactor with setTableSizes .dx-scheduler-date-table-scrollable-content, .dx-scheduler-date-table-container { @@ -194,16 +174,30 @@ $scheduler-timeline-min-height: 100px; display: block; } - .dx-scheduler-group-row .dx-scheduler-group-header { - border: none; - border-top: $scheduler-base-border; - border-top-color: $scheduler-base-border-color; + .dx-scheduler-group-table > .dx-scheduler-group-flex-container:not(.dx-scheduler-group-flex-container-hierarchical) { + flex-direction: column; + + .dx-scheduler-group-row { + @include flex-container(row, nowrap); + + flex: 1 1 0; + width: 100%; + min-width: 0; + + .dx-scheduler-group-header { + border: none; + border-top: $scheduler-base-border; + border-top-color: $scheduler-base-border-color; + } + } } &.dx-scheduler-work-space-group-by-date { - .dx-scheduler-group-row .dx-scheduler-group-header { - border-right: $scheduler-base-border; - border-right-color: $scheduler-base-border-color; + .dx-scheduler-group-table > .dx-scheduler-group-flex-container:not(.dx-scheduler-group-flex-container-hierarchical) { + .dx-scheduler-group-row .dx-scheduler-group-header { + border-right: $scheduler-base-border; + border-right-color: $scheduler-base-border-color; + } } .dx-scheduler-group-row .dx-scheduler-last-group-cell { diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx index 3ef844185a47..22e7a1a6a927 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx @@ -17,7 +17,7 @@ const renderGroupPanelContent = ( isHierarchical: boolean, groupByDate: boolean, ): JSX.Element | JSX.Element[] => { - if (isTimelineGroupTable) { + if (isTimelineGroupTable && !isHierarchical) { return getTimelineGroupPanelRows(groupPanelData, groupByDate) .map((group) => { } = this.props; const style = normalizeStyles(renderUtils.addHeightToStyle(height, styles)); const isTimelineGroupTable = className === TIMELINE_GROUP_TABLE_CLASS; - const isHierarchical = !isTimelineGroupTable && groupPanelData.maxDepth > 1; + const isHierarchical = groupPanelData.maxDepth > 1; const flexContainerClassName = isHierarchical ? `dx-scheduler-group-flex-container ${HIERARCHICAL_GROUP_FLEX_CONTAINER_CLASS}` : 'dx-scheduler-group-flex-container'; diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.test.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.test.tsx index 162eaf28593b..e03cff1c4793 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.test.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.test.tsx @@ -70,33 +70,7 @@ describe('GroupPanelVerticalNode', () => { expect(nestedContainer.children).toHaveLength(2); }); - it('should apply custom property width to non-leaf header cell', () => { - const parent: GroupPanelTreeNode = { - ...leafNode('parent', 'Building A', 2), - children: [leafNode('child1', 'Room A1', 1), leafNode('child2', 'Room A2', 1)], - }; - const component = new GroupPanelVerticalNode({ node: parent, index: 0 }); - const result = component.render() as VirtualNodeLike; - const children = result.children as VirtualNodeLike[]; - const headerCell = children[0]; - - expect(headerCell.props?.style).toEqual({ width: 'var(--dx-scheduler-group-header-width)' }); - }); - - it('should not apply width style to leaf header cell', () => { - const component = new GroupPanelVerticalNode({ node: leafNode('a', 'Room A', 1), index: 0 }); - const result = component.render() as VirtualNodeLike; - const children = result.children as VirtualNodeLike[]; - const headerCell = children[0]; - - expect(headerCell.props?.style).toEqual({}); - }); - - it('should apply custom property width consistently across non-uniform depth branches', () => { - const branch1Depth2: GroupPanelTreeNode = { - ...leafNode('building1', 'Building 1', 2), - children: [leafNode('room1', 'Room 1', 1), leafNode('room2', 'Room 2', 1)], - }; + it('should not set inline width on header cells (width is defined in CSS)', () => { const branch2Depth3: GroupPanelTreeNode = { ...leafNode('building2', 'Building 2', 3), children: [ @@ -108,17 +82,21 @@ describe('GroupPanelVerticalNode', () => { ], }; - const checkNonLeafWidth = (node: GroupPanelTreeNode) => { + const checkHeaderCell = (node: GroupPanelTreeNode) => { const component = new GroupPanelVerticalNode({ node, index: 0 }); const result = component.render() as VirtualNodeLike; const children = result.children as VirtualNodeLike[]; const headerCell = children[0]; - expect(headerCell.className).not.toContain('dx-scheduler-group-header-leaf'); - expect(headerCell.props?.style).toEqual({ width: 'var(--dx-scheduler-group-header-width)' }); + expect(headerCell.props?.style).toBeUndefined(); }; - checkNonLeafWidth(branch1Depth2); - checkNonLeafWidth(branch2Depth3); + checkHeaderCell(leafNode('a', 'Room A', 1)); + checkHeaderCell({ + ...leafNode('parent', 'Building A', 2), + children: [leafNode('child1', 'Room A1', 1), leafNode('child2', 'Room A2', 1)], + }); + checkHeaderCell(branch2Depth3); + checkHeaderCell(branch2Depth3.children[0]); }); }); diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.tsx index 49d4c532c04b..c85f3eb41dd4 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_node.tsx @@ -43,8 +43,6 @@ export class GroupPanelVerticalNode extends BaseInfernoComponent
{ cellTemplate 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 9c84e72de55f..ccbc16d5e258 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 @@ -3,6 +3,7 @@ import { } from '@jest/globals'; import type { dxElementWrapper } from '@js/core/renderer'; +import type { ResourceLoader } from '../utils/loader/resource_loader'; import type { GroupedStrategyConfig } from './work_space_grouped_strategy_config'; import VerticalGroupedStrategy from './work_space_grouped_strategy_vertical'; @@ -68,13 +69,18 @@ const createConfig = ( ...options, }); +const mockGroupLoaders = (count: number): ResourceLoader[] => ( + Array.from({ length: count }, () => ({} as unknown as ResourceLoader)) +); + describe('VerticalGroupedStrategy', () => { - it('should return undefined from getGroupCountClass (no workspace-root class needed for hierarchical width)', () => { + it('should return undefined from getGroupCountClass regardless of groups length', () => { const strategy = new VerticalGroupedStrategy(createConfig()); - const result = strategy.getGroupCountClass([]); - - expect(result).toBeUndefined(); + expect(strategy.getGroupCountClass([])).toBeUndefined(); + expect(strategy.getGroupCountClass(mockGroupLoaders(1))).toBeUndefined(); + expect(strategy.getGroupCountClass(mockGroupLoaders(2))).toBeUndefined(); + expect(strategy.getGroupCountClass(mockGroupLoaders(3))).toBeUndefined(); }); it('should use uniform group heights when group heights are not specified', () => { diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index 2389ed7be937..d6cfe36c167c 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -50,12 +50,78 @@

Te
-
+

Hierarchical Grouping Demo

+

+ Hierarchical resource (parentIdExpr): nested group sidebar (Building → Floor → Room) on + Day / Week / Work Week / Month and Timeline views with vertical grouping. +

+
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/timeline.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/timeline.markup.tests.js index 0f7affa43e3e..7630b7e05d2c 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/timeline.markup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/timeline.markup.tests.js @@ -139,7 +139,7 @@ QUnit.module('Timeline markup', moduleConfig, () => { assert.equal($secondColumnCells.length, 4, 'Cell count is OK'); }); - QUnit.test('Timeline should have correct group-count class depending on group count', async function(assert) { + QUnit.test('Timeline should not use group-count class on workspace (group header width is layout-driven)', async function(assert) { const $element = this.instance.$element(); await applyWorkspaceGroups(this.instance, [{ @@ -152,11 +152,11 @@ QUnit.module('Timeline markup', moduleConfig, () => { dataSource: [{ id: 1, text: '1' }, { id: 2, text: '2' }] }]); - assert.ok($element.hasClass('dx-scheduler-group-column-count-two'), 'Correct class'); + assert.notOk($element.hasClass('dx-scheduler-group-column-count-two'), 'group-count class is not applied'); await applyWorkspaceGroups(this.instance, []); - assert.notOk($element.hasClass('dx-scheduler-group-column-count-two'), 'group-count class was not applied'); + assert.notOk($element.hasClass('dx-scheduler-group-column-count-two'), 'group-count class is not applied'); }); }); From 77a8a501b744fd8d15839f781195e5c729053922 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 13:20:47 +0200 Subject: [PATCH 04/11] refactor: remove getGroupCountClass --- .../js/__internal/scheduler/classes.ts | 6 ----- .../js/__internal/scheduler/r1/utils/base.ts | 14 ---------- .../js/__internal/scheduler/r1/utils/index.ts | 1 - .../__internal/scheduler/workspaces/agenda.ts | 10 +------ .../scheduler/workspaces/work_space.ts | 27 ------------------- .../work_space_grouped_strategy_horizontal.ts | 6 ----- ...rk_space_grouped_strategy_vertical.test.ts | 16 ----------- .../work_space_grouped_strategy_vertical.ts | 6 ----- .../timeline.markup.tests.js | 20 -------------- 9 files changed, 1 insertion(+), 105 deletions(-) diff --git a/packages/devextreme/js/__internal/scheduler/classes.ts b/packages/devextreme/js/__internal/scheduler/classes.ts index 3e06d8bc1334..6f9245029857 100644 --- a/packages/devextreme/js/__internal/scheduler/classes.ts +++ b/packages/devextreme/js/__internal/scheduler/classes.ts @@ -43,9 +43,3 @@ export const GROUP_ROW_CLASS = 'dx-scheduler-group-row'; export const GROUP_HEADER_CONTENT_CLASS = 'dx-scheduler-group-header-content'; export const LAST_GROUP_CELL_CLASS = 'dx-scheduler-last-group-cell'; export const FIRST_GROUP_CELL_CLASS = 'dx-scheduler-first-group-cell'; - -export const VERTICAL_GROUP_COUNT_CLASSES = [ - 'dx-scheduler-group-column-count-one', - 'dx-scheduler-group-column-count-two', - 'dx-scheduler-group-column-count-three', -]; diff --git a/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts b/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts index 4030bceddb9a..8833292877ce 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts @@ -3,7 +3,6 @@ import dateUtils from '@js/core/utils/date'; import { isDefined } from '@js/core/utils/type'; import { dateUtilsTs } from '@ts/core/utils/date'; -import { VERTICAL_GROUP_COUNT_CLASSES } from '../../classes'; import { HORIZONTAL_GROUP_ORIENTATION, VERTICAL_GROUP_ORIENTATION, } from '../../constants'; @@ -99,19 +98,6 @@ export const getOverflowIndicatorColor = (color: string, colors: string[]): stri : undefined ); -export const getVerticalGroupCountClass = (groups: unknown[]): string | undefined => { - switch (groups?.length) { - case 1: - return VERTICAL_GROUP_COUNT_CLASSES[0]; - case 2: - return VERTICAL_GROUP_COUNT_CLASSES[1]; - case 3: - return VERTICAL_GROUP_COUNT_CLASSES[2]; - default: - return undefined; - } -}; - export const setOptionHour = (date: Date, optionHour: number): Date => { const nextDate = new Date(date); diff --git a/packages/devextreme/js/__internal/scheduler/r1/utils/index.ts b/packages/devextreme/js/__internal/scheduler/r1/utils/index.ts index c08e12029250..1856a323cfd2 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/utils/index.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/utils/index.ts @@ -62,7 +62,6 @@ export { getTotalCellCountByCompleteData, getTotalRowCountByCompleteData, getValidCellDateForLocalTimeFormat, - getVerticalGroupCountClass, getViewStartByOptions, isAppointmentTakesAllDay, isDateAndTimeView, diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/agenda.ts b/packages/devextreme/js/__internal/scheduler/workspaces/agenda.ts index b007f58caa95..c7eac2219570 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/agenda.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/agenda.ts @@ -21,7 +21,7 @@ import { GROUP_ROW_CLASS, TIME_PANEL_CLASS, } from '../classes'; -import { agendaUtils, formatWeekday, getVerticalGroupCountClass } from '../r1/utils/index'; +import { agendaUtils, formatWeekday } from '../r1/utils/index'; import tableCreatorModule, { type GroupRows } from '../table_creator'; import type { ResourceId } from '../utils/loader/types'; import { VIEWS } from '../utils/options/constants_view'; @@ -102,7 +102,6 @@ class SchedulerAgenda extends WorkSpace { if (this.$groupTable) { this.$groupTable.remove(); this.$groupTable = null; - this.detachGroupCountClass(); } } else if (!this.$groupTable) { this.initGroupTable(); @@ -230,13 +229,6 @@ class SchedulerAgenda extends WorkSpace { return rows.every((groupRow) => groupRow.every((cell) => !cell)); } - protected override attachGroupCountClass(): void { - const className = getVerticalGroupCountClass(this.option().groups); - if (className) { - this.$element().addClass(className); - } - } - private removeEmptyRows(rows: number[][]): number[][] { const isEmpty = (data: number[]): boolean => !data.some((value) => value > 0); return rows.filter((row) => row.length && !isEmpty(row)); diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts index 7655a5a89327..9e1e1a2d81b1 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts @@ -78,7 +78,6 @@ import { GROUP_HEADER_CONTENT_CLASS, GROUP_ROW_CLASS, TIME_PANEL_CLASS, - VERTICAL_GROUP_COUNT_CLASSES, VIRTUAL_CELL_CLASS, } from '../classes'; import { APPOINTMENT_SETTINGS_KEY } from '../constants'; @@ -2394,7 +2393,6 @@ class SchedulerWorkSpace extends Widget { }; if (this.option().groups?.length) { - this.attachGroupCountClass(); const $groupHeaderContainer = this.getGroupHeaderContainer(); if ($groupHeaderContainer) { this.renderRenovatedComponent( @@ -2404,8 +2402,6 @@ class SchedulerWorkSpace extends Widget { options, ); } - } else { - this.detachGroupCountClass(); } } @@ -2478,12 +2474,6 @@ class SchedulerWorkSpace extends Widget { } renderRHeaderPanel(isRenderDateHeader = true): void { - if (this.option().groups?.length) { - this.attachGroupCountClass(); - } else { - this.detachGroupCountClass(); - } - this.renderRenovatedComponent( this.$thead, this.renovatedHeaderPanelComponent, @@ -3221,20 +3211,6 @@ class SchedulerWorkSpace extends Widget { protected setIndicationUpdateInterval(): void { return noop(); } - protected detachGroupCountClass(): void { - VERTICAL_GROUP_COUNT_CLASSES.forEach((className) => { - this.$element().removeClass(className); - }); - } - - protected attachGroupCountClass(): void { - const className = this.groupedStrategy.getGroupCountClass(this.option().groups); - - if (className) { - this.$element().addClass(className); - } - } - protected getDateHeaderTemplate(): TemplateBase | null | undefined { return this.option().dateCellTemplate; } @@ -3353,12 +3329,9 @@ class SchedulerWorkSpace extends Widget { let cellTemplates: (() => dxElementWrapper)[] = []; if (groupCount && $container) { const groupRows = this.makeGroupRows(this.option().groups, this.option().groupByDate); - this.attachGroupCountClass(); const { elements } = groupRows; $container.append(Array.isArray(elements) ? elements : elements.toArray()); cellTemplates = groupRows.cellTemplates; - } else { - this.detachGroupCountClass(); } return cellTemplates; diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_horizontal.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_horizontal.ts index 6c7c18e41408..5e852d38e91a 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_horizontal.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space_grouped_strategy_horizontal.ts @@ -8,7 +8,6 @@ import type { import { WORK_SPACE_BORDER_PX } from '@ts/scheduler/workspaces/const'; import { FIRST_GROUP_CELL_CLASS, LAST_GROUP_CELL_CLASS } from '../classes'; -import type { ResourceLoader } from '../utils/loader/resource_loader'; import type { GroupedStrategyConfig } from './work_space_grouped_strategy_config'; class HorizontalGroupedStrategy { @@ -77,11 +76,6 @@ class HorizontalGroupedStrategy { return this.config.getAllDayHeight(); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getGroupCountClass(groups: ResourceLoader[]): string | undefined { - return undefined; - } - getLeftOffset(): number { return this.config.getTimePanelWidth(); } 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 ccbc16d5e258..73eb05320668 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 @@ -3,18 +3,15 @@ import { } from '@jest/globals'; import type { dxElementWrapper } from '@js/core/renderer'; -import type { ResourceLoader } from '../utils/loader/resource_loader'; import type { GroupedStrategyConfig } from './work_space_grouped_strategy_config'; import VerticalGroupedStrategy from './work_space_grouped_strategy_vertical'; jest.mock('@ts/scheduler/r1/utils/index', (): { calculateDayDuration: (startDayHour: number, endDayHour: number) => number; - getVerticalGroupCountClass: () => undefined; } => ({ calculateDayDuration: (startDayHour: number, endDayHour: number): number => ( endDayHour - startDayHour ), - getVerticalGroupCountClass: (): undefined => undefined, })); const createElement = ({ @@ -69,20 +66,7 @@ const createConfig = ( ...options, }); -const mockGroupLoaders = (count: number): ResourceLoader[] => ( - Array.from({ length: count }, () => ({} as unknown as ResourceLoader)) -); - describe('VerticalGroupedStrategy', () => { - it('should return undefined from getGroupCountClass regardless of groups length', () => { - const strategy = new VerticalGroupedStrategy(createConfig()); - - expect(strategy.getGroupCountClass([])).toBeUndefined(); - expect(strategy.getGroupCountClass(mockGroupLoaders(1))).toBeUndefined(); - expect(strategy.getGroupCountClass(mockGroupLoaders(2))).toBeUndefined(); - expect(strategy.getGroupCountClass(mockGroupLoaders(3))).toBeUndefined(); - }); - it('should use uniform group heights when group heights are not specified', () => { const strategy = new VerticalGroupedStrategy(createConfig()); 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 93952f39571f..bf3146d9269d 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 @@ -6,7 +6,6 @@ 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 { @@ -77,11 +76,6 @@ class VerticalGroupedStrategy { return 0; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getGroupCountClass(groups: ResourceLoader[]): string | undefined { - return undefined; - } - getLeftOffset(): number { return this.config.getTimePanelWidth() + this.config.getGroupTableWidth(); } diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/timeline.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/timeline.markup.tests.js index 7630b7e05d2c..a010732318f0 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/timeline.markup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/timeline.markup.tests.js @@ -138,26 +138,6 @@ QUnit.module('Timeline markup', moduleConfig, () => { assert.equal($firstColumnCells.length, 2, 'Cell count is OK'); assert.equal($secondColumnCells.length, 4, 'Cell count is OK'); }); - - QUnit.test('Timeline should not use group-count class on workspace (group header width is layout-driven)', async function(assert) { - const $element = this.instance.$element(); - - await applyWorkspaceGroups(this.instance, [{ - label: 'one', - fieldExpr: 'one', - dataSource: [{ id: 1, text: 'a' }, { id: 2, text: 'b' }] - }, { - label: 'two', - fieldExpr: 'two', - dataSource: [{ id: 1, text: '1' }, { id: 2, text: '2' }] - }]); - - assert.notOk($element.hasClass('dx-scheduler-group-column-count-two'), 'group-count class is not applied'); - - await applyWorkspaceGroups(this.instance, []); - - assert.notOk($element.hasClass('dx-scheduler-group-column-count-two'), 'group-count class is not applied'); - }); }); let timelineDayModuleConfig = { From 0f8dd04a864ece7b89eb6c456061422ed53a1d67 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 13:22:47 +0200 Subject: [PATCH 05/11] chore: revert jquery.html --- packages/devextreme/playground/jquery.html | 94 ++++------------------ 1 file changed, 14 insertions(+), 80 deletions(-) diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index d6cfe36c167c..69384d8c5c6f 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -44,87 +44,21 @@ -
-

Test header

+
+

Test header

- -
-

Hierarchical Grouping Demo

-

- Hierarchical resource (parentIdExpr): nested group sidebar (Building → Floor → Room) on - Day / Week / Work Week / Month and Timeline views with vertical grouping. -

-
- -
+ }); + +
From 90498bbeec02dc51a731959d30cf783a605a016f Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 13:34:13 +0200 Subject: [PATCH 06/11] fix: update scheduler styles to use variable for group header width --- .../widgets/base/scheduler/views/_index.scss | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss index 7dcafc9b6723..cef07613803c 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss @@ -120,8 +120,6 @@ $scheduler-month-date-text-padding: 6px; } .dx-scheduler-work-space { - --dx-scheduler-group-header-width: #{$scheduler-left-column-width}; - border: $scheduler-base-border; border-color: $scheduler-base-border-color; background-color: $scheduler-workspace-background-color; @@ -315,7 +313,7 @@ $scheduler-month-date-text-padding: 6px; flex: 0 0 auto; padding: 0 5px; height: 100%; - width: var(--dx-scheduler-group-header-width); + width: $scheduler-left-column-width; justify-content: flex-start; align-items: flex-start; text-align: left; @@ -327,6 +325,10 @@ $scheduler-month-date-text-padding: 6px; border-right: $scheduler-base-border; border-right-color: $scheduler-base-border-color; + .dx-scheduler-small & { + width: $scheduler-left-column-width * $scheduler-small-size-factor; + } + &.dx-scheduler-group-header-leaf { flex: 1 1 auto; width: auto; @@ -350,6 +352,11 @@ $scheduler-month-date-text-padding: 6px; flex: 0 0 auto; width: $scheduler-left-column-width; min-width: $scheduler-left-column-width; + + .dx-scheduler-small & { + width: $scheduler-left-column-width * $scheduler-small-size-factor; + min-width: $scheduler-left-column-width * $scheduler-small-size-factor; + } } } @@ -909,10 +916,6 @@ $scheduler-month-date-text-padding: 6px; } .dx-scheduler-small { - .dx-scheduler-work-space { - --dx-scheduler-group-header-width: #{$scheduler-small-size-factor * $scheduler-left-column-width}; - } - .dx-scheduler-work-space-week, .dx-scheduler-work-space-work-week, .dx-scheduler-work-space-day { From ec18267a6c139d4cdc0577cc7f50d8e3584b61f0 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 13:34:21 +0200 Subject: [PATCH 07/11] fix: fix tests --- .../scheduler/r1/components/base/group_panel_vertical.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx index 22e7a1a6a927..b5c2363d8d06 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx +++ b/packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx @@ -66,7 +66,10 @@ export class GroupPanelVertical extends BaseInfernoComponent { } = this.props; const style = normalizeStyles(renderUtils.addHeightToStyle(height, styles)); const isTimelineGroupTable = className === TIMELINE_GROUP_TABLE_CLASS; - const isHierarchical = groupPanelData.maxDepth > 1; + const useResourceHierarchyLayout = groupPanelData.hasHierarchy && groupPanelData.maxDepth > 1; + const isHierarchical = isTimelineGroupTable + ? useResourceHierarchyLayout + : groupPanelData.maxDepth > 1; const flexContainerClassName = isHierarchical ? `dx-scheduler-group-flex-container ${HIERARCHICAL_GROUP_FLEX_CONTAINER_CLASS}` : 'dx-scheduler-group-flex-container'; From 5074de9d0f03517a2bdbb78622343612e5651a6f Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 13:48:04 +0200 Subject: [PATCH 08/11] fix: fix tests --- .../scheduler/r1/utils/__tests__/base.test.ts | 21 +++++++++++++++++++ .../js/__internal/scheduler/r1/utils/base.ts | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/devextreme/js/__internal/scheduler/r1/utils/__tests__/base.test.ts b/packages/devextreme/js/__internal/scheduler/r1/utils/__tests__/base.test.ts index de77d84f4269..c1c2b6400302 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/utils/__tests__/base.test.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/utils/__tests__/base.test.ts @@ -672,6 +672,27 @@ describe('base utils', () => { ]); expect(timelineRows).not.toEqual(groupPanelData.groupPanelItems); }); + + it('should use one row per flat group when a single resource has multiple groups on timeline', () => { + const groups: GroupNode[] = [ + { + id: 1, resourceText: 'Group_1', resourceIndex: 'any', grouped: { any: 1 }, children: [], + }, + { + id: 2, resourceText: 'Group_2', resourceIndex: 'any', grouped: { any: 2 }, children: [], + }, + { + id: 3, resourceText: 'Group_3', resourceIndex: 'any', grouped: { any: 3 }, children: [], + }, + ]; + const groupPanelData = getGroupPanelData(groups, 1, false, 1, false); + const timelineRows = getTimelineGroupPanelRows(groupPanelData, false); + + expect(timelineRows).toHaveLength(3); + expect(timelineRows[0]).toEqual([expect.objectContaining({ text: 'Group_1' })]); + expect(timelineRows[1]).toEqual([expect.objectContaining({ text: 'Group_2' })]); + expect(timelineRows[2]).toEqual([expect.objectContaining({ text: 'Group_3' })]); + }); }); describe('getKeyByGroup', () => { diff --git a/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts b/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts index 8833292877ce..ba67a847244b 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts @@ -469,7 +469,10 @@ export const getTimelineGroupPanelRows = ( groupPanelData: GroupPanelData, groupByDate: boolean, ): GroupRenderItem[][] => { - let rows = groupPanelData.hasHierarchy + const shouldUseLeafRows = groupPanelData.hasHierarchy + || (groupPanelData.maxDepth === 1 && groupPanelData.groupTree.length > 1); + + let rows = shouldUseLeafRows ? flattenGroupPanelTreeToLeafRows(groupPanelData.groupTree, groupPanelData.baseColSpan) : groupPanelData.groupPanelItems; From 825f91bf1dac5271ab75157a4a29d93a9185fc9b Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 14:25:11 +0200 Subject: [PATCH 09/11] fix: fix tests --- .../widgets/base/scheduler/views/timelines/_index.scss | 9 ++++----- .../__internal/scheduler/r1/utils/__tests__/base.test.ts | 9 ++++----- .../devextreme/js/__internal/scheduler/r1/utils/base.ts | 5 +++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss index 0745c2589570..05e4e5f4feb5 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss @@ -175,19 +175,18 @@ $scheduler-timeline-min-height: 100px; } .dx-scheduler-group-table > .dx-scheduler-group-flex-container:not(.dx-scheduler-group-flex-container-hierarchical) { - flex-direction: column; - .dx-scheduler-group-row { - @include flex-container(row, nowrap); + @include flex-container(column, nowrap); - flex: 1 1 0; - width: 100%; + flex: 0 0 auto; min-width: 0; .dx-scheduler-group-header { border: none; border-top: $scheduler-base-border; border-top-color: $scheduler-base-border-color; + border-right: $scheduler-base-border; + border-right-color: $scheduler-base-border-color; } } } diff --git a/packages/devextreme/js/__internal/scheduler/r1/utils/__tests__/base.test.ts b/packages/devextreme/js/__internal/scheduler/r1/utils/__tests__/base.test.ts index c1c2b6400302..55ba533ff070 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/utils/__tests__/base.test.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/utils/__tests__/base.test.ts @@ -673,7 +673,7 @@ describe('base utils', () => { expect(timelineRows).not.toEqual(groupPanelData.groupPanelItems); }); - it('should use one row per flat group when a single resource has multiple groups on timeline', () => { + it('should keep a single stacked row when a single resource has multiple flat groups on timeline', () => { const groups: GroupNode[] = [ { id: 1, resourceText: 'Group_1', resourceIndex: 'any', grouped: { any: 1 }, children: [], @@ -688,10 +688,9 @@ describe('base utils', () => { const groupPanelData = getGroupPanelData(groups, 1, false, 1, false); const timelineRows = getTimelineGroupPanelRows(groupPanelData, false); - expect(timelineRows).toHaveLength(3); - expect(timelineRows[0]).toEqual([expect.objectContaining({ text: 'Group_1' })]); - expect(timelineRows[1]).toEqual([expect.objectContaining({ text: 'Group_2' })]); - expect(timelineRows[2]).toEqual([expect.objectContaining({ text: 'Group_3' })]); + expect(timelineRows).toEqual(groupPanelData.groupPanelItems); + expect(timelineRows).toHaveLength(1); + expect(timelineRows[0]).toHaveLength(3); }); }); diff --git a/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts b/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts index ba67a847244b..17bc0286d1d6 100644 --- a/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts +++ b/packages/devextreme/js/__internal/scheduler/r1/utils/base.ts @@ -469,8 +469,9 @@ export const getTimelineGroupPanelRows = ( groupPanelData: GroupPanelData, groupByDate: boolean, ): GroupRenderItem[][] => { - const shouldUseLeafRows = groupPanelData.hasHierarchy - || (groupPanelData.maxDepth === 1 && groupPanelData.groupTree.length > 1); + // Flat timeline keeps one stacked group-row (base CSS: column + flex:1 headers). + // Leaf rows are only for hierarchical resources rendered via this helper. + const shouldUseLeafRows = groupPanelData.hasHierarchy; let rows = shouldUseLeafRows ? flattenGroupPanelTreeToLeafRows(groupPanelData.groupTree, groupPanelData.baseColSpan) From 32474ebf42b5e3dc0c854bc7bb8169acfae45df9 Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 15:55:22 +0200 Subject: [PATCH 10/11] css: undo border thing --- .../scss/widgets/base/scheduler/views/timelines/_index.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss index 05e4e5f4feb5..71c3ef92da49 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss @@ -185,8 +185,6 @@ $scheduler-timeline-min-height: 100px; border: none; border-top: $scheduler-base-border; border-top-color: $scheduler-base-border-color; - border-right: $scheduler-base-border; - border-right-color: $scheduler-base-border-color; } } } From 2f14a54302b4e3f5280e4b149d139a72a90d222c Mon Sep 17 00:00:00 2001 From: Sergio Bur Date: Fri, 24 Jul 2026 15:58:32 +0200 Subject: [PATCH 11/11] fix: fix test --- .../integration.resources.tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.resources.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.resources.tests.js index e9510824e838..28b70a3d09cd 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.resources.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.resources.tests.js @@ -39,7 +39,7 @@ QUnit.module('Integration: Resources', moduleConfig, () => { }, 'appointment2': { top: 202, - left: 430 + left: 411 } }, { 'appointment1': {