Skip to content

Scheduler - Refactor Appointments Collection - KBN#33643

Open
bit-byte0 wants to merge 6 commits into
DevExpress:26_1from
bit-byte0:refactor/scheduler-appointments-kbn
Open

Scheduler - Refactor Appointments Collection - KBN#33643
bit-byte0 wants to merge 6 commits into
DevExpress:26_1from
bit-byte0:refactor/scheduler-appointments-kbn

Conversation

@bit-byte0
Copy link
Copy Markdown
Contributor

@bit-byte0 bit-byte0 commented May 20, 2026

What

Keyboard navigation for new appointments_new/ architecture (Del/Home/End/Enter/Space)

How

  • New props: editing.allowDeleting, onDeleteKeyPress, onItemActivate on AppointmentsProperties
  • focus_controller: switch(true) per key in onViewItemKeyDown
  • Del passes correct occurrence startDate, recurring delete excludes right instance

@bit-byte0 bit-byte0 self-assigned this May 20, 2026
@bit-byte0 bit-byte0 requested a review from a team as a code owner May 20, 2026 08:41
Copilot AI review requested due to automatic review settings May 20, 2026 08:41
@bit-byte0 bit-byte0 added the 26_1 label May 20, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the new Scheduler appointments architecture (appointments_new/) with keyboard navigation/activation and delete-by-key support, wiring these behaviors up from Scheduler into the new Appointments component and adding Jest coverage for the new key interactions.

Changes:

  • Added allowDelete, onDeleteKeyPress, and onItemActivate to AppointmentsProperties and wired them from Scheduler.
  • Expanded AppointmentsFocusController key handling to support Delete/Home/End/Enter/Space.
  • Updated/added Jest tests to use @testing-library/dom key events and to cover the new keyboard behaviors.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/devextreme/js/__internal/scheduler/m_scheduler.ts Passes delete/activate callbacks + allowDelete into new Appointments configuration.
packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts Extends AppointmentsProperties and provides default options for new callbacks/flags.
packages/devextreme/js/__internal/scheduler/appointments_new/appointments.focus_controller.ts Implements the new key handling logic (Delete/Home/End/Enter/Space).
packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts Updates keydown simulation and adds unit tests for Home/End and keyboard actions.
packages/devextreme/js/__internal/scheduler/tests/appointments_new.test.ts Adds integration tests for deleting appointments via the Delete key (including recurring occurrences).

const appointmentsConfig: Partial<AppointmentsProperties> = {
tabIndex: this.option('tabIndex'),
currentView: this.option('currentView') as ViewType,
allowDelete: this.editing.allowUpdating && this.editing.allowDeleting,
Copy link
Copy Markdown
Contributor

@Tucchhaa Tucchhaa May 21, 2026

Choose a reason for hiding this comment

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

allowDelete value is only passed to Appointments on init, but there wasn't added a code that would update allowDelete on runtime change.

But actually deleting works as expected, because the legacy implementation does it for new appointments here.

Even though it works, I think we can improve something there:

  1. this repaint call is redundant for the new appointments.

  2. Weird calculation for allowDelete. In tooltip to delete appointmen just editing.allowDelete is used.

What I suggest to do:

  1. pass scheduler.editing.allowDelete value to Appointments. Scheduler normalizes the 'editing' option here. We need to pass this to new Appointments

  2. Don't call bringEditingModeToAppointments here for new Appointments. Just update Appointments.option().allowDeleting directly via: Appointments.option('allowDeleting' this.editing.allowDeleting)

  3. Add integration tests for these cases (here):

a. Appt can be deleted when editing=true
b. Appt can be deleted when editing.allowDeleting=true
c. Appt can be deleted when editing.allowDeleting=true and editing.allowUpdating=false
d. Appt cant be deleted when editing.allowDeleting=false
e. Appt cant be deleted when editing=false

I think it can be a matrix test.

Please let me know if you have any questions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In case c (allowDeleting=true, allowUpdating=false) - should Delete key work? The tooltip only checks allowDeleting, but allowUpdating=false implies the data is read-only. Should keyboard delete follow the same logic as tooltip, or should it respect allowUpdating too?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should follow tooltip's behavior (only check allowDeleting), because doc doesn't state that allowUpdating must be enabled too to allow appt deletion

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

const entity = this.sortedAppointments[sortedIndex];
Copy link
Copy Markdown
Contributor

@Tucchhaa Tucchhaa May 21, 2026

Choose a reason for hiding this comment

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

Let's avoid variable name entity because it's very generic and tells nothing.

I see that the name of the type is SortedEntity, but it's also something that we can improve.

I suggest to rename entity to itemData, the same applies to all other places where name entity is used

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

SortedEntity already has a field called .itemData (the appointment data inside). So if we name the variable itemData, you'd write itemData.itemData to access the appointment.

How about sortedItem instead - consistent with sortedIndex, sortedAppointments, and sortedItems used elsewhere?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You're right, I agree with sortedItem. Can you please update names of other occurrences of itemData?
focusByItemData -> focusBySortedItem

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What do you think about renaming getSortedAppointments to getSortedItems, so that it would be consistent everywhere?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Let's try it out

if (!allowDelete) { return; }

const entity = this.sortedAppointments[sortedIndex];
if (!entity) { return; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If 'delete' was pressed on a collector item, e.g.:

Image

Then an exception is thrown in the console.

Old appointments implementation also throws some kind of error. I think we might need to discuss it in a call. But I would suggest to prevent appointment deleting in this case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed - the guard doesn't protect here because collector sortedIndex may overlap with sortedAppointments indices, potentially deleting the wrong appointment. We'll add an explicit instanceof AppointmentCollector check in onViewItemKeyDown to skip Delete on collectors entirely. This matches your suggestion to prevent deleting in this case. What do you think?

Copy link
Copy Markdown
Contributor

@Tucchhaa Tucchhaa May 21, 2026

Choose a reason for hiding this comment

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

Yep, that's ok for me. Please also add a test.

And small suggestion: let's move this if-statement inside the handleDeleteKeydown and pass viewItem to all the keydown handlers instead of sortedIndex

Comment on lines +121 to +124
const occurrence = { ...entity.itemData };
getDataAccessor().set('startDate', occurrence, new Date(entity.source.startDate));

onDeleteKeyPress({ appointment: entity.itemData, occurrence });
Copy link
Copy Markdown
Contributor

@Tucchhaa Tucchhaa May 21, 2026

Choose a reason for hiding this comment

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

This is a strange way to get occurrence data, because there's a dedicated function for that: getTargetedAppointmentData.

I suggest to make BaseAppointment.targetedAppointmentData and BaseAppointment.appointmentData properties to be public, so we could use it here:

onDeleteKeyPress({
  appointmentData: viewItem.appointmentData,
  targetedAppointmentData: viewItem.targetedAppointmentData
})

Actually I have made these props in my PR to be public


private handleHomeKeyDown(): void {
const firstAppointment = this.sortedAppointments[0];
if (firstAppointment) { this.focusByItemData(firstAppointment); }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We need e.preventDefault here, like in the legacy impl.

Otherwise body element may scroll to the bottom if it has large height.

Please add a jest test to check that event is prevented on 'home' press


private handleEndKeyDown(): void {
const lastAppointment = this.sortedAppointments[this.sortedAppointments.length - 1];
if (lastAppointment) { this.focusByItemData(lastAppointment); }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot AI review requested due to automatic review settings May 21, 2026 10:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Comment thread packages/devextreme/js/__internal/scheduler/appointments_new/appointments.test.ts Outdated
Copilot AI review requested due to automatic review settings May 21, 2026 11:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread packages/devextreme/js/__internal/scheduler/appointments_new/appointments.ts Outdated
Comment thread packages/devextreme/js/__internal/scheduler/m_scheduler.ts Outdated
@bit-byte0 bit-byte0 force-pushed the refactor/scheduler-appointments-kbn branch from c517882 to 9c60beb Compare May 21, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants