refactor directive to support scenarios where things are not ready

This commit is contained in:
Niels Lyngsø
2024-07-30 14:03:01 +02:00
parent 5ee5b947a1
commit ad7d3a56cc
2 changed files with 47 additions and 8 deletions

View File

@@ -1,9 +1,31 @@
import { AsyncDirective, directive, nothing, type ElementPart } from '@umbraco-cms/backoffice/external/lit';
function isDescendant(parent: any, child: any) {
let node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.host ? node.host : node.parentNode;
}
return false;
}
function hasFocus(current: any) {
if (current.shadowRoot) {
const node = current.shadowRoot.activeElement;
if (node) {
return hasFocus(node);
}
}
return current;
}
/**
* The `focus` directive sets focus on the given element once its connected to the DOM.
*/
class UmbFocusDirective extends AsyncDirective {
static _latestElement?: HTMLElement;
private _el?: HTMLElement;
override render() {
@@ -12,18 +34,34 @@ class UmbFocusDirective extends AsyncDirective {
override update(part: ElementPart) {
if (this._el !== part.element) {
// This does feel wrong that we need to wait one render. [NL]
// 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]
// 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]
// Which is why we need to await one render: [NL]
requestAnimationFrame(() => {
(this._el = part.element as HTMLElement).focus();
});
UmbFocusDirective._latestElement = this._el = part.element as HTMLElement;
this.#setFocus();
}
return nothing;
}
/**
* This method tries to set focus, if it did not succeed, it will try again.
* It always tests against the latest element, because the directive can be used multiple times in the same render.
* This is NOT needed because the elements focus method isn't ready to be called, but due to something with rendering of the DOM.
* But I'm not completely sure at this movement why the browser does not accept the focus call.
* But I have tested that everything is in place for it to be good, so something else must have an effect,
* setting the focus somewhere else, maybe a re-appending of some sort?
* cause Lit does not re-render the element but also notice reconnect callback on the directive is not triggered either. [NL]
*/
#setFocus = () => {
if (this._el && this._el === UmbFocusDirective._latestElement) {
this._el.focus();
if (hasFocus(document.activeElement)) {
setTimeout(this.#setFocus, 100);
}
}
};
override disconnected() {
if (this._el === UmbFocusDirective._latestElement) {
UmbFocusDirective._latestElement = undefined;
}
this._el = undefined;
}

View File

@@ -209,10 +209,11 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
}
override render() {
// TODO: Localize [NL]
return html`
<uui-input
id="name-input"
label="Document name (TODO: Localize)"
label="Document name"
.value=${this._name ?? ''}
@input=${this.#handleInput}
${umbFocus()}