Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui5-carousel): Implement F7 keyboard functionality #3559

Merged
merged 12 commits into from
Aug 5, 2021
3 changes: 3 additions & 0 deletions packages/base/src/Keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ const isF4 = event => {

const isF4Shift = event => (event.key ? event.key === "F4" : event.keyCode === KeyCodes.F4) && checkModifierKeys(event, false, false, true);

const isF7 = event => (event.key ? event.key === "F7" : event.keyCode === KeyCodes.F7) && !hasModifierKeys(event);

const isShowByArrows = event => {
return ((event.key === "ArrowDown" || event.key === "Down") || (event.key === "ArrowUp" || event.key === "Up")) && checkModifierKeys(event, /* Ctrl */ false, /* Alt */ true, /* Shift */ false);
};
Expand Down Expand Up @@ -230,6 +232,7 @@ export {
isShow,
isF4,
isF4Shift,
isF7,
isPageUp,
isPageDown,
isPageUpShift,
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/Carousel.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
tabindex="0"
role="listbox"
aria-activedescendant="{{ariaActiveDescendant}}"
@focusin="{{_onfocusin}}"
@keydown={{_onkeydown}}
@mouseout="{{_onmouseout}}"
@mouseover="{{_onmouseover}}"
Expand Down
67 changes: 66 additions & 1 deletion packages/main/src/Carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {
isRight,
isDown,
isUp,
isF7,
} from "@ui5/webcomponents-base/dist/Keys.js";
import {
fetchI18nBundle,
getI18nBundle,
} from "@ui5/webcomponents-base/dist/i18nBundle.js";
import ScrollEnablement from "@ui5/webcomponents-base/dist/delegate/ScrollEnablement.js";
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js";
import { isDesktop } from "@ui5/webcomponents-base/dist/Device.js";
import AnimationMode from "@ui5/webcomponents-base/dist/types/AnimationMode.js";
import { getAnimationMode } from "@ui5/webcomponents-base/dist/config/AnimationMode.js";
Expand Down Expand Up @@ -285,6 +287,9 @@ class Carousel extends UI5Element {
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
this._onResizeBound = this._onResize.bind(this);
this._resizing = false; // indicates if the carousel is in process of resizing

this._lastFocusedElements = [];
this._orderOfLastFocusedPages = [];
}

onBeforeRendering() {
Expand Down Expand Up @@ -348,15 +353,53 @@ class Carousel extends UI5Element {
}
}

_onkeydown(event) {
async _onkeydown(event) {
if (isF7(event)) {
this._handleF7Key(event);
return;
}

if (event.target !== this.getDomRef()) {
return;
}

if (isLeft(event) || isDown(event)) {
this.navigateLeft();
await renderFinished();
this.getDomRef().focus();
} else if (isRight(event) || isUp(event)) {
this.navigateRight();
await renderFinished();
this.getDomRef().focus();
}
}

_onfocusin(event) {
if (event.target === this.getDomRef()) {
return;
}

let pageIndex = -1;

for (let i = 0; i < this.content.length; i++) {
if (this.content[i].contains(event.target)) {
pageIndex = i;
break;
}
}

if (pageIndex === -1) {
return;
}

// Save reference of the last focused element for each page
this._lastFocusedElements[pageIndex] = event.target;

const sortedPageIndex = this._orderOfLastFocusedPages.indexOf(pageIndex);
if (sortedPageIndex === -1) {
this._orderOfLastFocusedPages.unshift(pageIndex);
} else {
this._orderOfLastFocusedPages.splice(0, 0, this._orderOfLastFocusedPages.splice(sortedPageIndex, 1)[0]);
}
}

Expand All @@ -372,6 +415,28 @@ class Carousel extends UI5Element {
}
}

_handleF7Key(event) {
const lastFocusedElement = this._lastFocusedElements[this._getLastFocusedActivePageIndex];

if (event.target === this.getDomRef() && lastFocusedElement) {
lastFocusedElement.focus();
} else {
this.getDomRef().focus();
}
}

get _getLastFocusedActivePageIndex() {
for (let i = 0; i < this._orderOfLastFocusedPages.length; i++) {
const pageIndex = this._orderOfLastFocusedPages[i];

if (this.isItemInViewport(pageIndex)) {
return pageIndex;
}
}

return this._selectedIndex;
}

navigateLeft() {
this._resizing = false;

Expand Down
65 changes: 64 additions & 1 deletion packages/main/test/pages/Carousel.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
height: 100%;
}
</style>

<ui5-carousel id="carouselCards" items-per-page-s="3" items-per-page-m="3" items-per-page-l="3">
<ui5-card id="card"
class="myCard">
Expand Down Expand Up @@ -475,6 +474,70 @@
<ui5-button>Button 3</ui5-button>
</ui5-carousel>

<ui5-title style="margin-top: 2rem;">F7 keyboard navigation testing</ui5-title>
<ui5-carousel id="carouselF7" items-per-page-s="3" items-per-page-m="3" items-per-page-l="3">
<ui5-card class="myCard">
<div>
Page 1 <br />
<ui5-button>Button 1</ui5-button>
<br />
<ui5-button id="carouselF7Button">Button 2</ui5-button>
<br />
<ui5-input></ui5-input>
</div>
</ui5-card>
<ui5-card class="myCard">
<div>
Page 2 <br />
<ui5-button>Button 1</ui5-button>
<br />
<ui5-button>Button 2</ui5-button>
<br />
<ui5-input></ui5-input>
</div>
</ui5-card>
<ui5-card class="myCard">
<div>
Page 3 <br />
<ui5-button>Button 1</ui5-button>
<br />
<ui5-button>Button 2</ui5-button>
<br />
<ui5-input id="carouselF7Input"></ui5-input>
</div>
</ui5-card>
<ui5-card class="myCard">
<div>
Page 4 <br />
<ui5-button>Button 1</ui5-button>
<br />
<ui5-button>Button 2</ui5-button>
<br />
<ui5-input></ui5-input>
</div>
</ui5-card>
<ui5-card class="myCard">
<div>
Page 5 <br />
<ui5-button>Button 1</ui5-button>
<br />
<ui5-button>Button 2</ui5-button>
<br />
<ui5-input></ui5-input>
</div>
</ui5-card>
<ui5-card class="myCard">
<div>
Page 6 <br />
<ui5-button>Button 1</ui5-button>
<br />
<ui5-button>Button 2</ui5-button>
<br />
<ui5-input></ui5-input>
</div>
</ui5-card>
</ui5-carousel>

</body>

<script>
Expand Down
64 changes: 61 additions & 3 deletions packages/main/test/specs/Carousel.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ describe("Carousel general interaction", () => {
carousel.scrollIntoView();

assert.strictEqual(carousel.shadow$$(".ui5-carousel-navigation > *").length, 0, "carousel has not rendered a page indicator")
})
});

