diff --git a/packages/main/src/Calendar.ts b/packages/main/src/Calendar.ts index e65249a7298a..c860ccec7dbd 100644 --- a/packages/main/src/Calendar.ts +++ b/packages/main/src/Calendar.ts @@ -49,12 +49,11 @@ import CalendarHeaderCss from "./generated/themes/CalendarHeader.css.js"; import { CALENDAR_HEADER_NEXT_BUTTON, CALENDAR_HEADER_PREVIOUS_BUTTON } from "./generated/i18n/i18n-defaults.js"; import type { YearRangePickerChangeEventDetail } from "./YearRangePicker.js"; -interface ICalendarPicker { - _showPreviousPage: () => void, - _showNextPage: () => void, +interface ICalendarPicker extends HTMLElement { + _showPreviousPage: () => void | Promise, + _showNextPage: () => void | Promise, _hasPreviousPage: () => boolean, _hasNextPage: () => boolean, - _autoFocus?: boolean, _currentYearRange?: CalendarYearRangeT, } @@ -335,6 +334,11 @@ class Calendar extends CalendarPart { this._valueIsProcessed = false; } + async _focusCurrentPicker() { + await renderFinished(); + this._currentPickerDOM.focus(); + } + /** * @private */ @@ -465,7 +469,6 @@ class Calendar extends CalendarPart { if (defaultTypes.includes(this._selectedItemType)) { this._selectedItemType = "None"; // In order to avoid filtering of default types } - this._currentPickerDOM._autoFocus = false; } /** @@ -530,8 +533,8 @@ class Calendar extends CalendarPart { } showMonth() { - this._currentPickerDOM._autoFocus = false; this._currentPicker = "month"; + this._focusCurrentPicker(); } /** @@ -543,8 +546,8 @@ class Calendar extends CalendarPart { } showYear() { - this._currentPickerDOM._autoFocus = false; this._currentPicker = "year"; + this._focusCurrentPicker(); } /** @@ -556,8 +559,8 @@ class Calendar extends CalendarPart { } showYearRange() { - this._currentPickerDOM._autoFocus = false; this._currentPicker = "yearrange"; + this._focusCurrentPicker(); } get _currentPickerDOM() { @@ -570,10 +573,6 @@ class Calendar extends CalendarPart { */ onHeaderPreviousPress() { this._currentPickerDOM._showPreviousPage(); - - if (this.calendarLegend) { - this._currentPickerDOM._autoFocus = true; - } } /** @@ -581,10 +580,6 @@ class Calendar extends CalendarPart { */ onHeaderNextPress() { this._currentPickerDOM._showNextPage(); - - if (this.calendarLegend) { - this._currentPickerDOM._autoFocus = true; - } } _setSecondaryCalendarTypeButtonText() { @@ -715,11 +710,10 @@ class Calendar extends CalendarPart { if (this._pickersMode === CalendarPickersMode.DAY_MONTH_YEAR) { this._currentPicker = "day"; + this._focusCurrentPicker(); } else { this._fireEventAndUpdateSelectedDates(e.detail.dates); } - - this._currentPickerDOM._autoFocus = true; } onSelectedYearChange(e: CustomEvent) { @@ -727,29 +721,31 @@ class Calendar extends CalendarPart { if (this._pickersMode === CalendarPickersMode.DAY_MONTH_YEAR) { this._currentPicker = "day"; + this._focusCurrentPicker(); } else if (this._pickersMode === CalendarPickersMode.MONTH_YEAR) { this._currentPicker = "month"; + this._focusCurrentPicker(); } else { this._fireEventAndUpdateSelectedDates(e.detail.dates); } - - this._currentPickerDOM._autoFocus = true; } onSelectedYearRangeChange(e: CustomEvent) { this.timestamp = e.detail.timestamp; this._currentPicker = "year"; - this._currentPickerDOM._autoFocus = true; + this._focusCurrentPicker(); } onNavigate(e: CustomEvent) { this.timestamp = e.detail.timestamp; + this._focusCurrentPicker(); } _onkeydown(e: KeyboardEvent) { if (isF4(e) && this._currentPicker !== "month") { this._currentPicker = "month"; this.fireDecoratorEvent("show-month-view"); + this._focusCurrentPicker(); } if (!isF4Shift(e)) { @@ -763,6 +759,8 @@ class Calendar extends CalendarPart { this._currentPicker = "yearrange"; this.fireDecoratorEvent("show-year-range-view"); } + + this._focusCurrentPicker(); } _onLegendFocusOut() { diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index 3c9af1711ae0..de95a8a4a3b0 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -8,6 +8,7 @@ import type LocaleData from "@ui5/webcomponents-localization/dist/LocaleData.js" import getCachedLocaleDataInstance from "@ui5/webcomponents-localization/dist/getCachedLocaleDataInstance.js"; import InvisibleMessageMode from "@ui5/webcomponents-base/dist/types/InvisibleMessageMode.js"; import announce from "@ui5/webcomponents-base/dist/util/InvisibleMessage.js"; +import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import { isSpace, @@ -195,8 +196,6 @@ class DayPicker extends CalendarPart implements ICalendarPicker { @query("[data-sap-focus-ref]") _focusableDay!: HTMLElement; - _autoFocus?: boolean; - @i18n("@ui5/webcomponents") static i18nBundle: I18nBundle; @@ -404,13 +403,8 @@ class DayPicker extends CalendarPart implements ICalendarPicker { return dayNames.some(dayName => dayName.length > 4); } - onAfterRendering() { - if (this._autoFocus && !this._hidden) { - this.focus(); - } - } - - _focusCorrectDay() { + async _focusCorrectDay() { + await renderFinished(); if (this._shouldFocusDay) { this._focusableDay.focus(); } @@ -421,14 +415,9 @@ class DayPicker extends CalendarPart implements ICalendarPicker { } _onfocusin() { - this._autoFocus = true; this._focusCorrectDay(); } - _onfocusout() { - this._autoFocus = false; - } - /** * Tells if the day is selected (dark blue). * @param timestamp @@ -618,6 +607,21 @@ class DayPicker extends CalendarPart implements ICalendarPicker { } } + /** + * Sets the focus reference to the day that was clicked with mousedown. + * @param e + * @private + */ + _onmousedown(e: MouseEvent) { + const target = e.target as HTMLElement; + const clickedItem = target.closest(".ui5-dp-item") as HTMLElement; + + if (clickedItem && this._isDayPressed(clickedItem)) { + const timestamp = this._getTimestampFromDom(clickedItem); + this._setTimestamp(timestamp); + } + } + _onkeydown(e: KeyboardEvent) { let preventDefault = true; @@ -725,6 +729,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker { */ _showPreviousPage() { this._modifyTimestampBy(-1, "month", false); + this._focusCorrectDay(); } /** @@ -733,6 +738,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker { */ _showNextPage() { this._modifyTimestampBy(1, "month", false); + this._focusCorrectDay(); } /** @@ -749,6 +755,8 @@ class DayPicker extends CalendarPart implements ICalendarPicker { // Notify the calendar to update its timestamp this.fireDecoratorEvent("navigate", { timestamp: this.timestamp! }); + + this._focusCorrectDay(); } /** diff --git a/packages/main/src/DayPickerTemplate.tsx b/packages/main/src/DayPickerTemplate.tsx index 6e9735d12493..867b0b27d1bd 100644 --- a/packages/main/src/DayPickerTemplate.tsx +++ b/packages/main/src/DayPickerTemplate.tsx @@ -14,9 +14,8 @@ export default function DayPickerTemplate(this: DayPicker) { onKeyDown={this._onkeydown} onKeyUp={this._onkeyup} onClick={this._onclick} + onMouseDown={this._onmousedown} onMouseOver={this._onmouseover} - onFocusIn={this._onfocusin} - onFocusOut={this._onfocusout} >
diff --git a/packages/main/src/MonthPicker.ts b/packages/main/src/MonthPicker.ts index 4c4065ad0a29..8c468c230b14 100644 --- a/packages/main/src/MonthPicker.ts +++ b/packages/main/src/MonthPicker.ts @@ -1,5 +1,6 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import query from "@ui5/webcomponents-base/dist/decorators/query.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import getCachedLocaleDataInstance from "@ui5/webcomponents-localization/dist/getCachedLocaleDataInstance.js"; @@ -35,6 +36,7 @@ import MonthPickerTemplate from "./MonthPickerTemplate.js"; // Styles import monthPickerStyles from "./generated/themes/MonthPicker.css.js"; import CalendarSelectionMode from "./types/CalendarSelectionMode.js"; +import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; const isBetween = (x: number, num1: number, num2: number) => x > Math.min(num1, num2) && x < Math.max(num1, num2); const PAGE_SIZE = 12; // total months on a single page @@ -132,6 +134,9 @@ class MonthPicker extends CalendarPart implements ICalendarPicker { @property({ type: Number }) _secondTimestamp?: number; + @query("[data-sap-focus-ref]") + _focusableMonth!: HTMLElement; + @i18n("@ui5/webcomponents") static i18nBundle: I18nBundle; @@ -143,17 +148,26 @@ class MonthPicker extends CalendarPart implements ICalendarPicker { this._buildMonths(); } - onAfterRendering() { - if (!this._hidden) { - this.focus(); - } - } - get rowSize() { return (this.secondaryCalendarType === CalendarType.Islamic && this.primaryCalendarType !== CalendarType.Islamic) || (this.secondaryCalendarType === CalendarType.Persian && this.primaryCalendarType !== CalendarType.Persian) ? 2 : 3; } + async _focusCorrectMonth() { + await renderFinished(); + if (this._shouldFocusMonth) { + this._focusableMonth.focus(); + } + } + + get _shouldFocusMonth() { + return document.activeElement !== this._focusableMonth; + } + + _onfocusin() { + this._focusCorrectMonth(); + } + _buildMonths() { if (this._hidden) { return; @@ -330,6 +344,21 @@ class MonthPicker extends CalendarPart implements ICalendarPicker { } } + /** + * Sets the focus reference to the month that was clicked with mousedown. + * @param e + * @private + */ + _onmousedown(e: MouseEvent) { + const target = e.target as HTMLElement; + const clickedItem = target.closest(".ui5-mp-item") as HTMLElement; + + if (clickedItem) { + const timestamp = this._getTimestampFromDom(clickedItem); + this._setTimestamp(timestamp); + } + } + /** * Modifies timestamp by a given amount of months and, * if necessary, loads the prev/next page. @@ -344,6 +373,8 @@ class MonthPicker extends CalendarPart implements ICalendarPicker { // Notify the calendar to update its timestamp this.fireDecoratorEvent("navigate", { timestamp: this.timestamp! }); + + this._focusCorrectMonth(); } _onkeyup(e: KeyboardEvent) { diff --git a/packages/main/src/MonthPickerTemplate.tsx b/packages/main/src/MonthPickerTemplate.tsx index d4a3a95e16af..435f9a9aebb2 100644 --- a/packages/main/src/MonthPickerTemplate.tsx +++ b/packages/main/src/MonthPickerTemplate.tsx @@ -13,6 +13,8 @@ export default function MonthPickerTemplate(this: MonthPicker) { onKeyDown={this._onkeydown} onKeyUp={this._onkeyup} onClick={this._selectMonth} + onMouseDown={this._onmousedown} + onFocusIn={this._onfocusin} > {this._monthsInterval.map(months =>
diff --git a/packages/main/src/YearPicker.ts b/packages/main/src/YearPicker.ts index d359cd24f711..7c97f1c373ae 100644 --- a/packages/main/src/YearPicker.ts +++ b/packages/main/src/YearPicker.ts @@ -1,7 +1,9 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import query from "@ui5/webcomponents-base/dist/decorators/query.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; +import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; import type LocaleT from "sap/ui/core/Locale"; import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js"; import { @@ -133,6 +135,9 @@ class YearPicker extends CalendarPart implements ICalendarPicker { _firstYear?: number; + @query("[data-sap-focus-ref]") + _focusableYear!: HTMLElement; + @i18n("@ui5/webcomponents") static i18nBundle: I18nBundle; @@ -140,6 +145,21 @@ class YearPicker extends CalendarPart implements ICalendarPicker { return YearPicker.i18nBundle.getText(YEAR_PICKER_DESCRIPTION); } + async _focusCorrectYear() { + await renderFinished(); + if (this._shouldFocusYear) { + this._focusableYear.focus(); + } + } + + get _shouldFocusYear() { + return document.activeElement !== this._focusableYear; + } + + _onfocusin() { + this._focusCorrectYear(); + } + onBeforeRendering() { if (this._hidden) { return; @@ -240,12 +260,6 @@ class YearPicker extends CalendarPart implements ICalendarPicker { this._yearsInterval = intervals; } - onAfterRendering() { - if (!this._hidden) { - this.focus(); - } - } - /** * Returns true if year timestamp is inside the selection range. * @private @@ -348,13 +362,16 @@ class YearPicker extends CalendarPart implements ICalendarPicker { * @param amount * @private */ - _modifyTimestampBy(amount: number) { + async _modifyTimestampBy(amount: number) { // Modify the current timestamp this._safelyModifyTimestampBy(amount, "year"); this._updateSecondTimestamp(); // Notify the calendar to update its timestamp this.fireDecoratorEvent("navigate", { timestamp: this.timestamp! }); + + await renderFinished(); + this._focusableYear.focus(); } _onkeyup(e: KeyboardEvent) { diff --git a/packages/main/src/YearRangePicker.ts b/packages/main/src/YearRangePicker.ts index 8802ae52eac6..1bff005f0986 100644 --- a/packages/main/src/YearRangePicker.ts +++ b/packages/main/src/YearRangePicker.ts @@ -1,7 +1,9 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import query from "@ui5/webcomponents-base/dist/decorators/query.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; +import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js"; import type LocaleT from "sap/ui/core/Locale"; import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js"; import { @@ -128,6 +130,9 @@ class YearRangePicker extends CalendarPart implements ICalendarPicker { _gridStartYear?: number; + @query("[data-sap-focus-ref]") + _focusableYearRange!: HTMLElement; + @i18n("@ui5/webcomponents") static i18nBundle: I18nBundle; @@ -135,6 +140,21 @@ class YearRangePicker extends CalendarPart implements ICalendarPicker { return YearRangePicker.i18nBundle.getText(YEAR_RANGE_PICKER_DESCRIPTION); } + async _focusCorrectYearRange() { + await renderFinished(); + if (this._shouldFocusYearRange) { + this._focusableYearRange.focus(); + } + } + + get _shouldFocusYearRange() { + return document.activeElement !== this._focusableYearRange; + } + + _onfocusin() { + this._focusCorrectYearRange(); + } + onBeforeRendering() { if (this._hidden) { return; @@ -313,12 +333,6 @@ class YearRangePicker extends CalendarPart implements ICalendarPicker { return isBetweenInclusive(timestamp, this.selectedDates[0], this.selectedDates[1]); } - onAfterRendering() { - if (!this._hidden) { - this.focus(); - } - } - _onkeydown(e: KeyboardEvent) { let preventDefault = true; const pageSize = this._getPageSize(); @@ -494,13 +508,16 @@ class YearRangePicker extends CalendarPart implements ICalendarPicker { * @param amount * @private */ - _modifyTimestampBy(amount: number) { + async _modifyTimestampBy(amount: number) { // Modify the current timestamp const amountInYears = amount * this._getRangeSize(); this._safelyModifyTimestampBy(amountInYears, "year"); // Notify the calendar to update its timestamp this.fireDecoratorEvent("navigate", { timestamp: this.timestamp! }); + + await renderFinished(); + this._focusableYearRange.focus(); } _modifyGridStartBy(years: number) {