feat(form-core): add [focused-visible] when matching :focus-visible
This commit is contained in:
parent
77250aab73
commit
3b187fa2c9
8 changed files with 355 additions and 33 deletions
5
.changeset/short-llamas-share.md
Normal file
5
.changeset/short-llamas-share.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/form-core': patch
|
||||
---
|
||||
|
||||
support [focused-visible] when focusable node within matches :focus-visible
|
||||
|
|
@ -1,25 +1,50 @@
|
|||
import { dedupeMixin } from '@lion/core';
|
||||
import { FormControlMixin } from './FormControlMixin.js';
|
||||
|
||||
const windowWithOptionalPolyfill = /** @type {Window & typeof globalThis & {applyFocusVisiblePolyfill?: function}} */ (window);
|
||||
const polyfilledNodes = new WeakMap();
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
*/
|
||||
function applyFocusVisiblePolyfillWhenNeeded(node) {
|
||||
if (windowWithOptionalPolyfill.applyFocusVisiblePolyfill && !polyfilledNodes.has(node)) {
|
||||
windowWithOptionalPolyfill.applyFocusVisiblePolyfill(node);
|
||||
polyfilledNodes.set(node, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/FocusMixinTypes').FocusMixin} FocusMixin
|
||||
* @type {FocusMixin}
|
||||
* @param {import('@open-wc/dedupe-mixin').Constructor<import('@lion/core').LitElement>} superclass
|
||||
*/
|
||||
const FocusMixinImplementation = superclass =>
|
||||
class FocusMixin extends FormControlMixin(superclass) {
|
||||
class FocusMixin extends superclass {
|
||||
/** @type {any} */
|
||||
static get properties() {
|
||||
return {
|
||||
focused: {
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
},
|
||||
focused: { type: Boolean, reflect: true },
|
||||
focusedVisible: { type: Boolean, reflect: true, attribute: 'focused-visible' },
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/**
|
||||
* Whether the focusable element within (`._focusableNode`) is focused.
|
||||
* Reflects to attribute '[focused]' as a styling hook
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.focused = false;
|
||||
|
||||
/**
|
||||
* Whether the focusable element within (`._focusableNode`) matches ':focus-visible'
|
||||
* Reflects to attribute '[focused-visible]' as a styling hook
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.focusedVisible = false;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
|
@ -32,18 +57,32 @@ const FocusMixinImplementation = superclass =>
|
|||
this.__teardownEventsForFocusMixin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `focus()` on focusable element within
|
||||
*/
|
||||
focus() {
|
||||
const native = this._inputNode;
|
||||
if (native) {
|
||||
native.focus();
|
||||
}
|
||||
this._focusableNode?.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `blur()` on focusable element within
|
||||
*/
|
||||
blur() {
|
||||
const native = this._inputNode;
|
||||
if (native) {
|
||||
native.blur();
|
||||
}
|
||||
this._focusableNode?.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* The focusable element:
|
||||
* could be an input, textarea, select, button or any other element with tabindex > -1
|
||||
* @protected
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
// @ts-ignore it's up to Subclassers to return the right element. This is needed for docs/types
|
||||
// eslint-disable-next-line class-methods-use-this, getter-return, no-empty-function
|
||||
get _focusableNode() {
|
||||
// TODO: [v1]: remove return of _inputNode (it's now here for backwards compatibility)
|
||||
// @ts-expect-error see above
|
||||
return /** @type {HTMLElement} */ (this._inputNode || document.createElement('input'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -51,6 +90,16 @@ const FocusMixinImplementation = superclass =>
|
|||
*/
|
||||
__onFocus() {
|
||||
this.focused = true;
|
||||
|
||||
if (typeof windowWithOptionalPolyfill.applyFocusVisiblePolyfill === 'function') {
|
||||
this.focusedVisible = this._focusableNode.hasAttribute('data-focus-visible-added');
|
||||
} else
|
||||
try {
|
||||
// Safari throws when matches is called
|
||||
this.focusedVisible = this._focusableNode.matches(':focus-visible');
|
||||
} catch (_) {
|
||||
this.focusedVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -58,12 +107,15 @@ const FocusMixinImplementation = superclass =>
|
|||
*/
|
||||
__onBlur() {
|
||||
this.focused = false;
|
||||
this.focusedVisible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
__registerEventsForFocusMixin() {
|
||||
applyFocusVisiblePolyfillWhenNeeded(this.getRootNode());
|
||||
|
||||
/**
|
||||
* focus
|
||||
* @param {Event} ev
|
||||
|
|
@ -72,7 +124,7 @@ const FocusMixinImplementation = superclass =>
|
|||
ev.stopPropagation();
|
||||
this.dispatchEvent(new Event('focus'));
|
||||
};
|
||||
this._inputNode.addEventListener('focus', this.__redispatchFocus);
|
||||
this._focusableNode.addEventListener('focus', this.__redispatchFocus);
|
||||
|
||||
/**
|
||||
* blur
|
||||
|
|
@ -82,7 +134,7 @@ const FocusMixinImplementation = superclass =>
|
|||
ev.stopPropagation();
|
||||
this.dispatchEvent(new Event('blur'));
|
||||
};
|
||||
this._inputNode.addEventListener('blur', this.__redispatchBlur);
|
||||
this._focusableNode.addEventListener('blur', this.__redispatchBlur);
|
||||
|
||||
/**
|
||||
* focusin
|
||||
|
|
@ -93,7 +145,7 @@ const FocusMixinImplementation = superclass =>
|
|||
this.__onFocus();
|
||||
this.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||
};
|
||||
this._inputNode.addEventListener('focusin', this.__redispatchFocusin);
|
||||
this._focusableNode.addEventListener('focusin', this.__redispatchFocusin);
|
||||
|
||||
/**
|
||||
* focusout
|
||||
|
|
@ -104,30 +156,35 @@ const FocusMixinImplementation = superclass =>
|
|||
this.__onBlur();
|
||||
this.dispatchEvent(new Event('focusout', { bubbles: true, composed: true }));
|
||||
};
|
||||
this._inputNode.addEventListener('focusout', this.__redispatchFocusout);
|
||||
this._focusableNode.addEventListener('focusout', this.__redispatchFocusout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
__teardownEventsForFocusMixin() {
|
||||
this._inputNode.removeEventListener(
|
||||
this._focusableNode.removeEventListener(
|
||||
'focus',
|
||||
/** @type {EventListenerOrEventListenerObject} */ (this.__redispatchFocus),
|
||||
);
|
||||
this._inputNode.removeEventListener(
|
||||
this._focusableNode.removeEventListener(
|
||||
'blur',
|
||||
/** @type {EventListenerOrEventListenerObject} */ (this.__redispatchBlur),
|
||||
);
|
||||
this._inputNode.removeEventListener(
|
||||
this._focusableNode.removeEventListener(
|
||||
'focusin',
|
||||
/** @type {EventListenerOrEventListenerObject} */ (this.__redispatchFocusin),
|
||||
);
|
||||
this._inputNode.removeEventListener(
|
||||
this._focusableNode.removeEventListener(
|
||||
'focusout',
|
||||
/** @type {EventListenerOrEventListenerObject} */ (this.__redispatchFocusout),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* For browsers that not support the [spec](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible),
|
||||
* be sure to load the polyfill into your application https://github.com/WICG/focus-visible
|
||||
* (or go for progressive enhancement).
|
||||
*/
|
||||
export const FocusMixin = dedupeMixin(FocusMixinImplementation);
|
||||
|
|
|
|||
|
|
@ -106,4 +106,11 @@ export class LionField extends FormControlMixin(
|
|||
get _feedbackConditionMeta() {
|
||||
return { ...super._feedbackConditionMeta, focused: this.focused };
|
||||
}
|
||||
|
||||
/**
|
||||
* @configure FocusMixin
|
||||
*/
|
||||
get _focusableNode() {
|
||||
return this._inputNode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,13 @@ const NativeTextFieldMixinImplementation = superclass =>
|
|||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @configure FocusMixin
|
||||
*/
|
||||
get _focusableNode() {
|
||||
return this._inputNode;
|
||||
}
|
||||
};
|
||||
|
||||
export const NativeTextFieldMixin = dedupeMixin(NativeTextFieldMixinImplementation);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,73 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { defineCE, expect, fixture, html, oneEvent, unsafeStatic } from '@open-wc/testing';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import { FocusMixin } from '../src/FocusMixin.js';
|
||||
|
||||
const windowWithOptionalPolyfill = /** @type {Window & typeof globalThis & {applyFocusVisiblePolyfill?: function}} */ (window);
|
||||
|
||||
/**
|
||||
* Checks two things:
|
||||
* 1. whether focus-visible should apply (if focus and keyboard interaction present)
|
||||
* 2. whether the polyfill is used or not
|
||||
* When the polyfill is used, it mocks `.hasAttribute` method, otherwise `.matches` method
|
||||
* of focusable element.
|
||||
* @param {HTMLElement} focusableEl focusable element
|
||||
* @param {{phase: 'focusin'|'focusout', hasKeyboardInteraction: boolean }} options
|
||||
* @returns {function} restore function
|
||||
*/
|
||||
function mockFocusVisible(focusableEl, { phase, hasKeyboardInteraction }) {
|
||||
const focusVisibleApplies = phase === 'focusin' && hasKeyboardInteraction;
|
||||
if (!focusVisibleApplies) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const originalMatches = focusableEl.matches;
|
||||
if (typeof windowWithOptionalPolyfill.applyFocusVisiblePolyfill !== 'function') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
focusableEl.matches = selector =>
|
||||
selector === ':focus-visible' || originalMatches.call(focusableEl, selector);
|
||||
return () => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
focusableEl.matches = originalMatches;
|
||||
};
|
||||
}
|
||||
|
||||
const originalHasAttribute = focusableEl.hasAttribute;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
focusableEl.hasAttribute = attr =>
|
||||
attr === 'data-focus-visible-added' || originalHasAttribute.call(focusableEl, attr);
|
||||
return () => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
focusableEl.hasAttribute = originalHasAttribute;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {function} restore function
|
||||
*/
|
||||
function mockPolyfill() {
|
||||
const originalApplyFocusVisiblePolyfill = windowWithOptionalPolyfill.applyFocusVisiblePolyfill;
|
||||
// @ts-ignore
|
||||
window.applyFocusVisiblePolyfill = () => {};
|
||||
return () => {
|
||||
// @ts-ignore
|
||||
window.applyFocusVisiblePolyfill = originalApplyFocusVisiblePolyfill;
|
||||
};
|
||||
}
|
||||
|
||||
describe('FocusMixin', () => {
|
||||
class Focusable extends FocusMixin(LitElement) {
|
||||
render() {
|
||||
return html`<slot name="input"></slot>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @configure FocusMixin
|
||||
*/
|
||||
get _focusableNode() {
|
||||
return /** @type {HTMLInputElement} */ (this.querySelector('input'));
|
||||
}
|
||||
}
|
||||
|
||||
const tagString = defineCE(Focusable);
|
||||
|
|
@ -17,12 +77,13 @@ describe('FocusMixin', () => {
|
|||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
el.focus();
|
||||
expect(document.activeElement === _inputNode).to.be.true;
|
||||
expect(document.activeElement === _focusableNode).to.be.true;
|
||||
el.blur();
|
||||
expect(document.activeElement === _inputNode).to.be.false;
|
||||
expect(document.activeElement === _focusableNode).to.be.false;
|
||||
});
|
||||
|
||||
it('has an attribute focused when focused', async () => {
|
||||
|
|
@ -43,12 +104,13 @@ describe('FocusMixin', () => {
|
|||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
expect(el.focused).to.be.false;
|
||||
_inputNode?.focus();
|
||||
_focusableNode?.focus();
|
||||
expect(el.focused).to.be.true;
|
||||
_inputNode?.blur();
|
||||
_focusableNode?.blur();
|
||||
expect(el.focused).to.be.false;
|
||||
});
|
||||
|
||||
|
|
@ -95,4 +157,155 @@ describe('FocusMixin', () => {
|
|||
expect(focusoutEv.bubbles).to.be.true;
|
||||
expect(focusoutEv.composed).to.be.true;
|
||||
});
|
||||
|
||||
describe('Having :focus-visible within', () => {
|
||||
it('sets focusedVisible to true when focusable element matches :focus-visible', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
const restoreMock1 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusout',
|
||||
hasKeyboardInteraction: true,
|
||||
});
|
||||
_focusableNode.dispatchEvent(new Event('focusout', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.focusedVisible).to.be.false;
|
||||
restoreMock1();
|
||||
|
||||
const restoreMock2 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusin',
|
||||
hasKeyboardInteraction: false,
|
||||
});
|
||||
_focusableNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.focusedVisible).to.be.false;
|
||||
restoreMock2();
|
||||
|
||||
const restoreMock3 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusout',
|
||||
hasKeyboardInteraction: false,
|
||||
});
|
||||
_focusableNode.dispatchEvent(new Event('focusout', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.focusedVisible).to.be.false;
|
||||
restoreMock3();
|
||||
|
||||
const restoreMock4 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusin',
|
||||
hasKeyboardInteraction: true,
|
||||
});
|
||||
_focusableNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.focusedVisible).to.be.true;
|
||||
restoreMock4();
|
||||
});
|
||||
|
||||
it('has an attribute focused-visible when focusedVisible is true', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
const restoreMock1 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusout',
|
||||
hasKeyboardInteraction: true,
|
||||
});
|
||||
_focusableNode.dispatchEvent(new Event('focusout', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.hasAttribute('focused-visible')).to.be.false;
|
||||
restoreMock1();
|
||||
|
||||
const restoreMock2 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusin',
|
||||
hasKeyboardInteraction: true,
|
||||
});
|
||||
_focusableNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.hasAttribute('focused-visible')).to.be.true;
|
||||
restoreMock2();
|
||||
});
|
||||
|
||||
// For polyfill, see https://github.com/WICG/focus-visible
|
||||
describe('Using polyfill', () => {
|
||||
const restoreMockPolyfill = mockPolyfill();
|
||||
after(() => {
|
||||
restoreMockPolyfill();
|
||||
});
|
||||
|
||||
it('calls polyfill once per node', async () => {
|
||||
class UniqueHost extends LitElement {
|
||||
render() {
|
||||
return html`<${tag}><input slot="input"></${tag}><${tag}><input slot="input"></${tag}>`;
|
||||
}
|
||||
}
|
||||
const hostTagString = defineCE(UniqueHost);
|
||||
const hostTag = unsafeStatic(hostTagString);
|
||||
|
||||
const polySpy = sinon.spy(windowWithOptionalPolyfill, 'applyFocusVisiblePolyfill');
|
||||
await fixture(html`<${hostTag}></${hostTag}>`);
|
||||
expect(polySpy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('sets focusedVisible to true when focusable element if :focus-visible polyfill is loaded', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
const restoreMock1 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusout',
|
||||
hasKeyboardInteraction: true,
|
||||
});
|
||||
const spy1 = sinon.spy(_focusableNode, 'hasAttribute');
|
||||
_focusableNode.dispatchEvent(new Event('focusout', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.focusedVisible).to.be.false;
|
||||
expect(spy1).to.not.have.been.calledWith('data-focus-visible-added');
|
||||
spy1.restore();
|
||||
restoreMock1();
|
||||
|
||||
const restoreMock2 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusin',
|
||||
hasKeyboardInteraction: false,
|
||||
});
|
||||
const spy2 = sinon.spy(_focusableNode, 'hasAttribute');
|
||||
_focusableNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.focusedVisible).to.be.false;
|
||||
expect(spy2).to.have.been.calledWith('data-focus-visible-added');
|
||||
spy2.restore();
|
||||
restoreMock2();
|
||||
|
||||
const restoreMock3 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusout',
|
||||
hasKeyboardInteraction: false,
|
||||
});
|
||||
const spy3 = sinon.spy(_focusableNode, 'hasAttribute');
|
||||
_focusableNode.dispatchEvent(new Event('focusout', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.focusedVisible).to.be.false;
|
||||
expect(spy3).to.not.have.been.calledWith('data-focus-visible-added');
|
||||
spy3.restore();
|
||||
restoreMock3();
|
||||
|
||||
const restoreMock4 = mockFocusVisible(_focusableNode, {
|
||||
phase: 'focusin',
|
||||
hasKeyboardInteraction: true,
|
||||
});
|
||||
const spy4 = sinon.spy(_focusableNode, 'hasAttribute');
|
||||
_focusableNode.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||
await el.updateComplete;
|
||||
expect(el.focusedVisible).to.be.true;
|
||||
expect(spy4).to.have.been.called;
|
||||
spy4.restore();
|
||||
restoreMock4();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -161,7 +161,14 @@ describe('FormControlMixin', () => {
|
|||
const groupTag = unsafeStatic(groupTagString);
|
||||
|
||||
const focusableTagString = defineCE(
|
||||
class extends FocusMixin(FormControlMixin(LitElement)) {},
|
||||
class extends FocusMixin(FormControlMixin(LitElement)) {
|
||||
/**
|
||||
* @configure FocusMixin
|
||||
*/
|
||||
get _focusableNode() {
|
||||
return this._inputNode;
|
||||
}
|
||||
},
|
||||
);
|
||||
const focusableTag = unsafeStatic(focusableTagString);
|
||||
|
||||
|
|
|
|||
25
packages/form-core/types/FocusMixinTypes.d.ts
vendored
25
packages/form-core/types/FocusMixinTypes.d.ts
vendored
|
|
@ -1,12 +1,33 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
import { LitElement } from '@lion/core';
|
||||
import { FormControlHost } from './FormControlMixinTypes';
|
||||
|
||||
export declare class FocusHost {
|
||||
/**
|
||||
* Whether the focusable element within (`._focusableNode`) is focused.
|
||||
* Reflects to attribute '[focused]' as a styling hook
|
||||
*/
|
||||
focused: boolean;
|
||||
/**
|
||||
* Whether the focusable element within (`._focusableNode`) matches ':focus-visible'
|
||||
* Reflects to attribute '[focused-visible]' as a styling hook
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible
|
||||
*/
|
||||
focusedVisible: boolean;
|
||||
/**
|
||||
* Calls `focus()` on focusable element within
|
||||
*/
|
||||
focus(): void;
|
||||
/**
|
||||
* Calls `blur()` on focusable element within
|
||||
*/
|
||||
blur(): void;
|
||||
|
||||
/**
|
||||
* The focusable element:
|
||||
* could be an input, textarea, select, button or any other element with tabindex > -1
|
||||
*/
|
||||
protected get _focusableNode(): HTMLElement;
|
||||
|
||||
private __onFocus(): void;
|
||||
private __onBlur(): void;
|
||||
private __registerEventsForFocusMixin(): void;
|
||||
|
|
@ -18,8 +39,6 @@ export declare function FocusImplementation<T extends Constructor<LitElement>>(
|
|||
): T &
|
||||
Constructor<FocusHost> &
|
||||
Pick<typeof FocusHost, keyof typeof FocusHost> &
|
||||
Constructor<FormControlHost> &
|
||||
Pick<typeof FormControlHost, keyof typeof FormControlHost> &
|
||||
Pick<typeof LitElement, keyof typeof LitElement>;
|
||||
|
||||
export type FocusMixin = typeof FocusImplementation;
|
||||
|
|
|
|||
|
|
@ -17,4 +17,11 @@ export class LionListbox extends ListboxMixin(
|
|||
get _feedbackConditionMeta() {
|
||||
return { ...super._feedbackConditionMeta, focused: this.focused };
|
||||
}
|
||||
|
||||
/**
|
||||
* @configure FocusMixin
|
||||
*/
|
||||
get _focusableNode() {
|
||||
return this._inputNode;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue