From a24cc464c3b992d761fce2e0724ce98fc59b07e1 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 27 Apr 2026 10:31:43 +0300 Subject: [PATCH] feat: add keydown handler to prev and next buttons --- packages/main/cypress/specs/Calendar.cy.tsx | 32 ++++++++++++ packages/main/src/Calendar.ts | 52 ++++++++++++++++++-- packages/main/src/CalendarHeaderTemplate.tsx | 4 ++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/packages/main/cypress/specs/Calendar.cy.tsx b/packages/main/cypress/specs/Calendar.cy.tsx index 63f5c2e5e192..f53b10890dcb 100644 --- a/packages/main/cypress/specs/Calendar.cy.tsx +++ b/packages/main/cypress/specs/Calendar.cy.tsx @@ -753,6 +753,38 @@ describe("Calendar general interaction", () => { .should("have.class", "ui5-calheader-arrowbtn-disabled"); }); + it("Should navigate when pressing Space/Enter on prev/next buttons", () => { + const date = new Date(Date.UTC(2024, 5, 15, 0, 0, 0)); // June 15, 2024 + cy.mount(getDefaultCalendar(date)); + + // Focus and press Enter on next button + cy.get("#calendar1") + .shadow() + .find("[data-ui5-cal-header-btn-next]") + .focus() + .realPress("Enter"); + + // Verify navigation to July + cy.get("#calendar1") + .shadow() + .find("[data-ui5-cal-header-btn-month]") + .should("contain.text", "July"); + + // Focus and press Space on prev button + cy.get("#calendar1") + .shadow() + .find("[data-ui5-cal-header-btn-prev]") + .focus() + .realPress("Space"); + + // Verify navigation back to June + cy.get("#calendar1") + .shadow() + .find("[data-ui5-cal-header-btn-month]") + .should("contain.text", "June"); + }); + + it("Second month and year are rendered in the header", () => { cy.mount(); const timestamp = new Date(Date.UTC(2000, 9, 10, 0, 0, 0)).valueOf() / 1000; diff --git a/packages/main/src/Calendar.ts b/packages/main/src/Calendar.ts index 3ee8e8ab30cf..e012dc354379 100644 --- a/packages/main/src/Calendar.ts +++ b/packages/main/src/Calendar.ts @@ -1082,6 +1082,7 @@ class Calendar extends CalendarPart { onYearButtonKeyUp(e: KeyboardEvent) { if (isSpace(e)) { + e.preventDefault(); this.switchToYearPicker(); this.fireDecoratorEvent("show-year-view"); } @@ -1100,12 +1101,13 @@ class Calendar extends CalendarPart { onYearRangeButtonKeyUp(e: KeyboardEvent) { if (isSpace(e)) { + e.preventDefault(); this.switchToYearRangePicker(); this.fireDecoratorEvent("show-year-range-view"); } } - _handleNavigationButtonKeyDown(e: MouseEvent, isDisabled: boolean, action: () => void) { + _handleNavigationButtonClick(e: MouseEvent, isDisabled: boolean, action: () => void) { if (isDisabled) { e.preventDefault(); return; @@ -1119,12 +1121,56 @@ class Calendar extends CalendarPart { e.preventDefault(); } + _handlePrevNextButtonKeyDown(e: KeyboardEvent, isDisabled: boolean, action: () => void) { + if (isDisabled) { + e.preventDefault(); + return; + } + + if (isSpace(e)) { + e.preventDefault(); + } + + if (isEnter(e)) { + action(); + e.preventDefault(); + } + } + + _handlePrevNextButtonKeyUp(e: KeyboardEvent, isDisabled: boolean, action: () => void) { + if (isDisabled) { + e.preventDefault(); + return; + } + + if (isSpace(e)) { + e.preventDefault(); + action(); + } + } + onPrevButtonClick(e: MouseEvent) { - this._handleNavigationButtonKeyDown(e, this._previousButtonDisabled, () => this.onHeaderPreviousPress()); + this._handleNavigationButtonClick(e, this._previousButtonDisabled, () => this.onHeaderPreviousPress()); } onNextButtonClick(e: MouseEvent) { - this._handleNavigationButtonKeyDown(e, this._nextButtonDisabled, () => this.onHeaderNextPress()); + this._handleNavigationButtonClick(e, this._nextButtonDisabled, () => this.onHeaderNextPress()); + } + + onPrevButtonKeyDown(e: KeyboardEvent) { + this._handlePrevNextButtonKeyDown(e, this._previousButtonDisabled, () => this.onHeaderPreviousPress()); + } + + onPrevButtonKeyUp(e: KeyboardEvent) { + this._handlePrevNextButtonKeyUp(e, this._previousButtonDisabled, () => this.onHeaderPreviousPress()); + } + + onNextButtonKeyDown(e: KeyboardEvent) { + this._handlePrevNextButtonKeyDown(e, this._nextButtonDisabled, () => this.onHeaderNextPress()); + } + + onNextButtonKeyUp(e: KeyboardEvent) { + this._handlePrevNextButtonKeyUp(e, this._nextButtonDisabled, () => this.onHeaderNextPress()); } /** diff --git a/packages/main/src/CalendarHeaderTemplate.tsx b/packages/main/src/CalendarHeaderTemplate.tsx index 898c80906e28..6d9a8f750d02 100644 --- a/packages/main/src/CalendarHeaderTemplate.tsx +++ b/packages/main/src/CalendarHeaderTemplate.tsx @@ -56,6 +56,8 @@ function renderPrevButton(this: Calendar, isFirst: boolean, isMultiple: boolean) part="calendar-header-arrow-button" role="button" onMouseDown={this.onPrevButtonClick} + onKeyDown={this.onPrevButtonKeyDown} + onKeyUp={this.onPrevButtonKeyUp} tabindex={this._previousButtonDisabled ? -1 : 0} title={this.accInfo.tooltipPrevButton} aria-label={this.accInfo.ariaLabelPrevButton} @@ -162,6 +164,8 @@ function renderNextButton(this: Calendar, isFirst: boolean, isLast: boolean, isM part="calendar-header-arrow-button" role="button" onMouseDown={this.onNextButtonClick} + onKeyDown={this.onNextButtonKeyDown} + onKeyUp={this.onNextButtonKeyUp} tabindex={this._nextButtonDisabled ? -1 : 0} title={this.accInfo.tooltipNextButton} aria-label={this.accInfo.ariaLabelNextButton}