Skip to content

Commit 974401b

Browse files
authoredAug 12, 2020
feat(ui5-combobox): enable handling of arrow down/up keys
Now, selected item can be changed with arrow down / arrow up keys. Fixes: #1939 In addition some issues are resolved: Change event could be fired on Enter key press. Change event could be fired on focusout.
1 parent 13f1d2a commit 974401b

File tree

4 files changed

+139
-4
lines changed

4 files changed

+139
-4
lines changed
 

‎packages/main/src/ComboBox.js

+86-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ import getEffectiveAriaLabelText from "@ui5/webcomponents-base/dist/util/getEffe
66
import "@ui5/webcomponents-icons/dist/icons/slim-arrow-down.js";
77
import "@ui5/webcomponents-icons/dist/icons/decline.js";
88
import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
9-
import { isBackSpace, isDelete, isShow } from "@ui5/webcomponents-base/dist/Keys.js";
9+
import {
10+
isBackSpace,
11+
isDelete,
12+
isShow,
13+
isUp,
14+
isDown,
15+
isEnter,
16+
} from "@ui5/webcomponents-base/dist/Keys.js";
1017
import * as Filters from "./ComboBoxFilters.js";
1118

1219
import {
@@ -324,6 +331,7 @@ class ComboBox extends UI5Element {
324331
this._filteredItems = [];
325332
this._initialRendering = true;
326333
this._itemFocused = false;
334+
this._tempFilterValue = "";
327335
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
328336
}
329337

@@ -348,7 +356,15 @@ class ComboBox extends UI5Element {
348356
}
349357

350358
this._selectMatchingItem();
359+
360+
if (this._isKeyNavigation && this.responsivePopover && this.responsivePopover.opened) {
361+
this.focused = false;
362+
} else {
363+
this.focused = this === document.activeElement;
364+
}
365+
351366
this._initialRendering = false;
367+
this._isKeyNavigation = false;
352368
}
353369

