Skip to content

Commit 15e915f

Browse files
authored
feat(ui5-datepicker): implement keyboard handling (#1706)
1 parent 14add07 commit 15e915f

File tree

8 files changed

+87
-14
lines changed

8 files changed

+87
-14
lines changed

packages/base/src/Keys.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -131,17 +131,25 @@ const isDelete = event => (event.key ? event.key === "Delete" : event.keyCode ==
131131

132132
const isShow = event => {
133133
if (event.key) {
134-
return (event.key === "F4" && !hasModifierKeys(event)) || (((event.key === "ArrowDown" || event.key === "Down") || (event.key === "ArrowUp" || event.key === "Up")) && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false));
134+
return isF4(event) || isShowByArrows(event);
135135
}
136136

137137
return (event.keyCode === KeyCodes.F4 && !hasModifierKeys(event)) || (event.keyCode === KeyCodes.ARROW_DOWN && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false));
138138
};
139139

140+
const isF4 = event => {
141+
return event.key === "F4" && !hasModifierKeys(event);
142+
};
143+
144+
const isShowByArrows = event => {
145+
return ((event.key === "ArrowDown" || event.key === "Down") || (event.key === "ArrowUp" || event.key === "Up")) && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false);
146+
};
147+
140148
const hasModifierKeys = event => event.shiftKey || event.altKey || getCtrlKey(event);
141149

142150
const getCtrlKey = event => !!(event.metaKey || event.ctrlKey); // double negation doesn't have effect on boolean but ensures null and undefined are equivalent to false.
143151

144-
const checkModifierKeys = (oEvent, bCtrlKey, bAltKey, bShiftKey) => oEvent.shiftKey === bShiftKey && oEvent.altKey === bAltKey && getCtrlKey(oEvent) === bCtrlKey;
152+
const checkModifierKeys = (event, bCtrlKey, bAltKey, bShiftKey) => event.shiftKey === bShiftKey && event.altKey === bAltKey && getCtrlKey(event) === bCtrlKey;
145153

