Skip to content
This repository was archived by the owner on Nov 6, 2025. It is now read-only.

Commit 8d6befc

Browse files
authored
Merge pull request #2151 from umbraco/v14/bugfix/focus-directive-late-focus
Bugfix: focus directive refactor to support late case
2 parents 79323dd + 7f8b203 commit 8d6befc

File tree

2 files changed

+57
-11
lines changed

2 files changed

+57
-11
lines changed

src/packages/core/lit-element/directives/focus.lit-directive.ts

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,76 @@
11
import { AsyncDirective, directive, nothing, type ElementPart } from '@umbraco-cms/backoffice/external/lit';
2+
/**
3+
*
4+
* test if a element has focus
5+
* this also returns true if the focused element is a child of the target.
6+
* @param current
7+
* @param target
8+
* @returns bool
9+
*/
10+
function hasFocus(current: any, target: HTMLElement): boolean {
11+
if (current === target) {
12+
return true;
13+
}
14+
if (current.shadowRoot) {
15+
const node = current.shadowRoot.activeElement;
16+
if (node) {
17+
return hasFocus(node, target);
18+
}
19+
}
20+
return false;
21+
}
222

323
/**
424
* The `focus` directive sets focus on the given element once its connected to the DOM.
525
*/
626
class UmbFocusDirective extends AsyncDirective {
7-
private _el?: HTMLElement;
27+
static #next?: HTMLElement;
28+
#el?: HTMLElement;
29+
#timeout?: number;
830

931
override render() {
1032
return nothing;
1133
}
1234

1335
override update(part: ElementPart) {
14-
if (this._el !== part.element) {
15-
// This does feel wrong that we need to wait one render. [NL]
16-
// Because even if our elements focus method is implemented so it can be called initially, my research shows that calling the focus method at this point is too early, thought the element is connected to the DOM and the focus method is available. [NL]
17-
// This smells a bit like the DOMPart of which the directive is in is not connected to the main DOM yet, and therefor cant receive focus. [NL]
18-
// Which is why we need to await one render: [NL]
19-
requestAnimationFrame(() => {
20-
(this._el = part.element as HTMLElement).focus();
21-
});
36+
if (this.#el !== part.element) {
37+
UmbFocusDirective.#next = this.#el = part.element as HTMLElement;
38+
this.#setFocus();
2239
}
2340
return nothing;
2441
}
2542

43+
/**
44+
* This method tries to set focus, if it did not succeed, it will try again.
45+
* It always tests against the latest element, because the directive can be used multiple times in the same render.
46+
* This is NOT needed because the elements focus method isn't ready to be called, but due to something with rendering of the DOM.
47+
* But I'm not completely sure at this movement why the browser does not accept the focus call.
48+
* But I have tested that everything is in place for it to be good, so something else must have an effect,
49+
* setting the focus somewhere else, maybe a re-appending of some sort?
50+
* cause Lit does not re-render the element but also notice reconnect callback on the directive is not triggered either. [NL]
51+
*/
52+
#setFocus = () => {
53+
// Make sure we clear the timeout, so we don't get multiple timeouts running.
54+
if (this.#timeout) {
55+
clearTimeout(this.#timeout);
56+
this.#timeout = undefined;
57+
}
58+
// If this is the next element to focus, then try to focus it.
59+
if (this.#el && this.#el === UmbFocusDirective.#next) {
60+
this.#el.focus();
61+
if (hasFocus(document.activeElement, this.#el) === false) {
62+
this.#timeout = setTimeout(this.#setFocus, 100) as unknown as number;
63+
} else {
64+
UmbFocusDirective.#next = undefined;
65+
}
66+
}
67+
};
68+
2669
override disconnected() {
27-
this._el = undefined;
70+
if (this.#el === UmbFocusDirective.#next) {
71+
UmbFocusDirective.#next = undefined;
72+
}
73+
this.#el = undefined;
2874
}
2975

3076
//override reconnected() {}

src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
212212
return html`
213213
<uui-input
214214
id="name-input"
215-
label="Document name (TODO: Localize)"
215+
label=${this.localize.term('placeholders_entername')}
216216
.value=${this._name ?? ''}
217217
@input=${this.#handleInput}
218218
${umbFocus()}

0 commit comments

Comments
 (0)