fix(form-core) sync autofocus to focusable node

This commit is contained in:
Sciurus7 2023-04-14 13:39:05 +02:00
parent 5b8d655f10
commit 2683a73080
4 changed files with 75 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/ui': patch
---
[FocusMixin] now syncs autofocus between host and the focusable node.

View file

@ -27,6 +27,7 @@ const FocusMixinImplementation = superclass =>
return {
focused: { type: Boolean, reflect: true },
focusedVisible: { type: Boolean, reflect: true, attribute: 'focused-visible' },
autofocus: { type: Boolean, reflect: true }, // Required in Lit to observe autofocus
};
}
@ -52,6 +53,7 @@ const FocusMixinImplementation = superclass =>
connectedCallback() {
super.connectedCallback();
this.__registerEventsForFocusMixin();
this.__syncAutofocusToFocusableElement();
}
disconnectedCallback() {
@ -59,6 +61,35 @@ const FocusMixinImplementation = superclass =>
this.__teardownEventsForFocusMixin();
}
/**
* Gets called when an attribute is changed.
* @param {String} name
* @param {String | null} _old
* @param {String | null} value
* @protected
*/
attributeChangedCallback(name, _old, value) {
super.attributeChangedCallback(name, _old, value);
if (name === 'autofocus') {
this.__syncAutofocusToFocusableElement();
}
}
/**
* @private
*/
__syncAutofocusToFocusableElement() {
if (!this._focusableNode) {
return;
}
if (this.hasAttribute('autofocus')) {
this._focusableNode.setAttribute('autofocus', '');
} else {
this._focusableNode.removeAttribute('autofocus');
}
}
/**
* Calls `focus()` on focusable element within
*/

View file

@ -323,6 +323,39 @@ describe('FocusMixin', () => {
spy4.restore();
restoreMock4();
});
it('has mirrors syncs autofocus on the focusable element when autofocus changes', async () => {
const el = /** @type {Focusable} */ (
await fixture(html`
<${tag}><input slot="input"></${tag}>
`)
);
// @ts-ignore [allow-protected] in test
const { _focusableNode } = el;
el.setAttribute('autofocus', '');
await el.updateComplete;
expect(_focusableNode.hasAttribute('autofocus')).to.be.true;
el.removeAttribute('autofocus');
await el.updateComplete;
expect(_focusableNode.hasAttribute('autofocus')).not.to.be.true;
});
it('has mirrors syncs autofocus on the focusable element when autofocus was set on render', async () => {
const el = /** @type {Focusable} */ (
await fixture(html`
<${tag} autofocus><input slot="input"></${tag}>
`)
);
// @ts-ignore [allow-protected] in test
const { _focusableNode } = el;
expect(el.hasAttribute('autofocus')).to.be.true;
expect(_focusableNode.hasAttribute('autofocus')).to.be.true;
});
});
});
});

View file

@ -25,6 +25,12 @@ export declare class FocusHost {
*/
blur(): void;
/**
* Synchronizes property values when attributes change.
* @category attributes
*/
attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
/**
* The focusable element:
* could be an input, textarea, select, button or any other element with tabindex > -1