diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/appointment_tooltip.test.ts b/packages/devextreme/js/__internal/scheduler/__tests__/appointment_tooltip.test.ts index f4d646cd3a42..9d3de292ac14 100644 --- a/packages/devextreme/js/__internal/scheduler/__tests__/appointment_tooltip.test.ts +++ b/packages/devextreme/js/__internal/scheduler/__tests__/appointment_tooltip.test.ts @@ -438,7 +438,7 @@ describe.each([ const initialTarget = POM.tooltip.target; - scheduler.addAppointment({ + await scheduler.addAppointment({ text: 'New Apt', startDate: new Date(2017, 4, 20, 9, 30), endDate: new Date(2017, 4, 20, 10, 30), diff --git a/packages/devextreme/js/__internal/scheduler/appointments/appointment_collection_options.ts b/packages/devextreme/js/__internal/scheduler/appointments/appointment_collection_options.ts new file mode 100644 index 000000000000..38e76729e96e --- /dev/null +++ b/packages/devextreme/js/__internal/scheduler/appointments/appointment_collection_options.ts @@ -0,0 +1,51 @@ +import type { + AppointmentClickEvent, + AppointmentContextMenuEvent, + AppointmentDblClickEvent, + AppointmentRenderedEvent, +} from '@js/ui/scheduler'; + +import type NotifyScheduler from '../base/widget_notify_scheduler'; +import type { TimeZoneCalculator } from '../r1/timezone_calculator/index'; +import type { DesktopTooltipStrategy } from '../tooltip_strategies/desktop_tooltip_strategy'; +import type { MobileTooltipStrategy } from '../tooltip_strategies/mobile_tooltip_strategy'; +import type { DOMMetaData, ScrollToGroupValuesOrOptions } from '../types'; +import type { AppointmentDataAccessor } from '../utils/data_accessor/appointment_data_accessor'; +import type { ResourceManager } from '../utils/resource_manager/resource_manager'; +import type { AppointmentDataSource } from '../view_model/m_appointment_data_source'; +import type { SortedEntity } from '../view_model/types'; +import type ViewDataProvider from '../workspaces/view_model/view_data_provider'; + +export interface AppointmentCollectionOptions { + getResourceManager: () => ResourceManager; + getAppointmentDataSource: () => AppointmentDataSource; + getSortedAppointments: () => SortedEntity[]; + scrollTo: ( + date: Date, + groupValuesOrOptions?: ScrollToGroupValuesOrOptions, + allDay?: boolean, + ) => void; + appointmentTooltip: MobileTooltipStrategy | DesktopTooltipStrategy; + dataAccessors: AppointmentDataAccessor; + notifyScheduler: NotifyScheduler; + onItemRendered: (args: AppointmentRenderedEvent) => void; + onItemClick: (args: AppointmentClickEvent) => void; + onItemContextMenu: (args: AppointmentContextMenuEvent) => void; + onAppointmentDblClick: (args: AppointmentDblClickEvent) => void; + tabIndex: number; + focusStateEnabled: boolean; + allowDrag: boolean; + allowDelete: boolean; + allowResize: boolean; + allowAllDayResize: boolean; + rtlEnabled: boolean; + groups: string[]; + groupByDate: boolean; + timeZoneCalculator: TimeZoneCalculator; + getResizableStep: () => number; + getDOMElementsMetaData: () => DOMMetaData | undefined; + getViewDataProvider: () => ViewDataProvider | undefined; + isVerticalGroupedWorkSpace: () => boolean; + isDateAndTimeView: () => boolean; + onContentReady: () => void; +} diff --git a/packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts b/packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts index dcbf80a8e1b8..d7b142f78d6a 100644 --- a/packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts +++ b/packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts @@ -91,7 +91,7 @@ export class CompactAppointmentsHelper { getItemSettings, }; - workSpace.createDragBehaviorBase($element, $schedulerElement, options); + workSpace?.createDragBehaviorBase($element, $schedulerElement, options); }; } @@ -105,7 +105,6 @@ export class CompactAppointmentsHelper { private createCompactButton(template, options: CompactAppointmentOptions) { const $button = this.createCompactButtonElement(options); - // @ts-expect-error return this.instance._createComponent($button, Button, { type: 'default', width: options.width, diff --git a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts index 5a02a006afb0..a6e6000861e2 100644 --- a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts @@ -1,19 +1,24 @@ import { triggerResizeEvent } from '@js/common/core/events/visibility_change'; import dateLocalization from '@js/common/core/localization/date'; import messageLocalization from '@js/common/core/localization/message'; +import type { DataSource } from '@js/common/data'; import registerComponent from '@js/core/component_registrator'; import config from '@js/core/config'; import { getPublicElement } from '@js/core/element'; +import type { PostponedOperations } from '@js/core/postponed_operations'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { BindableTemplate } from '@js/core/templates/bindable_template'; import { EmptyTemplate } from '@js/core/templates/empty_template'; +import type { FunctionTemplate } from '@js/core/templates/function_template'; +import type { TemplateBase } from '@js/core/templates/template_base'; import Callbacks from '@js/core/utils/callbacks'; import { noop } from '@js/core/utils/common'; import { compileGetter } from '@js/core/utils/data'; import dateUtils from '@js/core/utils/date'; import dateSerialization from '@js/core/utils/date_serialization'; -// @ts-expect-error +import type { DeferredObj } from '@js/core/utils/deferred'; +// @ts-expect-error untyped deferred module re-export import { Deferred, fromPromise, when } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { getBoundingRect } from '@js/core/utils/position'; @@ -26,21 +31,42 @@ import { isPromise, } from '@js/core/utils/type'; import { hasWindow } from '@js/core/utils/window'; +import type { DataSourceOptions } from '@js/data/data_source'; import DataHelperMixin from '@js/data_helper'; +import type { CustomDialogOptions } from '@js/ui/dialog'; import { custom as customDialog } from '@js/ui/dialog'; +import type { ItemContextMenuEvent } from '@js/ui/list'; import type { - Appointment, AppointmentTooltipShowingEvent, DayOfWeek, Occurrence, + Appointment, + AppointmentAddingEvent, + AppointmentClickEvent, + AppointmentContextMenuEvent, + AppointmentDblClickEvent, + AppointmentFormOpeningEvent, + AppointmentRenderedEvent, + AppointmentTooltipShowingEvent, + AppointmentUpdatingEvent, + CellClickEvent, + CellContextMenuEvent, + DayOfWeek, + Occurrence, + Properties as SchedulerProperties, + RecurrenceEditMode, + SelectionEndEvent, } from '@js/ui/scheduler'; import errors from '@js/ui/widget/ui.errors'; +import type { Options } from '@ts/core/options/m_index'; import { dateUtilsTs } from '@ts/core/utils/date'; import type { OptionChanged } from '@ts/core/widget/types'; +import type Scrollable from '@ts/ui/scroll_view/scrollable'; import { createA11yStatusContainer } from './a11y_status/a11y_status_render'; import { getA11yStatusText } from './a11y_status/a11y_status_text'; -import { AppointmentDragController } from './appointment_drag_controller'; +import { AppointmentDragController, type WorkSpaceDraggableOptions } from './appointment_drag_controller'; import type { AppointmentFormConfig } from './appointment_popup/form'; import { AppointmentForm } from './appointment_popup/form'; import { AppointmentPopup } from './appointment_popup/popup'; +import type { AppointmentCollectionOptions } from './appointments/appointment_collection_options'; import AppointmentCollection from './appointments/m_appointment_collection'; import type { AppointmentsProperties } from './appointments_new/appointments'; import { Appointments } from './appointments_new/appointments'; @@ -48,11 +74,12 @@ import NotifyScheduler from './base/widget_notify_scheduler'; import { SchedulerHeader } from './header/header'; import type { HeaderOptions } from './header/types'; import { hide as hideLoading, show as showLoading } from './loading'; +import type AppointmentDragBehavior from './m_appointment_drag_behavior'; import { CompactAppointmentsHelper } from './m_compact_appointments_helper'; import type { SubscribeKey, SubscribeMethods } from './m_subscribes'; import subscribes from './m_subscribes'; import { combineRemoteFilter } from './r1/filterting/remote'; -import { createTimeZoneCalculator } from './r1/timezone_calculator/index'; +import { createTimeZoneCalculator, type TimeZoneCalculator } from './r1/timezone_calculator/index'; import { excludeFromRecurrence, getToday, @@ -64,11 +91,17 @@ import { validateRRule } from './recurrence/validate_rule'; import { SchedulerOptionsBaseWidget } from './scheduler_options_base_widget'; import { DesktopTooltipStrategy } from './tooltip_strategies/desktop_tooltip_strategy'; import { MobileTooltipStrategy } from './tooltip_strategies/mobile_tooltip_strategy'; -import type { AppointmentTooltipExtraOptions } from './tooltip_strategies/tooltip_strategy_base'; +import type { AppointmentTooltipExtraOptions, AppointmentTooltipOptions } from './tooltip_strategies/tooltip_strategy_base'; import type { + AppointmentTooltipContextMenuEventArgs, AppointmentTooltipItem, + CreateComponentFn, + DOMMetaData, + GroupBoundsOffset, + MappedAppointmentFields, SafeAppointment, ScrollToGroupValuesOrOptions, ScrollToOptions, TargetedAppointment, + ViewCellData, ViewType, } from './types'; import { utils } from './utils'; @@ -82,14 +115,18 @@ import { VIEWS } from './utils/options/constants_view'; import type { NormalizedView, SafeSchedulerOptions } from './utils/options/types'; import { getAppointmentGroupValues, setAppointmentGroupValues } from './utils/resource_manager/appointment_groups_utils'; import { ResourceManager } from './utils/resource_manager/resource_manager'; +import type { GroupValues } from './utils/resource_manager/types'; import timeZoneUtils, { type TimezoneLabel } from './utils_time_zone'; import AppointmentLayoutManager from './view_model/appointments_layout_manager'; +import type { CollectorCSS, RealSize } from './view_model/generate_view_model/steps/add_geometry/types'; import { AppointmentDataSource } from './view_model/m_appointment_data_source'; -import type { AppointmentViewModelPlain } from './view_model/types'; +import type { AppointmentItemViewModel, AppointmentViewModelPlain, PanelName } from './view_model/types'; import SchedulerAgenda from './workspaces/agenda'; import SchedulerTimelineDay from './workspaces/timeline_day'; import SchedulerTimelineMonth from './workspaces/timeline_month'; import SchedulerTimelineWeek from './workspaces/timeline_week'; +import type ViewDataProvider from './workspaces/view_model/view_data_provider'; +import type { WorkspaceOptionsInternal } from './workspaces/work_space'; import SchedulerWorkSpaceDay from './workspaces/work_space_day'; import SchedulerWorkSpaceMonth from './workspaces/work_space_month'; import SchedulerWorkSpaceWeek from './workspaces/work_space_week'; @@ -154,7 +191,9 @@ const StoreEventNames = { UPDATING: 'onAppointmentUpdating', UPDATED: 'onAppointmentUpdated', -}; +} as const; + +type StoreEventName = typeof StoreEventNames[keyof typeof StoreEventNames]; const RECURRENCE_EDITING_MODE = { SERIES: 'editSeries', @@ -162,81 +201,282 @@ const RECURRENCE_EDITING_MODE = { CANCEL: 'cancel', }; +type DroppableCellData = Pick; + +interface WorkSpaceCoordinates { + top: number; + left: number; + groupIndex?: number; +} + +interface WorkSpacePositionHelper { + getResizableStep: () => number; + getHorizontalMax: (groupIndex: number) => number; + getVerticalMax: (options: { + groupIndex: number; + isVirtualScrolling: boolean; + showAllDayPanel: boolean; + supportAllDayRow: boolean; + isGroupedAllDayPanel: boolean; + isVerticalGrouping: boolean; + }) => number; +} + +interface VirtualScrollingDispatcherLike { + cellCountInsideLeftVirtualCell: number; + cellCountInsideRightVirtualCell: number; + cellCountInsideTopVirtualRow: number; +} + +interface SchedulerWorkSpaceLike { + type: ViewType; + NAME?: string; + dragBehavior: AppointmentDragBehavior | null; + positionHelper: WorkSpacePositionHelper; + virtualScrollingDispatcher: VirtualScrollingDispatcherLike; + viewDataProvider: ViewDataProvider; + option: (name: string | Record, value?: unknown) => unknown; + getDateRange: () => Date[]; + getCellFromDragTarget: ($dragTarget: dxElementWrapper) => dxElementWrapper | null; + renderAgendaLayout?: (viewModel: AppointmentViewModelPlain[]) => void; + calculateEndDate: (startDate: Date) => Date; + updateScrollPosition: (date: Date, groupValues: GroupValues, inAllDayRow: boolean) => void; + supportAllDayRow: () => boolean; + getAllDayContainer: () => dxElementWrapper | null; + getFixedContainer: () => dxElementWrapper; + getDOMElementsMetaData: () => DOMMetaData; + isVerticalGroupedWorkSpace: () => boolean; + isVirtualScrolling: () => boolean; + isGroupedByDate: () => boolean; + needRecalculateResizableArea: () => boolean; + getHeaderDate: () => Date; + updateHeaderEmptyCellWidth: () => void; + initDragBehavior: (scheduler: unknown) => void; + attachTablesEvents: () => void; + getWorkArea: () => dxElementWrapper; + $element: () => dxElementWrapper; + renderCurrentDateTimeLineAndShader: () => void; + _dispose: () => void; + _dimensionChanged: () => void; + getScrollable: () => Scrollable; + getScrollableContainer: () => dxElementWrapper; + getCellData: ($cell: dxElementWrapper) => DroppableCellData; + getCellWidth: () => number; + getCellHeight: () => number; + getGroupCount: () => number; + getGroupBounds: (coordinates: WorkSpaceCoordinates) => GroupBoundsOffset | undefined; + getPanelDOMSize: (panelName: PanelName) => RealSize; + getCollectorDimension: (isCompactCollector: boolean, panelName: PanelName) => CollectorCSS; + getAgendaVerticalStepHeight: () => number; + createDragBehaviorBase: ( + targetElement: dxElementWrapper, + rootElement: dxElementWrapper, + options: Record, + ) => void; + removeDroppableCellClass: () => void; + keepOriginalHours: () => boolean; + getDataByDroppableCell: () => DroppableCellData; + getStartViewDate: () => Date | undefined; + getEndViewDate: () => Date; + scrollTo: ( + date: Date, + groupValuesOrOptions?: ScrollToGroupValuesOrOptions, + allDay?: boolean, + throwWarning?: boolean, + align?: 'start' | 'center', + ) => void; + focus: () => void; +} + +type SchedulerActionCancel = boolean | PromiseLike | DeferredObj; + +type SchedulerActionOptions = { + cancel: SchedulerActionCancel; +} & Record; + +type ProcessActionCallback = ( + canceled: boolean, +) => unknown; + +interface SchedulerDragEvent { + cancel: DeferredObj; +} + +type SchedulerEventArgs = Omit; + +type AppointmentAddingOptions = Pick; + +type AppointmentUpdatingOptions = Pick; + +interface AppointmentCompletedOptions { + appointmentData: SafeAppointment; + error?: Error; +} + +interface AppointmentDeletingOptions { + [key: string]: unknown; + appointmentData: SafeAppointment; + targetedAppointmentData?: SafeAppointment | AppointmentAdapter; + cancel: SchedulerActionCancel; +} + +interface SchedulerActions { + onAppointmentAdding: (args: AppointmentAddingOptions) => void; + onAppointmentAdded: (args: AppointmentCompletedOptions) => void; + onAppointmentUpdating: (args: AppointmentUpdatingOptions) => void; + onAppointmentUpdated: (args: AppointmentCompletedOptions) => void; + onAppointmentDeleting: (args: AppointmentDeletingOptions) => void; + onAppointmentDeleted: (args: AppointmentCompletedOptions) => void; + onAppointmentFormOpening: (args: SchedulerEventArgs) => void; + onAppointmentTooltipShowing: (args: SchedulerEventArgs) => void; + onSelectionEnd: (args: SchedulerEventArgs) => void; + onAppointmentRendered: (args: AppointmentRenderedEvent) => void; + onAppointmentClick: (args: AppointmentClickEvent) => void; + onAppointmentDblClick: (args: AppointmentDblClickEvent) => void; + onAppointmentContextMenu: (args: AppointmentContextMenuEvent) => void; +} + +interface SelectedCellsClickCellData { + startDate?: Date; + endDate?: Date; + startDateUTC: number; + endDateUTC: number; + allDay?: boolean; +} + +interface SchedulerEditing { + allowAdding: boolean; + allowUpdating: boolean; + allowDeleting: boolean; + allowResizing: boolean; + allowDragging: boolean; +} + +type SchedulerEditingObject = Exclude, boolean>; + +type SchedulerEditingState = SchedulerEditing & Partial; + +interface RecurrenceDialog { + show: () => DeferredObj; + hide: (mode: string) => void; +} + +type AppointmentsEditingOptions = Partial<{ + allowDelete: boolean; + allowDrag: boolean; + allowResize: boolean; + allowAllDayResize: boolean; +}>; + +interface SchedulerAppointmentsHost { + option: ((options: Record) => unknown) & ((name: string, value?: unknown) => unknown); + repaint: () => void; + $element: () => dxElementWrapper; + focus: () => void; + moveAppointmentBack: (dragEvent?: SchedulerDragEvent | null) => void; + updateResizableArea: () => void; + renderDragClone: (...args: unknown[]) => dxElementWrapper; + getAppointmentData: (...args: unknown[]) => unknown; + // eslint-disable-next-line @typescript-eslint/naming-convention + _createComponent: CreateComponentFn; + _renderItem: (index: number, item: unknown, container?: dxElementWrapper) => dxElementWrapper; + // eslint-disable-next-line @typescript-eslint/naming-convention + _getItemData: (element: dxElementWrapper) => unknown; + notifyObserver: (subject: string, args?: unknown) => void; + invoke: (funcName: string, ...args: unknown[]) => unknown; + // eslint-disable-next-line @typescript-eslint/naming-convention + _setDragSourceAppointment: (appointment: unknown, settings: unknown) => void; + // eslint-disable-next-line @typescript-eslint/naming-convention + _removeDragSourceClassFromDraggedAppointment: () => void; +} + class Scheduler extends SchedulerOptionsBaseWidget { // NOTE: Do not initialize variables here, because `_initMarkup` function runs before constructor, // and initialization in constructor will erase the data - private timeZoneCalculatorInstance!: any; + private timeZoneCalculatorInstance: TimeZoneCalculator | null = null; + + declare postponedOperations: PostponedOperations; + + declare _disposed?: boolean; - postponedOperations: any; + declare _options: Options; private a11yStatus!: dxElementWrapper; - // TODO: used externally in m_appointment_drag_behavior.ts, m_subscribes.ts, workspaces/work_space.ts - _workSpace: any; + // TODO: used externally in m_appointment_drag_behavior.ts, + // m_subscribes.ts, workspaces/work_space.ts + _workSpace!: SchedulerWorkSpaceLike; private header?: SchedulerHeader; - // TODO: used externally in m_appointment_drag_behavior.ts, m_subscribes.ts, workspaces/work_space.ts - _appointments: any; + // TODO: used externally in m_appointment_drag_behavior.ts, + // m_subscribes.ts, workspaces/work_space.ts + _appointments!: SchedulerAppointmentsHost; private appointmentDragController!: AppointmentDragController; appointmentDataSource!: AppointmentDataSource; - _dataSource: any; + declare _dataSource: DataSource | undefined; - // TODO: used externally in m_subscribes.ts, view_model/preparation/prepare_appointments.ts, view_model/filtration/utils/get_filter_options/get_filter_options.ts + // TODO: used externally in workspaces/work_space.ts + // eslint-disable-next-line @typescript-eslint/naming-convention -- inherited Widget API + declare _createComponent: CreateComponentFn; + + // TODO: used externally in m_subscribes.ts, + // view_model/preparation/prepare_appointments.ts, + // view_model/filtration/utils/get_filter_options/get_filter_options.ts _dataAccessors!: AppointmentDataAccessor; resourceManager!: ResourceManager; - private actions: any; + private actions!: SchedulerActions; - _createActionByOption: any; + // TODO: used externally in workspaces/work_space.ts + declare _createActionByOption: ( + optionName: string, + config?: { excludeValidators?: string[] }, + ) => (event?: unknown) => void; private appointmentTooltip!: MobileTooltipStrategy | DesktopTooltipStrategy; private readyToRenderAppointments?: boolean; - private editing: any; + private editing!: SchedulerEditingState; - private workSpaceRecalculation: any; + private workSpaceRecalculation!: DeferredObj; - private appointmentPopup: any; + private appointmentPopup!: AppointmentPopup; // TODO: used externally in m_subscribes.ts _compactAppointmentsHelper!: CompactAppointmentsHelper; - private asyncTemplatesTimers!: any[]; + private asyncTemplatesTimers: ReturnType[] = []; private readonly updatingAppointments: Set = new Set(); - private dataSourceLoadedCallback: any; + private dataSourceLoadedCallback!: ReturnType; - private subscribes: any; + private subscribes: SubscribeMethods = subscribes; private notifyScheduler!: NotifyScheduler; - private recurrenceDialog: any; + private recurrenceDialog?: RecurrenceDialog; // TODO: used externally in m_subscribes.ts _layoutManager!: AppointmentLayoutManager; - private appointmentForm: any; + private appointmentForm!: AppointmentForm; - private mainContainer: any; + private mainContainer!: dxElementWrapper; private $draggableContainer?: dxElementWrapper; - private readonly all: any; - - _options: any; - - private editAppointmentData: any; + private editAppointmentData?: SafeAppointment; private timeZonesPromise!: Promise; - get timeZoneCalculator() { + get timeZoneCalculator(): TimeZoneCalculator { if (!this.timeZoneCalculatorInstance) { this.timeZoneCalculatorInstance = createTimeZoneCalculator(this.option('timeZone')); } @@ -244,19 +484,18 @@ class Scheduler extends SchedulerOptionsBaseWidget { return this.timeZoneCalculatorInstance; } - private postponeDataSourceLoading(promise?: any) { + private postponeDataSourceLoading(promise?: DeferredObj): void { this.postponedOperations.add('_reloadDataSource', this.reloadDataSource.bind(this), promise); } - private postponeResourceLoading(forceReload = false) { + private postponeResourceLoading(forceReload = false): DeferredObj { const whenLoaded = this.postponedOperations.add('loadResources', () => { const groups = this.getViewOption('groups'); return fromPromise(this.resourceManager.loadGroupResources(groups, forceReload)); - }); + }, undefined); - // @ts-expect-error - const resolveCallbacks = new Deferred(); + const resolveCallbacks: DeferredObj = Deferred(); whenLoaded.done(() => { resolveCallbacks.resolve(); @@ -264,7 +503,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.postponeDataSourceLoading(whenLoaded); - return resolveCallbacks.promise(); + return resolveCallbacks; } _optionChanged(args: OptionChanged): void { @@ -279,6 +518,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { case 'firstDayOfWeek': this.updateOption('workSpace', name, value); this.updateOption('header', name, value); + this.updateOption('header', 'startViewDate', this.getStartViewDate()); this.createAppointmentPopupForm(); break; case 'currentDate': { @@ -298,16 +538,22 @@ class Scheduler extends SchedulerOptionsBaseWidget { this._initDataSource(); this.postponeResourceLoading().done(() => { - this.appointmentDataSource.setDataSource(this._dataSource); + if (this._dataSource) { + if (this.appointmentDataSource) { + this.appointmentDataSource.setDataSource(this._dataSource); + } else { + this.createAppointmentDataSource(); + } + } this.setRemoteFilterIfNeeded(); this.updateOption('workSpace', 'showAllDayPanel', this.option('showAllDayPanel')); }); break; case 'min': case 'max': { - const value = this.getViewOption(name); - this.updateOption('header', name, value); - this.updateOption('workSpace', name, value); + const viewValue = this.getViewOption(name); + this.updateOption('header', name, viewValue); + this.updateOption('workSpace', name, viewValue); break; } case 'views': @@ -603,9 +849,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.updateOption('workSpace', 'draggingMode', value); break; case 'toolbar': - this.header - ? this.header.onToolbarOptionChanged(args.fullName, value) - : this.repaint(); + if (this.header) { + this.header.onToolbarOptionChanged(args.fullName, value); + } else { + this.repaint(); + } break; default: // @ts-expect-error @@ -613,8 +861,8 @@ class Scheduler extends SchedulerOptionsBaseWidget { } } - private bringEditingModeToAppointments(editing) { - const editingConfig: any = { + private bringEditingModeToAppointments(editing: SchedulerEditing): void { + const editingConfig: AppointmentsEditingOptions = { allowDelete: editing.allowUpdating && editing.allowDeleting, }; @@ -628,7 +876,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.repaint(); } - private isAgenda() { + private isAgenda(): boolean { return this.currentView.type === 'agenda'; } @@ -640,19 +888,19 @@ class Scheduler extends SchedulerOptionsBaseWidget { return this.allowDragging() && !this._isAppointmentBeingUpdated(appointmentData); } - private allowResizing() { + private allowResizing(): boolean { return this.editing.allowResizing && !this.isAgenda(); } - private allowAllDayResizing() { + private allowAllDayResizing(): boolean { return this.editing.allowResizing && this.supportAllDayResizing(); } - private supportAllDayResizing() { + private supportAllDayResizing(): boolean { return this.currentView.type !== 'day' || this.currentView.intervalCount > 1; } - private isAllDayExpanded() { + private isAllDayExpanded(): boolean { return this.option('showAllDayPanel') && this._layoutManager.hasAllDayAppointments(); } @@ -683,12 +931,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { dataSource.filter(filter); } - private reloadDataSource() { - // @ts-expect-error - const result = new Deferred(); + private reloadDataSource(): Promise { + const result: DeferredObj = Deferred(); if (this._dataSource) { - this._dataSource.load().done(() => { + when(fromPromise(this._dataSource.load())).done(() => { hideLoading().catch(noop); this._fireContentReadyAction(result); @@ -701,7 +948,8 @@ class Scheduler extends SchedulerOptionsBaseWidget { showLoading({ container: this.$element().get(0), position: { - of: this.$element() as any, + // @ts-expect-error + of: this.$element(), }, }).catch(noop); } @@ -712,16 +960,16 @@ class Scheduler extends SchedulerOptionsBaseWidget { return result.promise(); } - _fireContentReadyAction(result?: any) { + _fireContentReadyAction(result?: DeferredObj): void { // @ts-expect-error const contentReadyBase = super._fireContentReadyAction.bind(this); - const fireContentReady = () => { + const fireContentReady = (): void => { contentReadyBase(); result?.resolve(); }; - if (this.workSpaceRecalculation) { - this.workSpaceRecalculation?.done(() => { + if (isDeferred(this.workSpaceRecalculation)) { + this.workSpaceRecalculation.done(() => { fireContentReady(); }); } else { @@ -729,7 +977,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { } } - _dimensionChanged(value, isForce = false) { + _dimensionChanged(value: unknown, isForce = false): void { const isFixedHeight = typeof this.option('height') === 'number'; const isFixedWidth = typeof this.option('width') === 'number'; @@ -764,36 +1012,39 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.appointmentPopup.updatePopupFullScreenMode(); } - _clean() { + _clean(): void { this.cleanPopup(); // @ts-expect-error super._clean(); } - private toggleSmallClass() { - const { width } = getBoundingRect((this.$element() as any).get(0)); - (this.$element() as any).toggleClass(WIDGET_SMALL_CLASS, width < WIDGET_SMALL_WIDTH); + private toggleSmallClass(): void { + const element = this.$element().get(0); + const { width } = getBoundingRect(element); + this.$element().toggleClass(WIDGET_SMALL_CLASS, width < WIDGET_SMALL_WIDTH); } - private toggleAdaptiveClass() { - (this.$element() as any).toggleClass(WIDGET_ADAPTIVE_CLASS, this.option('adaptivityEnabled')); + private toggleAdaptiveClass(): void { + (this.$element()).toggleClass(WIDGET_ADAPTIVE_CLASS, this.option('adaptivityEnabled')); } - _visibilityChanged(visible) { - visible && this._dimensionChanged(null, true); + _visibilityChanged(visible: boolean): void { + if (visible) { + this._dimensionChanged(null, true); + } } - _dataSourceOptions() { + _dataSourceOptions(): DataSourceOptions { return { paginate: false }; } - private initAllDayPanel() { + private initAllDayPanel(): void { if (this.option('allDayPanelMode') === 'hidden') { this.option('showAllDayPanel', false); } } - _init() { + _init(): void { this.timeZonesPromise = timeZoneUtils.cacheTimeZones(); this.initExpressions({ startDateExpr: this.option('startDateExpr'), @@ -818,7 +1069,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.customizeDataSourceLoadOptions(); - (this.$element() as any).addClass(WIDGET_CLASS); + this.$element().addClass(WIDGET_CLASS); this.initEditing(); @@ -840,9 +1091,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.appointmentDragController = new AppointmentDragController({ component: this, - $draggableContainer: () => this.$draggableContainer!, + $draggableContainer: (): dxElementWrapper => this.$draggableContainer ?? this.$element(), canDragAppointment: this.canDragAppointment.bind(this), - getCellFromDragTarget: ($dragTarget) => this._workSpace.getCellFromDragTarget($dragTarget), + getCellFromDragTarget: ($dragTarget: dxElementWrapper): dxElementWrapper | null => ( + this._workSpace.getCellFromDragTarget($dragTarget) + ), // @ts-expect-error _createComponent is not defined in ts createComponent: this._createComponent.bind(this), @@ -852,12 +1105,15 @@ class Scheduler extends SchedulerOptionsBaseWidget { }); } - createAppointmentDataSource() { + createAppointmentDataSource(): void { this.appointmentDataSource?.destroy(); + if (!this._dataSource) { + return; + } this.appointmentDataSource = new AppointmentDataSource(this._dataSource); } - updateAppointmentDataSource() { + updateAppointmentDataSource(): void { this.timeZoneCalculatorInstance = null; if (this.getWorkSpace()) { @@ -865,14 +1121,15 @@ class Scheduler extends SchedulerOptionsBaseWidget { } } - private customizeDataSourceLoadOptions() { + private customizeDataSourceLoadOptions(): void { + // @ts-expect-error customizeStoreLoadOptions is a custom DataSource event this._dataSource?.on('customizeStoreLoadOptions', ({ storeLoadOptions }) => { storeLoadOptions.startDate = this.getStartViewDate(); storeLoadOptions.endDate = this.getEndViewDate(); }); } - _initTemplates() { + _initTemplates(): void { this.initAppointmentTemplate(); this._templateManager.addDefaultTemplates({ @@ -884,11 +1141,13 @@ class Scheduler extends SchedulerOptionsBaseWidget { } // TODO: delete this method when old impl is removed - private initAppointmentTemplate() { + private initAppointmentTemplate(): void { const { expr } = this._dataAccessors; - const createGetter = (property) => compileGetter(`appointmentData.${property}`); + const createGetter = (property: string): ((data: unknown) => unknown) => ( + compileGetter(`appointmentData.${property}`) as (data: unknown) => unknown + ); - const getDate = (getter) => (data) => { + const getDate = (getter: (data: unknown) => unknown) => (data: unknown): unknown => { const value = getter(data); if (value instanceof Date) { return value.valueOf(); @@ -898,7 +1157,8 @@ class Scheduler extends SchedulerOptionsBaseWidget { this._templateManager.addDefaultTemplates({ item: new BindableTemplate( - ($container, data, model) => this.getAppointmentsInstance()._renderAppointmentTemplate($container, data, model), + ($container, data, model) => this.getAppointmentsInstance() + ._renderAppointmentTemplate($container, data, model), [ 'html', 'text', @@ -925,12 +1185,12 @@ class Scheduler extends SchedulerOptionsBaseWidget { }); } - _renderContent() { + _renderContent(): void { // @ts-expect-error this._renderContentImpl(); } - _dataSourceChangedHandler(result?: Appointment[]) { + _dataSourceChangedHandler(result?: Appointment[]): void { if (this.readyToRenderAppointments) { this.workSpaceRecalculation.done(() => { this._layoutManager.prepareAppointments(result); @@ -952,11 +1212,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { return scrolling?.mode === 'virtual'; } - private renderAppointments() { + private renderAppointments(): void { const workspace = this.getWorkSpace(); this._layoutManager.filterAppointments(); - workspace.option('allDayExpanded', this.isAllDayExpanded()); + workspace?.option('allDayExpanded', this.isAllDayExpanded()); // @ts-expect-error const viewModel: AppointmentViewModelPlain[] = this._isVisible() @@ -964,14 +1224,14 @@ class Scheduler extends SchedulerOptionsBaseWidget { : []; this._appointments.option('items', viewModel); - this.appointmentDataSource.cleanState(); + this.appointmentDataSource?.cleanState(); if (this.isAgenda()) { - this._workSpace.renderAgendaLayout(viewModel); + this._workSpace.renderAgendaLayout?.(viewModel); } } - private initExpressions(fields: IFieldExpr) { + private initExpressions(fields: IFieldExpr): void { this._dataAccessors = new AppointmentDataAccessor( fields, Boolean(config().forceIsoDateParsing), @@ -979,11 +1239,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { ); } - private updateExpression(name: string, value: string) { + private updateExpression(name: string, value: string): void { this._dataAccessors.updateExpression(name, value); } - private initEditing() { + private initEditing(): void { const editing = this.option('editing'); this.editing = { @@ -1007,10 +1267,10 @@ class Scheduler extends SchedulerOptionsBaseWidget { popup: undefined, }).every((value) => !value); - (this.$element() as any).toggleClass(WIDGET_READONLY_CLASS, isReadOnly); + this.$element().toggleClass(WIDGET_READONLY_CLASS, isReadOnly); } - _dispose() { + _dispose(): void { this.resourceManager?.dispose(); this.appointmentTooltip?.dispose(); this.recurrenceDialog?.hide(RECURRENCE_EDITING_MODE.CANCEL); @@ -1028,7 +1288,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { super._dispose(); } - private initActions() { + private initActions(): void { this.actions = { onAppointmentAdding: this._createActionByOption(StoreEventNames.ADDING), onAppointmentAdded: this._createActionByOption(StoreEventNames.ADDED), @@ -1043,19 +1303,19 @@ class Scheduler extends SchedulerOptionsBaseWidget { onAppointmentClick: this._createActionByOption('onAppointmentClick'), onAppointmentDblClick: this._createActionByOption('onAppointmentDblClick'), onAppointmentContextMenu: this._createActionByOption('onAppointmentContextMenu'), - }; + } as SchedulerActions; } // TODO: delete this method when old impl is removed - private getAppointmentRenderedAction() { + private getAppointmentRenderedAction(): (args: AppointmentRenderedEvent) => void { return this._createActionByOption('onAppointmentRendered', { excludeValidators: ['disabled', 'readOnly'], - }); + }) as (args: AppointmentRenderedEvent) => void; } - _renderFocusTarget() { return noop(); } + _renderFocusTarget(): void { return noop(); } - private updateA11yStatus() { + private updateA11yStatus(): void { const dateRange = this._workSpace.getDateRange(); const indicatorTime = this.option('showCurrentTimeIndicator') ? getToday(this.option('indicatorTime') as Date, this.timeZoneCalculator) @@ -1073,17 +1333,17 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.a11yStatus.text(label); } - private renderA11yStatus() { + private renderA11yStatus(): void { this.a11yStatus = createA11yStatusContainer(); this.a11yStatus.prependTo(this.$element()); // @ts-expect-error this.setAria({ role: 'application' }); } - private initMarkupOnResourceLoaded() { - if (!(this as any)._disposed) { + private initMarkupOnResourceLoaded(): void { + if (!this._disposed) { this.initMarkupCore(); - this.reloadDataSource(); + this.reloadDataSource().catch(noop); } } @@ -1110,7 +1370,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { appointmentCollectorTemplate: this.getViewOption('appointmentCollectorTemplate'), onAppointmentRendered: (...args) => this.actions.onAppointmentRendered(...args), - onAppointmentClick: (...args) => this.actions.onAppointmentClick(...args), + onAppointmentClick: (args: AppointmentClickEvent) => this.actions.onAppointmentClick(args), onAppointmentDblClick: (...args) => this.actions.onAppointmentDblClick(...args), onAppointmentContextMenu: (...args) => this.actions.onAppointmentContextMenu(...args), onDeleteKeyPress: (e) => { @@ -1120,7 +1380,13 @@ class Scheduler extends SchedulerOptionsBaseWidget { getResourceManager: () => this.resourceManager, getAppointmentDataSource: () => this.appointmentDataSource, getDataAccessor: () => this._dataAccessors, - getStartViewDate: () => this.getStartViewDate(), + getStartViewDate: (): Date => { + const startViewDate = this.getStartViewDate(); + if (!startViewDate) { + throw errors.Error('E0001'); + } + return startViewDate; + }, getSortedItems: () => this._layoutManager.sortedItems, isVirtualScrolling: () => this.isVirtualScrolling(), @@ -1147,21 +1413,22 @@ class Scheduler extends SchedulerOptionsBaseWidget { // @ts-expect-error if (this.isDataSourceLoaded() || this._isDataSourceLoading()) { this.initMarkupCore(); - this._dataSourceChangedHandler(this._dataSource.items()); + this._dataSourceChangedHandler(this._dataSource?.items() ?? []); this._fireContentReadyAction(); } else { const groups = this.getViewOption('groups'); if (groups?.length) { this.resourceManager.loadGroupResources(groups, true) - .then(() => this.initMarkupOnResourceLoaded()); + .then(() => this.initMarkupOnResourceLoaded()) + .catch(noop); } else { this.initMarkupOnResourceLoaded(); } } } - private createAppointmentPopupForm() { + private createAppointmentPopupForm(): void { if (this.appointmentForm) { this.appointmentForm.dispose(); } @@ -1171,7 +1438,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.appointmentPopup = this.createAppointmentPopup(this.appointmentForm); } - private renderMainContainer() { + private renderMainContainer(): void { this.mainContainer = $('
').addClass('dx-scheduler-container'); this.$draggableContainer = $('
') .addClass('dx-scheduler-draggable-container') @@ -1180,42 +1447,52 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.$element().append(this.mainContainer); } - createAppointmentForm() { - const config: AppointmentFormConfig = { + createAppointmentForm(): AppointmentForm { + const appointmentFormConfig: AppointmentFormConfig = { dataAccessors: this._dataAccessors, editing: this.editing, resourceManager: this.resourceManager, firstDayOfWeek: this.getFirstDayOfWeek(), startDayHour: this.option('startDayHour') ?? 0, - // @ts-expect-error - createComponent: (element, component, options) => this._createComponent(element, component, options), - getCalculatedEndDate: (startDateWithStartHour) => this._workSpace.calculateEndDate(startDateWithStartHour), + createComponent: this._createComponent.bind(this), + getCalculatedEndDate: (startDateWithStartHour: Date): Date => ( + this._workSpace.calculateEndDate(startDateWithStartHour) + ), }; - return new AppointmentForm(config); + return new AppointmentForm(appointmentFormConfig); } - createAppointmentPopup(form) { + createAppointmentPopup(form: AppointmentForm): AppointmentPopup { const scheduler = { - getElement: () => this.$element(), - // @ts-expect-error - createComponent: (element, component, options) => this._createComponent(element, component, options), - focus: () => this.focus(), + getElement: (): dxElementWrapper => this.$element(), + createComponent: this._createComponent.bind(this), + focus: (): void => { this.focus(); }, - getResourceManager: () => this.resourceManager, + getResourceManager: (): ResourceManager => this.resourceManager, - getEditingConfig: () => this.editing, + getEditingConfig: (): SchedulerEditingState => this.editing, - getTimeZoneCalculator: () => this.timeZoneCalculator, - getDataAccessors: () => this._dataAccessors, - getAppointmentFormOpening: () => this.actions.onAppointmentFormOpening, - processActionResult: (arg, canceled) => this.processActionResult(arg, canceled), + getTimeZoneCalculator: (): TimeZoneCalculator => this.timeZoneCalculator, + getDataAccessors: (): AppointmentDataAccessor => this._dataAccessors, + getAppointmentFormOpening: (): SchedulerActions['onAppointmentFormOpening'] => ( + this.actions.onAppointmentFormOpening + ), + processActionResult: ( + arg: SchedulerActionOptions, + canceled: ProcessActionCallback, + ): Promise => this.processActionResult(arg, canceled), - addAppointment: (appointment) => this.addAppointment(appointment), - updateAppointment: (sourceAppointment, updatedAppointment) => this.updateAppointment(sourceAppointment, updatedAppointment), + addAppointment: (appointment: SafeAppointment): Promise => ( + this.addAppointment(appointment) + ), + updateAppointment: ( + sourceAppointment: SafeAppointment, + updatedAppointment: SafeAppointment, + ): Promise => this.updateAppointment(sourceAppointment, updatedAppointment), }; - return new AppointmentPopup(scheduler, form); + return new AppointmentPopup(scheduler as never, form); } private scrollToAppointment(appointment: Record): void { @@ -1239,66 +1516,95 @@ class Scheduler extends SchedulerOptionsBaseWidget { this._workSpace.updateScrollPosition(startDate, appointmentGroupValues, inAllDayRow); } - private getAppointmentTooltipOptions() { - const that = this; + private saveAppointmentAndScroll( + saveAction: () => PromiseLike, + appointment: SafeAppointment, + ): PromiseLike { + return when(saveAction()).done(() => { + this.scrollToAppointment(appointment); + }).promise(); + } + + private getAppointmentTooltipOptions(): AppointmentTooltipOptions { return { - // @ts-expect-error - createComponent: that._createComponent.bind(that), - container: that.$element(), - getScrollableContainer: that.getWorkSpaceScrollableContainer.bind(that), - addDefaultTemplates: that._templateManager.addDefaultTemplates.bind(that._templateManager), - getAppointmentTemplate: that.getAppointmentTemplate.bind(that), - showAppointmentPopup: that.showAppointmentPopup.bind(that), - checkAndDeleteAppointment: that.checkAndDeleteAppointment.bind(that), - isAppointmentInAllDayPanel: that.isAppointmentInAllDayPanel.bind(that), - - createFormattedDateText: (appointment, targetedAppointment, format) => this.fire('createFormattedDateText', appointment, targetedAppointment, format), + createComponent: this._createComponent.bind(this) as AppointmentTooltipOptions['createComponent'], + container: this.$element(), + getScrollableContainer: this.getWorkSpaceScrollableContainer.bind(this), + addDefaultTemplates: this._templateManager.addDefaultTemplates.bind(this._templateManager), + getAppointmentTemplate: this.getAppointmentTemplate.bind(this), + showAppointmentPopup: this.showAppointmentPopup.bind(this), + checkAndDeleteAppointment: this.checkAndDeleteAppointment.bind(this), + isAppointmentInAllDayPanel: this.isAppointmentInAllDayPanel.bind(this), + + createFormattedDateText: (appointment, targetedAppointment, format) => this.fire( + 'createFormattedDateText', + appointment, + (targetedAppointment ?? appointment) as TargetedAppointment, + format, + ), getAppointmentDisabled: (appointment) => this._dataAccessors.get('disabled', appointment), - onItemContextMenu: that._createActionByOption('onAppointmentContextMenu'), - createEventArgs: that._createEventArgs.bind(that), + onItemContextMenu: this._createActionByOption('onAppointmentContextMenu'), + createEventArgs: this._createEventArgs.bind(this), newAppointments: Boolean(this.option('_newAppointments')), - onAppointmentClick: (...args) => this.actions.onAppointmentClick(...args), - onListInitialized: (e) => { - if (this.option('_newAppointments')) { + onAppointmentClick: (args: AppointmentClickEvent) => this.actions.onAppointmentClick(args), + onListInitialized: (e): void => { + if (this.option('_newAppointments') && e.element) { this.appointmentDragController.createTooltipDraggable( - e.element, + $(e.element), { dragTemplate: this._appointments.renderDragClone.bind(this._appointments), }, ); } }, - onListDisposing: () => { + onListDisposing: (): void => { this.appointmentDragController.disposeTooltipDraggable(); }, - }; + } satisfies AppointmentTooltipOptions; } // TODO: delete this method when old impl is removed - _createEventArgs(e) { - const config = { - itemData: e.itemData.appointment, - itemElement: e.itemElement, - targetedAppointment: e.itemData.targetedAppointment, + _createEventArgs( + e: ItemContextMenuEvent, + ): AppointmentTooltipContextMenuEventArgs { + const itemData = e.itemData?.appointment; + if (!itemData) { + throw errors.Error('E0001'); + } + + const eventConfig: { + itemData: SafeAppointment; + itemElement: dxElementWrapper; + targetedAppointment?: SafeAppointment | TargetedAppointment; + } = { + itemData, + itemElement: $(e.itemElement), + targetedAppointment: e.itemData?.targetedAppointment, }; - return extend({}, (this.fire as any)('mapAppointmentFields', config), { + const mappedFields = this.fire('mapAppointmentFields', eventConfig) as MappedAppointmentFields; + + return extend({}, mappedFields, { component: e.component, element: e.element, event: e.event, model: e.model, - }); + }) as AppointmentTooltipContextMenuEventArgs; } - checkAndDeleteAppointment(appointment, targetedAppointment) { + checkAndDeleteAppointment( + appointment: SafeAppointment, + targetedAppointment?: SafeAppointment | TargetedAppointment, + ): void { + const targetedData = targetedAppointment ?? appointment; const targetedAdapter = new AppointmentAdapter( - targetedAppointment, + targetedData, this._dataAccessors, ); const deletingOptions = this.fireOnAppointmentDeleting(appointment, targetedAdapter); this.checkRecurringAppointment( appointment, - targetedAppointment, + targetedData, targetedAdapter.startDate, () => { this.processDeleteAppointment(appointment, deletingOptions); @@ -1307,7 +1613,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { ); } - private getExtraAppointmentTooltipOptions() { + private getExtraAppointmentTooltipOptions(): AppointmentTooltipExtraOptions { return { rtlEnabled: this.option('rtlEnabled'), focusStateEnabled: this.option('focusStateEnabled'), @@ -1316,17 +1622,19 @@ class Scheduler extends SchedulerOptionsBaseWidget { }; } - isAppointmentInAllDayPanel(appointmentData) { + isAppointmentInAllDayPanel(appointmentData: SafeAppointment | Appointment): boolean { const workSpace = this._workSpace; const itTakesAllDay = this.appointmentTakesAllDay(appointmentData); - return itTakesAllDay && workSpace.supportAllDayRow() && workSpace.option('showAllDayPanel'); + return itTakesAllDay && workSpace.supportAllDayRow() && Boolean(workSpace.option('showAllDayPanel')); } - private initMarkupCore() { + private initMarkupCore(): void { this.readyToRenderAppointments = hasWindow(); - this._workSpace && this.cleanWorkspace(); + if (this._workSpace) { + this.cleanWorkspace(); + } this.renderWorkSpace(); if (this.option('_newAppointments')) { @@ -1337,7 +1645,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { allDayContainer: this._workSpace.getAllDayContainer(), }); } - this.waitAsyncTemplate(() => this.workSpaceRecalculation?.resolve()); + this.waitAsyncTemplate((): void => { this.workSpaceRecalculation?.resolve(); }); this.createAppointmentDataSource(); this.setRemoteFilterIfNeeded(); @@ -1345,18 +1653,18 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.updateA11yStatus(); } - private isDataSourceLoaded() { - return this._dataSource?.isLoaded(); + private isDataSourceLoaded(): boolean { + return Boolean(this._dataSource?.isLoaded()); } - _render() { + _render(): void { this.getWorkSpace()?.updateHeaderEmptyCellWidth(); // @ts-expect-error super._render(); } - private renderHeader() { + private renderHeader(): void { const toolbarOptions = this.option('toolbar'); const isHeaderShown = Boolean( toolbarOptions.visible ?? toolbarOptions.items?.length, @@ -1365,7 +1673,6 @@ class Scheduler extends SchedulerOptionsBaseWidget { if (isHeaderShown) { const $header = $('
').appendTo(this.mainContainer); const headerOptions = this.headerConfig(); - // @ts-expect-error this.header = this._createComponent($header, SchedulerHeader, headerOptions); } } @@ -1379,7 +1686,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { min: this.getViewOption('min'), max: this.getViewOption('max'), indicatorTime: this.option('indicatorTime'), - startViewDate: this.getStartViewDate(), + startViewDate: this.getStartViewDate() ?? this.getViewOption('currentDate'), tabIndex: this.option('tabIndex'), focusStateEnabled: this.option('focusStateEnabled'), useDropDownViewSwitcher: this.option('useDropDownViewSwitcher'), @@ -1395,8 +1702,8 @@ class Scheduler extends SchedulerOptionsBaseWidget { }; } - private appointmentsConfig() { - const config = { + private appointmentsConfig(): AppointmentCollectionOptions { + const collectionConfig: AppointmentCollectionOptions = { getResourceManager: () => this.resourceManager, getAppointmentDataSource: () => this.appointmentDataSource, getSortedAppointments: () => this._layoutManager.sortedItems, @@ -1405,20 +1712,22 @@ class Scheduler extends SchedulerOptionsBaseWidget { dataAccessors: this._dataAccessors, notifyScheduler: this.notifyScheduler, onItemRendered: this.getAppointmentRenderedAction(), - onItemClick: this._createActionByOption('onAppointmentClick'), - onItemContextMenu: this._createActionByOption('onAppointmentContextMenu'), - onAppointmentDblClick: this._createActionByOption('onAppointmentDblClick'), - tabIndex: this.option('tabIndex'), - focusStateEnabled: this.option('focusStateEnabled'), + onItemClick: this._createActionByOption('onAppointmentClick') as (args: AppointmentClickEvent) => void, + onItemContextMenu: this._createActionByOption('onAppointmentContextMenu') as (args: AppointmentContextMenuEvent) => void, + onAppointmentDblClick: this._createActionByOption('onAppointmentDblClick') as (args: AppointmentDblClickEvent) => void, + tabIndex: this.option('tabIndex') ?? 0, + focusStateEnabled: Boolean(this.option('focusStateEnabled')), allowDrag: this.allowDragging(), allowDelete: this.editing.allowUpdating && this.editing.allowDeleting, allowResize: this.allowResizing(), allowAllDayResize: this.allowAllDayResizing(), - rtlEnabled: this.option('rtlEnabled'), + rtlEnabled: Boolean(this.option('rtlEnabled')), groups: this.getViewOption('groups'), - groupByDate: this.getViewOption('groupByDate'), + groupByDate: Boolean(this.getViewOption('groupByDate')), timeZoneCalculator: this.timeZoneCalculator, - getResizableStep: () => (this._workSpace ? this._workSpace.positionHelper.getResizableStep() : 0), + getResizableStep: () => ( + this._workSpace ? this._workSpace.positionHelper.getResizableStep() : 0 + ), getDOMElementsMetaData: () => this._workSpace?.getDOMElementsMetaData(), getViewDataProvider: () => this._workSpace?.viewDataProvider, isVerticalGroupedWorkSpace: () => this._workSpace.isVerticalGroupedWorkSpace(), @@ -1428,10 +1737,10 @@ class Scheduler extends SchedulerOptionsBaseWidget { }, }; - return config; + return collectionConfig; } - private renderWorkSpace() { + private renderWorkSpace(): void { const currentViewOptions = this.currentView; if (!currentViewOptions) { return; @@ -1444,6 +1753,14 @@ class Scheduler extends SchedulerOptionsBaseWidget { } this.recalculateWorkspace(); + + if (this.header) { + const startViewDate = this.getStartViewDate(); + if (startViewDate) { + this.updateOption('header', 'startViewDate', startViewDate); + } + } + if (currentViewOptions.startDate) { this.updateOption('header', 'currentDate', this._workSpace.getHeaderDate()); } @@ -1456,7 +1773,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { Promise.resolve().then(() => { this.toggleSmallClass(); this._workSpace?.updateHeaderEmptyCellWidth(); - }); + }).catch(noop); } const $workSpace = $('
').appendTo(this.mainContainer); @@ -1464,10 +1781,12 @@ class Scheduler extends SchedulerOptionsBaseWidget { const workSpaceComponent = VIEWS_CONFIG[currentViewType].workSpace; const workSpaceConfig = this.workSpaceConfig(this.currentView); // @ts-expect-error - this._workSpace = this._createComponent($workSpace, workSpaceComponent, workSpaceConfig); + this._workSpace = this._createComponent($workSpace, workSpaceComponent, workSpaceConfig) as SchedulerWorkSpaceLike; if (!this.option('_newAppointments')) { - this.allowDragging() && this._workSpace.initDragBehavior(this, this.all); + if (this.allowDragging()) { + this._workSpace.initDragBehavior(this); + } } this._workSpace.attachTablesEvents(); this._workSpace.getWorkArea().append(this._appointments.$element()); @@ -1478,11 +1797,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { const workSpaceConfig = this.workSpaceConfig(this.currentView); const workSpaceComponent = VIEWS_CONFIG.agenda.workSpace; // @ts-expect-error - this._workSpace = this._createComponent($workSpace, workSpaceComponent, workSpaceConfig); + this._workSpace = this._createComponent($workSpace, workSpaceComponent, workSpaceConfig) as SchedulerWorkSpaceLike; this._workSpace.getWorkArea().append(this._appointments.$element()); } - private recalculateWorkspace() { + private recalculateWorkspace(): void { // @ts-expect-error this.workSpaceRecalculation = new Deferred(); triggerResizeEvent(this._workSpace.$element()); @@ -1491,7 +1810,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { }); } - private workSpaceConfig(currentViewOptions: NormalizedView) { + private workSpaceConfig(currentViewOptions: NormalizedView): WorkspaceOptionsInternal { const scrolling = this.getViewOption('scrolling'); const isVirtualScrolling = scrolling.mode === 'virtual'; const horizontalVirtualScrollingAllowed = isVirtualScrolling @@ -1503,7 +1822,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { || horizontalVirtualScrollingAllowed || isTimelineView(currentViewOptions.type); - const result = extend({ + const workSpaceOptions = extend({ newAppointments: Boolean(this.option('_newAppointments')), resources: this.option('resources'), getResourceManager: () => this.resourceManager, @@ -1514,10 +1833,9 @@ class Scheduler extends SchedulerOptionsBaseWidget { startDayHour: this.option('startDayHour'), endDayHour: this.option('endDayHour'), viewOffset: this.getViewOffsetMs(), - tabIndex: this.option('tabIndex'), - accessKey: this.option('accessKey'), - focusStateEnabled: this.option('focusStateEnabled'), - cellDuration: this.option('cellDuration'), + tabIndex: this.option('tabIndex') ?? 0, + accessKey: this.option('accessKey') ?? '', + focusStateEnabled: Boolean(this.option('focusStateEnabled')), showAllDayPanel: this.option('showAllDayPanel'), showCurrentTimeIndicator: this.option('showCurrentTimeIndicator'), indicatorTime: this.option('indicatorTime'), @@ -1530,17 +1848,15 @@ class Scheduler extends SchedulerOptionsBaseWidget { dateCellTemplate: this.option('dateCellTemplate'), allowMultipleCellSelection: this.option('allowMultipleCellSelection'), selectedCellData: this.option('selectedCellData'), - onSelectionChanged: (args) => { + onSelectionChanged: (args: { selectedCellData: ViewCellData[] }) => { this.option('selectedCellData', args.selectedCellData); }, - onSelectionEnd: (args) => { + onSelectionEnd: (args: { selectedCellData: ViewCellData[] }) => { this.actions.onSelectionEnd({ - component: this, - element: this.$element(), selectedCellData: args.selectedCellData, }); }, - groupByDate: this.getViewOption('groupByDate'), + groupByDate: Boolean(this.getViewOption('groupByDate')), skippedDays: this.getViewOption('hiddenWeekDays') as number[], scrolling, draggingMode: this.option('_draggingMode'), @@ -1549,45 +1865,61 @@ class Scheduler extends SchedulerOptionsBaseWidget { schedulerWidth: this.option('width'), allDayPanelMode: this.option('allDayPanelMode'), onSelectedCellsClick: this.showAddAppointmentPopup.bind(this), - renderAppointments: () => { this.renderAppointments(); }, - onShowAllDayPanel: (value) => this.option('showAllDayPanel', value), - getHeaderHeight: () => utils.DOM.getHeaderHeight(this.header), - onScrollEnd: () => this._appointments.updateResizableArea(), + renderAppointments: (): void => { this.renderAppointments(); }, + onShowAllDayPanel: (value: boolean) => this.option('showAllDayPanel', value), + getHeaderHeight: (): number => utils.DOM.getHeaderHeight(this.header), + onScrollEnd: (): void => { + if (!this.option('_newAppointments')) { + (this._appointments as unknown as AppointmentCollection).updateResizableArea(); + } + }, onInitialized: (e) => { if (this.option('_newAppointments')) { this.appointmentDragController.createWorkSpaceDraggable( - e.element, + $(e.element), { - getAppointmentData: this._appointments.getAppointmentData.bind(this._appointments), + getAppointmentData: this._appointments.getAppointmentData.bind( + this._appointments, + ) as WorkSpaceDraggableOptions['getAppointmentData'], }, ); } }, - onDisposing: () => { + onDisposing: (): void => { if (this.option('_newAppointments')) { this.appointmentDragController.disposeWorkSpaceDraggable(); } }, - - }, currentViewOptions); - - result.notifyScheduler = this.notifyScheduler; - result.groups = this.resourceManager.groupResources(); - result.onCellClick = this._createActionByOption('onCellClick'); - result.onCellContextMenu = this._createActionByOption('onCellContextMenu'); - result.currentDate = this.getViewOption('currentDate'); - result.skippedDays = this.getViewOption('hiddenWeekDays') as number[]; - result.hoursInterval = result.cellDuration / 60; - result.allDayExpanded = false; - result.dataCellTemplate = result.dataCellTemplate ? this._getTemplate(result.dataCellTemplate) : null; - result.timeCellTemplate = result.timeCellTemplate ? this._getTemplate(result.timeCellTemplate) : null; - result.resourceCellTemplate = result.resourceCellTemplate ? this._getTemplate(result.resourceCellTemplate) : null; - result.dateCellTemplate = result.dateCellTemplate ? this._getTemplate(result.dateCellTemplate) : null; - - return result; - } - - private waitAsyncTemplate(callback) { + rtlEnabled: Boolean(this.option('rtlEnabled')), + cellDuration: this.option('cellDuration'), + allDayExpanded: false, + currentDate: this.getViewOption('currentDate'), + }, currentViewOptions) as WorkspaceOptionsInternal; + + workSpaceOptions.hoursInterval = workSpaceOptions.cellDuration / 60; + + workSpaceOptions.notifyScheduler = this.notifyScheduler; + workSpaceOptions.groups = this.resourceManager.groupResources(); + workSpaceOptions.onCellClick = this._createActionByOption('onCellClick') as (e: CellClickEvent) => void; + workSpaceOptions.onCellContextMenu = this._createActionByOption('onCellContextMenu') as (e: CellContextMenuEvent) => void; + workSpaceOptions.skippedDays = this.getViewOption('hiddenWeekDays') as number[]; + workSpaceOptions.dataCellTemplate = workSpaceOptions.dataCellTemplate + ? this._getTemplate(workSpaceOptions.dataCellTemplate) as TemplateBase + : null; + workSpaceOptions.timeCellTemplate = workSpaceOptions.timeCellTemplate + ? this._getTemplate(workSpaceOptions.timeCellTemplate) as TemplateBase + : null; + workSpaceOptions.resourceCellTemplate = workSpaceOptions.resourceCellTemplate + ? this._getTemplate(workSpaceOptions.resourceCellTemplate) as TemplateBase + : null; + workSpaceOptions.dateCellTemplate = workSpaceOptions.dateCellTemplate + ? this._getTemplate(workSpaceOptions.dateCellTemplate) as TemplateBase + : null; + + return workSpaceOptions; + } + + private waitAsyncTemplate(callback: () => void): void { if (this._options.silent('templatesRenderAsynchronously')) { const timer = setTimeout(() => { callback(); @@ -1599,13 +1931,13 @@ class Scheduler extends SchedulerOptionsBaseWidget { } } - getAppointmentTemplate(optionName) { + getAppointmentTemplate(optionName: string): FunctionTemplate { if (this.currentView?.[optionName]) { return this._getTemplate(this.currentView[optionName]); } // @ts-expect-error - return this._getTemplateByOption(optionName); + return this._getTemplateByOption(optionName) as FunctionTemplate; } private updateOption(viewName: 'workSpace' | 'header', optionName: string, value: T): void { @@ -1619,7 +1951,8 @@ class Scheduler extends SchedulerOptionsBaseWidget { private refreshWorkSpace(): void { this.cleanWorkspace(); - delete this._workSpace; + // @ts-expect-error + this._workSpace = undefined; this.renderWorkSpace(); @@ -1633,11 +1966,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { }); } - this.waitAsyncTemplate(() => this.workSpaceRecalculation.resolve()); + this.waitAsyncTemplate((): void => { this.workSpaceRecalculation.resolve(); }); } } - private cleanWorkspace() { + private cleanWorkspace(): void { this._appointments.$element().detach(); this._workSpace._dispose(); this._workSpace.$element().remove(); @@ -1645,23 +1978,23 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.option('selectedCellData', []); } - getWorkSpaceScrollable() { + getWorkSpaceScrollable(): Scrollable { return this._workSpace.getScrollable(); } - getWorkSpaceScrollableContainer() { + getWorkSpaceScrollableContainer(): dxElementWrapper { return this._workSpace.getScrollableContainer(); } - getWorkSpace() { + getWorkSpace(): SchedulerWorkSpaceLike { return this._workSpace; } - getHeader() { + getHeader(): SchedulerHeader | undefined { return this.header; } - private cleanPopup() { + private cleanPopup(): void { this.appointmentPopup?.dispose(); } @@ -1680,10 +2013,10 @@ class Scheduler extends SchedulerOptionsBaseWidget { newAppointmentData, startDate, () => { - this.updateAppointmentCore( + when(fromPromise(this.updateAppointmentCore( appointmentData, newAppointmentData, - ).always(resolve); + ))).always(resolve); }, false, undefined, @@ -1695,16 +2028,16 @@ class Scheduler extends SchedulerOptionsBaseWidget { } checkRecurringAppointment( - rawAppointment, - singleAppointment, - exceptionDate, - callback, - isDeleted, - isPopupEditing?: any, - dragEvent?: any, - recurrenceEditMode?: any, + rawAppointment: SafeAppointment, + singleAppointment: SafeAppointment, + exceptionDate: Date, + callback: () => void, + isDeleted: boolean, + isPopupEditing?: boolean, + dragEvent?: SchedulerDragEvent | null, + recurrenceEditMode?: RecurrenceEditMode, onCancel?: () => void, - ) { + ): void { const recurrenceRule = this._dataAccessors.get('recurrenceRule', rawAppointment); if (!validateRRule(recurrenceRule) || !this.editing.allowUpdating) { @@ -1718,7 +2051,14 @@ class Scheduler extends SchedulerOptionsBaseWidget { callback(); break; case 'occurrence': - this.excludeAppointmentFromSeries(rawAppointment, singleAppointment, exceptionDate, isDeleted, isPopupEditing, dragEvent); + this.excludeAppointmentFromSeries( + rawAppointment, + singleAppointment, + exceptionDate, + isDeleted, + Boolean(isPopupEditing), + dragEvent, + ); break; default: if (dragEvent) { @@ -1727,16 +2067,20 @@ class Scheduler extends SchedulerOptionsBaseWidget { } this.showRecurrenceChangeConfirm(isDeleted) .done((editingMode) => { - editingMode === RECURRENCE_EDITING_MODE.SERIES && callback(); - - editingMode === RECURRENCE_EDITING_MODE.OCCURRENCE && this.excludeAppointmentFromSeries( - rawAppointment, - singleAppointment, - exceptionDate, - isDeleted, - isPopupEditing, - dragEvent, - ); + if (editingMode === RECURRENCE_EDITING_MODE.SERIES) { + callback(); + } + + if (editingMode === RECURRENCE_EDITING_MODE.OCCURRENCE) { + this.excludeAppointmentFromSeries( + rawAppointment, + singleAppointment, + exceptionDate, + isDeleted, + Boolean(isPopupEditing), + dragEvent, + ); + } }) .fail(() => { if (this.option('_newAppointments')) { @@ -1748,7 +2092,14 @@ class Scheduler extends SchedulerOptionsBaseWidget { } } - private excludeAppointmentFromSeries(rawAppointment, newRawAppointment, exceptionDate, isDeleted, isPopupEditing, dragEvent) { + private excludeAppointmentFromSeries( + rawAppointment: SafeAppointment, + newRawAppointment: SafeAppointment, + exceptionDate: Date, + isDeleted: boolean, + isPopupEditing: boolean, + dragEvent?: SchedulerDragEvent | null, + ): void { const appointment = excludeFromRecurrence( rawAppointment, exceptionDate, @@ -1766,15 +2117,17 @@ class Scheduler extends SchedulerOptionsBaseWidget { const canCreateNewAppointment = !isDeleted && !isPopupEditing; if (canCreateNewAppointment) { - this.addAppointment(singleRawAppointment); + this.addAppointment(singleRawAppointment).catch(noop); } if (isPopupEditing) { this.appointmentPopup.show(singleRawAppointment, { onSave: (newAppointment) => { - this.updateAppointment(rawAppointment, appointment.source); - return when(this.addAppointment(newAppointment)) - .done(() => this.scrollToAppointment(newAppointment)); + this.updateAppointment(rawAppointment, appointment.source).catch(noop); + return this.saveAppointmentAndScroll( + () => this.addAppointment(newAppointment as SafeAppointment), + newAppointment as SafeAppointment, + ); }, title: messageLocalization.format('dxScheduler-editPopupTitle'), readOnly: Boolean(appointment.source) && appointment.disabled, @@ -1783,33 +2136,39 @@ class Scheduler extends SchedulerOptionsBaseWidget { } else { this.updateAppointmentCore(rawAppointment, appointment.source, () => { this._appointments.moveAppointmentBack(dragEvent); - }, dragEvent); + }, dragEvent).catch(noop); } } - private createRecurrenceException(appointment, exceptionDate) { - const result: any[] = []; + private createRecurrenceException( + appointment: SafeAppointment & { recurrenceException?: string }, + exceptionDate: Date, + ): string { + const result: string[] = []; if (appointment.recurrenceException) { result.push(appointment.recurrenceException); } - result.push(this.getSerializedDate(exceptionDate, appointment.startDate, appointment.allDay)); + const adapter = new AppointmentAdapter(appointment, this._dataAccessors); + result.push(this.getSerializedDate(exceptionDate, adapter.startDate, Boolean(adapter.allDay))); return result.join(); } - private getSerializedDate(date, startDate, isAllDay) { - isAllDay && date.setHours( - startDate.getHours(), - startDate.getMinutes(), - startDate.getSeconds(), - startDate.getMilliseconds(), - ); + private getSerializedDate(date: Date, startDate: Date, isAllDay: boolean): string { + if (isAllDay) { + date.setHours( + startDate.getHours(), + startDate.getMinutes(), + startDate.getSeconds(), + startDate.getMilliseconds(), + ); + } - return dateSerialization.serializeDate(date, UTC_FULL_DATE_FORMAT); + return dateSerialization.serializeDate(date, UTC_FULL_DATE_FORMAT) as string; } - private showRecurrenceChangeConfirm(isDeleted) { + private showRecurrenceChangeConfirm(isDeleted: boolean): DeferredObj { const title = messageLocalization.format(isDeleted ? 'dxScheduler-confirmRecurrenceDeleteTitle' : 'dxScheduler-confirmRecurrenceEditTitle'); const message = messageLocalization.format(isDeleted ? 'dxScheduler-confirmRecurrenceDeleteMessage' : 'dxScheduler-confirmRecurrenceEditMessage'); const seriesText = messageLocalization.format(isDeleted ? 'dxScheduler-confirmRecurrenceDeleteSeries' : 'dxScheduler-confirmRecurrenceEditSeries'); @@ -1821,24 +2180,24 @@ class Scheduler extends SchedulerOptionsBaseWidget { showCloseButton: true, showTitle: true, buttons: [ - { text: seriesText, onClick() { return RECURRENCE_EDITING_MODE.SERIES; } }, - { text: occurrenceText, onClick() { return RECURRENCE_EDITING_MODE.OCCURRENCE; } }, + { text: seriesText, onClick: (): string => RECURRENCE_EDITING_MODE.SERIES }, + { text: occurrenceText, onClick: (): string => RECURRENCE_EDITING_MODE.OCCURRENCE }, ], popupOptions: { wrapperAttr: { class: POPUP_DIALOG_CLASS }, - onHidden: () => { + onHidden: (): void => { this._appointments?.focus(); }, }, - } as any); + } as CustomDialogOptions) as RecurrenceDialog; return this.recurrenceDialog.show(); } - getUpdatedData(rawAppointment, $cell?) { + getUpdatedData(rawAppointment: Appointment, $cell?: dxElementWrapper): SafeAppointment { const viewOffset = this.getViewOffsetMs(); - const getConvertedFromGrid = (date: any): Date | undefined => { + const getConvertedFromGrid = (date: Date | undefined): Date | undefined => { if (!date) { return undefined; } @@ -1868,8 +2227,8 @@ class Scheduler extends SchedulerOptionsBaseWidget { appointmentStartDate = resultedStartDate; } - if (!dateUtilsTs.isValidDate(appointmentEndDate)) { - appointmentEndDate = cellEndDate!; + if (!dateUtilsTs.isValidDate(appointmentEndDate) && cellEndDate) { + appointmentEndDate = cellEndDate; } const duration = appointmentEndDate.getTime() - appointmentStartDate.getTime(); @@ -1899,11 +2258,19 @@ class Scheduler extends SchedulerOptionsBaseWidget { let resultedEndDate = new Date(resultedStartDate.getTime() + duration); - if (this.appointmentTakesAllDay(rawAppointment) && !result.allDay && this._workSpace.supportAllDayRow()) { + if ( + this.appointmentTakesAllDay(rawAppointment) + && !result.allDay + && this._workSpace.supportAllDayRow() + ) { resultedEndDate = this._workSpace.calculateEndDate(resultedStartDate); } - if (appointment.allDay && !this._workSpace.supportAllDayRow() && !this._workSpace.keepOriginalHours()) { + if ( + appointment.allDay + && !this._workSpace.supportAllDayRow() + && !this._workSpace.keepOriginalHours() + ) { const dateCopy = new Date(resultedStartDate); dateCopy.setHours(0); @@ -1924,8 +2291,14 @@ class Scheduler extends SchedulerOptionsBaseWidget { } // TODO: delete this method when old impl is removed - getTargetedAppointment(appointment: SafeAppointment, element: dxElementWrapper): TargetedAppointment { - const settings = utils.dataAccessors.getAppointmentSettings(element)!; + getTargetedAppointment( + appointment: SafeAppointment, + element: dxElementWrapper, + ): TargetedAppointment { + const settings = utils.dataAccessors.getAppointmentSettings(element); + if (!settings) { + throw errors.Error('E0001'); + } return getTargetedAppointment( appointment, settings, @@ -1934,8 +2307,9 @@ class Scheduler extends SchedulerOptionsBaseWidget { ); } - subscribe(subject, action) { - this.subscribes[subject] = subscribes[subject] = action; + subscribe(subject: K, action: SubscribeMethods[K]): void { + this.subscribes[subject] = action; + subscribes[subject] = action; } fire( @@ -1948,26 +2322,36 @@ class Scheduler extends SchedulerOptionsBaseWidget { throw errors.Error('E1031', subject); } - return callback.call(this, ...args); + // subscribes callbacks are not fully typed + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return (callback as ( + this: Scheduler, + ...callbackArgs: Parameters + ) => ReturnType).apply(this, args); } - getTargetCellData() { + getTargetCellData(): DroppableCellData { return this._workSpace.getDataByDroppableCell(); } - updateAppointmentCore(target, rawAppointment, onUpdatePrevented?: any, dragEvent?: any) { + updateAppointmentCore( + target: SafeAppointment, + rawAppointment: SafeAppointment, + onUpdatePrevented?: () => void, + dragEvent?: SchedulerDragEvent | null, + ): Promise { const updatingOptions = { newData: rawAppointment, oldData: extend({}, target), cancel: false, }; - const performFailAction = function (err?: any) { + const performFailAction = function performFailAction(this: Scheduler, err?: Error): void { if (onUpdatePrevented) { onUpdatePrevented.call(this); } - if (err && err.name === 'Error') { + if (err?.name === 'Error') { throw err; } }.bind(this); @@ -1975,17 +2359,15 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.actions[StoreEventNames.UPDATING](updatingOptions); if (dragEvent && !isDeferred(dragEvent.cancel)) { - // @ts-expect-error - dragEvent.cancel = new Deferred(); + dragEvent.cancel = Deferred(); } if (isPromise(updatingOptions.cancel) && (dragEvent || this.option('_newAppointments'))) { this.updatingAppointments.add(target); } - return this.processActionResult(updatingOptions, function (canceled) { - // @ts-expect-error - let deferred = new Deferred(); + return this.processActionResult(updatingOptions, function onUpdate(this: Scheduler, canceled) { + let deferred: DeferredObj = Deferred(); if (!canceled) { this.expandAllDayPanel(rawAppointment); @@ -1994,15 +2376,18 @@ class Scheduler extends SchedulerOptionsBaseWidget { deferred = this.appointmentDataSource .update(target, rawAppointment) .done(() => { - dragEvent?.cancel.resolve(false); + dragEvent?.cancel?.resolve(false); }) .always((storeAppointment) => { this.updatingAppointments.delete(target); - this.onDataPromiseCompleted(StoreEventNames.UPDATED, storeAppointment); + this.onDataPromiseCompleted( + StoreEventNames.UPDATED, + storeAppointment as SafeAppointment, + ); }) .fail(() => performFailAction()); - } catch (err) { - performFailAction(err); + } catch (err: unknown) { + performFailAction(err instanceof Error ? err : undefined); this.updatingAppointments.delete(target); deferred.resolve(); } @@ -2016,60 +2401,70 @@ class Scheduler extends SchedulerOptionsBaseWidget { }); } - private processActionResult(actionOptions, callback) { - // @ts-expect-error - const deferred = new Deferred(); - const resolveCallback = (callbackResult) => { + private processActionResult( + actionOptions: SchedulerActionOptions, + callback: ProcessActionCallback, + ): Promise { + const deferred: DeferredObj = Deferred(); + const resolveCallback = (callbackResult: unknown): void => { when(fromPromise(callbackResult)) - .always(deferred.resolve); + .always(() => { deferred.resolve(); }); }; if (isPromise(actionOptions.cancel)) { when(fromPromise(actionOptions.cancel)).always((cancel) => { - if (!isDefined(cancel)) { - cancel = actionOptions.cancel.state() === 'rejected'; + let isCanceled = cancel; + if (!isDefined(isCanceled)) { + isCanceled = (actionOptions.cancel as DeferredObj).state() === 'rejected'; } - resolveCallback(callback.call(this, cancel)); + resolveCallback(callback.call(this, isCanceled)); }); } else { - resolveCallback(callback.call(this, actionOptions.cancel)); + resolveCallback(callback.call(this, actionOptions.cancel as boolean)); } return deferred.promise(); } - private expandAllDayPanel(appointment) { + private expandAllDayPanel(appointment: SafeAppointment | Appointment): void { if (!this.isAllDayExpanded() && this.appointmentTakesAllDay(appointment)) { this.updateOption('workSpace', 'allDayExpanded', true); } } - private onDataPromiseCompleted(handlerName, storeAppointment, appointment?: any) { - const args: any = { appointmentData: appointment || storeAppointment }; + private onDataPromiseCompleted( + handlerName: StoreEventName, + storeAppointment: SafeAppointment | Error, + appointment?: SafeAppointment, + ): void { + const args: AppointmentCompletedOptions = { + appointmentData: (appointment ?? storeAppointment) as SafeAppointment, + }; if (storeAppointment instanceof Error) { args.error = storeAppointment; - } else { - this.appointmentPopup.visible && this.appointmentPopup.hide(); + } else if (this.appointmentPopup.visible) { + this.appointmentPopup.hide(); } - this.actions[handlerName](args); + const handler = this.actions[handlerName] as (args: AppointmentCompletedOptions) => void; + handler(args); this._fireContentReadyAction(); } - getAppointmentsInstance() { - return this._appointments; + getAppointmentsInstance(): Appointments | AppointmentCollection { + return this._appointments as unknown as Appointments | AppointmentCollection; } - getLayoutManager() { + getLayoutManager(): AppointmentLayoutManager { return this._layoutManager; } - getActions() { + getActions(): SchedulerActions { return this.actions; } - appointmentTakesAllDay(rawAppointment) { + appointmentTakesAllDay(rawAppointment: SafeAppointment | Appointment): boolean { const appointment = new AppointmentAdapter( rawAppointment, this._dataAccessors, @@ -2081,8 +2476,12 @@ class Scheduler extends SchedulerOptionsBaseWidget { ); } - dayHasAppointment(day, rawAppointment, trimTime) { - const getConvertedToTimeZone = (date) => this.timeZoneCalculator.createDate(date, 'toGrid'); + dayHasAppointment( + day: Date, + rawAppointment: SafeAppointment | Appointment, + trimTime: boolean, + ): boolean { + const getConvertedToTimeZone = (date: Date): Date => this.timeZoneCalculator.createDate(date, 'toGrid'); const appointment = new AppointmentAdapter( rawAppointment, @@ -2099,76 +2498,98 @@ class Scheduler extends SchedulerOptionsBaseWidget { return startDate.getTime() === endDate.getTime(); } + let dayStart = day; + let startDateStart = startDate; + let endDateEnd = endDate; + if (trimTime) { - day = dateUtils.trimTime(day); - startDate = dateUtils.trimTime(startDate); - endDate = dateUtils.trimTime(endDate); + dayStart = dateUtils.trimTime(day); + startDateStart = dateUtils.trimTime(startDate); + endDateEnd = dateUtils.trimTime(endDate); } - const dayTimeStamp = day.getTime(); - const startDateTimeStamp = startDate.getTime(); - const endDateTimeStamp = endDate.getTime(); + const dayTimeStamp = dayStart.getTime(); + const startDateTimeStamp = startDateStart.getTime(); + const endDateTimeStamp = endDateEnd.getTime(); return startDateTimeStamp <= dayTimeStamp && dayTimeStamp <= endDateTimeStamp; } - getStartViewDate() { + getStartViewDate(): Date | undefined { return this._workSpace?.getStartViewDate(); } - getEndViewDate() { + getEndViewDate(): Date { return this._workSpace.getEndViewDate(); } - showAddAppointmentPopup(cellData, cellGroups) { + showAddAppointmentPopup(cellData: SelectedCellsClickCellData, cellGroups: GroupValues): void { const appointmentAdapter = new AppointmentAdapter({}, this._dataAccessors); appointmentAdapter.allDay = Boolean(cellData.allDay); - appointmentAdapter.startDate = cellData.startDateUTC; - appointmentAdapter.endDate = cellData.endDateUTC; + appointmentAdapter.startDate = new Date(cellData.startDateUTC); + appointmentAdapter.endDate = new Date(cellData.endDateUTC); const resultAppointment = extend(appointmentAdapter.source, cellGroups); this.showAppointmentPopup(resultAppointment, true); } - showAppointmentPopup(rawAppointment?: any, createNewAppointment?: boolean, rawTargetedAppointment?: any) { - const newRawTargetedAppointment = { ...rawTargetedAppointment }; - if (newRawTargetedAppointment) { + showAppointmentPopup( + rawAppointment?: SafeAppointment, + createNewAppointment?: boolean, + rawTargetedAppointment?: SafeAppointment | TargetedAppointment, + ): void { + const newRawTargetedAppointment: SafeAppointment = rawTargetedAppointment + ? { ...rawTargetedAppointment } + : {} as SafeAppointment; + if (rawTargetedAppointment) { delete newRawTargetedAppointment.displayStartDate; delete newRawTargetedAppointment.displayEndDate; } - const newTargetedAppointment = extend({}, rawAppointment, newRawTargetedAppointment); + const newTargetedAppointment = extend( + {}, + rawAppointment, + newRawTargetedAppointment, + ) as SafeAppointment; const isCreateAppointment = createNewAppointment ?? isEmptyObject(rawAppointment); - if (isEmptyObject(rawAppointment)) { - rawAppointment = this.createPopupAppointment(); + let appointment: SafeAppointment = rawAppointment ?? {} as SafeAppointment; + if (isEmptyObject(appointment)) { + appointment = this.createPopupAppointment(); } if (isCreateAppointment) { delete this.editAppointmentData; // TODO if (this.editing.allowAdding) { - this.appointmentPopup.show(rawAppointment, { - onSave: (appointment) => when(this.addAppointment(appointment)) - .done(() => this.scrollToAppointment(appointment)), + this.appointmentPopup.show(appointment, { + onSave: (appointmentData) => this.saveAppointmentAndScroll( + () => this.addAppointment(appointmentData as SafeAppointment), + appointmentData as SafeAppointment, + ), title: messageLocalization.format('dxScheduler-newPopupTitle'), readOnly: false, }); } } else { - const startDate = this._dataAccessors.get('startDate', newRawTargetedAppointment || rawAppointment); + const startDate = this._dataAccessors.get( + 'startDate', + Object.keys(newRawTargetedAppointment).length ? newRawTargetedAppointment : appointment, + ); - this.checkRecurringAppointment(rawAppointment, newTargetedAppointment, startDate, () => { - this.editAppointmentData = rawAppointment; // TODO + this.checkRecurringAppointment(appointment, newTargetedAppointment, startDate, () => { + this.editAppointmentData = appointment; // TODO - const adapter = new AppointmentAdapter(rawAppointment, this._dataAccessors); + const adapter = new AppointmentAdapter(appointment, this._dataAccessors); const isDisabled = Boolean(adapter.source) && adapter.disabled; const readOnly = isDisabled || !this.editing.allowUpdating; - this.appointmentPopup.show(rawAppointment, { - onSave: (appointment) => when(this.updateAppointment(rawAppointment, appointment)) - .done(() => this.scrollToAppointment(appointment)), + this.appointmentPopup.show(appointment, { + onSave: (appointmentData) => this.saveAppointmentAndScroll( + () => this.updateAppointment(appointment, appointmentData as SafeAppointment), + appointmentData as SafeAppointment, + ), title: messageLocalization.format('dxScheduler-editPopupTitle'), readOnly, }); @@ -2176,12 +2597,12 @@ class Scheduler extends SchedulerOptionsBaseWidget { } } - createPopupAppointment() { - const result: any = {}; - const toMs = dateUtils.dateToMilliseconds; + createPopupAppointment(): SafeAppointment { + const result: SafeAppointment = {}; + const msPerMinute = dateUtils.dateToMilliseconds('minute'); const startDate = new Date(this.option('currentDate')); - const endDate = new Date(startDate.getTime() + this.option('cellDuration') * toMs('minute')); + const endDate = new Date(startDate.getTime() + this.option('cellDuration') * msPerMinute); this._dataAccessors.set('startDate', result, startDate); this._dataAccessors.set('endDate', result, endDate); @@ -2189,9 +2610,11 @@ class Scheduler extends SchedulerOptionsBaseWidget { return result; } - hideAppointmentPopup(saveChanges?: any) { + hideAppointmentPopup(saveChanges?: boolean): void { if (this.appointmentPopup?.visible) { - saveChanges && this.appointmentPopup.saveChangesAsync(); + if (saveChanges) { + this.appointmentPopup.saveChangesAsync().catch(noop); + } this.appointmentPopup.hide(); } } @@ -2201,19 +2624,20 @@ class Scheduler extends SchedulerOptionsBaseWidget { appointment: SafeAppointment, element: dxElementWrapper, targetedAppointment?: SafeAppointment, - ) { + ): void { if (appointment) { - const settings: any = utils.dataAccessors.getAppointmentSettings(element); + const settings = utils.dataAccessors.getAppointmentSettings(element); + const appointmentConfig = { itemData: targetedAppointment ?? appointment, - groupIndex: settings?.groupIndex, + groupIndex: settings?.groupIndex ?? 0, }; const info: AppointmentTooltipItem = { appointment, targetedAppointment, color: this.resourceManager.getAppointmentColor(appointmentConfig), - settings, + settings: settings as AppointmentItemViewModel, }; this.showAppointmentTooltipCore(element, [info]); @@ -2224,7 +2648,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { target: dxElementWrapper, data: AppointmentTooltipItem[], options?: AppointmentTooltipExtraOptions, - ) { + ): void { const arg: Omit = { cancel: false, appointments: data.map((item) => ({ @@ -2240,16 +2664,19 @@ class Scheduler extends SchedulerOptionsBaseWidget { if (this.appointmentTooltip.isShownForTarget(target)) { this.hideAppointmentTooltip(); } else { + // @ts-expect-error this.processActionResult(arg, (canceled) => { - !canceled && this.appointmentTooltip.show(target, data, { - ...this.getExtraAppointmentTooltipOptions(), - ...options, - }); + if (!canceled) { + this.appointmentTooltip.show(target, data, { + ...this.getExtraAppointmentTooltipOptions(), + ...options, + }); + } }); } } - hideAppointmentTooltip() { + hideAppointmentTooltip(): void { this.appointmentTooltip?.hide(); } @@ -2257,33 +2684,43 @@ class Scheduler extends SchedulerOptionsBaseWidget { date: Date, groupValuesOrOptions?: ScrollToGroupValuesOrOptions, allDay?: boolean | undefined, - ) { - let groupValues; - let allDayValue; - let align: 'start' | 'center' = 'center'; - + ): void { if (this.isScrollOptionsObject(groupValuesOrOptions)) { - groupValues = groupValuesOrOptions.group; - allDayValue = groupValuesOrOptions.allDay; - align = groupValuesOrOptions.alignInView ?? 'center'; - } else { - if (isDefined(groupValuesOrOptions) || isDefined(allDay)) { - errors.log('W0002', 'dxScheduler', 'scrollTo(date, group, allDay)', '26.1', 'Use scrollTo(date, { group, allDay, alignInView }) instead.'); - } + this._workSpace.scrollTo( + date, + groupValuesOrOptions.group, + groupValuesOrOptions.allDay, + true, + groupValuesOrOptions.alignInView ?? 'center', + ); + return; + } - groupValues = groupValuesOrOptions; - allDayValue = allDay; + if (isDefined(groupValuesOrOptions) || isDefined(allDay)) { + errors.log( + 'W0002', + 'dxScheduler', + 'scrollTo(date, group, allDay)', + '26.1', + 'Use scrollTo(date, { group, allDay, alignInView }) instead.', + ); } - this._workSpace.scrollTo(date, groupValues, allDayValue, true, align); + this._workSpace.scrollTo(date, groupValuesOrOptions, allDay, true, 'center'); } - private isScrollOptionsObject(options?: ScrollToGroupValuesOrOptions): options is ScrollToOptions { + private isScrollOptionsObject( + options?: ScrollToGroupValuesOrOptions, + ): options is ScrollToOptions { return Boolean(options) && typeof options === 'object' - && ('alignInView' in options || 'allDay' in options || 'group' in options); + && ( + 'alignInView' in options + || 'allDay' in options + || 'group' in options + ); } - private isHorizontalVirtualScrolling() { + private isHorizontalVirtualScrolling(): boolean { const scrolling = this.option('scrolling'); const { orientation, mode } = scrolling; const isVirtualScrolling = mode === 'virtual'; @@ -2292,7 +2729,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { && (orientation === 'horizontal' || orientation === 'both'); } - addAppointment(rawAppointment) { + addAppointment(rawAppointment: SafeAppointment): Promise { // NOTE: mutation of raw appointment const appointment = new AppointmentAdapter( rawAppointment, @@ -2311,29 +2748,33 @@ class Scheduler extends SchedulerOptionsBaseWidget { return this.processActionResult(addingOptions, (canceled) => { if (canceled) { - // @ts-expect-error - return new Deferred().resolve(); + return Deferred().resolve().promise(); } this.expandAllDayPanel(serializedAppointment); return this.appointmentDataSource .add(serializedAppointment) - .always((storeAppointment) => this.onDataPromiseCompleted(StoreEventNames.ADDED, storeAppointment)); + .always((storeAppointment) => { + this.onDataPromiseCompleted(StoreEventNames.ADDED, storeAppointment); + }); }); } - updateAppointment(target, appointment) { + updateAppointment(target: SafeAppointment, appointment: SafeAppointment): Promise { return this.updateAppointmentCore(target, appointment); } - deleteAppointment(rawAppointment) { + deleteAppointment(rawAppointment: SafeAppointment): void { const deletingOptions = this.fireOnAppointmentDeleting(rawAppointment); this.processDeleteAppointment(rawAppointment, deletingOptions); } - fireOnAppointmentDeleting(rawAppointment, targetedAppointmentData?: any) { - const deletingOptions = { + fireOnAppointmentDeleting( + rawAppointment: SafeAppointment, + targetedAppointmentData?: SafeAppointment | AppointmentAdapter, + ): AppointmentDeletingOptions { + const deletingOptions: AppointmentDeletingOptions = { appointmentData: rawAppointment, targetedAppointmentData, cancel: false, @@ -2344,46 +2785,51 @@ class Scheduler extends SchedulerOptionsBaseWidget { return deletingOptions; } - processDeleteAppointment(rawAppointment, deletingOptions) { - this.processActionResult(deletingOptions, function (canceled) { + processDeleteAppointment( + rawAppointment: SafeAppointment, + deletingOptions: AppointmentDeletingOptions, + ): void { + this.processActionResult(deletingOptions, (canceled) => { if (!canceled) { this.appointmentDataSource .remove(rawAppointment) - .always((storeAppointment) => this.onDataPromiseCompleted( - StoreEventNames.DELETED, - storeAppointment, - rawAppointment, - )); + .always((storeAppointment) => { + if (isDefined(storeAppointment)) { + this.onDataPromiseCompleted( + StoreEventNames.DELETED, + storeAppointment, + rawAppointment, + ); + } + }); } - }); + }).catch(noop); } deleteRecurrence( - appointment, + appointment: SafeAppointment, date: Date | string, - recurrenceEditMode, - ) { - if (typeof date === 'string') { - date = new Date(date); - } + recurrenceEditMode: RecurrenceEditMode, + ): void { + const dateValue = typeof date === 'string' ? new Date(date) : date; this.checkRecurringAppointment( appointment, {}, - date, + dateValue, () => { this.processDeleteAppointment( appointment, - { cancel: false }, + { appointmentData: appointment, cancel: false }, ); }, true, false, - null, + undefined, recurrenceEditMode, ); } - focus() { + focus(): void { if (this.editAppointmentData) { this._appointments.focus(); } else { @@ -2401,8 +2847,8 @@ class Scheduler extends SchedulerOptionsBaseWidget { : dateLocalization.firstDayOfWeekIndex() as DayOfWeek; } - private validateKeyFieldIfAgendaExist() { - if (!this.appointmentDataSource.isDataSourceInit) { + private validateKeyFieldIfAgendaExist(): void { + if (!this.appointmentDataSource?.isDataSourceInit) { return; } @@ -2415,7 +2861,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { } // TODO: used externally in m_appointment_drag_behavior.ts - _getDragBehavior() { + _getDragBehavior(): AppointmentDragBehavior | null { return this._workSpace.dragBehavior; } @@ -2438,9 +2884,10 @@ class Scheduler extends SchedulerOptionsBaseWidget { } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any (Scheduler as any).include(DataHelperMixin); -// @ts-ignore +// @ts-expect-error DataHelperMixin changes Scheduler prototype registerComponent('dxScheduler', Scheduler); export default Scheduler; diff --git a/packages/devextreme/js/__internal/scheduler/tooltip_strategies/tooltip_strategy_base.ts b/packages/devextreme/js/__internal/scheduler/tooltip_strategies/tooltip_strategy_base.ts index 0592e3c43d9c..f34d44d08455 100644 --- a/packages/devextreme/js/__internal/scheduler/tooltip_strategies/tooltip_strategy_base.ts +++ b/packages/devextreme/js/__internal/scheduler/tooltip_strategies/tooltip_strategy_base.ts @@ -19,7 +19,11 @@ import { createPromise } from '@ts/core/utils/promise'; import List from '@ts/ui/list/list.edit'; import type Tooltip from '@ts/ui/tooltip'; -import type { AppointmentTooltipItem, TargetedAppointment } from '../types'; +import type { + AppointmentTooltipContextMenuEventArgs, + AppointmentTooltipItem, + TargetedAppointment, +} from '../types'; const TOOLTIP_APPOINTMENT_ITEM = 'dx-tooltip-appointment-item'; const TOOLTIP_APPOINTMENT_ITEM_CONTENT = `${TOOLTIP_APPOINTMENT_ITEM}-content`; @@ -33,7 +37,7 @@ const TOOLTIP_APPOINTMENT_ITEM_DELETE_BUTTON = `${TOOLTIP_APPOINTMENT_ITEM}-dele const APPOINTMENT_TOOLTIP_TEMPLATE = 'appointmentTooltipTemplate'; -interface AppointmentTooltipOptions { +export interface AppointmentTooltipOptions { createComponent: ( element: dxElementWrapper, component: unknown, @@ -63,7 +67,8 @@ interface AppointmentTooltipOptions { }; getAppointmentDisabled: (appointment: Appointment) => boolean | undefined; onItemContextMenu: (eventArgs: unknown) => void; - createEventArgs: (e: ItemContextMenuEvent) => unknown; + createEventArgs: (e: ItemContextMenuEvent) => + AppointmentTooltipContextMenuEventArgs; newAppointments?: boolean; // TODO onAppointmentClick: (e: AppointmentClickEvent) => void; onListInitialized: (e: InitializedEvent) => void; diff --git a/packages/devextreme/js/__internal/scheduler/types.ts b/packages/devextreme/js/__internal/scheduler/types.ts index 7ddba344ba65..11d0bcbf465b 100644 --- a/packages/devextreme/js/__internal/scheduler/types.ts +++ b/packages/devextreme/js/__internal/scheduler/types.ts @@ -1,4 +1,5 @@ import type { dxElementWrapper } from '@js/core/renderer'; +import type { ItemContextMenuEvent } from '@js/ui/list'; import type { Appointment, Properties } from '@js/ui/scheduler'; import type { Component } from '@ts/core/widget/component'; @@ -298,6 +299,17 @@ export interface AppointmentTooltipItem { settings: AppointmentItemViewModel; } +export interface MappedAppointmentFields { + appointmentData: SafeAppointment; + appointmentElement: dxElementWrapper; + targetedAppointmentData: SafeAppointment | TargetedAppointment; +} + +export type AppointmentTooltipContextMenuEventArgs = MappedAppointmentFields & Pick< + ItemContextMenuEvent, + 'component' | 'element' | 'event' | 'model' +>; + export interface CompactAppointmentOptions { $container: dxElementWrapper; coordinates: { top: number; left: number }; diff --git a/packages/devextreme/js/__internal/scheduler/view_model/common/get_compare_options.ts b/packages/devextreme/js/__internal/scheduler/view_model/common/get_compare_options.ts index 6fbd9b9b0391..f6c0a4e628fe 100644 --- a/packages/devextreme/js/__internal/scheduler/view_model/common/get_compare_options.ts +++ b/packages/devextreme/js/__internal/scheduler/view_model/common/get_compare_options.ts @@ -6,7 +6,7 @@ export const getCompareOptions = ( schedulerStore: Scheduler, ): CompareOptions => { const workspace = schedulerStore.getWorkSpace(); - const dateRange = workspace.getDateRange() as Date[]; + const dateRange = workspace.getDateRange(); const compareOptions = { startDayHour: schedulerStore.getViewOption('startDayHour'), endDayHour: schedulerStore.getViewOption('endDayHour'), diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts index b6f56ca6b301..6a04f340bcd2 100644 --- a/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts +++ b/packages/devextreme/js/__internal/scheduler/workspaces/work_space.ts @@ -266,7 +266,7 @@ interface WorkspaceOptionActionMap { type WorkspaceCoordinates = Coordinates & { groupIndex?: number }; -type DroppableCellData = Pick; +export type DroppableCellData = Pick; export interface WorkspaceOptionsInternal extends WidgetProperties { newAppointments: boolean; @@ -319,6 +319,7 @@ export interface WorkspaceOptionsInternal extends WidgetProperties void) | undefined; onCellContextMenu: ((e: CellContextMenuEvent) => void) | undefined; currentDate: Date; + cellDuration: number; hoursInterval: number; allDayExpanded: boolean; @@ -2620,6 +2621,7 @@ class SchedulerWorkSpace extends Widget { endDayHour: 24, viewOffset: 0, hoursInterval: 0.5, + cellDuration: 30, activeStateEnabled: true, hoverStateEnabled: true, groups: [],