146154
export {
147155
isEnter,
@@ -158,4 +166,5 @@ export {
158166
isBackSpace,
159167
isDelete,
160168
isShow,
169+
isF4,
161170
};

packages/base/src/delegate/ItemNavigation.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class ItemNavigation extends EventProvider {
2727
this.horizontalNavigationOn = autoNavigation || navigationMode === NavigationMode.Horizontal;
2828
this.verticalNavigationOn = autoNavigation || navigationMode === NavigationMode.Vertical;
2929

30+
this.pageSize = options.pageSize;
31+
3032
this.rootWebComponent = rootWebComponent;
3133
this.rootWebComponent.addEventListener("keydown", this.onkeydown.bind(this));
3234
this.rootWebComponent._onComponentStateFinalized = () => {
@@ -192,6 +194,10 @@ class ItemNavigation extends EventProvider {
192194

193195
const currentItem = items[this.currentIndex];
194196

197+
if (!currentItem) {
198+
return;
199+
}
200+
195201
if (currentItem.isUI5Element) {
196202
return currentItem.getFocusDomRef();
197203
}
@@ -254,18 +260,17 @@ class ItemNavigation extends EventProvider {
254260
if (!this.hasNextPage) {
255261
this.currentIndex = items.length - 1;
256262
} else {
257-
this.currentIndex = 0;
263+
this.currentIndex -= this.pageSize;
258264
}
259265
}
260266

261267
_handlePrevPage() {
262268
this.fireEvent(ItemNavigation.PAGE_TOP);
263-
const items = this._getItems();
264269

265270
if (!this.hasPrevPage) {
266271
this.currentIndex = 0;
267272
} else {
268-
this.currentIndex = (this.pageSize || items.length) - 1;
273+
this.currentIndex = this.pageSize + this.currentIndex;
269274
}
270275
}
271276
}

packages/base/src/types/NavigationMode.js

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ const NavigationMode = {
22
Auto: "Auto",
33
Vertical: "Vertical",
44
Horizontal: "Horizontal",
5+
Paging: "Paging",
56
};
67
export default NavigationMode;

packages/main/src/DatePicker.js

+24-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js";
99
import CalendarType from "@ui5/webcomponents-base/dist/types/CalendarType.js";
1010
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
1111
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
12-
import { isShow } from "@ui5/webcomponents-base/dist/Keys.js";
12+
import { isShow, isF4 } from "@ui5/webcomponents-base/dist/Keys.js";
1313
import { isPhone } from "@ui5/webcomponents-base/dist/Device.js";
1414
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
1515
import "@ui5/webcomponents-icons/dist/icons/appointment-2.js";
@@ -335,14 +335,14 @@ class DatePicker extends UI5Element {
335335
this._focusInputAfterClose = false;
336336
}
337337

338-
const calendar = this.responsivePopover.querySelector(`#${this._id}-calendar`);
338+
const calendar = this.calendar;
339339
if (calendar) {
340340
calendar._hideMonthPicker();
341341
calendar._hideYearPicker();
342342
}
343343
},
344344
afterOpen: () => {
345-
const calendar = this.responsivePopover.querySelector(`#${this._id}-calendar`);
345+
const calendar = this.calendar;
346346

347347
if (!calendar) {
348348
return;
@@ -435,11 +435,26 @@ class DatePicker extends UI5Element {
435435

436436
_onkeydown(event) {
437437
if (isShow(event)) {
438-
this.togglePicker();
439-
this._getInput().focus();
438+
event.preventDefault(); // Prevent scroll on Alt/Option + Arrow Up/Down
439+
if (this.isOpen()) {
440+
if (isF4(event)) {
441+
if (this.calendar._monthPicker._hidden) {
442+
this.calendar._showYearPicker();
443+
}
444+
} else {
445+
this._toggleAndFocusInput();
446+
}
447+
} else {
448+
this._toggleAndFocusInput();
449+
}
440450
}
441451
}
442452

453+
_toggleAndFocusInput() {
454+
this.togglePicker();
455+
this._getInput().focus();
456+
}
457+
443458
_getInput() {
444459
return this.shadowRoot.querySelector("ui5-input");
445460
}
@@ -539,6 +554,10 @@ class DatePicker extends UI5Element {
539554
return this.getFormat().format(new Date());
540555
}
541556

557+
get calendar() {
558+
return this.responsivePopover.querySelector(`#${this._id}-calendar`);
559+
}
560+
542561
get _calendarDate() {
543562
const millisecondsUTC = this.getFormat().parse(this.validValue, true).getTime();
544563
const oCalDate = CalendarDate.fromTimestamp(

packages/main/src/DatePickerPopover.hbs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
with-padding
99
no-stretch
1010
?_hide-header={{_shouldHideHeader}}
11+
@keydown="{{_onkeydown}}"
1112
@ui5-after-close="{{_respPopoverConfig.afterClose}}"
1213
@ui5-after-open="{{_respPopoverConfig.afterOpen}}"
1314
>

packages/main/src/MonthPicker.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,12 @@ class MonthPicker extends UI5Element {
129129
this._oLocale = getLocale();
130130
this._oLocaleData = new LocaleData(this._oLocale);
131131

132-
this._itemNav = new ItemNavigation(this, { rowSize: 3, behavior: ItemNavigationBehavior.Cyclic });
132+
this._itemNav = new ItemNavigation(this, {
133+
pageSize: 12,
134+
rowSize: 3,
135+
behavior: ItemNavigationBehavior.Paging,
136+
});
137+
133138
this._itemNav.getItemsCallback = function getItemsCallback() {
134139
const focusableMonths = [];
135140

packages/main/src/YearPicker.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import getLocale from "@ui5/webcomponents-base/dist/locale/getLocale.js";
99
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
1010
import CalendarType from "@ui5/webcomponents-base/dist/types/CalendarType.js";
1111
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
12+
import ItemNavigationBehavior from "@ui5/webcomponents-base/dist/types/ItemNavigationBehavior.js";
1213
import YearPickerTemplate from "./generated/templates/YearPickerTemplate.lit.js";
1314

1415
// Styles
@@ -133,7 +134,12 @@ class YearPicker extends UI5Element {
133134

134135
this._oLocale = getLocale();
135136

136-
this._itemNav = new ItemNavigation(this, { rowSize: 4 });
137+
this._itemNav = new ItemNavigation(this, {
138+
pageSize: 20,
139+
rowSize: 4,
140+
behavior: ItemNavigationBehavior.Paging,
141+
});
142+
137143
this._itemNav.getItemsCallback = function getItemsCallback() {
138144
const focusableYears = [];
139145

packages/main/test/specs/DatePicker.spec.js

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
const datepicker = require("../pageobjects/DatePickerTestPage");
32
const assert = require("chai").assert;
43

@@ -325,6 +324,10 @@ describe("Date Picker Tests", () => {
325324
browser.keys(["Alt", "ArrowUp", "NULL"]);
326325

327326
assert.ok(datepicker.isPickerOpen(), "datepicker is open");
327+
328+
browser.keys(["Alt", "ArrowUp", "NULL"]);
329+
330+
assert.notOk(datepicker.isPickerOpen(), "datepicker is closed");
328331
});
329332

330333
it("[Alt] + [DOWN] toggles the calendar", () => {
@@ -336,6 +339,31 @@ describe("Date Picker Tests", () => {
336339
browser.keys(["Alt", "ArrowDown", "NULL"]);
337340

338341
assert.ok(datepicker.isPickerOpen(), "datepicker is open");
342+
343+
browser.keys(["Alt", "ArrowDown", "NULL"]);
344+
345+
assert.notOk(datepicker.isPickerOpen(), "datepicker is closed");
346+
});
347+
348+
it("[F4] shows year picker after date picker is open", () => {
349+
datepicker.id = "#dp11";
350+
351+
datepicker.valueHelpIcon.click()
352+
browser.keys("F4");
353+
354+
assert.notOk(datepicker.calendar.getProperty("_yearPicker")._hidden, "Year picker is open");
355+
datepicker.valueHelpIcon.click(); // close the datepicker
356+
});
357+
358+
it("[F4] on year picker doesn't close the date picker", () => {
359+
datepicker.id = "#dp11";
360+
361+
datepicker.valueHelpIcon.click();
362+
browser.keys("F4");
363+
364+
browser.keys("F4");
365+
366+
assert.ok(datepicker.isPickerOpen, "Datepicker remains open");
339367
});
340368

341369
it("daypicker extreme values max", () => {
@@ -631,7 +659,6 @@ describe("Date Picker Tests", () => {
631659
datepicker.root.keys("ArrowRight");
632660
datepicker.root.keys("ArrowRight");
633661
datepicker.root.keys("ArrowRight");
634-
datepicker.root.keys("ArrowDown");
635662
assert.ok(datepicker.getDisplayedYear(7).isFocusedDeep(), "Years out of range can not be reached with keyboard");
636663
});
637664

0 commit comments

Comments
 (0)