diff --git a/src/modules/esl-share/core/esl-share-popup.ts b/src/modules/esl-share/core/esl-share-popup.ts index c652f1725..efc14b416 100644 --- a/src/modules/esl-share/core/esl-share-popup.ts +++ b/src/modules/esl-share/core/esl-share-popup.ts @@ -1,6 +1,6 @@ import {ExportNs} from '../../esl-utils/environment/export-ns'; import {ESLTooltip} from '../../esl-tooltip/core/esl-tooltip'; -import {bind, listen, memoize} from '../../esl-utils/decorators'; +import {bind, listen, memoize, prop} from '../../esl-utils/decorators'; import {ESLShareButton} from './esl-share-button'; import {ESLShareConfig} from './esl-share-config'; @@ -54,6 +54,8 @@ export class ESLSharePopup extends ESLTooltip { return ESLSharePopup.create(); } + @prop(true) public override hasFocusLoop: boolean; + /** Hashstring with a list of buttons already rendered in the popup */ protected _list: string = ''; diff --git a/src/modules/esl-tooltip/core/esl-tooltip.ts b/src/modules/esl-tooltip/core/esl-tooltip.ts index dbd21e413..16d00b75a 100644 --- a/src/modules/esl-tooltip/core/esl-tooltip.ts +++ b/src/modules/esl-tooltip/core/esl-tooltip.ts @@ -1,8 +1,8 @@ import {ExportNs} from '../../esl-utils/environment/export-ns'; import {ESLPopup} from '../../esl-popup/core'; -import {memoize, attr, boolAttr, listen} from '../../esl-utils/decorators'; +import {memoize, attr, boolAttr, listen, prop} from '../../esl-utils/decorators'; import {TAB} from '../../esl-utils/dom/keys'; -import {getKeyboardFocusableElements} from '../../esl-utils/dom/focus'; +import {getKeyboardFocusableElements, handleFocusChain} from '../../esl-utils/dom/focus'; import {CSSClassUtils} from '../../esl-utils/dom/class'; import type {PopupActionParams} from '../../esl-popup/core'; @@ -25,6 +25,8 @@ export interface TooltipActionParams extends PopupActionParams { export class ESLTooltip extends ESLPopup { static override is = 'esl-tooltip'; + @prop(false) public hasFocusLoop: boolean; + /** * Tooltip position relative to the trigger. * Currently supported: 'top', 'bottom', 'left', 'right' position types ('top' by default) @@ -43,21 +45,17 @@ export class ESLTooltip extends ESLPopup { return document.createElement('esl-tooltip'); } - /** List of all focusable elements inside Tooltip */ - public get focusableElements(): Element[] { - return getKeyboardFocusableElements(this); - } - - /** First focusable element inside Tooltip */ - public get firstFocusableElement(): Element | null { - const els = this.focusableElements; - return els.length ? els[0] : null; + /** List of all focusable elements inside instance */ + public get $focusables(): HTMLElement[] { + return getKeyboardFocusableElements(this) as HTMLElement[]; } - /** Last focusable element inside Tooltip */ - public get lastFocusableElement(): Element | null { - const els = this.focusableElements; - return els.length ? els[els.length - 1] : null; + /** First and last focusable elements inside instance */ + public get $boundaryFocusable(): {$first: HTMLElement | undefined, $last: HTMLElement | undefined} { + const {$focusables} = this; + const $first = $focusables[0]; + const $last = $focusables.pop(); + return {$first, $last}; } /** Active state marker */ @@ -137,16 +135,13 @@ export class ESLTooltip extends ESLPopup { if (e.key === TAB) this._onTabKey(e); } + /** Actions on TAB keypressed */ protected _onTabKey(e: KeyboardEvent): void { if (!this.activator) return; - const {firstFocusableElement, lastFocusableElement} = this; - if ( - !lastFocusableElement || - e.target === lastFocusableElement && !e.shiftKey || - e.target === firstFocusableElement && e.shiftKey - ) { + const {$first, $last} = this.$boundaryFocusable; + if (this.hasFocusLoop) return handleFocusChain(e, $first, $last) as void; + if (!$last || e.target === (e.shiftKey ? $first : $last)) { this.activator.focus(); - e.stopPropagation(); e.preventDefault(); } } diff --git a/src/modules/esl-utils/dom/focus.ts b/src/modules/esl-utils/dom/focus.ts index 806783e8f..e78d63e4a 100644 --- a/src/modules/esl-utils/dom/focus.ts +++ b/src/modules/esl-utils/dom/focus.ts @@ -4,7 +4,7 @@ import {TAB} from './keys'; * Chain focus order between passed elements. * Passive (should be called inside keyboard event handler) */ -export const handleFocusChain = (e: KeyboardEvent, first: HTMLElement, last: HTMLElement): boolean | undefined => { +export const handleFocusChain = (e: KeyboardEvent, first: HTMLElement | undefined, last: HTMLElement | undefined): boolean | undefined => { if (e.key !== TAB) return; if (last && e.target === first && e.shiftKey) { last.focus();