Skip to content

Commit c667df3

Browse files
sjburCopilot
andauthored
Scheduler: refactor Header module (#32258)
Signed-off-by: Sergei Burkatskii <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 089fb52 commit c667df3

File tree

10 files changed

+328
-288
lines changed

10 files changed

+328
-288
lines changed

packages/devextreme/eslint.config.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,25 @@ export default [
579579
'devextreme-custom/no-deferred': 'off',
580580
},
581581
},
582+
// Strict TypeScript rules for scheduler/header
583+
{
584+
files: ['js/__internal/scheduler/header/**/*.ts?(x)'],
585+
languageOptions: {
586+
parser: tsParser,
587+
ecmaVersion: 5,
588+
sourceType: 'script',
589+
parserOptions: {
590+
project: './tsconfig.json',
591+
tsconfigRootDir: `${__dirname}/js/__internal`,
592+
},
593+
},
594+
rules: {
595+
'@typescript-eslint/no-explicit-any': 'error',
596+
'@typescript-eslint/explicit-function-return-type': 'error',
597+
'@typescript-eslint/no-unsafe-return': 'error',
598+
'@typescript-eslint/explicit-module-boundary-types': 'error',
599+
},
600+
},
582601
// Rules for grid controls
583602
{
584603
files: [
Lines changed: 59 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,65 @@
11
import registerComponent from '@js/core/component_registrator';
22
import devices from '@js/core/devices';
3+
import type { dxElementWrapper } from '@js/core/renderer';
34
import $ from '@js/core/renderer';
4-
import Calendar from '@js/ui/calendar';
55
import Popover from '@js/ui/popover/ui.popover';
66
import Popup from '@js/ui/popup/ui.popup';
7-
import type { dxSchedulerOptions } from '@js/ui/scheduler';
8-
import Scrollable from '@js/ui/scroll_view/ui.scrollable';
9-
import Widget from '@js/ui/widget/ui.widget';
7+
import type { OptionChanged } from '@ts/core/widget/types';
8+
import Widget from '@ts/core/widget/widget';
9+
import type { KeyboardKeyDownEvent } from '@ts/events/core/m_keyboard_processor';
10+
import type { CalendarProperties } from '@ts/ui/calendar/calendar';
11+
import Calendar from '@ts/ui/calendar/calendar';
12+
import Scrollable from '@ts/ui/scroll_view/scrollable';
13+
14+
import type { HeaderCalendarOptions } from './types';
1015

1116
const CALENDAR_CLASS = 'dx-scheduler-navigator-calendar';
1217
const CALENDAR_POPOVER_CLASS = 'dx-scheduler-navigator-calendar-popover';
1318

14-
export default class SchedulerCalendar extends Widget<dxSchedulerOptions> {
15-
_overlay: any;
19+
export default class SchedulerCalendar extends Widget<HeaderCalendarOptions> {
20+
_overlay?: Popup | Popover;
1621

17-
_calendar: any;
22+
_calendar?: Calendar;
1823

19-
show(target) {
20-
if (!this._isMobileLayout()) {
21-
this._overlay.option('target', target);
24+
public async show(target: HTMLElement): Promise<void> {
25+
if (!SchedulerCalendar._isMobileLayout()) {
26+
this._overlay?.option('target', target);
2227
}
23-
this._overlay.show();
28+
29+
await this._overlay?.show();
2430
}
2531

26-
hide() {
27-
this._overlay.hide();
32+
public async hide(): Promise<void> {
33+
await this._overlay?.hide();
2834
}
2935

30-
_keyboardHandler(opts): void {
31-
this._calendar?._keyboardHandler(opts);
36+
public _keyboardHandler(opts: KeyboardKeyDownEvent): boolean {
37+
return this._calendar?._keyboardHandler(opts) ?? false;
3238
}
3339

34-
_init(): void {
35-
// @ts-expect-error
40+
public _init(): void {
3641
super._init();
3742
this.$element();
3843
}
3944

40-
_render(): void {
41-
// @ts-expect-error
45+
public _render(): void {
4246
super._render();
4347
this._renderOverlay();
4448
}
4549

46-
_renderOverlay(): void {
50+
private _renderOverlay(): void {
4751
this.$element().addClass(CALENDAR_POPOVER_CLASS);
4852

49-
const isMobileLayout = this._isMobileLayout();
53+
const isMobileLayout = SchedulerCalendar._isMobileLayout();
5054

51-
const overlayType = isMobileLayout ? Popup : Popover;
52-
53-
// @ts-expect-error
54-
this._overlay = this._createComponent(this.$element(), overlayType, {
55-
contentTemplate: () => this._createOverlayContent(),
56-
onShown: () => this._calendar.focus(),
55+
const overlayConfig = {
56+
contentTemplate: (): dxElementWrapper => this._createOverlayContent(),
57+
onShown: (): void => {
58+
this._calendar?.focus();
59+
},
5760
defaultOptionsRules: [
5861
{
59-
device: () => isMobileLayout,
62+
device: (): boolean => isMobileLayout,
6063
options: {
6164
fullScreen: true,
6265
showCloseButton: false,
@@ -67,24 +70,28 @@ export default class SchedulerCalendar extends Widget<dxSchedulerOptions> {
6770
},
6871
},
6972
],
70-
});
73+
};
74+
75+
if (isMobileLayout) {
76+
this._overlay = this._createComponent(this.$element(), Popup, overlayConfig);
77+
} else {
78+
this._overlay = this._createComponent(this.$element(), Popover, overlayConfig);
79+
}
7180
}
7281

73-
_createOverlayContent() {
82+
private _createOverlayContent(): dxElementWrapper {
7483
const result = $('<div>').addClass(CALENDAR_CLASS);
75-
// @ts-expect-error
7684
this._calendar = this._createComponent(result, Calendar, this._getCalendarOptions());
7785

78-
if (this._isMobileLayout()) {
86+
if (SchedulerCalendar._isMobileLayout()) {
7987
const scrollable = this._createScrollable(result);
8088
return scrollable.$element();
8189
}
8290

8391
return result;
8492
}
8593

86-
_createScrollable(content) {
87-
// @ts-expect-error
94+
private _createScrollable(content: dxElementWrapper): Scrollable {
8895
const result = this._createComponent('<div>', Scrollable, {
8996
height: 'auto',
9097
direction: 'both',
@@ -94,7 +101,11 @@ export default class SchedulerCalendar extends Widget<dxSchedulerOptions> {
94101
return result;
95102
}
96103

97-
_optionChanged({ name, value }) {
104+
public _optionChanged(
105+
args: OptionChanged<HeaderCalendarOptions>,
106+
): void {
107+
const { name, value } = args;
108+
98109
switch (name) {
99110
case 'value':
100111
this._calendar?.option('value', value);
@@ -104,23 +115,26 @@ export default class SchedulerCalendar extends Widget<dxSchedulerOptions> {
104115
}
105116
}
106117

107-
_getCalendarOptions() {
118+
private _getCalendarOptions(): CalendarProperties {
119+
const {
120+
value, min, max, firstDayOfWeek, focusStateEnabled, tabIndex, onValueChanged,
121+
} = this.option();
108122
return {
109-
value: this.option('value'),
110-
min: this.option('min'),
111-
max: this.option('max'),
112-
firstDayOfWeek: this.option('firstDayOfWeek'),
113-
focusStateEnabled: this.option('focusStateEnabled'),
114-
onValueChanged: this.option('onValueChanged'),
123+
value,
124+
min,
125+
max,
126+
firstDayOfWeek,
127+
focusStateEnabled,
128+
tabIndex,
129+
onValueChanged,
130+
// @ts-expect-error skipFocusCheck is an internal Calendar property
115131
skipFocusCheck: true,
116-
tabIndex: this.option('tabIndex'),
117132
};
118133
}
119134

120-
_isMobileLayout() {
135+
private static _isMobileLayout(): boolean {
121136
return !devices.current().generic;
122137
}
123138
}
124139

125-
// @ts-expect-error
126140
registerComponent('dxSchedulerCalendarPopup', SchedulerCalendar);

packages/devextreme/js/__internal/scheduler/header/m_date_navigator.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import {
22
describe, expect, it, jest,
33
} from '@jest/globals';
4+
import type { ToolbarItem } from '@js/ui/scheduler';
45

56
import {
67
CLASS, DEFAULT_ITEMS, getDateNavigator, ITEMS_NAME,
78
} from './m_date_navigator';
9+
import type { SchedulerHeader } from './m_header';
810

911
describe('getDateNavigator', () => {
1012
it('should return default options in case of item is empty', () => {
11-
expect(getDateNavigator({} as any, {})).toEqual({
13+
expect(getDateNavigator({} as SchedulerHeader, {})).toEqual({
1214
location: 'before',
1315
name: 'dateNavigator',
1416
widget: 'dxButtonGroup',
@@ -26,13 +28,13 @@ describe('getDateNavigator', () => {
2628
});
2729
});
2830
it('should return replace items in correct order with custom options', () => {
29-
expect(getDateNavigator({} as any, {
31+
expect(getDateNavigator({} as SchedulerHeader, {
3032
customField: 'customField',
3133
options: {
3234
customOption: 'customOption',
3335
items: ['dateInterval', 'next', { key: 'customButton' }],
3436
},
35-
} as any)).toEqual({
37+
} as ToolbarItem)).toEqual({
3638
location: 'before',
3739
name: 'dateNavigator',
3840
widget: 'dxButtonGroup',
@@ -54,7 +56,7 @@ describe('getDateNavigator', () => {
5456
it('should handle default and custom click callback', () => {
5557
const customClick = jest.fn();
5658
const event = { itemData: { clickHandler: jest.fn() } };
57-
const config = getDateNavigator({} as any, {
59+
const config = getDateNavigator({} as SchedulerHeader, {
5860
options: { onItemClick: customClick },
5961
});
6062

packages/devextreme/js/__internal/scheduler/header/m_date_navigator.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import messageLocalization from '@js/common/core/localization/message';
22
import dateUtils from '@js/core/utils/date';
33
import type { ContentReadyEvent } from '@js/ui/button';
44
import type { Item as ButtonGroupItem, ItemClickEvent, Properties as ButtonGroupOptions } from '@js/ui/button_group';
5-
import { isMaterialBased } from '@js/ui/themes';
5+
import { current, isMaterialBased } from '@js/ui/themes';
66
import type { Item as ToolbarItem } from '@js/ui/toolbar';
77
import { dateUtilsTs } from '@ts/core/utils/date';
88
import { extend } from '@ts/core/utils/m_extend';
@@ -31,12 +31,12 @@ const { trimTime } = dateUtils;
3131

3232
interface DateNavigatorItem extends ButtonGroupItem {
3333
key: string;
34-
clickHandler: (event: ItemClickEvent) => void;
34+
clickHandler: (event: ItemClickEvent) => Promise<void> | void;
3535
onContentReady: (event: ContentReadyEvent) => void;
3636
}
3737

3838
const isPreviousButtonDisabled = (header: SchedulerHeader): boolean => {
39-
const minOption = header.option('min');
39+
const minOption = header.option().min;
4040

4141
if (!dateUtilsTs.isValidDate(minOption)) return false;
4242

@@ -50,7 +50,7 @@ const isPreviousButtonDisabled = (header: SchedulerHeader): boolean => {
5050
};
5151

5252
const isNextButtonDisabled = (header: SchedulerHeader): boolean => {
53-
const maxOption = header.option('max');
53+
const maxOption = header.option().max;
5454

5555
if (!dateUtilsTs.isValidDate(maxOption)) return false;
5656

@@ -154,9 +154,28 @@ const getNextButtonOptions = (header: SchedulerHeader): DateNavigatorItem => {
154154
};
155155
};
156156

157+
export const getTodayButtonOptions = (
158+
header: SchedulerHeader,
159+
item: ToolbarItem,
160+
): ToolbarItem => extend(true, {}, {
161+
location: 'before',
162+
locateInMenu: 'auto',
163+
widget: 'dxButton',
164+
cssClass: 'dx-scheduler-today',
165+
options: {
166+
text: messageLocalization.format('dxScheduler-navigationToday'),
167+
icon: 'today',
168+
stylingMode: 'outlined',
169+
type: 'normal',
170+
onClick() {
171+
const { indicatorTime } = header.option();
172+
header._updateCurrentDate(indicatorTime ?? new Date());
173+
},
174+
},
175+
}, item) as ToolbarItem;
176+
157177
export const getDateNavigator = (header: SchedulerHeader, item: ToolbarItem): ToolbarItem => {
158-
// @ts-expect-error current theme used
159-
const stylingMode = isMaterialBased() ? 'text' : 'contained';
178+
const stylingMode = isMaterialBased(current()) ? 'text' : 'contained';
160179
const config: ToolbarItem = extend(true, {}, {
161180
location: 'before',
162181
name: 'dateNavigator',
@@ -170,7 +189,8 @@ export const getDateNavigator = (header: SchedulerHeader, item: ToolbarItem): To
170189
const options = config.options as ButtonGroupOptions;
171190
const { onItemClick } = options;
172191

173-
options.items = (options.items ?? DEFAULT_ITEMS).map((groupItem) => {
192+
const items = (options.items ?? DEFAULT_ITEMS);
193+
options.items = items.map((groupItem: ButtonGroupItem | string) => {
174194
switch (groupItem) {
175195
case ITEMS_NAME.previousButton:
176196
return getPreviousButtonOptions(header);
@@ -179,7 +199,7 @@ export const getDateNavigator = (header: SchedulerHeader, item: ToolbarItem): To
179199
case ITEMS_NAME.calendarButton:
180200
return getCalendarButtonOptions(header);
181201
default:
182-
return groupItem;
202+
return groupItem as ButtonGroupItem;
183203
}
184204
});
185205
options.onItemClick = (event): void => {

0 commit comments

Comments
 (0)