Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,23 +114,21 @@ export const createAppointmentPopup = async (
const onSave = options.onSave
?? jest.fn<(appointment: Record<string, unknown>) => PromiseLike<unknown>>(resolvedDeferred);

const formSchedulerProxy = {
getResourceById: (): Record<string, unknown> => resourceManager.resourceById,
getDataAccessors: (): AppointmentDataAccessor => dataAccessors,
const formConfig = {
dataAccessors,
editing,
resourceManager,
firstDayOfWeek: options.firstDayOfWeek ?? 0,
startDayHour: options.startDayHour ?? 0,
createComponent,
getEditingConfig: (): typeof editing => editing,
getResourceManager: (): ResourceManager => resourceManager,
getFirstDayOfWeek: (): number => options.firstDayOfWeek ?? 0,
getStartDayHour: (): number => options.startDayHour ?? 0,
getCalculatedEndDate: (startDate: Date): Date => {
const endDate = new Date(startDate);
endDate.setHours(endDate.getHours() + 1);
return endDate;
},
getTimeZoneCalculator: (): typeof timeZoneCalculator => timeZoneCalculator,
};

const form = new AppointmentForm(formSchedulerProxy);
const form = new AppointmentForm(formConfig);

const noop = (): void => {};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type Popup from '@ts/ui/popup/m_popup';

import timeZoneUtils from '../m_utils_time_zone';
import type { SafeAppointment } from '../types';
import type { AppointmentDataAccessor } from '../utils/data_accessor/appointment_data_accessor';
import type { ResourceLoader } from '../utils/loader/resource_loader';
import { DEFAULT_ICONS_SHOW_MODE } from '../utils/options/constants';
import { getAppointmentGroupIndex, getRawAppointmentGroupValues, getSafeGroupValues } from '../utils/resource_manager/appointment_groups_utils';
Expand All @@ -37,6 +38,16 @@ import { customizeFormItems } from './m_customize_form_items';
import { RecurrenceForm } from './m_recurrence_form';
import { createFormIconTemplate, getStartDateCommonConfig, RecurrenceRule } from './utils';

export interface AppointmentFormConfig {
dataAccessors: AppointmentDataAccessor;
editing: SchedulerProperties['editing'];
resourceManager: ResourceManager;
firstDayOfWeek: number;
startDayHour: number;
createComponent: (element: dxElementWrapper, Component: any, options: any) => any;
getCalculatedEndDate: (startDate: Date) => Date;
}
Comment on lines +49 to +57
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppointmentFormConfig.createComponent is currently typed as (element: dxElementWrapper, Component: any, options: any) => any, but the scheduler’s _createComponent helper can be called with non-wrapper element types (e.g., selector/HTML strings) and this mismatch is already forcing @ts-expect-error at the call site. Consider widening the element parameter type (or introducing a shared CreateComponent type used across scheduler internals) to avoid TS suppressions and keep this config-based API consistently type-safe.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


const CLASSES = {
form: 'dx-scheduler-form',
icon: 'dx-icon',
Expand Down Expand Up @@ -118,9 +129,7 @@ const RESOURCES_GROUP_ICON_NAME = 'resourcesGroupIcon';
const DESCRIPTION_ICON_NAME = 'descriptionIcon';

export class AppointmentForm {
private readonly scheduler: any;

private readonly resourceManager!: ResourceManager;
private readonly config: AppointmentFormConfig;

private dxFormInstance?: dxForm;

Expand All @@ -132,6 +141,10 @@ export class AppointmentForm {

private $recurrenceGroup?: dxElementWrapper;

private get resourceManager(): ResourceManager {
return this.config.resourceManager;
}

get dxForm(): dxForm {
return this.dxFormInstance as dxForm;
}
Expand All @@ -158,29 +171,28 @@ export class AppointmentForm {
}

get startDate(): Date | null {
const { startDateExpr } = this.scheduler.getDataAccessors().expr;
const { startDateExpr } = this.config.dataAccessors.expr;
const value = this.getFormDataField(startDateExpr);

return value ? new Date(dateSerialization.deserializeDate(value)) : null;
}

get endDate(): Date | null {
const { endDateExpr } = this.scheduler.getDataAccessors().expr;
const { endDateExpr } = this.config.dataAccessors.expr;
const value = this.getFormDataField(endDateExpr);

return value ? new Date(dateSerialization.deserializeDate(value)) : null;
}

get recurrenceRuleRaw(): string | null {
const { recurrenceRuleExpr } = this.scheduler.getDataAccessors().expr;
const { recurrenceRuleExpr } = this.config.dataAccessors.expr;
const value = this.getFormDataField(recurrenceRuleExpr) as string | undefined;

return value ?? null;
}

constructor(scheduler: any) {
this.scheduler = scheduler;
this.resourceManager = scheduler.getResourceManager();
constructor(config: AppointmentFormConfig) {
this.config = config;
}

private getFormDataField(field: string): any {
Expand All @@ -200,7 +212,10 @@ export class AppointmentForm {

const mainGroup = this.createMainFormGroup();

this.recurrenceForm = new RecurrenceForm(this.scheduler);
this.recurrenceForm = new RecurrenceForm({
firstDayOfWeek: this.config.firstDayOfWeek,
createComponent: this.config.createComponent,
});
const recurrenceGroup = this.recurrenceForm.createRecurrenceFormGroup();

const items = [mainGroup, recurrenceGroup];
Expand All @@ -212,28 +227,28 @@ export class AppointmentForm {
this.applyFormItemDefaults(mainGroup, showMainGroupIcons);
this.applyFormItemDefaults(recurrenceGroup, showRecurrenceGroupIcons);

const editingConfig = this.scheduler.getEditingConfig();
const customizedItems = customizeFormItems(items, editingConfig?.form?.items);
const customizedItems = customizeFormItems(items, this.getEditingForm()?.items);

this.createForm(customizedItems);
}

private getIconsShowMode(): AppointmentFormIconsShowMode {
const editingConfig = this.scheduler.getEditingConfig() as SchedulerProperties['editing'];

if (isBoolean(editingConfig)) {
return DEFAULT_ICONS_SHOW_MODE;
private getEditingForm(): NonNullable<NonNullable<SchedulerProperties['editing']>['form']> | undefined {
const { editing } = this.config;
if (isBoolean(editing) || !editing) {
return undefined;
}
return editing.form ?? undefined;
}

return editingConfig?.form?.iconsShowMode ?? DEFAULT_ICONS_SHOW_MODE;
private getIconsShowMode(): AppointmentFormIconsShowMode {
return this.getEditingForm()?.iconsShowMode ?? DEFAULT_ICONS_SHOW_MODE;
}

private createForm(items: FormProperties['items']): dxForm {
const element = $('<div>');
const editingConfig = this.scheduler.getEditingConfig();
const {
items: formItems, onContentReady, onInitialized, ...customFormOptions
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formItems is destructured from this.getEditingForm() but never used. If the intent is just to omit items from customFormOptions, consider renaming it to an intentionally-unused name (e.g. _formItems) or adjusting the destructuring to avoid introducing an unused local (helps keep eslint/TS no-unused-vars clean).

Suggested change
items: formItems, onContentReady, onInitialized, ...customFormOptions
items: _formItems, onContentReady, onInitialized, ...customFormOptions

Copilot uses AI. Check for mistakes.
} = editingConfig?.form ?? {};
} = this.getEditingForm() ?? {};

const defaultOptions: FormProperties = {
items,
Expand All @@ -251,7 +266,7 @@ export class AppointmentForm {
onFieldDataChanged: (e) => {
const {
startDateExpr, endDateExpr, recurrenceRuleExpr,
} = this.scheduler.getDataAccessors().expr;
} = this.config.dataAccessors.expr;

const { dataField } = e;

Expand All @@ -261,7 +276,7 @@ export class AppointmentForm {

const isDateRangeChanged = [startDateExpr, endDateExpr].includes(dataField);
const isRecurrenceRuleChanged = dataField === recurrenceRuleExpr;
const isResourceChanged = Object.keys(this.scheduler.getResourceById()).includes(dataField);
const isResourceChanged = Object.keys(this.config.resourceManager.resourceById).includes(dataField);

if (isDateRangeChanged) {
this.updateDateEditorsValues();
Expand Down Expand Up @@ -300,7 +315,7 @@ export class AppointmentForm {
} as FormProperties;

const formOptions = extend(true, defaultOptions, customFormOptions);
return this.scheduler.createComponent(element, dxForm, formOptions) as dxForm;
return this.config.createComponent(element, dxForm, formOptions) as dxForm;
}

private createMainFormGroup(): GroupItem {
Expand All @@ -320,7 +335,7 @@ export class AppointmentForm {
}

private createSubjectGroup(): GroupItem {
const { textExpr } = this.scheduler.getDataAccessors().expr;
const { textExpr } = this.config.dataAccessors.expr;

return {
name: SUBJECT_GROUP_NAME,
Expand Down Expand Up @@ -375,7 +390,7 @@ export class AppointmentForm {
}

private createAllDaySwitch(): SimpleItem {
const { allDayExpr, startDateExpr, endDateExpr } = this.scheduler.getDataAccessors().expr;
const { allDayExpr, startDateExpr, endDateExpr } = this.config.dataAccessors.expr;

return {
name: ALL_DAY_EDITOR_NAME,
Expand Down Expand Up @@ -403,10 +418,9 @@ export class AppointmentForm {
this.dxForm.updateData(startDateExpr, allDayStartDate);
this.dxForm.updateData(endDateExpr, allDayStartDate);
} else {
const startHour = this.scheduler.getStartDayHour();
startDate.setHours(startHour);
startDate.setHours(this.config.startDayHour);

const calculatedEndDate = this.scheduler.getCalculatedEndDate(startDate);
const calculatedEndDate = this.config.getCalculatedEndDate(startDate);

this.dxForm.updateData(startDateExpr, startDate);
this.dxForm.updateData(endDateExpr, calculatedEndDate);
Expand All @@ -419,7 +433,7 @@ export class AppointmentForm {
private createStartDateGroup(): GroupItem {
const {
startDateExpr, startDateTimeZoneExpr, endDateTimeZoneExpr,
} = this.scheduler.getDataAccessors().expr;
} = this.config.dataAccessors.expr;

return this.createDateGroup(
startDateExpr,
Expand Down Expand Up @@ -459,7 +473,7 @@ export class AppointmentForm {
}

private createEndDateGroup(): GroupItem {
const { endDateExpr, endDateTimeZoneExpr } = this.scheduler.getDataAccessors().expr;
const { endDateExpr, endDateTimeZoneExpr } = this.config.dataAccessors.expr;

return this.createDateGroup(
endDateExpr,
Expand Down Expand Up @@ -498,8 +512,9 @@ export class AppointmentForm {
timeItemOptions?: SimpleItem,
timezoneItemOptions?: SimpleItem,
): GroupItem {
const { allowTimeZoneEditing } = this.scheduler.getEditingConfig();
const { startDateExpr, endDateExpr } = this.scheduler.getDataAccessors().expr;
const { editing } = this.config;
const allowTimeZoneEditing = !isBoolean(editing) ? editing?.allowTimeZoneEditing : undefined;
const { startDateExpr, endDateExpr } = this.config.dataAccessors.expr;
const isStartDateEditor = dateExpr === startDateExpr;

const getEditorsDate = (): Date | null => (isStartDateEditor ? this.startDate : this.endDate);
Expand Down Expand Up @@ -567,7 +582,7 @@ export class AppointmentForm {
items: [
extend(
true,
getStartDateCommonConfig(this.scheduler.getFirstDayOfWeek()),
getStartDateCommonConfig(this.config.firstDayOfWeek),
{
editorOptions: {
onValueChanged: (e) => {
Expand All @@ -593,7 +608,7 @@ export class AppointmentForm {
type: 'time',
useMaskBehavior: true,
calendarOptions: {
firstDayOfWeek: this.scheduler.getFirstDayOfWeek(),
firstDayOfWeek: this.config.firstDayOfWeek,
},
onValueChanged: (e) => {
dateValueChanged(e, (date: Date): void => {
Expand Down Expand Up @@ -625,7 +640,7 @@ export class AppointmentForm {
}

private createRepeatGroup(): GroupItem {
const { recurrenceRuleExpr } = this.scheduler.getDataAccessors().expr;
const { recurrenceRuleExpr } = this.config.dataAccessors.expr;

return {
name: REPEAT_GROUP_NAME,
Expand Down Expand Up @@ -677,7 +692,7 @@ export class AppointmentForm {
}

private createDescriptionGroup(): GroupItem {
const { descriptionExpr } = this.scheduler.getDataAccessors().expr;
const { descriptionExpr } = this.config.dataAccessors.expr;

return {
name: DESCRIPTION_GROUP_NAME,
Expand Down Expand Up @@ -709,7 +724,7 @@ export class AppointmentForm {
}

private createResourcesGroup(): GroupItem {
const resourcesLoaders: ResourceLoader[] = Object.values(this.scheduler.getResourceById());
const resourcesLoaders: ResourceLoader[] = Object.values(this.config.resourceManager.resourceById);

let resourcesItems: FormItem[] = resourcesLoaders.map((resourceLoader) => {
const { dataSource, dataAccessor } = resourceLoader;
Expand Down Expand Up @@ -854,8 +869,8 @@ export class AppointmentForm {

showMainGroup(): void {
const currentHeight = this.dxPopup.option('height') as string | number | undefined;
const editingConfig = this.scheduler.getEditingConfig();
const configuredHeight = editingConfig?.popup?.height ?? 'auto';
const { editing } = this.config;
const configuredHeight = (!isBoolean(editing) && editing?.popup?.height) || 'auto';
Comment thread
aleksei-semikozov marked this conversation as resolved.
Outdated

if (typeof currentHeight === 'number') {
this.dxPopup.option('height', configuredHeight);
Expand Down Expand Up @@ -908,7 +923,7 @@ export class AppointmentForm {

saveRecurrenceValue(): void {
const { recurrenceRule } = this.recurrenceForm;
const { recurrenceRuleExpr } = this.scheduler.getDataAccessors().expr;
const { recurrenceRuleExpr } = this.config.dataAccessors.expr;

const recurrenceRuleSerialized = recurrenceRule.toString() ?? '';

Expand Down Expand Up @@ -1003,7 +1018,7 @@ export class AppointmentForm {
}

private updateDateTimeEditorsVisibility(): void {
const { allDayExpr } = this.scheduler.getDataAccessors().expr;
const { allDayExpr } = this.config.dataAccessors.expr;
const visible = !this.getFormDataField(allDayExpr);

const dateOptionsGroupPath = `${MAIN_GROUP_NAME}.${DATE_GROUP_NAME}.${DATE_OPTIONS_GROUP_NAME}`;
Expand Down
Loading
Loading