Skip to content

Commit 1ab4e77

Browse files
authoredApr 28, 2021
fix(ui5-busyindicator): focus handling improvements (#3189)
Should fix #3171 as well. This has to be tested after a release is made.
1 parent 80dce9d commit 1ab4e77

File tree

6 files changed

+111
-17
lines changed

6 files changed

+111
-17
lines changed
 

‎packages/base/hash.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
52riaJNf3/W5XsbYKCniEmwezbc=
1+
VtcT3O0SUP+WFCXL1txQutIxxPY=

‎packages/base/src/util/FocusableElements.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const getLastFocusableElement = async container => {
2121
return findFocusableElement(container, false);
2222
};
2323

24+
const isElemFocusable = el => {
25+
return el.hasAttribute("data-ui5-focus-redirect") || !isNodeHidden(el);
26+
};
27+
2428
const findFocusableElement = async (container, forward) => {
2529
let child;
2630

@@ -48,7 +52,7 @@ const findFocusableElement = async (container, forward) => {
4852
return null;
4953
}
5054

51-
if (child.nodeType === 1 && !isNodeHidden(child) && !isFocusTrap(child)) {
55+
if (child.nodeType === 1 && isElemFocusable(child) && !isFocusTrap(child)) {
5256
if (isNodeClickable(child)) {
5357
return (child && typeof child.focus === "function") ? child : null;
5458
}

‎packages/main/src/BusyIndicator.hbs

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,9 @@
2323
</div>
2424
{{/if}}
2525

26-
<slot tabindex="{{slotTabIndex}}"></slot>
26+
<slot></slot>
27+
28+
{{#if active}}
29+
<span data-ui5-focus-redirect tabindex="0" @focusin="{{_redirectFocus}}"></span>
30+
{{/if}}
2731
</div>

‎packages/main/src/BusyIndicator.js

+32-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
22
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
33
import { isIE } from "@ui5/webcomponents-base/dist/Device.js";
44
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
5+
import { isTabNext } from "@ui5/webcomponents-base/dist/Keys.js";
56
import BusyIndicatorSize from "./types/BusyIndicatorSize.js";
67
import Label from "./Label.js";
78

@@ -127,22 +128,22 @@ class BusyIndicator extends UI5Element {
127128
super();
128129

129130
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
130-
this._preventHandler = this._preventEvent.bind(this);
131+
this._keydownHandler = this._handleKeydown.bind(this);
132+
this._preventEventHandler = this._preventEvent.bind(this);
131133
}
132134

133135
onEnterDOM() {
134-
this.addEventListener("keyup", this._preventHandler, {
136+
this.addEventListener("keydown", this._keydownHandler, {
135137
capture: true,
136138
});
137-
138-
this.addEventListener("keydown", this._preventHandler, {
139+
this.addEventListener("keyup", this._preventEventHandler, {
139140
capture: true,
140141
});
141142
}
142143

143144
onExitDOM() {
144-
this.removeEventListener("keyup", this._preventHandler, true);
145-
this.removeEventListener("keydown", this._preventHandler, true);
145+
this.removeEventListener("keydown", this._keydownHandler, true);
146+
this.removeEventListener("keyup", this._preventEventHandler, true);
146147
}
147148

148149
static get metadata() {
@@ -182,15 +183,38 @@ class BusyIndicator extends UI5Element {
182183
};
183184
}
184185

185-
get slotTabIndex() {
186-
return this.active ? -1 : 0;
186+
_handleKeydown(event) {
187+
if (!this.active) {
188+
return;
189+
}
190+
191+
event.stopImmediatePropagation();
192+
193+
// move the focus to the last element in this DOM and let TAB continue to the next focusable element
194+
if (isTabNext(event)) {
195+
this.focusForward = true;
196+
this.shadowRoot.querySelector("[data-ui5-focus-redirect]").focus();
197+
this.focusForward = false;
198+
}
187199
}
188200

189201
_preventEvent(event) {
190202
if (this.active) {
191203
event.stopImmediatePropagation();
192204
}
193205
}
206+
207+
/**
208+
* Moves the focus to busy area when coming with SHIFT + TAB
209+
*/
210+
_redirectFocus(event) {
211+
if (this.focusForward) {
212+
return;
213+
}
214+
215+
event.preventDefault();
216+
this.shadowRoot.querySelector(".ui5-busyindicator-busy-area").focus();
217+
}
194218
}
195219

196220
BusyIndicator.define();

‎packages/main/test/pages/BusyIndicator.html

+43-3
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,13 @@
4545
<br />
4646
<br />
4747

48-
<ui5-busyindicator id="indicatorWithBtn" size="Medium" active>
49-
<ui5-button>Hello World</ui5-button>
50-
</ui5-busyindicator>
48+
<div>
49+
<ui5-button id="beforeIndicatorWithBtn">focus stop before</ui5-button>
50+
<ui5-busyindicator id="indicatorWithBtn" size="Medium" active>
51+
<ui5-button>Hello World</ui5-button>
52+
</ui5-busyindicator>
53+
<ui5-button id="afterIndicatorWithBtn" >focus stop after</ui5-button>
54+
</div>
5155

5256
<br />
5357
<br />
@@ -139,6 +143,34 @@
139143

140144
<ui5-input value="0" id="tree-input"></ui5-input>
141145

146+
<br>
147+
<br>
148+
149+
<ui5-button id="open-dialog-inactive-indicator">Open Dialog with Inactive Indicator</ui5-button>
150+
<ui5-dialog id="dialog-inactive-indicator">
151+
<div slot="header">Dialog with focus issue</div>
152+
<div>
153+
<div>Buttons are wrapped in busy indicator, which is inactive.</div>
154+
<ui5-busyindicator>
155+
<ui5-button id="dialog-inactive-indicator-focused-button">button 1</ui5-button>
156+
<ui5-button>button 2</ui5-button>
157+
<ui5-button>button 3</ui5-button>
158+
</ui5-busyindicator>
159+
</div>
160+
</ui5-dialog>
161+
162+
<ui5-button id="open-dialog-active-indicator">Open Dialog with Active Indicator</ui5-button>
163+
<ui5-dialog id="dialog-active-indicator">
164+
<div slot="header">Dialog with focus issue</div>
165+
<div>
166+
<div>Buttons are wrapped in busy indicator, which is active.</div>
167+
<ui5-busyindicator active>
168+
<ui5-button>button 1</ui5-button>
169+
<ui5-button>button 2</ui5-button>
170+
<ui5-button>button 3</ui5-button>
171+
</ui5-busyindicator>
172+
</div>
173+
</ui5-dialog>
142174
<script>
143175
var busyIndocator = document.getElementById("busy-container");
144176
var list = document.getElementById("fetch-list");
@@ -189,6 +221,14 @@
189221
input.value = ++inputValue;
190222
}
191223
});
224+
225+
document.getElementById("open-dialog-inactive-indicator").addEventListener("click", function () {
226+
document.getElementById("dialog-inactive-indicator").open();
227+
});
228+
229+
document.getElementById("open-dialog-active-indicator").addEventListener("click", function () {
230+
document.getElementById("dialog-active-indicator").open();
231+
});
192232
</script>
193233
</body>
194234

‎packages/main/test/specs/BusyIndicator.spec.js

+25-3
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,32 @@ describe("BusyIndicator general interaction", () => {
4343
assert.strictEqual(innerFocusElement.getAttribute("aria-valuetext"), "Busy", "Correct 'aria-valuetext' is set");
4444
});
4545

46-
it("tests content is not reachable with keyboard when active", () => {
46+
it("tests content is not reachable with keyboard when active in both directions", () => {
47+
const beforeBusyIndicator = browser.$("#beforeIndicatorWithBtn");
4748
const busyIndicator = browser.$("#indicatorWithBtn");
48-
const defaultSLot = busyIndicator.shadow$("slot");
49+
const afterBusyIndicator = browser.$("#afterIndicatorWithBtn");
4950

50-
assert.strictEqual(defaultSLot.getAttribute("tabindex"), "-1", "Slot is not reachable via keyboard");
51+
beforeBusyIndicator.click();
52+
browser.keys("Tab");
53+
assert.strictEqual($(browser.getActiveElement()).getAttribute("id"), busyIndicator.getAttribute("id"), "Correct element is focused with TAB");
54+
55+
browser.keys("Tab");
56+
assert.strictEqual($(browser.getActiveElement()).getAttribute("id"), afterBusyIndicator.getAttribute("id"), "Correct element is focused with TAB");
57+
58+
browser.keys(["Shift", "Tab"]);
59+
assert.strictEqual($(browser.getActiveElement()).getAttribute("id"), busyIndicator.getAttribute("id"), "Correct element is focused with SHIFT + TAB");
60+
61+
browser.keys(["Shift", "Tab"]);
62+
assert.strictEqual($(browser.getActiveElement()).getAttribute("id"), beforeBusyIndicator.getAttribute("id"), "Correct element is focused with SHIFT + TAB");
63+
});
64+
65+
it("test inactive indicator in dialog - correct element from default slot is focused", () => {
66+
browser.$("#open-dialog-inactive-indicator").click();
67+
68+
assert.strictEqual(
69+
$(browser.getActiveElement()).getAttribute("id"),
70+
browser.$("#dialog-inactive-indicator-focused-button").getAttribute("id"),
71+
"Correct element from default slot is focused"
72+
);
5173
});
5274
});

0 commit comments

Comments
 (0)
Please sign in to comment.