Skip to content

Commit 806fde4

Browse files
Navigating the App Language Selector with the keyboard (#19887)
* dropdown keyboard accessibility issue * Eslint update * Improve accessibility of the app language dropdown. * Bring back combobox list element * Add keyboard support for arrowup and arrowdown * use change event for value change * Change button element for a div as trigger * Unused import --------- Co-authored-by: Mads Rasmussen <[email protected]>
1 parent 1a65f27 commit 806fde4

File tree

1 file changed

+79
-36
lines changed

1 file changed

+79
-36
lines changed

src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/app-language-select.element.ts

Lines changed: 79 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@ import { UmbLanguageCollectionRepository } from '../collection/index.js';
22
import type { UmbLanguageDetailModel } from '../types.js';
33
import type { UmbAppLanguageContext } from '../global-contexts/index.js';
44
import { UMB_APP_LANGUAGE_CONTEXT } from '../constants.js';
5-
import type { UUIPopoverContainerElement } from '@umbraco-cms/backoffice/external/uui';
6-
import {
7-
css,
8-
html,
9-
customElement,
10-
state,
11-
repeat,
12-
ifDefined,
13-
query,
14-
nothing,
15-
} from '@umbraco-cms/backoffice/external/lit';
5+
import type {
6+
UUIComboboxListElement,
7+
UUIComboboxListEvent,
8+
UUIPopoverContainerElement,
9+
} from '@umbraco-cms/backoffice/external/uui';
10+
import { css, html, customElement, state, repeat, query, nothing } from '@umbraco-cms/backoffice/external/lit';
1611
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
1712
import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user';
1813

@@ -98,12 +93,11 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
9893
}
9994
}
10095

101-
#onPopoverToggle(event: ToggleEvent) {
96+
#onBeforePopoverToggle(event: ToggleEvent) {
10297
// TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
10398
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
10499
// @ts-ignore
105-
this._isOpen = event.newState === 'open';
106-
if (this._isOpen && !this.#languagesObserver) {
100+
if (event.newState === 'open' && !this.#languagesObserver) {
107101
if (this._popoverElement) {
108102
const host = this.getBoundingClientRect();
109103
this._popoverElement.style.width = `${host.width}px`;
@@ -113,51 +107,101 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
113107
}
114108
}
115109

110+
#onPopoverToggle(event: ToggleEvent) {
111+
// TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
112+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
113+
// @ts-ignore
114+
this._isOpen = event.newState === 'open';
115+
}
116+
117+
#onTriggerClick() {
118+
if (this._isOpen) {
119+
this._popoverElement?.hidePopover();
120+
} else {
121+
this._popoverElement?.showPopover();
122+
}
123+
this.requestUpdate();
124+
}
125+
126+
#onTriggerKeydown = (e: KeyboardEvent) => {
127+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
128+
this._popoverElement?.showPopover();
129+
}
130+
if (e.key === ' ') {
131+
this._popoverElement?.togglePopover();
132+
}
133+
};
134+
116135
#chooseLanguage(unique: string) {
117136
this.#appLanguageContext?.setLanguage(unique);
118-
this._isOpen = false;
119137
this._popoverElement?.hidePopover();
120138
}
121139

140+
#onLanguageSelectionChange(event: UUIComboboxListEvent) {
141+
if (!this._isOpen) return;
142+
143+
const target = event.target as UUIComboboxListElement;
144+
const value = target?.value as string;
145+
if (value) {
146+
this.#chooseLanguage(value);
147+
}
148+
}
149+
122150
override render() {
123151
return html`${this.#renderTrigger()} ${this.#renderContent()}`;
124152
}
125153

126154
#renderTrigger() {
127-
return html`<button id="toggle" data-mark="action:open" popovertarget="dropdown-popover">
128-
<span
129-
>${this._appLanguage?.name}
130-
${this._appLanguageIsReadOnly ? this.#renderReadOnlyTag(this._appLanguage?.unique) : nothing}</span
131-
>
155+
return html` <div
156+
id="toggle"
157+
data-mark="action:open"
158+
popovertarget="dropdown-popover"
159+
tabindex="0"
160+
@click=${this.#onTriggerClick}
161+
@keydown=${this.#onTriggerKeydown}>
162+
<span>
163+
${this._appLanguage?.name}
164+
${this._appLanguageIsReadOnly ? this.#renderReadOnlyTag(this._appLanguage?.unique) : nothing}
165+
</span>
132166
<uui-symbol-expand .open=${this._isOpen}></uui-symbol-expand>
133-
</button>`;
167+
</div>`;
134168
}
135169

136170
#renderContent() {
137171
return html` <uui-popover-container
138172
id="dropdown-popover"
139173
data-mark="app-language-menu"
140-
@beforetoggle=${this.#onPopoverToggle}>
174+
@beforetoggle=${this.#onBeforePopoverToggle}
175+
@toggle=${this.#onPopoverToggle}>
141176
<umb-popover-layout>
142177
<uui-scroll-container style="max-height:calc(100vh - (var(--umb-header-layout-height) + 60px));">
143-
${repeat(
144-
this._languages,
145-
(language) => language.unique,
146-
(language) => html`
147-
<uui-menu-item
148-
label=${ifDefined(language.name)}
149-
data-mark="${language.entityType}:${language.unique}"
150-
?active=${language.unique === this._appLanguage?.unique}
151-
@click-label=${() => this.#chooseLanguage(language.unique)}>
152-
${this.#isLanguageReadOnly(language.unique) ? this.#renderReadOnlyTag(language.unique) : nothing}
153-
</uui-menu-item>
154-
`,
155-
)}
178+
${this.#renderOptions()}
156179
</uui-scroll-container>
157180
</umb-popover-layout>
158181
</uui-popover-container>`;
159182
}
160183

184+
#renderOptions() {
185+
if (!this._isOpen) return nothing;
186+
187+
return html`<uui-combobox-list
188+
aria-label="App language"
189+
.for=${this}
190+
.value=${this._appLanguage?.unique || ''}
191+
@change=${this.#onLanguageSelectionChange}>
192+
${repeat(
193+
this._languages,
194+
(language) => language.unique,
195+
(language) => html`
196+
<uui-combobox-list-option tabindex="0" value=${language.unique}>
197+
${language.name}
198+
${this.#isLanguageReadOnly(language.unique) ? this.#renderReadOnlyTag(language.unique) : nothing}
199+
</uui-combobox-list-option>
200+
`,
201+
)}
202+
</uui-combobox-list>`;
203+
}
204+
161205
#isLanguageReadOnly(culture?: string) {
162206
if (!culture) return false;
163207
return this._disallowedLanguages.find((language) => language.unique === culture) ? true : false;
@@ -178,7 +222,6 @@ export class UmbAppLanguageSelectElement extends UmbLitElement {
178222
179223
#toggle {
180224
color: var(--uui-color-text);
181-
width: 100%;
182225
text-align: left;
183226
background: none;
184227
border: none;

0 commit comments

Comments
 (0)