Skip to content

Commit dba0265

Browse files
fix(ui5-dialog): apply initial focus after rendering (#2551)
Now the initial focus is applied also in cases when the dialog is open before it is fully rendered. Fixes: #2537
1 parent d40ae7c commit dba0265

File tree

5 files changed

+67
-13
lines changed

5 files changed

+67
-13
lines changed

packages/base/src/UI5Element.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,16 @@ class UI5Element extends HTMLElement {
638638
}
639639
}
640640

641+
/**
642+
* Waits for dom ref and then returns the DOM Element marked with "data-sap-focus-ref" inside the template.
643+
* This is the element that will receive the focus by default.
644+
* @public
645+
*/
646+
async getFocusDomRefAsync() {
647+
await this._waitForDomRef();
648+
return this.getFocusDomRef();
649+
}
650+
641651
/**
642652
* Use this method in order to get a reference to an element in the shadow root of the web component or the static area item of the component
643653
* @public

packages/base/src/util/FocusableElements.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ const isFocusTrap = el => {
55
return el.hasAttribute("data-ui5-focus-trap");
66
};
77

8-
const getFirstFocusableElement = container => {
8+
const getFirstFocusableElement = async container => {
99
if (!container || isNodeHidden(container)) {
1010
return null;
1111
}
1212

1313
return findFocusableElement(container, true);
1414
};
1515

16-
const getLastFocusableElement = container => {
16+
const getLastFocusableElement = async container => {
1717
if (!container || isNodeHidden(container)) {
1818
return null;
1919
}
2020

2121
return findFocusableElement(container, false);
2222
};
2323

24-
const findFocusableElement = (container, forward) => {
24+
const findFocusableElement = async (container, forward) => {
2525
let child;
2626

2727
if (container.shadowRoot) {
@@ -35,10 +35,15 @@ const findFocusableElement = (container, forward) => {
3535

3636
let focusableDescendant;
3737

38+
/* eslint-disable no-await-in-loop */
39+
3840
while (child) {
3941
const originalChild = child;
4042

41-
child = child.isUI5Element ? child.getFocusDomRef() : child;
43+
if (child.isUI5Element) {
44+
child = await child.getFocusDomRefAsync();
45+
}
46+
4247
if (!child) {
4348
return null;
4449
}
@@ -48,7 +53,7 @@ const findFocusableElement = (container, forward) => {
4853
return (child && typeof child.focus === "function") ? child : null;
4954
}
5055

51-
focusableDescendant = findFocusableElement(child, forward);
56+
focusableDescendant = await findFocusableElement(child, forward);
5257
if (focusableDescendant) {
5358
return (focusableDescendant && typeof focusableDescendant.focus === "function") ? focusableDescendant : null;
5459
}
@@ -57,6 +62,8 @@ const findFocusableElement = (container, forward) => {
5762
child = forward ? originalChild.nextSibling : originalChild.previousSibling;
5863
}
5964

65+
/* eslint-enable no-await-in-loop */
66+
6067
return null;
6168
};
6269

packages/main/src/Popup.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ class Popup extends UI5Element {
246246
* Focus trapping
247247
* @private
248248
*/
249-
forwardToFirst() {
250-
const firstFocusable = getFirstFocusableElement(this);
249+
async forwardToFirst() {
250+
const firstFocusable = await getFirstFocusableElement(this);
251251

252252
if (firstFocusable) {
253253
firstFocusable.focus();
@@ -258,8 +258,8 @@ class Popup extends UI5Element {
258258
* Focus trapping
259259
* @private
260260
*/
261-
forwardToLast() {
262-
const lastFocusable = getLastFocusableElement(this);
261+
async forwardToLast() {
262+
const lastFocusable = await getLastFocusableElement(this);
263263

264264
if (lastFocusable) {
265265
lastFocusable.focus();
@@ -270,19 +270,21 @@ class Popup extends UI5Element {
270270
* Use this method to focus the element denoted by "initialFocus", if provided, or the first focusable element otherwise.
271271
* @protected
272272
*/
273-
applyInitialFocus() {
274-
this.applyFocus();
273+
async applyInitialFocus() {
274+
await this.applyFocus();
275275
}
276276

277277
/**
278278
* Focuses the element denoted by <code>initialFocus</code>, if provided,
279279
* or the first focusable element otherwise.
280280
* @public
281281
*/
282-
applyFocus() {
282+
async applyFocus() {
283+
await this._waitForDomRef();
284+
283285
const element = this.getRootNode().getElementById(this.initialFocus)
284286
|| document.getElementById(this.initialFocus)
285-
|| getFirstFocusableElement(this);
287+
|| await getFirstFocusableElement(this);
286288

287289
if (element) {
288290
element.focus();

packages/main/test/pages/Dialog.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
<br>
5151
<br>
5252
<ui5-button id="resizable-open">Open resizable dialog</ui5-button>
53+
<br>
54+
<br>
55+
<ui5-button id="dynamic-open">Open dialog which is created dynamically</ui5-button>
5356

5457
<ui5-block-layer></ui5-block-layer>
5558

@@ -330,6 +333,24 @@
330333
window["draggable-close"].addEventListener("click", function () { window["draggable-dialog"].close(); });
331334
window["resizable-open"].addEventListener("click", function () { window["resizable-dialog"].open(); });
332335
window["resizable-close"].addEventListener("click", function () { window["resizable-dialog"].close(); });
336+
337+
window["dynamic-open"].addEventListener("click", function () {
338+
var dialog = document.createElement("ui5-dialog"),
339+
button = document.createElement("ui5-button");
340+
341+
button.setAttribute("id", "dynamic-dialog-close-button");
342+
button.appendChild(document.createTextNode("Close"));
343+
button.addEventListener("click", function () {
344+
dialog.close();
345+
});
346+
347+
dialog.setAttribute("id", "dynamic-dialog");
348+
dialog.appendChild(button);
349+
350+
document.body.appendChild(dialog);
351+
352+
dialog.open();
353+
});
333354
</script>
334355
</body>
335356

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ describe("Dialog general interaction", () => {
113113

114114
closeResizableDialogButton.click();
115115
});
116+
117+
it("initial focus after dynamic dialog creation", () => {
118+
const openDynamicDialog = browser.$("#dynamic-open");
119+
openDynamicDialog.click();
120+
121+
const closeButton = browser.$("#dynamic-dialog-close-button");
122+
123+
browser.pause(500);
124+
125+
const activeElement = $(browser.getActiveElement());
126+
assert.strictEqual(activeElement.getProperty("id"), closeButton.getProperty("id"), "the active element is the close button");
127+
128+
closeButton.click();
129+
});
116130
});
117131

118132

0 commit comments

Comments
 (0)