Skip to content

Commit b13a00a

Browse files
authored
feat(ui5-*): support autofocus attribute (#12572)
Adds support for the global HTML `autofocus` attribute on every UI5 web component, so authors can write: ```html <ui5-textarea autofocus></ui5-textarea> <ui5-input autofocus></ui5-input> ``` Fixes: #9063
1 parent ab89a5f commit b13a00a

8 files changed

Lines changed: 88 additions & 4 deletions

File tree

packages/ai/src/PromptInputTemplate.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default function PromptInputTemplate(this: PromptInput) {
2626
onInput={this._onInnerInput}
2727
onChange={this._onInnerChange}
2828
onTypeAhead={this._onTypeAhead}
29+
data-sap-focus-ref
2930
>
3031
<slot></slot>
3132
{this.valueStateMessage.length > 0 &&

packages/base/src/UI5Element.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,16 @@ abstract class UI5Element extends HTMLElement {
359359
this._domRefReadyPromise._deferredResolve!();
360360
this._fullyConnected = true;
361361
this.onEnterDOM();
362+
363+
if (this.hasAttribute("autofocus")) {
364+
// Honor the global `autofocus` HTML attribute. Done manually because
365+
// Firefox/Safari close the autofocus window at end-of-parse, before
366+
// async UI5 components have rendered their shadow DOM. Per HTML spec,
367+
// only the first element with `autofocus` in document order wins.
368+
requestAnimationFrame(() => {
369+
this.focus();
370+
});
371+
}
362372
}
363373

364374
get definePromise(): Promise<void> {

packages/main/cypress/specs/Input.cy.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,13 @@ describe("Change event behavior when selecting the same suggestion item", () =>
13291329
});
13301330

13311331
describe("Accessibility", () => {
1332+
it("tests autofocus attribute", () => {
1333+
cy.mount(<Input autofocus />);
1334+
1335+
cy.get("[ui5-input]")
1336+
.should("be.focused");
1337+
});
1338+
13321339
it("tests accessibleDescription property", () => {
13331340
cy.mount(<Input accessibleDescription="This is an input" />);
13341341

packages/main/src/Popup.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,13 +521,18 @@ abstract class Popup extends UI5Element {
521521
* @returns Promise that resolves when the focus is applied
522522
*/
523523
async applyFocus(): Promise<void> {
524-
// do nothing if the standard HTML autofocus is used
525-
if (this.querySelector("[autofocus]")) {
524+
await this._waitForDomRef();
525+
526+
const elementWithAutoFocus = this.querySelector("[autofocus]");
527+
if (elementWithAutoFocus) {
528+
// If the "autofocus" is set on UI5Element, focus it manually.
529+
if ("isUI5Element" in elementWithAutoFocus) {
530+
(elementWithAutoFocus as UI5Element).focus();
531+
}
532+
// Otherwise, the browser will focus it automatically.
526533
return;
527534
}
528535

529-
await this._waitForDomRef();
530-
531536
if (this.getRootNode() === this) {
532537
return;
533538
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>TextArea autofocus repro (#9063)</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<script src="%VITE_BUNDLE_PATH%" type="module"></script>
8+
</head>
9+
<body>
10+
<section>
11+
<ui5-textarea></ui5-textarea>
12+
<ui5-select></ui5-select>
13+
<ui5-input ></ui5-input>
14+
<ui5-select autofocus></ui5-select>
15+
<ui5-date-picker autofocus></ui5-date-picker>
16+
<ui5-combobox autofocus></ui5-combobox>
17+
</section>
18+
19+
<section>
20+
<ui5-button id="openDialogBtn">Open Dialog</ui5-button>
21+
<ui5-dialog id="myDialog" header-text="Dialog with TextArea">
22+
<button id="openPopoverBtn">Press</button>
23+
<input />
24+
<ui5-textarea placeholder="TextArea in Dialog"></ui5-textarea>
25+
</ui5-dialog>
26+
</section>
27+
<script type="module">
28+
const dialog = document.getElementById("myDialog");
29+
document.getElementById("openDialogBtn").addEventListener("click", () => {
30+
dialog.open = true;
31+
});
32+
document.getElementById("openPopoverBtn").addEventListener("click", () => {
33+
popover.open = true;
34+
});
35+
36+
window.addEventListener("load", () => {
37+
requestAnimationFrame(() => {
38+
const ae = document.activeElement;
39+
const desc = ae === document.body
40+
? "document.body (nothing focused)"
41+
: `<${ae.tagName.toLowerCase()}> placeholder="${ae.getAttribute?.("placeholder") ?? ""}"`;
42+
document.getElementById("log").textContent = `document.activeElement: ${desc}`;
43+
});
44+
});
45+
</script>
46+
</body>
47+
</html>

packages/main/test/pages/Dialog.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
<div slot="footer" class="dialogFooter">
112112
<ui5-button design="Emphasized" id="btnCloseDialogWithInput">Close</ui5-button>
113113
</div>
114+
<ui5-input autofocus placeholder="Input with autofocus"></ui5-input>
114115
</ui5-dialog>
115116

116117
<ui5-dialog id="msg-dialog" header-text="Message dialog" class="dialog1auto">

packages/main/test/pages/Input.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ <h3> Input with suggestions: type 'a'</h3>
2222
<ui5-label id="myLabelSelectionChange">Event [selectionChange] :: N/A</ui5-label><br />
2323

2424
<div>
25+
<h3>Input with autofocus</h3>
26+
<ui5-input autofocus placeholder="Input with autofocus"></ui5-input>
27+
2528
<h3>Input in Cozy</h3>
2629
<ui5-input id="myInput" class="input2auto" show-suggestions placeholder="Search for a country ...">
2730
</ui5-input>

packages/main/test/pages/Popover.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@
9696
<ui5-button id="btnCloseWithAttr">Close with Attribute</ui5-button>
9797
</ui5-popover>
9898

99+
<ui5-button id="btnOpenWithAttr2">Open with Attribute</ui5-button>
100+
<ui5-popover id="popoverAttr2" opener="btnOpenWithAttr2">
101+
<ui5-button id="btnCloseWithMethod">Close with Method</ui5-button>
102+
<ui5-button id="btnCloseWithAttr">Close with Attribute</ui5-button>
103+
<ui5-input autofocus placeholder="Input with autofocus"></ui5-input>
104+
</ui5-popover>
105+
99106
<br>
100107
<br>
101108

@@ -681,6 +688,9 @@ <h3>Popover in ShadowRoot, Opener set as ID in window.document</h3>
681688
btnOpenWithAttr.addEventListener("click", function () {
682689
popoverAttr.setAttribute("open", "");
683690
});
691+
btnOpenWithAttr2.addEventListener("click", function () {
692+
popoverAttr2.setAttribute("open", "");
693+
});
684694

685695
btnCloseWithMethod.addEventListener("click", function () {
686696
popoverAttr.open = false;

0 commit comments

Comments
 (0)