354370
async onAfterRendering() {
@@ -381,14 +397,19 @@ class ComboBox extends UI5Element {
381397

382398
_focusout() {
383399
this.focused = false;
400+
401+
this._inputChange();
384402
}
385403

386404
_afterOpenPopover() {
387405
this._iconPressed = true;
406+
this._clearFocus();
388407
}
389408

390409
_afterClosePopover() {
391410
this._iconPressed = false;
411+
this._filteredItems = this.items;
412+
this._tempFilterValue = "";
392413

393414
// close device's keyboard and prevent further typing
394415
if (isPhone()) {
@@ -424,6 +445,8 @@ class ComboBox extends UI5Element {
424445
event.stopImmediatePropagation();
425446
}
426447

448+
this._clearFocus();
449+
this._tempFilterValue = value;
427450
this.filterValue = value;
428451
this.fireEvent("input");
429452

@@ -440,9 +463,56 @@ class ComboBox extends UI5Element {
440463
return Filters.StartsWith(str, this._filteredItems);
441464
}
442465

466+
_clearFocus() {
467+
this._filteredItems.map(item => {
468+
item.focused = false;
469+
470+
return item;
471+
});
472+
}
473+
474+
handleArrowKeyPress(event) {
475+
if (this.readonly || !this._filteredItems.length) {
476+
return;
477+
}
478+
479+
const isArrowDown = isDown(event);
480+
const isArrowUp = isUp(event);
481+
const currentItem = this._filteredItems.find(item => {
482+
return this.responsivePopover.opened ? item.focused : item.selected;
483+
});
484+
let indexOfItem = this._filteredItems.indexOf(currentItem);
485+
486+
event.preventDefault();
487+
488+
if ((indexOfItem === 0 && isArrowUp) || (this._filteredItems.length - 1 === indexOfItem && isArrowDown)) {
489+
return;
490+
}
491+
492+
this._clearFocus();
493+
494+
indexOfItem += isArrowDown ? 1 : -1;
495+
indexOfItem = indexOfItem < 0 ? 0 : indexOfItem;
496+
497+
this._filteredItems[indexOfItem].focused = true;
498+
this.filterValue = this._filteredItems[indexOfItem].text;
499+
this._isKeyNavigation = true;
500+
this._itemFocused = true;
501+
this.fireEvent("input");
502+
}
503+
443504
_keydown(event) {
505+
const isArrowKey = isDown(event) || isUp(event);
444506
this._autocomplete = !(isBackSpace(event) || isDelete(event));
445507

508+
if (isArrowKey) {
509+
this.handleArrowKeyPress(event);
510+
}
511+
512+
if (isEnter(event)) {
513+
this._inputChange();
514+
}
515+
446516
if (isShow(event) && !this.readonly && !this.disabled) {
447517
event.preventDefault();
448518
this._resetFilter();
@@ -472,16 +542,21 @@ class ComboBox extends UI5Element {
472542
_autoCompleteValue(current) {
473543
const currentValue = current;
474544
const matchingItems = this._startsWithMatchingItems(currentValue);
545+
const selectionValue = this._tempFilterValue ? this._tempFilterValue : currentValue;
475546

476547
if (matchingItems.length) {
477548
this._tempValue = matchingItems[0] ? matchingItems[0].text : current;
478549
} else {
479550
this._tempValue = current;
480551
}
481552

482-
if (matchingItems.length && (currentValue !== this._tempValue)) {
553+
if (matchingItems.length && (selectionValue !== this._tempValue)) {
554+
setTimeout(() => {
555+
this.inner.setSelectionRange(selectionValue.length, this._tempValue.length);
556+
}, 0);
557+
} else if (this._isKeyNavigation) {
483558
setTimeout(() => {
484-
this.inner.setSelectionRange(currentValue.length, this._tempValue.length);
559+
this.inner.setSelectionRange(0, this._tempValue.length);
485560
}, 0);
486561
}
487562
}
@@ -504,6 +579,10 @@ class ComboBox extends UI5Element {
504579
this._closeRespPopover();
505580
}
506581

582+
_itemMousedown(event) {
583+
event.preventDefault();
584+
}
585+
507586
_selectItem(event) {
508587
const listItem = event.detail.item;
509588

@@ -569,6 +648,10 @@ class ComboBox extends UI5Element {
569648
return this.responsivePopover ? this.responsivePopover.opened : false;
570649
}
571650

651+
get itemTabIndex() {
652+
return undefined;
653+
}
654+
572655
get ariaLabelText() {
573656
return getEffectiveAriaLabelText(this);
574657
}

‎packages/main/src/ComboBoxPopover.hbs

+3
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,16 @@
5050
separators="None"
5151
@ui5-item-click={{_selectItem}}
5252
@ui5-item-focused={{_onItemFocus}}
53+
@mousedown={{_itemMousedown}}
5354
mode="SingleSelect"
5455
>
5556
{{#each _filteredItems}}
5657
<ui5-li
5758
type="Active"
59+
._tabIndex={{itemTabIndex}}
5860
.mappedItem={{this}}
5961
?selected={{this.selected}}
62+
?focused={{this.focused}}
6063
>
6164
{{this.text}}
6265
</ui5-li>

‎packages/main/test/pages/ComboBox.html

+19
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@
107107
Change call count: <span id="change-count">0</span><br>
108108
</div>
109109

110+
<div class="demo-section">
111+
<span>Input event testing</span>
112+
113+
<ui5-combobox id="input-cb" style="width: 360px;">
114+
<ui5-cb-item text="Argentina"></ui5-cb-item>
115+
<ui5-cb-item text="Germany"></ui5-cb-item>
116+
</ui5-combobox>
117+
118+
<br>
119+
120+
Input value: <span id="input-placeholder"></span><br>
121+
Input call count: <span id="input-count">0</span><br>
122+
</div>
123+
110124

111125
<br>
112126
<br>
@@ -182,6 +196,11 @@ <h3>ComboBox in Compact</h3>
182196
document.getElementById("change-placeholder").innerHTML = event.target.value;
183197
document.getElementById("change-count").innerHTML = parseInt(document.getElementById("change-count").innerHTML) + 1;
184198
});
199+
200+
document.getElementById("input-cb").addEventListener("ui5-input", function(event) {
201+
document.getElementById("input-placeholder").innerHTML = event.target.filterValue;
202+
document.getElementById("input-count").innerHTML = parseInt(document.getElementById("input-count").innerHTML) + 1;
203+
});
185204
</script>
186205

187206
</body>

‎packages/main/test/specs/ComboBox.spec.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,36 @@ describe("General interaction", () => {
191191
assert.strictEqual(counter.getText(), "1", "Call count should be 1");
192192
});
193193

194+
it ("Tests input event", () => {
195+
browser.url("http://localhost:8080/test-resources/pages/ComboBox.html");
196+
197+
const counter = $("#input-count");
198+
const combo = $("#input-cb");
199+
const placeholder = $("#input-placeholder");
200+
const input = combo.shadow$("#ui5-combobox-input");
201+
202+
input.click();
203+
input.keys("ArrowDown");
204+
205+
assert.strictEqual(placeholder.getText(), "Argentina", "First items is selected");
206+
assert.strictEqual(counter.getText(), "1", "Call count should be 1");
207+
208+
input.keys("ArrowUp");
209+
210+
assert.strictEqual(placeholder.getText(), "Argentina", "Selection not changed");
211+
assert.strictEqual(counter.getText(), "1", "Input event is not fired when first item is selected and navigating with arrow up");
212+
213+
input.keys("ArrowDown");
214+
215+
assert.strictEqual(placeholder.getText(), "Germany", "Last item is selected");
216+
assert.strictEqual(counter.getText(), "2", "Call count should be 2");
217+
218+
input.keys("ArrowDown");
219+
220+
assert.strictEqual(placeholder.getText(), "Germany", "Selection not changed");
221+
assert.strictEqual(counter.getText(), "2", "Input event is not fired when last item is selected and navigating with arrow down");
222+
});
223+
194224
it ("Tests Combo with contains filter", () => {
195225
const combo = $("#contains-cb");
196226
const input = combo.shadow$("#ui5-combobox-input");
@@ -240,6 +270,6 @@ describe("General interaction", () => {
240270

241271
input.keys("a");
242272
listItems = popover.$("ui5-list").$$("ui5-li");
243-
assert.strictEqual(listItems.length, 0, "Items should be 0");
273+
assert.notOk(popover.opened, "Popover should be closed when no match");
244274
});
245275
});

0 commit comments

Comments
 (0)
Please sign in to comment.