Skip to content
Draft
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
172 changes: 130 additions & 42 deletions packages/main/cypress/specs/Calendar.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,47 +38,60 @@ const getCalendarsWithWeekNumbers = () => (<>
</>);

describe("Calendar general interaction", () => {
it("Focus goes into the current day item of the day picker", () => {
const date = new Date(Date.UTC(2000, 10, 22, 0, 0, 0));
cy.mount(getDefaultCalendar(date));

cy.ui5CalendarGetDay("#calendar1", "974851200")
.as("selectedDay");

cy.get("@selectedDay")
.realClick();

cy.get("@selectedDay")
.should("have.focus")
.realPress("Tab");
it("Focus goes into the header items and then to the current day item of the day picker", () => {
const calendarTestDate = new Date(Date.UTC(2000, 10, 22, 0, 0, 0));
cy.mount(getDefaultCalendar(calendarTestDate));

cy.get<Calendar>("#calendar1")
.shadow()
.find(".ui5-calheader")
.as("calheader");

cy.ui5CalendarGetDay("#calendar1", "974851200").as("selectedDay");

cy.get("#calendar1")
.realClick();

cy.realPress("Tab");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-prev]")
.as("prevBtn");

cy.get("@prevBtn")
.should("be.focused");

cy.realPress("Tab");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-month]")
.as("monthBtn");

cy.get("@monthBtn")
.should("have.focus")
.realPress("Tab");
.should("be.focused");

cy.realPress("Tab");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-year]")
.as("yearBtn");

cy.get("@yearBtn")
.should("have.focus")
.realPress(["Shift", "Tab"]);
.should("be.focused");

cy.realPress("Tab");

cy.get("@monthBtn")
.should("have.focus")
.realPress(["Shift", "Tab"]);
cy.get("@calheader")
.find("[data-ui5-cal-header-btn-next]")
.as("nextBtn");

cy.get("@nextBtn")
.should("be.focused");

cy.realPress("Tab");

cy.get("@selectedDay")
.should("have.focus");
.should("be.focused");
});

it("Calendar focuses the selected year when yearpicker is opened", () => {
Expand Down Expand Up @@ -108,11 +121,32 @@ describe("Calendar general interaction", () => {
const date = new Date(Date.UTC(2000, 10, 22, 0, 0, 0));
cy.mount(getDefaultCalendar(date));

cy.ui5CalendarGetDay("#calendar1", "974851200")
cy.get<Calendar>("#calendar1")
.shadow()
.find(".ui5-calheader")
.as("calheader");

cy.ui5CalendarGetDay("#calendar1", "974851200").as("selectedDay");

cy.get("#calendar1")
.realClick();

cy.focused().realPress("Tab");
cy.focused().realPress("Space");
cy.realPress("Tab");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-prev]")
.as("prevBtn");

cy.get("@prevBtn")
.should("be.focused");

cy.realPress("Tab");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-month]")
.as("monthBtn");

cy.realPress("Space");

cy.get<Calendar>("#calendar1")
.shadow()
Expand All @@ -135,12 +169,46 @@ describe("Calendar general interaction", () => {
const date = new Date(Date.UTC(2000, 10, 22, 0, 0, 0));
cy.mount(getDefaultCalendar(date));

cy.ui5CalendarGetDay("#calendar1", "974851200")
.realClick();

cy.get<Calendar>("#calendar1")
.shadow()
.find(".ui5-calheader")
.as("calheader");

cy.focused().realPress("Tab");
cy.focused().realPress("Tab");
cy.focused().realPress("Space");
cy.ui5CalendarGetDay("#calendar1", "974851200").as("selectedDay");

cy.get("#calendar1")
.realClick();

cy.realPress("Tab");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-prev]")
.as("prevBtn");

cy.get("@prevBtn")
.should("be.focused");

cy.realPress("Tab");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-month]")
.as("monthBtn");

cy.get("@monthBtn")
.should("be.focused");


cy.realPress("Tab");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-year]")
.as("yearBtn");

cy.get("@yearBtn")
.should("be.focused");

cy.realPress("Space");

cy.get<Calendar>("#calendar1")
.shadow()
Expand Down Expand Up @@ -414,15 +482,15 @@ describe("Calendar general interaction", () => {
.should("have.focus");

cy.focused().realPress(["Shift", "F4"]);

// Wait for focus to settle before proceeding
cy.get<Calendar>("#calendar1")
.shadow()
.find("[ui5-yearpicker]")
.shadow()
.find("[tabindex='0']")
.should("have.focus");

cy.focused().realPress("PageUp");

cy.get<Calendar>("#calendar1")
Expand Down Expand Up @@ -996,6 +1064,26 @@ describe("Calendar general interaction", () => {
});

describe("Calendar accessibility", () => {
it("Header prev/next buttons have correct title and tabindex", () => {
const date = new Date(Date.UTC(2025, 0, 15, 0, 0, 0));
cy.mount(getDefaultCalendar(date));

cy.get<Calendar>("#calendar1")
.shadow()
.find(".ui5-calheader")
.as("calheader");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-prev]")
.should("have.attr", "title")
.and("contain", "Previous Month (Pagedown)");

cy.get("@calheader")
.find("[data-ui5-cal-header-btn-next]")
.should("have.attr", "title")
.and("contain", "Next Month (Pageup)");
});

it("Should have proper aria-label attributes on header buttons", () => {
const date = new Date(Date.UTC(2000, 10, 22, 0, 0, 0));
cy.mount(getDefaultCalendar(date));
Expand Down Expand Up @@ -1271,7 +1359,7 @@ describe("Calendar accessibility", () => {
// Get the selected days and verify their aria-labels
cy.get("@selectedDays").each(($day, index) => {
cy.wrap($day).should("have.attr", "aria-label");

if (index === 0) {
// First day should contain "First date of range"
cy.wrap($day)
Expand All @@ -1293,22 +1381,22 @@ describe("Calendar accessibility", () => {
});

describe("Day Picker Tests", () => {
it.skip("Select day with Space", () => {
it.skip("Select day with Space", () => {
cy.mount(<Calendar id="calendar1"></Calendar>);

cy.get<Calendar>("#calendar1")
.shadow()
.find("[ui5-daypicker]")
.shadow()
.find(".ui5-dp-item--now")
.as("today");

cy.get("@today")
.realClick()
.should("be.focused")
.realPress("ArrowRight")
.realPress("Space");

cy.focused()
.invoke("attr", "data-sap-timestamp")
.then(timestampAttr => {
Expand All @@ -1317,7 +1405,7 @@ describe("Day Picker Tests", () => {
const expectedDate = new Date(Date.now() + 24 * 3600 * 1000).getDate();
expect(selectedDate).to.eq(expectedDate);
});

cy.get<Calendar>("#calendar1")
.should(($calendar) => {
const selectedDates = $calendar.prop("selectedDates");
Expand All @@ -1330,7 +1418,7 @@ describe("Day Picker Tests", () => {
const tomorrow = Math.floor(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate() + 1, 0, 0, 0, 0) / 1000);

cy.mount(<Calendar id="calendar1"></Calendar>);

cy.get<Calendar>("#calendar1")
.shadow()
.find("[ui5-daypicker]")
Expand Down Expand Up @@ -1369,7 +1457,7 @@ describe("Day Picker Tests", () => {

it("Day names are correctly displayed", () => {
cy.mount(<Calendar id="calendar1"></Calendar>);

cy.get<Calendar>("#calendar1")
.shadow()
.find("[ui5-daypicker]")
Expand Down Expand Up @@ -1429,7 +1517,7 @@ describe("Day Picker Tests", () => {
const timestamp = parseInt(timestampAttr!);
const todayFromTimestamp = new Date(timestamp * 1000);
const actualToday = new Date();

expect(todayFromTimestamp.getDate()).to.equal(actualToday.getDate());
expect(todayFromTimestamp.getMonth()).to.equal(actualToday.getMonth());
expect(todayFromTimestamp.getFullYear()).to.equal(actualToday.getFullYear());
Expand Down
10 changes: 10 additions & 0 deletions packages/main/src/Calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ import {
CALENDAR_HEADER_YEAR_BUTTON_SHORTCUT,
CALENDAR_HEADER_YEAR_RANGE_BUTTON,
CALENDAR_HEADER_YEAR_RANGE_BUTTON_SHORTCUT,
CALENDAR_HEADER_MONTH_NEXT_BUTTON_TITLE,
CALENDAR_HEADER_MONTH_NEXT_BUTTON_SHORTCUT,
CALENDAR_HEADER_MONTH_PREVIOUS_BUTTON_TITLE,
CALENDAR_HEADER_MONTH_PREVIOUS_BUTTON_SHORTCUT,
} from "./generated/i18n/i18n-defaults.js";
import type { YearRangePickerChangeEventDetail } from "./YearRangePicker.js";

Expand Down Expand Up @@ -802,11 +806,15 @@ class Calendar extends CalendarPart {
const monthLabel = Calendar.i18nBundle?.getText(CALENDAR_HEADER_MONTH_BUTTON, headerMonthButtonText);
const yearLabel = Calendar.i18nBundle?.getText(CALENDAR_HEADER_YEAR_BUTTON, this._headerYearButtonText as string);
const yearRangeLabel = Calendar.i18nBundle?.getText(CALENDAR_HEADER_YEAR_RANGE_BUTTON, rangeStartText, rangeEndText);
const nextBtnLabel = Calendar.i18nBundle?.getText(CALENDAR_HEADER_MONTH_NEXT_BUTTON_TITLE);
const prevBtnLabel = Calendar.i18nBundle?.getText(CALENDAR_HEADER_MONTH_PREVIOUS_BUTTON_TITLE);

// Get shortcuts
const monthShortcut = Calendar.i18nBundle?.getText(CALENDAR_HEADER_MONTH_BUTTON_SHORTCUT);
const yearShortcut = Calendar.i18nBundle?.getText(CALENDAR_HEADER_YEAR_BUTTON_SHORTCUT);
const yearRangeShortcut = Calendar.i18nBundle?.getText(CALENDAR_HEADER_YEAR_RANGE_BUTTON_SHORTCUT);
const nextBtnShortcut = Calendar.i18nBundle?.getText(CALENDAR_HEADER_MONTH_NEXT_BUTTON_SHORTCUT);
const prevBtnShortcut = Calendar.i18nBundle?.getText(CALENDAR_HEADER_MONTH_PREVIOUS_BUTTON_SHORTCUT);

return {
ariaLabelMonthButton: monthLabel,
Expand All @@ -822,6 +830,8 @@ class Calendar extends CalendarPart {
tooltipMonthButton: `${monthLabel} (${monthShortcut})`,
tooltipYearButton: `${yearLabel} (${yearShortcut})`,
tooltipYearRangeButton: `${yearRangeLabel} (${yearRangeShortcut})`,
tooltipNextButton: `${nextBtnLabel} (${nextBtnShortcut})`,
tooltipPrevButton: `${prevBtnLabel} (${prevBtnShortcut})`,
};
}

Expand Down
4 changes: 4 additions & 0 deletions packages/main/src/CalendarHeaderTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export default function CalendarTemplate(this: Calendar) {
part="calendar-header-arrow-button"
role="button"
onMouseDown={this.onPrevButtonClick}
tabindex={this._previousButtonDisabled ? -1 : 0}
title={this.accInfo.tooltipPrevButton}
>
<Icon class="ui5-calheader-arrowicon" name={slimArowLeft}/>
</div>
Expand Down Expand Up @@ -93,6 +95,8 @@ export default function CalendarTemplate(this: Calendar) {
part="calendar-header-arrow-button"
role="button"
onMouseDown={this.onNextButtonClick}
tabindex={this._nextButtonDisabled ? -1 : 0}
title={this.accInfo.tooltipNextButton}
>
<Icon class="ui5-calheader-arrowicon" name={slimArowRight}/>
</div>
Expand Down
7 changes: 3 additions & 4 deletions packages/main/src/CalendarTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export default function CalendarTemplate(this: Calendar) {
class="ui5-cal-root"
onKeyDown={this._onkeydown}
>
<div class="ui5-calheader" exportparts="calendar-header-arrow-button, calendar-header-middle-button">
{ CalendarHeaderTemplate.call(this) }
</div>
<div id={`${this._id}-content`} class="ui5-cal-content">
<DayPicker
id={`${this._id}-daypicker`}
Expand Down Expand Up @@ -87,10 +90,6 @@ export default function CalendarTemplate(this: Calendar) {
exportparts="year-range-cell, year-range-cell-selected, year-range-cell-selected-between, year-range-picker-root"
/>
</div>

<div class="ui5-calheader" exportparts="calendar-header-arrow-button, calendar-header-middle-button">
{ CalendarHeaderTemplate.call(this) }
</div>
</div>

<div
Expand Down
12 changes: 12 additions & 0 deletions packages/main/src/i18n/messagebundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,18 @@ CALENDAR_HEADER_YEAR_BUTTON_SHORTCUT = Shift + F4
#XACT: Keyboard shortcut for year range button in the Calendar Header
CALENDAR_HEADER_YEAR_RANGE_BUTTON_SHORTCUT = Shift + F4

#XACT: Title for month next button in the Calendar Header
CALENDAR_HEADER_MONTH_NEXT_BUTTON_TITLE = Next Month

#XACT: Keyboard shortcut for month next button in the Calendar Header
CALENDAR_HEADER_MONTH_NEXT_BUTTON_SHORTCUT = Pageup

#XACT: Title for month previous button in the Calendar Header
CALENDAR_HEADER_MONTH_PREVIOUS_BUTTON_TITLE = Previous Month

#XACT: Keyboard shortcut for month previous button in the Calendar Header
CALENDAR_HEADER_MONTH_PREVIOUS_BUTTON_SHORTCUT = Pagedown

#XACT: ARIA label for day picker selected range start
DAY_PICKER_SELECTED_RANGE_START = {0} First date of range

Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/themes/Calendar.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
width: var(--_ui5_calendar_width);
padding: var(--_ui5_calendar_top_bottom_padding) var(--_ui5_calendar_left_right_padding) 0;
display: flex;
flex-direction: column-reverse;
flex-direction: column;
justify-content: flex-end;
overflow: hidden;
}
Expand Down
Loading
Loading