Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,88 @@ 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' });
await new Promise(process.nextTick);

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' });
await new Promise(process.nextTick);

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' });
await new Promise(process.nextTick);

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' });
await new Promise(process.nextTick);

expect(POM.getAppointments().length).toBe(1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export interface BaseAppointmentViewProperties
export class BaseAppointmentView<
TProperties extends BaseAppointmentViewProperties = BaseAppointmentViewProperties,
> extends ViewItem<TProperties> {
protected get targetedAppointmentData(): TargetedAppointment {
public get targetedAppointmentData(): TargetedAppointment {
return this.option().targetedAppointmentData;
}

protected get appointmentData(): SafeAppointment {
public get appointmentData(): SafeAppointment {
return this.option().appointmentData;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { focus } from '@ts/events/m_short';

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

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

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

private get isVirtualScrolling(): boolean {
Expand Down Expand Up @@ -50,8 +52,27 @@ 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':
this.handleDeleteKeyDown(viewItem);
break;
case e.key === 'Home':
this.handleHomeKeyDown(e);
break;
case e.key === 'End':
this.handleEndKeyDown(e);
break;
case e.key === 'Enter':
this.handleEnterKeyDown(viewItem, e);
break;
case e.key === ' ':
Comment thread
bit-byte0 marked this conversation as resolved.
this.handleEnterKeyDown(viewItem, e);
break;
default:
break;
}
}

Expand Down Expand Up @@ -89,20 +110,64 @@ export class AppointmentsFocusController {
}

e.originalEvent.preventDefault();
this.focusByItemData(nextItemData);
this.focusBySortedItem(nextItemData);
}

private focusByItemData(itemData: SortedEntity): void {
private handleDeleteKeyDown(viewItem: ViewItem): void {
if (viewItem instanceof AppointmentCollector) { return; }

const { allowDelete, onDeleteKeyPress } = this.appointments.option();
if (!allowDelete) { return; }

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

const appointmentViewItem = viewItem as BaseAppointmentView;
onDeleteKeyPress({
appointmentData: sortedItem.itemData,
targetedAppointmentData: appointmentViewItem.targetedAppointmentData,
});
}

private handleHomeKeyDown(e: KeyboardKeyDownEvent): void {
const firstSortedItem = this.sortedAppointments[0];
if (firstSortedItem) {
e.originalEvent.preventDefault();
this.focusBySortedItem(firstSortedItem);
}
}

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

private handleEnterKeyDown(viewItem: ViewItem, e: KeyboardKeyDownEvent): void {
const { onItemActivate } = this.appointments.option();
const sortedItem = this.sortedAppointments[viewItem.option().sortedIndex];
if (!sortedItem) { return; }
e.originalEvent.preventDefault();
const appointmentViewItem = viewItem as BaseAppointmentView;
onItemActivate({
data: sortedItem.itemData,
targetedAppointmentData: appointmentViewItem.targetedAppointmentData,
});
}

private focusBySortedItem(sortedItem: SortedEntity): void {
if (this.isVirtualScrolling) {
this.scrollToItem(itemData);
this.scrollToItem(sortedItem);
}

const viewItem = this.appointments.getViewItemBySortedIndex(itemData.sortedIndex);
const viewItem = this.appointments.getViewItemBySortedIndex(sortedItem.sortedIndex);

if (viewItem) {
this.focusViewItem(viewItem);
} else if (this.isVirtualScrolling) {
this.needRestoreFocusIndex = itemData.sortedIndex;
this.needRestoreFocusIndex = sortedItem.sortedIndex;
}
}

Expand All @@ -111,19 +176,19 @@ export class AppointmentsFocusController {
focus.trigger(viewItem?.$element());
}

private scrollToItem(itemData: SortedEntity): void {
private scrollToItem(sortedItem: SortedEntity): void {
const { getStartViewDate, getResourceManager, scrollTo } = this.appointments.option();

const date = new Date(Math.max(
getStartViewDate().getTime(),
itemData.source.startDate,
sortedItem.source.startDate,
));

const group = getRawAppointmentGroupValues(
itemData.itemData,
sortedItem.itemData,
getResourceManager().resources,
);

scrollTo(date, { group, allDay: itemData.allDay });
scrollTo(date, { group, allDay: sortedItem.allDay });
}
}
Loading
Loading