Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ESLTogglable based focus management (UPDATED v2) #2753

Open
wants to merge 18 commits into
base: main-beta
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
376f388
feat(esl-utils): add extended `handleFocusFlow` keyboard based focus …
ala-n Nov 8, 2024
c954d72
feat(esl-toggleable): add out of the box `ESLToggleable` focus manager
ala-n Nov 8, 2024
6ef1f2e
feat(esl-tooltip): get rid from inner `hasFocusLoop` and custom focus…
ala-n Nov 8, 2024
b5260b9
feat(esl-share): separate `ESLSharePopup` implementation from `ESLToo…
ala-n Nov 8, 2024
ea8dd94
fix(esl-share): fix inner ESLToggleableActionParams instances type
ala-n Nov 8, 2024
28a5bc9
Merge branch 'main-beta' into feat/focus-management
ala-n Nov 11, 2024
2b8a0c7
feat(esl-toggleable): update focusBehaviour option to smoothly suppor…
ala-n Nov 14, 2024
11b10ea
feat(esl-popup): get rid from all focus management code
ala-n Nov 14, 2024
04d6a63
fix(esl-share): simplify code and remove overrides (according to esl-…
ala-n Nov 14, 2024
699ac7f
fix(esl-tooltip): simplify code and remove overrides (according to es…
ala-n Nov 14, 2024
b3c62f3
Merge pull request #2766 from exadel-inc/feat/focus-management-2
ala-n Nov 14, 2024
1e457ad
style(esl-popup): fix type import style for `FocusFlowType`
ala-n Nov 14, 2024
ea50c3f
Merge branch 'feat/focus-management-2' into feat/focus-management
ala-n Nov 14, 2024
f5e906b
docs: fix usage of british version of the word `behavior`
ala-n Nov 14, 2024
b729e08
docs(esl-toggleable): TS doc fixes
ala-n Nov 14, 2024
8c338c6
Merge remote-tracking branch 'origin/main-beta' into feat/focus-manag…
ala-n Nov 14, 2024
cf1ed1b
style(esl-toggleable): fix focus behavior for chain focus flow
ala-n Nov 14, 2024
60e951f
style(esl-toggleable): fix false observation for closed toggleable
ala-n Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/COMMIT_CONVENTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ You can increase the importance of the patch changes to the minor using the `MIN
feat!: Hey I'm breaking something that already exist
```

**Identify everything that break or change existing API or behaviour with the `BREACKING CHAGES:` list
**Identify everything that break or change existing API or behavior with the `BREACKING CHAGES:` list
```text
feat!: Hey I'm breaking something that already exist
Expand Down
2 changes: 1 addition & 1 deletion site/views/components/esl-panel-group.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: content
title: ESL Panel Group
seoTitle: ESL Panel Group - custom element to group ESLPanel-s to have Tabs or Accordion behaviour
seoTitle: ESL Panel Group - custom element to group ESLPanel-s to have Tabs or Accordion behavior
name: ESL Panel Group
tags: components
aside:
Expand Down
2 changes: 1 addition & 1 deletion site/views/components/esl-toggleable.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: content
title: ESL Toggleable
seoTitle: ESL Toggleable - custom element to have basic show/hide or other state-full behaviour
seoTitle: ESL Toggleable - custom element to have basic show/hide or other state-full behavior
name: ESL Toggleable
tags: components
aside:
Expand Down
2 changes: 1 addition & 1 deletion site/views/examples/image.njk
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ aside:
<h3>Mode: cover</h3>
<p>No inner image, image is rendered by background image.</p>
<p>ESL Image has no own size. Can be used with img-container classes</p>
<p><b>Inscribe</b> can be used to declare inscribe image behaviour</p>
<p><b>Inscribe</b> can be used to declare inscribe image behavior</p>
</div>
</div>
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class ESLNoneCarouselRenderer extends ESLCarouselRenderer {

constructor($carousel: ESLCarousel, options: ESLCarouselConfig) {
super($carousel, options);
// Note blocks touch plugin from activating (consider rework if scroll behaviour is requested)
// Note blocks touch plugin from activating (consider rework if scroll behavior is requested)
Object.defineProperty(this, 'count', {get: () => this.size});
}

Expand Down
2 changes: 1 addition & 1 deletion src/modules/esl-image/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Was originally developed as an alternative to `<picture>` element, but with more
- Attributes observing.
- A11y.

### Accessibility behaviour
### Accessibility behavior
ESL Image uses 'img' role if the role is not explicitly provided.
If the role is 'img' then `alt` attribute is used as the `aria-label` for the image.
In case `alt` is not provided then an empty value is used as a fallback.
Expand Down
2 changes: 1 addition & 1 deletion src/modules/esl-image/core/esl-image.shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface ESLImageTagShape extends ESLBaseElementShape<ESLImage> {
lazy?: boolean | 'none' | 'manual' | 'auto';
/** Define load-allowed marker for lazy images */
'lazy-triggered'?: boolean;
/** Define query change behaviour */
/** Define query change behavior */
'refresh-on-update'?: boolean;
/** Define CSS class for inner image */
'inner-image-class'?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/modules/esl-panel-group/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ESLPanelGroup.register();
- `mode-cls-target` - Element [ESLTraversingQuery](../esl-traversing-query/README.md) selector to add class that identifies mode (ESLPanelGroup itself by default)
- `animation-class` - class(es) to be added during animation ('animate' by default)
- `no-animate` - list of breakpoints to skip collapse/expand animation (for both Group and Panel animations)
- `refresh-strategy` - defines behaviour of active panel(s) in case of configuration change:
- `refresh-strategy` - defines behavior of active panel(s) in case of configuration change:
* `initial` - activates initially opened panel(s)
* `last` - maintains a currently active panel(s) open
* `open` - open max of available panels
Expand Down
2 changes: 1 addition & 1 deletion src/modules/esl-panel-group/core/esl-panel-group.shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface ESLPanelGroupTagShape extends ESLBaseElementShape<ESLPanelGroup
/** Define a list of breakpoints to disable collapse/expand animation (for both Group and Panel animations)*/
'no-animate'?: string;

/** Define active panel(s) behaviour in case of configuration change. Supported values: `last|initial|close|open`*/
/** Define active panel(s) behavior in case of configuration change. Supported values: `last|initial|close|open`*/
'refresh-strategy'?: string;

/** Define minimum number of panels that could be opened ('1' by default, supported values: values: `0 | 1 | number | all`) */
Expand Down
2 changes: 1 addition & 1 deletion src/modules/esl-panel-group/core/esl-panel-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class ESLPanelGroup extends ESLBaseElement {
@attr({defaultValue: '1'}) public maxOpenItems: string;

/**
* Define active panel(s) behaviour in case of configuration change (mode, min-open-items, max-open-items)
* Define active panel(s) behavior in case of configuration change (mode, min-open-items, max-open-items)
* `last` (default) - try to preserve currently active panel(s)
* `initial` - activates initially opened panel(s)
* `open` - open max of available panels
Expand Down
17 changes: 11 additions & 6 deletions src/modules/esl-popup/core/esl-popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import {calcPopupPosition, isOnHorizontalAxis} from './esl-popup-position';
import {ESLPopupPlaceholder} from './esl-popup-placeholder';

import type {FocusFlowType} from '../../esl-utils/dom';
import type {ESLToggleableActionParams} from '../../esl-toggleable/core';
import type {PositionType, PositionOriginType, IntersectionRatioRect} from './esl-popup-position';

Expand Down Expand Up @@ -44,8 +45,6 @@
container?: string;
/** Container element that defines bounds of popups visibility (is not taken into account if the container attr is set on popup) */
containerEl?: HTMLElement;
/** Autofocus on popup/activator */
autofocus?: boolean;

/** Extra class to add to popup on activation */
extraClass?: string;
Expand Down Expand Up @@ -111,6 +110,16 @@
@attr({parser: parseBoolean, serializer: toBooleanAttribute, defaultValue: true})
public override closeOnOutsideAction: boolean;

/**
* Focus behavior. Available values:
* - 'none' - no focus management
* - 'grab' - focus on the first focusable element
* - 'chain' (default) - focus on the first focusable element first and return focus to the activator after the last focusable element
* - 'loop' - focus on the first focusable element and loop through the focusable elements
*/
@attr({defaultValue: 'chain'})
public override focusBehavior: FocusFlowType;

public $placeholder: ESLPopupPlaceholder | null;

protected _extraClass?: string;
Expand Down Expand Up @@ -227,9 +236,6 @@
// running as a separate task solves the problem with incorrect positioning on the first showing
if (wasOpened) this.afterOnShow(params);
else afterNextRender(() => this.afterOnShow(params));

// Autofocus logic
afterNextRender(() => params.autofocus && this.focus({preventScroll: true}));
}

/**
Expand All @@ -241,7 +247,6 @@
this.beforeOnHide(params);
super.onHide(params);
this.afterOnHide(params);
params.autofocus && this.activator?.focus({preventScroll: true});
}

/**
Expand Down Expand Up @@ -443,7 +448,7 @@
declare global {
export interface ESLLibrary {
Popup: typeof ESLPopup;
}

Check warning on line 451 in src/modules/esl-popup/core/esl-popup.ts

View workflow job for this annotation

GitHub Actions / Linting

File has too many lines (455). Maximum allowed is 450
export interface HTMLElementTagNameMap {
'esl-popup': ESLPopup;
}
Expand Down
2 changes: 1 addition & 1 deletion src/modules/esl-share/actions/external-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {ESLShareUrlGenericAction} from './url-generic-action';

import type {ESLShareButton} from '../core/esl-share-button';

/** Sharing using default browser link behaviour {@link ESLShareBaseAction} implementation */
/** Sharing using default browser link behavior {@link ESLShareBaseAction} implementation */
@ESLShareUrlGenericAction.register
export class ESLShareExternalAction extends ESLShareUrlGenericAction {
public static override readonly is: string = 'external';
Expand Down
4 changes: 2 additions & 2 deletions src/modules/esl-share/core/esl-share-popup.shape.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type {ESLTooltipTagShape} from '../../esl-tooltip/core/esl-tooltip.shape';
import type {ESLPopupTagShape} from '../../esl-popup/core/esl-popup.shape';
import type {ESLSharePopup} from './esl-share-popup';

/**
* Tag declaration interface of ESL Share Popup element
* Used for TSX declaration
*/
export interface ESLSharePopupTagShape extends ESLTooltipTagShape<ESLSharePopup> {
export interface ESLSharePopupTagShape extends ESLPopupTagShape<ESLSharePopup> {
/** Allowed children */
children?: any;
}
Expand Down
41 changes: 32 additions & 9 deletions src/modules/esl-share/core/esl-share-popup.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {ExportNs} from '../../esl-utils/environment/export-ns';
import {ESLTooltip} from '../../esl-tooltip/core/esl-tooltip';
import {bind, listen, memoize, prop} from '../../esl-utils/decorators';
import {ESLPopup} from '../../esl-popup/core/esl-popup';
import {bind, boolAttr, listen, memoize} from '../../esl-utils/decorators';
import {ESLShareButton} from './esl-share-button';
import {ESLShareConfig} from './esl-share-config';

import type {ESLTooltipActionParams} from '../../esl-tooltip/core/esl-tooltip';
import type {ESLPopupActionParams} from '../../esl-popup/core/esl-popup';
import type {ESLShareButtonConfig} from './esl-share-config';

export type {ESLSharePopupTagShape} from './esl-share-popup.shape';
Expand All @@ -13,7 +13,7 @@ function stringifyButtonsList(btns: ESLShareButtonConfig[]): string {
return btns.map((btn) => btn.name).join(',');
}

export interface ESLSharePopupActionParams extends ESLTooltipActionParams {
export interface ESLSharePopupActionParams extends ESLPopupActionParams {
/** list of social networks or groups of them to display */
list?: string;
}
Expand All @@ -28,12 +28,12 @@ export interface ESLSharePopupActionParams extends ESLTooltipActionParams {
* - forwards the sharing attributes from the host share {@link ESLShare} component
*/
@ExportNs('SharePopup')
export class ESLSharePopup extends ESLTooltip {
export class ESLSharePopup extends ESLPopup {
static override is = 'esl-share-popup';

/** Default params to pass into the share popup */
static override DEFAULT_PARAMS: ESLSharePopupActionParams = {
...ESLTooltip.DEFAULT_PARAMS,
...ESLPopup.DEFAULT_PARAMS,
position: 'top',
hideDelay: 300
};
Expand All @@ -49,24 +49,47 @@ export class ESLSharePopup extends ESLTooltip {

/** Shared instance of ESLSharePopup */
@memoize()
public static override get sharedInstance(): ESLSharePopup {
public static get sharedInstance(): ESLSharePopup {
return ESLSharePopup.create();
}

@prop(true) public override hasFocusLoop: boolean;
/** Disable arrow at Tooltip */
@boolAttr() public disableArrow: boolean;

/** Hashstring with a list of buttons already rendered in the popup */
protected _list: string = '';

public override onShow(params: ESLTooltipActionParams): void {
public override connectedCallback(): void {
super.connectedCallback();
this.classList.add(ESLPopup.is);
this.classList.toggle('disable-arrow', this.disableArrow);
this.tabIndex = 0;
}

/** Sets initial state of the Tooltip */
protected override setInitialState(): void {}

public override onShow(params: ESLSharePopupActionParams): void {
if (params.disableArrow) {
this.disableArrow = params.disableArrow;
}
if (params.list) {
const buttonsList = ESLShareConfig.instance.get(params.list);
this.appendButtonsFromList(buttonsList);
}
this.forwardAttributes();
this.dir = params.dir || '';
this.lang = params.lang || '';
this.parentNode !== document.body && document.body.appendChild(this);
super.onShow(params);
}

/** Actions to execute on Tooltip hiding. */
public override onHide(params: ESLSharePopupActionParams): void {
super.onHide(params);
this.parentNode === document.body && document.body.removeChild(this);
}

/** Checks that the button list from the config was already rendered in the popup. */
protected isEqual(config: ESLShareButtonConfig[]): boolean {
return stringifyButtonsList(config) === this._list;
Expand Down
9 changes: 9 additions & 0 deletions src/modules/esl-toggleable/core/esl-toggleable.shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ export interface ESLToggleableTagShape<T extends ESLToggleable = ESLToggleable>
/** Open toggleable marker. Can be used to define initial state */
'open'?: boolean;

/**
* Define focus behavior
* - 'none' - no focus management
* - 'grab' - focus on the first focusable element
* - 'chain' - focus on the first focusable element first and return focus to the activator after the last focusable element
* - 'loop' - focus on the first focusable element and loop through the focusable elements
*/
'focus-behavior'?: 'none' | 'chain' | 'loop';

/** Define Toggleable group meta information to organize groups */
'group'?: string;

Expand Down
Loading