diff --git a/packages/base/src/features/F6Navigation.ts b/packages/base/src/features/F6Navigation.ts
index b9b1aa188155..3f1831bf8cee 100644
--- a/packages/base/src/features/F6Navigation.ts
+++ b/packages/base/src/features/F6Navigation.ts
@@ -3,6 +3,7 @@ import { isF6Next, isF6Previous } from "../Keys.js";
import { instanceOfUI5Element } from "../UI5Element.js";
import { getFirstFocusableElement } from "../util/FocusableElements.js";
import getFastNavigationGroups from "../util/getFastNavigationGroups.js";
+import isElementClickable from "../util/isElementClickable.js";
class F6Navigation {
static _instance: F6Navigation;
@@ -19,56 +20,61 @@ class F6Navigation {
document.addEventListener("keydown", this.keydownHandler);
}
- async _keydownHandler(event: KeyboardEvent) {
- if (isF6Next(event)) {
- this.updateGroups();
+ async groupElementToFocus(nextElement: HTMLElement) {
+ const nextElementDomRef = instanceOfUI5Element(nextElement) ? nextElement.getDomRef() : nextElement;
- if (this.groups.length < 1) {
- return;
+ if (nextElementDomRef) {
+ if (isElementClickable(nextElementDomRef)) {
+ return nextElementDomRef;
}
- event.preventDefault();
+ const elementToFocus = await getFirstFocusableElement(nextElementDomRef);
- let nextIndex = -1;
- let nextElement;
- if (this.selectedGroup) {
- nextIndex = this.groups.indexOf(this.selectedGroup);
+ if (elementToFocus) {
+ return elementToFocus;
}
+ }
+ }
+
+ async findNextFocusableGroupElement(currentIndex: number) {
+ let elementToFocus;
- if (nextIndex > -1) {
- if (nextIndex + 1 >= this.groups.length) {
- nextElement = this.groups[0];
+ /* eslint-disable no-await-in-loop */
+ for (let index = 0; index < this.groups.length; index++) {
+ let nextElement;
+
+ if (currentIndex > -1) {
+ if (currentIndex + 1 >= this.groups.length) {
+ currentIndex = 0;
+ nextElement = this.groups[currentIndex];
} else {
- nextElement = this.groups[nextIndex + 1];
+ currentIndex += 1;
+ nextElement = this.groups[currentIndex];
}
} else {
- nextElement = this.groups[0];
+ currentIndex = 0;
+ nextElement = this.groups[currentIndex];
}
- const nextElementDomRef = instanceOfUI5Element(nextElement) ? nextElement.getDomRef() : nextElement;
+ elementToFocus = await this.groupElementToFocus(nextElement);
- if (nextElementDomRef) {
- const elementToFocus = await getFirstFocusableElement(nextElementDomRef, true);
- elementToFocus?.focus();
+ if (elementToFocus) {
+ break;
}
}
+ /* eslint-enable no-await-in-loop */
- if (isF6Previous(event)) {
- this.updateGroups();
-
- if (this.groups.length < 1) {
- return;
- }
+ return elementToFocus;
+ }
- event.preventDefault();
+ async findPreviousFocusableGroupElement(currentIndex: number) {
+ let elementToFocus;
- let nextIndex = -1;
+ /* eslint-disable no-await-in-loop */
+ for (let index = 0; index < this.groups.length; index++) {
let nextElement;
- if (this.selectedGroup) {
- nextIndex = this.groups.indexOf(this.selectedGroup);
- }
- if (nextIndex > 0) {
+ if (currentIndex > 0) {
// Handle the situation where the first focusable element of two neighbor groups is the same
// For example:
//
@@ -77,22 +83,70 @@ class F6Navigation {
//
//
// Here for both FCL & List the firstFoccusableElement is the same (the ui5-li)
+ const firstFocusable = await this.groupElementToFocus(this.groups[currentIndex - 1]);
+ const shouldSkipParent = firstFocusable === await this.groupElementToFocus(this.groups[currentIndex]);
- const firstFocusable = await getFirstFocusableElement(this.groups[nextIndex - 1], true);
- const shouldSkipParent = firstFocusable === await getFirstFocusableElement(this.groups[nextIndex], true);
+ currentIndex = shouldSkipParent ? currentIndex - 2 : currentIndex - 1;
- nextElement = this.groups[shouldSkipParent ? nextIndex - 2 : nextIndex - 1];
+ if (currentIndex < 0) {
+ currentIndex = this.groups.length - 1;
+ }
+
+ nextElement = this.groups[currentIndex];
} else {
- nextElement = this.groups[this.groups.length - 1];
+ currentIndex = this.groups.length - 1;
+ nextElement = this.groups[currentIndex];
}
- const nextElementDomRef = instanceOfUI5Element(nextElement) ? nextElement.getDomRef() : nextElement;
+ elementToFocus = await this.groupElementToFocus(nextElement);
- if (nextElementDomRef) {
- const elementToFocus = await getFirstFocusableElement(nextElementDomRef, true);
- elementToFocus?.focus();
+ if (elementToFocus) {
+ break;
}
}
+ /* eslint-enable no-await-in-loop */
+
+ return elementToFocus;
+ }
+
+ async _keydownHandler(event: KeyboardEvent) {
+ const forward = isF6Next(event);
+ const backward = isF6Previous(event);
+ if (!(forward || backward)) {
+ return;
+ }
+
+ this.updateGroups();
+
+ if (this.groups.length < 1) {
+ return;
+ }
+
+ event.preventDefault();
+
+ let elementToFocus;
+
+ if (this.groups.length === 0) {
+ elementToFocus = await this.groupElementToFocus(this.groups[0]);
+
+ return elementToFocus?.focus();
+ }
+
+ let currentIndex = -1;
+
+ if (this.selectedGroup) {
+ currentIndex = this.groups.indexOf(this.selectedGroup);
+ }
+
+ if (forward) {
+ elementToFocus = await this.findNextFocusableGroupElement(currentIndex);
+ }
+
+ if (backward) {
+ elementToFocus = await this.findPreviousFocusableGroupElement(currentIndex);
+ }
+
+ elementToFocus?.focus();
}
removeEventListeners() {
@@ -109,19 +163,19 @@ class F6Navigation {
let element: Element | null | ParentNode = this.deepActive(root);
while (element && (element as Element).getAttribute("data-sap-ui-fastnavgroup") !== "true" && element !== htmlElement) {
- element = element.parentElement ? element.parentNode : (element.parentNode as ShadowRoot).host;
+ element = element.parentElement ? element.parentNode : (element.parentNode as ShadowRoot).host;
}
this.selectedGroup = element as HTMLElement;
- }
+ }
- deepActive(root: DocumentOrShadowRoot): Element | null {
+ deepActive(root: DocumentOrShadowRoot): Element | null {
if (root.activeElement && root.activeElement.shadowRoot) {
- return this.deepActive(root.activeElement.shadowRoot);
+ return this.deepActive(root.activeElement.shadowRoot);
}
return root.activeElement;
- }
+ }
destroy() {
this.removeEventListeners();
diff --git a/packages/main/test/pages/F6Test1.html b/packages/main/test/pages/F6Test1.html
new file mode 100644
index 000000000000..bdf2744dde14
--- /dev/null
+++ b/packages/main/test/pages/F6Test1.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+
+
+
+
+
+ Before element
+
+
+ First focusable
+
+
+ Something focusable
+
+
+ Second focusable
+
+
+ Something focusable
+
+
+ Third focusable
+
+
+ After Element
+
+
+
+
\ No newline at end of file
diff --git a/packages/main/test/pages/F6Test2.html b/packages/main/test/pages/F6Test2.html
new file mode 100644
index 000000000000..6b7887ffee99
--- /dev/null
+++ b/packages/main/test/pages/F6Test2.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+
+
+
+
+
+ Before element
+
+
+ First focusable
+
+
+ Something focusable
+
+
+ Group without focusable element
+
+
+ Something focusable
+
+
+ Second focusable
+
+
+ After Element
+
+
+
+
\ No newline at end of file
diff --git a/packages/main/test/pages/F6Test3.html b/packages/main/test/pages/F6Test3.html
new file mode 100644
index 000000000000..422926fca16e
--- /dev/null
+++ b/packages/main/test/pages/F6Test3.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+
+
+
+
+
+ Before element
+
+
+
First focusable
+
+ Second focusable
+
+
+
+ Something focusable
+
+
+ Third focusable
+
+
+ After Element
+
+
+
+
\ No newline at end of file
diff --git a/packages/main/test/pages/F6Test4.html b/packages/main/test/pages/F6Test4.html
new file mode 100644
index 000000000000..1e9e280dc0aa
--- /dev/null
+++ b/packages/main/test/pages/F6Test4.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+
+
+
+
+
+ Before element
+
+
+
+ Something focusable
+
+
+
+ Something focusable
+
+
+ Second focusable
+
+
+ After Element
+
+
+
+
\ No newline at end of file
diff --git a/packages/main/test/pages/F6Test5.html b/packages/main/test/pages/F6Test5.html
new file mode 100644
index 000000000000..67780f36e47a
--- /dev/null
+++ b/packages/main/test/pages/F6Test5.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+
+
+
+
+
+ Before element
+
+
+ First focusable
+
+
+ Something focusable
+
+
+ Second focusable
+
+
+ Something focusable
+
+
+ Third focusable
+
+
+ After Element
+
+
+
+
\ No newline at end of file
diff --git a/packages/main/test/pages/F6Test6.html b/packages/main/test/pages/F6Test6.html
new file mode 100644
index 000000000000..818d4ec2bb47
--- /dev/null
+++ b/packages/main/test/pages/F6Test6.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+
+
+
+
+
+ Before element
+
+
+ Group without focusable element
+
+
+ Something focusable
+
+
+ Group without focusable element
+
+
+ Something focusable
+
+
+ After Element
+
+
+
+
\ No newline at end of file
diff --git a/packages/main/test/pages/F6Test7.html b/packages/main/test/pages/F6Test7.html
new file mode 100644
index 000000000000..40cafaa5f27a
--- /dev/null
+++ b/packages/main/test/pages/F6Test7.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+
+
+
+
+
+ Before element
+
+
+ Before element
+
+
+ Something focusable
+
+
+ Something focusable
+
+
+ After Element
+
+
+
+
\ No newline at end of file
diff --git a/packages/main/test/pages/Test.html b/packages/main/test/pages/Test.html
new file mode 100644
index 000000000000..47e56cd78d72
--- /dev/null
+++ b/packages/main/test/pages/Test.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Table
+
+
+
+
+
+
+
+
+
diff --git a/packages/main/test/pages/styles/F6Test.css b/packages/main/test/pages/styles/F6Test.css
new file mode 100644
index 000000000000..f1d68ece0032
--- /dev/null
+++ b/packages/main/test/pages/styles/F6Test.css
@@ -0,0 +1,9 @@
+.section {
+ margin: 10px 0;
+ padding: 10px;
+ border: 1px solid red;
+}
+[data-sap-ui-fastnavgroup] {
+ border-width: 3px;
+ border-color: green;
+}
\ No newline at end of file
diff --git a/packages/main/test/specs/F6Test.spec.js b/packages/main/test/specs/F6Test.spec.js
new file mode 100644
index 000000000000..55480570e61f
--- /dev/null
+++ b/packages/main/test/specs/F6Test.spec.js
@@ -0,0 +1,242 @@
+const assert = require("chai").assert;
+
+
+describe("F6 Test", () => {
+ describe("Forward", () => {
+ it("Basic", async () => {
+ await browser.url(`test/pages/F6Test1.html`);
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("Basic with an empty group", async () => {
+ await browser.url(`test/pages/F6Test2.html`);
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("Nested groups", async () => {
+ await browser.url(`test/pages/F6Test3.html`);
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("Nested inside empty fastnav-group parent", async () => {
+ await browser.url(`test/pages/F6Test4.html`);
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct ");
+ });
+
+ it("Basic with group as focusable element", async () => {
+ await browser.url(`test/pages/F6Test5.html`);
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("Groups without focusable element", async () => {
+ await browser.url(`test/pages/F6Test6.html`);
+
+ const button = await browser.$("#first");
+
+ await button.click();
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("One group", async () => {
+ await browser.url(`test/pages/F6Test7.html`);
+
+ // Go to next group
+ await browser.keys("F6");
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+ })
+
+ describe("Backward", () => {
+ it("Basic", async () => {
+ await browser.url(`test/pages/F6Test1.html`);
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("Basic with an empty group", async () => {
+ await browser.url(`test/pages/F6Test2.html`);
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("Nested groups", async () => {
+ await browser.url(`test/pages/F6Test3.html`);
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("Nested inside empty fastnav-group parent", async () => {
+ await browser.url(`test/pages/F6Test4.html`);
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct ");
+ });
+
+ it("Basic with group as focusable element", async () => {
+ await browser.url(`test/pages/F6Test5.html`);
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to first group (circle)
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("Groups without focusable element", async () => {
+ await browser.url(`test/pages/F6Test6.html`);
+
+ const button = await browser.$("#first");
+
+ await button.click();
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+
+ it("One group", async () => {
+ await browser.url(`test/pages/F6Test7.html`);
+
+ // Go to next group
+ await browser.keys(["Shift", "F6"]);
+ assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus");
+ });
+ })
+});