Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
import type { dxElementWrapper } from '@js/core/renderer';
import $ from '@js/core/renderer';
import type { Properties } from '@js/ui/scheduler';
import { fireEvent } from '@testing-library/dom';

import { createScheduler as baseCreateScheduler } from './__mock__/create_scheduler';
import { setupSchedulerTestEnvironment } from './__mock__/m_mock_scheduler';
Expand Down Expand Up @@ -375,4 +376,84 @@ describe('New Appointments', () => {
expect(onAppointmentRendered).toHaveBeenCalledTimes(1);
});
});

describe('Keyboard navigation', () => {
it('should delete appointment by delete key', async () => {
const { POM } = await createScheduler({
dataSource: [{
startDate: new Date(2015, 1, 9, 8),
endDate: new Date(2015, 1, 9, 9),
}],
currentDate: new Date(2015, 1, 9, 8),
});

const appointment = POM.getAppointments()[0];
appointment.element.focus();
fireEvent.keyDown(appointment.element, { key: 'Delete' });

expect(POM.getAppointments().length).toBe(0);
});
Comment thread
bit-byte0 marked this conversation as resolved.

it('should delete recurring appointment occurrence by delete key', async () => {
const { POM } = await createScheduler({
dataSource: [{
startDate: new Date(2015, 1, 9, 8),
endDate: new Date(2015, 1, 9, 9),
recurrenceRule: 'FREQ=DAILY;COUNT=3',
}],
currentDate: new Date(2015, 1, 9),
currentView: 'week',
recurrenceEditMode: 'occurrence',
});

expect(POM.getAppointments().length).toBe(3);

const appointment = POM.getAppointments()[0];
appointment.element.focus();
fireEvent.keyDown(appointment.element, { key: 'Delete' });

expect(POM.getAppointments().length).toBe(2);
});
Comment thread
bit-byte0 marked this conversation as resolved.

it.each([
{ editing: true },
{ editing: { allowDeleting: true } },
{ editing: { allowDeleting: true, allowUpdating: false } },
])('should delete appointment when editing=$editing', async ({ editing }) => {
const { POM } = await createScheduler({
dataSource: [{
startDate: new Date(2015, 1, 9, 8),
endDate: new Date(2015, 1, 9, 9),
}],
currentDate: new Date(2015, 1, 9, 8),
editing,
});

const appointment = POM.getAppointments()[0];
appointment.element.focus();
fireEvent.keyDown(appointment.element, { key: 'Delete' });

expect(POM.getAppointments().length).toBe(0);
});

it.each([
{ editing: { allowDeleting: false } },
{ editing: false },
])('should NOT delete appointment when editing=$editing', async ({ editing }) => {
const { POM } = await createScheduler({
dataSource: [{
startDate: new Date(2015, 1, 9, 8),
endDate: new Date(2015, 1, 9, 9),
}],
currentDate: new Date(2015, 1, 9, 8),
editing,
});

const appointment = POM.getAppointments()[0];
appointment.element.focus();
fireEvent.keyDown(appointment.element, { key: 'Delete' });

expect(POM.getAppointments().length).toBe(1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { focus } from '@ts/events/m_short';

import { getRawAppointmentGroupValues } from '../utils/resource_manager/appointment_groups_utils';
import type { SortedEntity } from '../view_model/types';
import { AppointmentCollector } from './appointment_collector';
import type { Appointments } from './appointments';
import type { ViewItem } from './view_item';

Expand All @@ -14,15 +15,15 @@ export class AppointmentsFocusController {
private needRestoreFocusIndex = -1;

private get sortedAppointments(): SortedEntity[] {
return this.appointments.option().getSortedAppointments();
return (this.appointments.option()).getSortedAppointments();
}

private get isVirtualScrolling(): boolean {
return this.appointments.option().isVirtualScrolling();
return (this.appointments.option()).isVirtualScrolling();
}

private get tabIndex(): number | undefined {
return this.appointments.option().tabIndex;
return (this.appointments.option()).tabIndex;
}

constructor(private readonly appointments: Appointments) { }
Expand Down Expand Up @@ -50,8 +51,28 @@ export class AppointmentsFocusController {
}

public onViewItemKeyDown(viewItem: ViewItem, e: KeyboardKeyDownEvent): void {
if (e.key === 'Tab') {
this.handleTabKeyDown(e, viewItem.option().sortedIndex);
switch (true) {
case e.key === 'Tab':
this.handleTabKeyDown(e, viewItem.option().sortedIndex);
break;
Comment thread
bit-byte0 marked this conversation as resolved.
case e.key === 'Delete':
if (viewItem instanceof AppointmentCollector) { break; }
this.handleDeleteKeyDown(viewItem.option().sortedIndex, e);
break;
case e.key === 'Home':
this.handleHomeKeyDown(e);
break;
case e.key === 'End':
this.handleEndKeyDown(e);
break;
case e.key === 'Enter':
this.handleEnterKeyDown(viewItem.option().sortedIndex);
break;
case e.key === ' ':
Comment thread
bit-byte0 marked this conversation as resolved.
this.handleEnterKeyDown(viewItem.option().sortedIndex);
break;
default:
break;
}
}

Expand Down Expand Up @@ -92,6 +113,41 @@ export class AppointmentsFocusController {
this.focusByItemData(nextItemData);
}

private handleDeleteKeyDown(sortedIndex: number, e: KeyboardKeyDownEvent): void {
const { editing, onDeleteKeyPress, getDataAccessor } = this.appointments.option();
if (!editing.allowDeleting) { return; }

const sortedItem = this.sortedAppointments[sortedIndex];
if (!sortedItem) { return; }

e.originalEvent.preventDefault();
const occurrence = { ...sortedItem.itemData };
getDataAccessor().set('startDate', occurrence, new Date(sortedItem.source.startDate));

onDeleteKeyPress({ appointment: sortedItem.itemData, occurrence });
}

private handleHomeKeyDown(e: KeyboardKeyDownEvent): void {
const firstAppointment = this.sortedAppointments[0];
if (!firstAppointment) { return; }
e.originalEvent.preventDefault();
this.focusByItemData(firstAppointment);
}

private handleEndKeyDown(e: KeyboardKeyDownEvent): void {
const lastAppointment = this.sortedAppointments[this.sortedAppointments.length - 1];
if (!lastAppointment) { return; }
e.originalEvent.preventDefault();
this.focusByItemData(lastAppointment);
}

private handleEnterKeyDown(sortedIndex: number): void {
const { onItemActivate } = this.appointments.option();
const sortedItem = this.sortedAppointments[sortedIndex];
if (!sortedItem) { return; }
onItemActivate({ data: sortedItem.itemData, target: null });
Comment thread
bit-byte0 marked this conversation as resolved.
}

private focusByItemData(itemData: SortedEntity): void {
if (this.isVirtualScrolling) {
this.scrollToItem(itemData);
Expand Down
Loading
Loading