it("navigateTo method and visibleItemsIndices", () => {
const carousel = browser.$("#carousel9");
Expand All @@ -203,8 +203,66 @@ describe("Carousel general interaction", () => {

browser.execute(() => {
document.getElementById("carousel9").navigateTo(1);
})
});

assert.deepEqual(carousel.getProperty("visibleItemsIndices"), [ 1, 2 ], "The indices after navigation are correct.");
})
});

it("F7 keyboard navigation", () => {
const carousel = browser.$("#carouselF7");
const button = browser.$("#carouselF7Button");
const input = browser.$("#carouselF7Input");
carousel.scrollIntoView();

button.click();

browser.keys("F7");

let innerFocusedElement = browser.execute(() => {
return document.getElementById("carouselF7").shadowRoot.activeElement;
});

assert.ok($(innerFocusedElement).hasClass("ui5-carousel-root"), "Carousel is focused");

browser.keys("F7");

innerFocusedElement = browser.execute(() => {
return document.getElementById("carouselF7Button").shadowRoot.activeElement;
});

assert.ok($(innerFocusedElement).hasClass("ui5-button-root"), "Button is focused");

input.click();

browser.keys("F7");

innerFocusedElement = browser.execute(() => {
return document.getElementById("carouselF7").shadowRoot.activeElement;
});

assert.ok($(innerFocusedElement).hasClass("ui5-carousel-root"), "Carousel is focused");

browser.keys("F7");

innerFocusedElement = browser.execute(() => {
return document.getElementById("carouselF7Input").shadowRoot.activeElement;
});

assert.ok($(innerFocusedElement).hasClass("ui5-input-inner"), "Input is focused");

button.click();
browser.keys("F7");

browser.execute(() => {
document.getElementById("carouselF7").navigateTo(1);
});

browser.keys("F7");

innerFocusedElement = browser.execute(() => {
return document.getElementById("carouselF7Input").shadowRoot.activeElement;
});

assert.ok($(innerFocusedElement).hasClass("ui5-input-inner"), "Input is focused");
});
});