Skip to content

Commit 1a02b1d

Browse files
authored
Scheduler - Refactor layout pipeline for auto-height support (#34106)
1 parent 0a4c520 commit 1a02b1d

22 files changed

Lines changed: 753 additions & 41 deletions

packages/devextreme/js/__internal/scheduler/r1/components/base/date_table.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export class DateTable extends InfernoWrapperComponent<DateTableProps> {
3535
dataCellTemplate,
3636
groupOrientation,
3737
addVerticalSizesClassToRows,
38+
rowHeights,
3839
...restProps
3940
} = this.props;
4041
const classes = addDateTableClass ? 'dx-scheduler-date-table' : undefined;
@@ -67,6 +68,7 @@ export class DateTable extends InfernoWrapperComponent<DateTableProps> {
6768
rightVirtualCellWidth={rightVirtualCellWidth}
6869
groupOrientation={groupOrientation}
6970
addVerticalSizesClassToRows={addVerticalSizesClassToRows}
71+
rowHeights={rowHeights}
7072
topVirtualRowHeight={DateTableBodyDefaultProps.topVirtualRowHeight}
7173
bottomVirtualRowHeight={DateTableBodyDefaultProps.bottomVirtualRowHeight}
7274
addDateTableClass={DateTableBodyDefaultProps.addDateTableClass}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import {
2+
describe, expect, it, jest,
3+
} from '@jest/globals';
4+
5+
import type { CellTemplateProps } from '../types';
6+
import { DateTableBody } from './date_table_body';
7+
import { Row } from './row';
8+
9+
interface RenderUtilsMock {
10+
renderUtils: {
11+
addHeightToStyle: (
12+
height: number | undefined,
13+
styles?: Record<string, unknown>,
14+
) => Record<string, unknown>;
15+
};
16+
}
17+
18+
jest.mock('../../utils/index', (): RenderUtilsMock => ({
19+
renderUtils: {
20+
addHeightToStyle: (
21+
height: number | undefined,
22+
styles: Record<string, unknown> = {},
23+
): Record<string, unknown> => (height === undefined ? styles : { ...styles, height }),
24+
},
25+
}));
26+
27+
const viewContext = {
28+
view: {
29+
type: 'day',
30+
},
31+
crossScrollingEnabled: false,
32+
} as const;
33+
34+
const CellTemplate = (): JSX.Element => <td />;
35+
36+
interface VirtualNodeLike {
37+
type?: unknown;
38+
props?: {
39+
children?: unknown;
40+
styles?: unknown;
41+
};
42+
children?: unknown;
43+
}
44+
45+
const toArray = (value: unknown): unknown[] => {
46+
if (Array.isArray(value)) {
47+
return value;
48+
}
49+
50+
return value === undefined || value === null ? [] : [value];
51+
};
52+
53+
const getRowNodes = (node: unknown): VirtualNodeLike[] => {
54+
if (typeof node !== 'object' || node === null) {
55+
return [];
56+
}
57+
58+
const virtualNode = node as VirtualNodeLike;
59+
const currentNode = virtualNode.type === Row ? [virtualNode] : [];
60+
const children = [
61+
...toArray(virtualNode.children),
62+
...toArray(virtualNode.props?.children),
63+
];
64+
65+
return [
66+
...currentNode,
67+
...children.flatMap(getRowNodes),
68+
];
69+
};
70+
71+
const createCell = (
72+
key: number,
73+
): CellTemplateProps => ({
74+
key,
75+
startDate: new Date(2025, 0, 1),
76+
endDate: new Date(2025, 0, 1, 0, 30),
77+
index: key,
78+
isFirstGroupCell: false,
79+
isLastGroupCell: false,
80+
isSelected: false,
81+
isFocused: false,
82+
});
83+
84+
describe('DateTableBody', () => {
85+
it('should apply row heights', () => {
86+
const component = new DateTableBody({
87+
viewContext,
88+
viewData: {
89+
groupedData: [{
90+
dateTable: [
91+
{ key: 0, cells: [createCell(0)] },
92+
{ key: 1, cells: [createCell(1)] },
93+
],
94+
groupIndex: 0,
95+
key: '0',
96+
}, {
97+
dateTable: [
98+
{ key: 2, cells: [createCell(2)] },
99+
],
100+
groupIndex: 1,
101+
key: '1',
102+
}],
103+
leftVirtualCellCount: 0,
104+
rightVirtualCellCount: 0,
105+
topVirtualRowCount: 0,
106+
bottomVirtualRowCount: 0,
107+
},
108+
cellTemplate: CellTemplate,
109+
leftVirtualCellWidth: 0,
110+
rightVirtualCellWidth: 0,
111+
topVirtualRowHeight: 0,
112+
bottomVirtualRowHeight: 0,
113+
addDateTableClass: true,
114+
addVerticalSizesClassToRows: true,
115+
rowHeights: [120, 80, 160],
116+
});
117+
118+
const rows = getRowNodes(component.render());
119+
120+
expect(rows[0].props?.styles).toEqual({ height: 120 });
121+
expect(rows[1].props?.styles).toEqual({ height: 80 });
122+
expect(rows[2].props?.styles).toEqual({ height: 160 });
123+
});
124+
});

packages/devextreme/js/__internal/scheduler/r1/components/base/date_table_body.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { PublicTemplate } from '@ts/scheduler/r1/components/templates/index';
44
import { Fragment } from 'inferno';
55

66
import { combineClasses } from '../../../../core/r1/utils/render_utils';
7+
import { renderUtils } from '../../utils/index';
78
import { DATE_TABLE_ROW_CLASS } from '../const';
89
import type { CellTemplateProps, DefaultProps } from '../types';
910
import { AllDayPanelTableBody, AllDayPanelTableBodyDefaultProps } from './all_day_panel_table_body';
@@ -29,11 +30,15 @@ export class DateTableBody extends BaseInfernoComponent<DateTableBodyProps> {
2930
addVerticalSizesClassToRows,
3031
cellTemplate,
3132
dataCellTemplate,
33+
rowHeights,
3234
} = this.props;
3335
const rowClasses = combineClasses({
3436
[DATE_TABLE_ROW_CLASS]: true,
3537
'dx-scheduler-cell-sizes-vertical': addVerticalSizesClassToRows,
3638
});
39+
const getGroupRowOffset = (groupIndex: number): number => viewData.groupedData
40+
.slice(0, groupIndex)
41+
.reduce((offset, { dateTable }) => offset + dateTable.length, 0);
3742

3843
return (
3944
<>
@@ -43,7 +48,7 @@ export class DateTableBody extends BaseInfernoComponent<DateTableBodyProps> {
4348
dateTable,
4449
isGroupedAllDayPanel,
4550
key: fragmentKey,
46-
}) => (
51+
}, groupIndex) => (
4752
<Fragment key={fragmentKey}>
4853
{
4954
isGroupedAllDayPanel && <AllDayPanelTableBody
@@ -63,10 +68,17 @@ export class DateTableBody extends BaseInfernoComponent<DateTableBodyProps> {
6368
dateTable.map(({
6469
cells,
6570
key: rowKey,
66-
}) => (
71+
}, rowIndex) => {
72+
const rowHeight = rowHeights?.[getGroupRowOffset(groupIndex) + rowIndex];
73+
const rowStyles = rowHeight === undefined
74+
? undefined
75+
: renderUtils.addHeightToStyle(rowHeight);
76+
77+
return (
6778
<Row
6879
key={rowKey}
6980
className={rowClasses}
81+
styles={rowStyles}
7082
leftVirtualCellWidth={viewData.leftVirtualCellWidth
7183
?? RowDefaultProps.leftVirtualCellWidth}
7284
rightVirtualCellWidth={viewData.rightVirtualCellWidth
@@ -112,7 +124,8 @@ export class DateTableBody extends BaseInfernoComponent<DateTableBodyProps> {
112124
} as CellTemplateProps} />)
113125
}
114126
</Row>
115-
))
127+
);
128+
})
116129
}
117130
</Fragment>
118131
))

packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class GroupPanel extends InfernoWrapperComponent<GroupPanelProps> {
3939
groupOrientation,
4040
groups,
4141
styles,
42+
rowHeights,
4243
} = this.props;
4344
const isVerticalLayout = isVerticalGroupingApplied(groups.length, groupOrientation);
4445

@@ -53,6 +54,7 @@ export class GroupPanel extends InfernoWrapperComponent<GroupPanelProps> {
5354
groupPanelData={groupPanelData}
5455
elementRef={elementRef}
5556
styles={styles}
57+
rowHeights={rowHeights}
5658
groups={GroupPanelDefaultProps.groups}
5759
groupOrientation={GroupPanelDefaultProps.groupOrientation}
5860
groupByDate={GroupPanelDefaultProps.groupByDate}

packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_props.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface GroupPanelBaseProps extends
1111
groupPanelData: GroupPanelData;
1212
groupByDate: boolean;
1313
height?: number;
14+
rowHeights?: number[];
1415
resourceCellTemplate?: JSXTemplate<ResourceCellTemplateProps>;
1516
}
1617

@@ -43,6 +44,7 @@ export const GroupPanelCellDefaultProps = {
4344

4445
export interface GroupPanelRowProps extends PropsWithClassName {
4546
groupItems: GroupRenderItem[];
47+
height?: number;
4648
cellTemplate?: JSXTemplate<ResourceCellTemplateProps>;
4749
}
4850

packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class GroupPanelVertical extends BaseInfernoComponent<GroupPanelProps> {
1414
resourceCellTemplate,
1515
height,
1616
styles,
17+
rowHeights,
1718
} = this.props;
1819
const style = normalizeStyles(renderUtils.addHeightToStyle(height, styles));
1920

@@ -26,9 +27,10 @@ export class GroupPanelVertical extends BaseInfernoComponent<GroupPanelProps> {
2627
<div className="dx-scheduler-group-flex-container">
2728
{
2829
groupPanelData.groupPanelItems
29-
.map((group) => <GroupPanelVerticalRow
30+
.map((group, index) => <GroupPanelVerticalRow
3031
key={group[0].key}
3132
groupItems={group}
33+
height={rowHeights?.[index]}
3234
cellTemplate={resourceCellTemplate}
3335
/>)
3436
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
describe, expect, it, jest,
3+
} from '@jest/globals';
4+
5+
import { GroupPanelVerticalRow } from './group_panel_vertical_row';
6+
7+
interface RenderUtilsMock {
8+
renderUtils: {
9+
addHeightToStyle: (
10+
height: number | undefined,
11+
styles?: Record<string, unknown>,
12+
) => Record<string, unknown>;
13+
};
14+
}
15+
16+
jest.mock('../../utils/index', (): RenderUtilsMock => ({
17+
renderUtils: {
18+
addHeightToStyle: (
19+
height: number | undefined,
20+
styles: Record<string, unknown> = {},
21+
): Record<string, unknown> => (height === undefined ? styles : { ...styles, height }),
22+
},
23+
}));
24+
25+
interface VirtualNodeLike {
26+
props?: {
27+
style?: unknown;
28+
};
29+
}
30+
31+
describe('GroupPanelVerticalRow', () => {
32+
it('should apply row height', () => {
33+
const component = new GroupPanelVerticalRow({
34+
groupItems: [{
35+
key: '0',
36+
id: 0,
37+
text: 'Group 0',
38+
data: { id: 0 },
39+
resourceName: 'ownerId',
40+
}],
41+
height: 140,
42+
className: '',
43+
});
44+
const result = component.render() as VirtualNodeLike;
45+
46+
expect(result.props?.style).toEqual({ height: '140px' });
47+
});
48+
});

packages/devextreme/js/__internal/scheduler/r1/components/base/group_panel_vertical_row.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { BaseInfernoComponent } from '@ts/core/r1/runtime/inferno/index';
1+
import { BaseInfernoComponent, normalizeStyles } from '@ts/core/r1/runtime/inferno/index';
22

3+
import { renderUtils } from '../../utils/index';
34
import type { GroupPanelRowProps } from './group_panel_props';
45
import { GroupPanelRowDefaultProps } from './group_panel_props';
56
import { GroupPanelVerticalCell } from './group_panel_vertical_cell';
@@ -9,11 +10,18 @@ export class GroupPanelVerticalRow extends BaseInfernoComponent<GroupPanelRowPro
910
const {
1011
className,
1112
groupItems,
13+
height,
1214
cellTemplate,
1315
} = this.props;
16+
const styles = height === undefined
17+
? undefined
18+
: normalizeStyles(renderUtils.addHeightToStyle(height));
1419

1520
return (
16-
<div className={`dx-scheduler-group-row ${className}`}>
21+
<div
22+
className={`dx-scheduler-group-row ${className}`}
23+
style={styles}
24+
>
1725
{
1826
groupItems.map(({
1927
color,

packages/devextreme/js/__internal/scheduler/r1/components/base/layout_props.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface LayoutProps extends PropsWithViewContext {
1515
addDateTableClass: boolean;
1616
addVerticalSizesClassToRows: boolean;
1717
width?: number;
18+
rowHeights?: number[];
1819
dataCellTemplate?: JSXTemplate<DataCellTemplateProps>;
1920
}
2021

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
describe, expect, it, jest,
3+
} from '@jest/globals';
4+
import $ from '@js/core/renderer';
5+
6+
import VerticalCurrentTimeShader from './current_time_shader_vertical';
7+
8+
const DATE_TIME_SHADER_CLASS = 'dx-scheduler-date-time-shader';
9+
const DATE_TIME_SHADER_TOP_CLASS = 'dx-scheduler-date-time-shader-top';
10+
const DATE_TIME_SHADER_BOTTOM_CLASS = 'dx-scheduler-date-time-shader-bottom';
11+
12+
describe('VerticalCurrentTimeShader', () => {
13+
it('should use group max height for each shader part', () => {
14+
const $container = $('<div>');
15+
const getShaderMaxHeight = jest.fn((groupIndex?: number): number => (
16+
groupIndex === 0 ? 100 : 200
17+
));
18+
const workSpace = {
19+
getScrollable: () => ({ $content: () => $container }),
20+
isGroupedByDate: () => false,
21+
getGroupedStrategy: () => ({
22+
getShaderHeight: () => 150,
23+
getShaderMaxHeight,
24+
getShaderWidth: () => 50,
25+
getShaderTopOffset: () => 0,
26+
getShaderOffset: () => 0,
27+
}),
28+
getCellWidth: () => 10,
29+
option: () => false,
30+
$element: () => $('<div>'),
31+
} as any;
32+
const shader = new VerticalCurrentTimeShader(workSpace);
33+
34+
shader.render(false, 2, 0);
35+
36+
expect(getShaderMaxHeight).toHaveBeenCalledWith(0);
37+
expect(getShaderMaxHeight).toHaveBeenCalledWith(1);
38+
expect($container.find(`.${DATE_TIME_SHADER_CLASS}`).css('height')).toBe('150px');
39+
expect($container.find(`.${DATE_TIME_SHADER_TOP_CLASS}`).eq(0).css('height')).toBe('100px');
40+
expect($container.find(`.${DATE_TIME_SHADER_TOP_CLASS}`).eq(1).css('height')).toBe('150px');
41+
expect($container.find(`.${DATE_TIME_SHADER_BOTTOM_CLASS}`).length).toBe(1);
42+
});
43+
});

0 commit comments

Comments
 (0)