-
Notifications
You must be signed in to change notification settings - Fork 163
fix(left-nav): fix focus trapping #11842
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
Changes from all commits
887f078
a840177
f295746
bf99f81
a8a09d9
28464bb
3f7ec94
e54bfca
ab93cca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,6 @@ | |
| import { LitElement, html } from 'lit'; | ||
| import { property } from 'lit/decorators.js'; | ||
| import { classMap } from 'lit/directives/class-map.js'; | ||
| import on from '../../globals/mixins/on'; | ||
| import { prefix } from '../../globals/settings'; | ||
| import HostListenerMixin from '../../globals/mixins/host-listener'; | ||
| import HostListener from '../../globals/decorators/host-listener'; | ||
|
|
@@ -43,18 +42,6 @@ class CDSSideNav extends HostListenerMixin(LitElement) { | |
| */ | ||
| private _hTransition: Handle | null = null; | ||
|
|
||
| /** | ||
| * A promise that is resolved when the transition is complete. | ||
| */ | ||
| private _transitionPromise = Promise.resolve(); | ||
|
|
||
| /** | ||
| * A promise that is resolved when the transition upon proprety update is complete. | ||
| */ | ||
| private get _updateAndTransitionPromise() { | ||
| return this.updateComplete.then(() => this._transitionPromise); | ||
| } | ||
|
|
||
| /** | ||
| * Cleans the handle for `transitionend` event listener. | ||
| */ | ||
|
|
@@ -69,19 +56,8 @@ class CDSSideNav extends HostListenerMixin(LitElement) { | |
| */ | ||
| @HostListener('parentRoot:eventButtonToggle') | ||
| // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to | ||
| private _handleButtonToggle = async (event: CustomEvent) => { | ||
| protected _handleButtonToggle = async (event: CustomEvent) => { | ||
| this.expanded = event.detail.active; | ||
| if (this.expanded) { | ||
| await this._updateAndTransitionPromise; | ||
| // Checks if the side nav is not collapsed during the animation | ||
| if (this.expanded) { | ||
| ( | ||
| this.querySelector( | ||
| (this.constructor as typeof CDSSideNav).selectorNavItems | ||
| ) as HTMLElement | ||
| )?.focus(); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -138,19 +114,6 @@ class CDSSideNav extends HostListenerMixin(LitElement) { | |
| super.disconnectedCallback(); | ||
| } | ||
|
|
||
| shouldUpdate(changedProperties) { | ||
| if (changedProperties.has('expanded')) { | ||
| this._transitionPromise = new Promise((resolve) => { | ||
| this._cleanHTransition(); | ||
| this._hTransition = on(this, 'transitionend', () => { | ||
| this._cleanHTransition(); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| updated(changedProperties) { | ||
| const doc = this.getRootNode() as Document; | ||
| if (changedProperties.has('collapseMode')) { | ||
|
|
@@ -179,6 +142,11 @@ class CDSSideNav extends HostListenerMixin(LitElement) { | |
| forEach(headerItems, (item) => { | ||
| item.setAttribute('tabindex', '-1'); | ||
| }); | ||
| ( | ||
| this.querySelector( | ||
| (this.constructor as typeof CDSSideNav).selectorNavItems | ||
| ) as HTMLElement | ||
| )?.focus(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving the logic out of the event listener and into the update lifecycle ensures we focus after the seletorNavItems are added to the DOM. |
||
| } else { | ||
| forEach(headerItems, (item) => { | ||
| item.removeAttribute('tabindex'); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,11 +62,31 @@ class C4DLeftNav extends StableSelectorMixin(CDSSideNav) { | |
| private _importedSideNav = false; | ||
|
|
||
| /** | ||
| * Handles `c4d-request-focus-wrap` event on the document. | ||
| * Handles `${prefix}-header-menu-button-toggle` event on the document. | ||
| */ | ||
| @HostListener('parentRoot:eventButtonToggle') | ||
| // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to | ||
| protected _handleButtonToggle = async (event: CustomEvent) => { | ||
| if (!this._importedSideNav) { | ||
| await Promise.all([ | ||
| import('./left-nav-name'), | ||
| import('./left-nav-menu'), | ||
| import('./left-nav-menu-section'), | ||
| import('./left-nav-menu-item'), | ||
| import('./left-nav-menu-category-heading'), | ||
| import('./left-nav-overlay'), | ||
| ]); | ||
| this._importedSideNav = true; | ||
| } | ||
| this.expanded = event.detail.active; | ||
| }; | ||
|
|
||
| /** | ||
| * Handles `cds-request-focus-wrap` event on the document dispatched from focuswrap. | ||
| * | ||
| * @param event The event. | ||
| */ | ||
| @HostListener('document:c4d-request-focus-wrap') | ||
| @HostListener('document:cds-request-focus-wrap') | ||
| // @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to | ||
| private _handleRequestMenuButtonFocusWrap = (event: CustomEvent) => { | ||
| const { selectorButtonToggle } = this.constructor as typeof C4DLeftNav; | ||
|
|
@@ -221,7 +241,7 @@ class C4DLeftNav extends StableSelectorMixin(CDSSideNav) { | |
| document.addEventListener('click', this._handleClickOut.bind(this)); | ||
| } | ||
|
|
||
| updated(changedProperties) { | ||
| async updated(changedProperties) { | ||
| super.updated(changedProperties); | ||
| const { usageMode } = this; | ||
| if ( | ||
|
|
@@ -256,15 +276,6 @@ class C4DLeftNav extends StableSelectorMixin(CDSSideNav) { | |
| ${c4dPrefix}-masthead-composite` | ||
| ) | ||
| ?.querySelector(`${c4dPrefix}-masthead`); | ||
| if (expanded && !this._importedSideNav) { | ||
| import('./left-nav-name'); | ||
| import('./left-nav-menu'); | ||
| import('./left-nav-menu-section'); | ||
| import('./left-nav-menu-item'); | ||
| import('./left-nav-menu-category-heading'); | ||
| import('./left-nav-overlay'); | ||
| this._importedSideNav = true; | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if (expanded && masthead) { | ||
| this._hFocusWrap = focuswrap(this.shadowRoot!, [ | ||
| startSentinelNode, | ||
|
|
@@ -312,7 +323,7 @@ class C4DLeftNav extends StableSelectorMixin(CDSSideNav) { | |
| } | ||
| } | ||
|
|
||
| private _renderSentinel = (side: String) => { | ||
| private _renderSentinel = (side: string) => { | ||
| return html` | ||
| <button | ||
| id="${side}-sentinel" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we change the value of
this.expandedhere, we then immediately wait for the resolution ofthis.updatefrom the component's update/render cycle.this.updateis already resolved from the last run of the component lifecycle and our current function hasn't been off the main thread to let the component start a new update.Since we're awaiting a resolved promise, we immediate move on to try to focus on elements that are not yet in the DOM.