Skip to content

Commit f18fd45

Browse files
authored
fix(ui5-select): keyboard/selection handling improvement (#2907)
1 parent c073ad4 commit f18fd45

File tree

5 files changed

+155
-110
lines changed

5 files changed

+155
-110
lines changed

packages/main/src/Option.js

+11
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ const metadata = {
6666
stableDomRef: {
6767
type: String,
6868
},
69+
70+
/**
71+
* Defines the focused state of the <code>ui5-option</code>.
72+
* @type {boolean}
73+
* @defaultvalue false
74+
* @since 1.0.0-rc.13
75+
* @private
76+
*/
77+
_focused: {
78+
type: Boolean,
79+
},
6980
},
7081
slots: /** @lends sap.ui.webcomponents.main.Option.prototype */ {
7182
/**

packages/main/src/Select.js

+45-39
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,6 @@ class Select extends UI5Element {
324324
if (!this._listWidth) {
325325
this._listWidth = this.responsivePopover.offsetWidth;
326326
}
327-
if (this.responsivePopover.querySelector("[ui5-li][focused]:not([selected])")) {
328-
// selection changed programmatically => apply focus to the newly selected item
329-
this._applyFocusAfterOpen();
330-
}
331327
}
332328
}
333329

@@ -385,9 +381,11 @@ class Select extends UI5Element {
385381
}
386382

387383
opt.selected = false;
384+
opt._focused = false;
388385

389386
return {
390387
selected: false,
388+
_focused: false,
391389
disabled: opt.disabled,
392390
icon: opt.icon,
393391
value: opt.value,
@@ -399,15 +397,19 @@ class Select extends UI5Element {
399397

400398
if (lastSelectedOptionIndex > -1 && !opts[lastSelectedOptionIndex].disabled) {
401399
opts[lastSelectedOptionIndex].selected = true;
400+
opts[lastSelectedOptionIndex]._focused = true;
402401
this.options[lastSelectedOptionIndex].selected = true;
402+
this.options[lastSelectedOptionIndex]._focused = true;
403403
this._text = opts[lastSelectedOptionIndex].textContent;
404404
this._selectedIndex = lastSelectedOptionIndex;
405405
} else {
406406
this._text = "";
407407
this._selectedIndex = -1;
408408
if (opts[firstEnabledOptionIndex]) {
409409
opts[firstEnabledOptionIndex].selected = true;
410+
opts[firstEnabledOptionIndex]._focused = true;
410411
this.options[firstEnabledOptionIndex].selected = true;
412+
this.options[firstEnabledOptionIndex]._focused = true;
411413
this._selectedIndex = firstEnabledOptionIndex;
412414
this._text = this.options[firstEnabledOptionIndex].textContent;
413415
}
@@ -444,14 +446,24 @@ class Select extends UI5Element {
444446
event.preventDefault();
445447
}
446448

447-
if (!this._isPickerOpen) {
448-
this._handleArrowNavigation(event, true);
449+
if (isEscape(event) && this._isPickerOpen) {
450+
this._escapePressed = true;
451+
}
452+
453+
if (isEnter(event)) {
454+
this._handleSelectionChange();
449455
}
456+
457+
this._handleArrowNavigation(event, true);
450458
}
451459

452460
_onkeyup(event) {
453-
if (isSpace(event) && !this._isPickerOpen) {
454-
this._toggleRespPopover();
461+
if (isSpace(event)) {
462+
if (this._isPickerOpen) {
463+
this._handleSelectionChange();
464+
} else {
465+
this._toggleRespPopover();
466+
}
455467
}
456468
}
457469

@@ -472,9 +484,13 @@ class Select extends UI5Element {
472484
_handleItemPress(event) {
473485
const item = event.detail.item;
474486
const selectedItemIndex = this._getSelectedItemIndex(item);
475-
this._select(selectedItemIndex);
476487

477-
this._toggleRespPopover();
488+
this._handleSelectionChange(selectedItemIndex);
489+
}
490+
491+
_itemMousedown(event) {
492+
// prevent actual focus of items
493+
event.preventDefault();
478494
}
479495

480496
_onclick(event) {
@@ -483,36 +499,13 @@ class Select extends UI5Element {
483499
}
484500

485501
/**
486-
* The user used arrow up/down on the list
502+
* The user selected an item with Enter or Space
487503
* @private
488504
*/
489-
_handleSelectionChange(event) {
490-
const item = event.detail.selectedItems[0];
491-
const selectedItemIndex = this._getSelectedItemIndex(item);
492-
this._select(selectedItemIndex);
493-
}
505+
_handleSelectionChange(index = this._selectedIndex) {
506+
this._select(index);
494507

495-
_applyFocusAfterOpen() {
496-
if (!this._currentlySelectedOption) {
497-
return;
498-
}
499-
500-
const li = this.responsivePopover.querySelector(`#${this._currentlySelectedOption._id}-li`);
501-
if (!li) {
502-
return;
503-
}
504-
505-
this.responsivePopover.querySelector("[ui5-list]").focusItem(li);
506-
}
507-
508-
_handlePickerKeydown(event) {
509-
if (isEscape(event) && this._isPickerOpen) {
510-
this._escapePressed = true;
511-
}
512-
513-
if (isEnter(event) || isSpace(event)) {
514-
this._shouldClosePopover = true;
515-
}
508+
this._toggleRespPopover();
516509
}
517510

518511
_handleArrowNavigation(event, shouldFireEvent) {
@@ -530,14 +523,22 @@ class Select extends UI5Element {
530523
}
531524

532525
this.options[this._selectedIndex].selected = false;
526+
this.options[this._selectedIndex]._focused = false;
527+
533528
this.options[nextIndex].selected = true;
529+
this.options[nextIndex]._focused = true;
530+
534531
this._selectedIndex = nextIndex === -1 ? this._selectedIndex : nextIndex;
535532

536533
if (currentIndex !== this._selectedIndex) {
534+
// Announce new item even if picker is opened.
535+
// The aria-activedescendents attribute can't be used,
536+
// because listitem elements are in different shadow dom
537537
this.itemSelectionAnnounce();
538538
}
539539

540-
if (shouldFireEvent) {
540+
if (shouldFireEvent && !this._isPickerOpen) {
541+
// arrow pressed on closed picker - do selection change
541542
this._fireChangeEvent(this.options[nextIndex]);
542543
}
543544
}
@@ -556,7 +557,12 @@ class Select extends UI5Element {
556557
this._lastSelectedOption = this.options[this._selectedIndex];
557558
}
558559

560+
_afterOpen() {
561+
this.opened = true;
562+
}
563+
559564
_afterClose() {
565+
this.opened = false;
560566
this._iconPressed = false;
561567
this._listWidth = 0;
562568

@@ -674,7 +680,7 @@ class Select extends UI5Element {
674680
itemSelectionAnnounce() {
675681
const invisibleText = this.shadowRoot.querySelector(`#${this._id}-selectionText`);
676682

677-
if (this.focused && !this._isPickerOpen && this._currentlySelectedOption) {
683+
if (this.focused && this._currentlySelectedOption) {
678684
invisibleText.textContent = this._currentlySelectedOption.textContent;
679685
} else {
680686
invisibleText.textContent = "";

packages/main/src/SelectPopover.hbs

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
content-only-on-desktop
66
placement-type="Bottom"
77
horizontal-align="Left"
8-
@ui5-after-open="{{_applyFocusAfterOpen}}"
8+
@ui5-after-open="{{_afterOpen}}"
99
@ui5-before-open="{{_beforeOpen}}"
1010
@ui5-after-close="{{_afterClose}}"
1111
@keydown="{{_onkeydown}}"
@@ -41,15 +41,15 @@
4141
<ui5-list
4242
mode="SingleSelectAuto"
4343
separators="None"
44-
@keydown="{{_handlePickerKeydown}}"
45-
@ui5-selection-change="{{_handleSelectionChange}}"
44+
@mousedown="{{_itemMousedown}}"
4645
@ui5-item-press="{{_handleItemPress}}"
4746
>
4847
{{#each _syncedOptions}}
4948
<ui5-li
5049
id="{{this.id}}-li"
5150
icon="{{this.icon}}"
5251
?selected="{{this.selected}}"
52+
?focused="{{this._focused}}"
5353
?disabled="{{this.disabled}}"
5454
?aria-selected="{{this.selected}}"
5555
data-ui5-stable="{{this.stableDomRef}}"

packages/main/src/themes/Select.css

+5
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,8 @@
3535
:host(:not([disabled])) {
3636
cursor: pointer;
3737
}
38+
39+
:host([focused][opened]),
40+
:host([value-state]:not([value-state="None"])[focused][opened]) {
41+
outline: none;
42+
}

0 commit comments

Comments
 (0)