Skip to content

Commit 2ebc6a7

Browse files
authored
fix(ui5-multi-combobox): prefilter selected items on n more click (#3931)
FIXES: #3639 FIXES: #3640 FIXES: #3761 FIXES: #3762
1 parent f2093a0 commit 2ebc6a7

File tree

5 files changed

+156
-63
lines changed

5 files changed

+156
-63
lines changed

packages/main/src/MultiComboBox.hbs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
show-more
1111
class="ui5-multi-combobox-tokenizer"
1212
?disabled="{{disabled}}"
13-
@ui5-show-more-items-press="{{_showMorePopover}}"
13+
@ui5-show-more-items-press="{{_showFilteredItems}}"
1414
@ui5-token-delete="{{_tokenDelete}}"
1515
@focusout="{{_tokenizerFocusOut}}"
1616
@focusin="{{_tokenizerFocusIn}}"
@@ -65,6 +65,7 @@
6565
tabindex="-1"
6666
@click="{{togglePopover}}"
6767
@mousedown="{{_onIconMousedown}}"
68+
@focusin="{{_forwardFocusToInner}}"
6869
?pressed="{{open}}"
6970
dir="{{effectiveDir}}"
7071
accessible-name="{{_iconAccessibleNameText}}"

packages/main/src/MultiComboBox.js

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ class MultiComboBox extends UI5Element {
410410
this._filteredItems = [];
411411
this.selectedValues = [];
412412
this._inputLastValue = "";
413+
this._valueBeforeOpen = "";
413414
this._deleting = false;
414415
this._validationTimeout = null;
415416
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
@@ -432,34 +433,24 @@ class MultiComboBox extends UI5Element {
432433
this.fireEvent("change");
433434
}
434435

435-
_showMorePopover() {
436-
this.filterSelected = true;
437-
this._toggleRespPopover();
436+
togglePopover() {
437+
this.allItemsPopover.toggle(this);
438438
}
439439

440-
togglePopover() {
441-
if (!isPhone()) {
442-
this._inputDom.focus();
443-
}
440+
_showFilteredItems() {
441+
this.filterSelected = true;
442+
this._showMorePressed = true;
444443

445-
this._toggleRespPopover();
444+
this.togglePopover();
446445
}
447446

448447
filterSelectedItems(event) {
449-
if (this.allItemsSelected) {
450-
this.filterSelected = true;
451-
return;
452-
}
453-
454448
this.filterSelected = event.target.pressed;
449+
this.selectedItems = this._filteredItems.filter(item => item.selected);
455450
}
456451

457452
get _showAllItemsButtonPressed() {
458-
return this.filterSelected || this.allItemsSelected;
459-
}
460-
461-
get allItemsSelected() {
462-
return this.items.length === this.selectedValues.length;
453+
return this.filterSelected;
463454
}
464455

465456
get _inputDom() {
@@ -472,6 +463,10 @@ class MultiComboBox extends UI5Element {
472463
const filteredItems = this._filterItems(value);
473464
const oldValueState = this.valueState;
474465

466+
if (this.filterSelected) {
467+
this.filterSelected = false;
468+
}
469+
475470
/* skip calling change event when an input with a placeholder is focused on IE
476471
- value of the host and the internal input should be differnt in case of actual input
477472
- input is called when a key is pressed => keyup should not be called yet
@@ -576,7 +571,7 @@ class MultiComboBox extends UI5Element {
576571

577572
if (isShow(event) && !this.readonly && !this.disabled) {
578573
event.preventDefault();
579-
this._toggleRespPopover();
574+
this.togglePopover();
580575
}
581576

582577
if (isDown(event) && this.allItemsPopover.opened && this.items.length) {
@@ -651,13 +646,19 @@ class MultiComboBox extends UI5Element {
651646
return (Filters[this.filter] || Filters.StartsWithPerTerm)(str, this.items);
652647
}
653648

649+
_afterOpenPicker() {
650+
this._toggle();
651+
652+
if (!isPhone()) {
653+
this._innerInput.focus();
654+
} else {
655+
this.allItemsPopover.focus();
656+
}
657+
}
658+
654659
_toggle() {
655660
this.open = !this.open;
656661
this.fireEvent("open-change");
657-
658-
if (!this.open) {
659-
this._afterClosePopover();
660-
}
661662
}
662663

663664
_getSelectedItems() {
@@ -714,28 +715,41 @@ class MultiComboBox extends UI5Element {
714715
this.list = staticAreaItem.querySelector(".ui5-multi-combobox-all-items-list");
715716
}
716717

717-
_toggleRespPopover() {
718-
this.allItemsPopover.toggle(this);
719-
}
720-
721718
_click(event) {
722-
if (isPhone() && !this.readonly && !this._showMorePressed) {
719+
if (isPhone() && !this.readonly && !this._showMorePressed && !this._deleting) {
723720
this.allItemsPopover.showAt(this);
724721
}
725722

726723
this._showMorePressed = false;
727724
}
728725

729-
_afterClosePopover() {
726+
_afterClosePicker() {
730727
// close device's keyboard and prevent further typing
731728
if (isPhone()) {
732729
this.blur();
733730
}
734731

732+
this._toggle();
733+
735734
this._iconPressed = false;
736735
this.filterSelected = false;
737736
}
738737

738+
_beforeOpen() {
739+
this._itemsBeforeOpen = this.items.map(item => {
740+
return {
741+
ref: item,
742+
selected: item.selected,
743+
};
744+
});
745+
746+
this._valueBeforeOpen = this.value;
747+
748+
if (this.filterSelected) {
749+
this.selectedItems = this._filteredItems.filter(item => item.selected);
750+
}
751+
}
752+
739753
onBeforeRendering() {
740754
const input = this.shadowRoot.querySelector("input");
741755
this._inputLastValue = this.value;
@@ -746,20 +760,6 @@ class MultiComboBox extends UI5Element {
746760

747761
const filteredItems = this._filterItems(this.value);
748762
this._filteredItems = filteredItems;
749-
750-
if (isPhone() && this.allItemsPopover && this.allItemsPopover.opened) {
751-
// Set initial focus to the dialog
752-
this.allItemsPopover.focus();
753-
}
754-
}
755-
756-
_beforeOpen() {
757-
this._itemsBeforeOpen = this.items.map(item => {
758-
return {
759-
ref: item,
760-
selected: item.selected,
761-
};
762-
});
763763
}
764764

765765
async onAfterRendering() {
@@ -768,6 +768,8 @@ class MultiComboBox extends UI5Element {
768768

769769
this.toggle(this.shouldDisplayOnlyValueStateMessage);
770770
this.storeResponsivePopoverWidth();
771+
772+
this._deleting = false;
771773
}
772774

773775
get _isPhone() {
@@ -798,6 +800,8 @@ class MultiComboBox extends UI5Element {
798800
});
799801

800802
this.togglePopover();
803+
804+
this.value = this._valueBeforeOpen;
801805
}
802806

803807
handleOK() {
@@ -816,6 +820,10 @@ class MultiComboBox extends UI5Element {
816820
}
817821
}
818822

823+
_forwardFocusToInner() {
824+
this._innerInput.focus();
825+
}
826+
819827
async closePopover() {
820828
const popover = await this._getPopover();
821829

@@ -834,12 +842,19 @@ class MultiComboBox extends UI5Element {
834842
inputFocusIn() {
835843
if (!isPhone()) {
836844
this.focused = true;
845+
} else {
846+
this._innerInput.blur();
837847
}
838848
}
839849

840850
inputFocusOut(event) {
841851
if (!this.shadowRoot.contains(event.relatedTarget) && !this._deleting) {
842852
this.focused = false;
853+
854+
// remove the value if user focus out the input and focus is not going in the popover
855+
if (!isPhone() && !this.allowCustomValues && (this.staticAreaItem !== event.relatedTarget)) {
856+
this.value = "";
857+
}
843858
}
844859
}
845860

@@ -848,7 +863,7 @@ class MultiComboBox extends UI5Element {
848863
}
849864

850865
get _isFocusInside() {
851-
return this.focused || this._tokenizerFocused;
866+
return !isPhone() && (this.focused || this._tokenizerFocused);
852867
}
853868

854869
get selectedItemsListMode() {

packages/main/src/MultiComboBoxPopover.hbs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
class="ui5-multi-combobox-all-items-responsive-popover"
55
hide-arrow
66
_disable-initial-focus
7-
@ui5-after-close={{_toggle}}
7+
@ui5-selection-change={{_listSelectionChange}}
8+
@ui5-after-close={{_afterClosePicker}}
89
@ui5-before-open={{_beforeOpen}}
9-
@ui5-after-open={{_toggle}}
10+
@ui5-after-open={{_afterOpenPicker}}
1011
>
1112
{{#if _isPhone}}
1213
<div slot="header" class="ui5-responsive-popover-header" style="{{styles.popoverHeader}}">
@@ -44,7 +45,6 @@
4445
icon="multiselect-all"
4546
design="Transparent"
4647
?pressed={{_showAllItemsButtonPressed}}
47-
?disabled={{allItemsSelected}}
4848
@click="{{filterSelectedItems}}"
4949
></ui5-toggle-button>
5050
</div>
@@ -65,19 +65,19 @@
6565
{{/if}}
6666
{{/unless}}
6767

68-
<ui5-list @ui5-selection-change={{_listSelectionChange}} separators="None" mode="MultiSelect" class="ui5-multi-combobox-all-items-list">
69-
{{#each _filteredItems}}
70-
<ui5-li
71-
type="{{../_listItemsType}}"
72-
additional-text={{this.additionalText}}
73-
?selected={{this.selected}}
74-
data-ui5-token-id="{{this._id}}"
75-
data-ui5-stable="{{this.stableDomRef}}"
76-
>
77-
{{this.text}}
78-
</ui5-li>
79-
{{/each}}
80-
</ui5-list>
68+
{{#if filterSelected}}
69+
<ui5-list separators="None" mode="MultiSelect" class="ui5-multi-combobox-all-items-list">
70+
{{#each selectedItems}}
71+
{{> listItem}}
72+
{{/each}}
73+
</ui5-list>
74+
{{else}}
75+
<ui5-list separators="None" mode="MultiSelect" class="ui5-multi-combobox-all-items-list">
76+
{{#each _filteredItems}}
77+
{{> listItem}}
78+
{{/each}}
79+
</ui5-list>
80+
{{/if}}
8181

8282
{{#if _isPhone}}
8383
<div slot="footer" class="ui5-responsive-popover-footer">
@@ -115,3 +115,15 @@
115115
{{/each}}
116116
{{/if}}
117117
{{/inline}}
118+
119+
{{#*inline "listItem"}}
120+
<ui5-li
121+
type="{{../_listItemsType}}"
122+
additional-text={{this.additionalText}}
123+
?selected={{this.selected}}
124+
data-ui5-token-id="{{this._id}}"
125+
data-ui5-stable="{{this.stableDomRef}}"
126+
>
127+
{{this.text}}
128+
</ui5-li>
129+
{{/inline}}

packages/main/src/Tokenizer.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,10 @@ class Tokenizer extends UI5Element {
213213
nextTokenIndex = deletedTokenIndex === this._getVisibleTokens().length - 1 ? deletedTokenIndex - 1 : deletedTokenIndex + 1;
214214
}
215215
const nextToken = this._getVisibleTokens()[nextTokenIndex]; // if the last item was deleted this will be undefined
216-
this._itemNav.setCurrentItem(nextToken); // update the item navigation with the new token or undefined, if the last was deleted
217216

218-
if (nextToken) {
217+
if (nextToken && !isPhone()) {
218+
this._itemNav.setCurrentItem(nextToken); // update the item navigation with the new token or undefined, if the last was deleted
219+
219220
setTimeout(() => {
220221
nextToken.focus();
221222
}, 0);

packages/main/test/specs/MultiComboBox.spec.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,70 @@ describe("MultiComboBox general interaction", () => {
222222

223223
assert.ok(await nMoreText.getText(), "1 More", "token 1 should be visible");
224224
});
225+
226+
it("tests if clicking n more will prefilter items before opening the popover", async () => {
227+
await browser.url(`http://localhost:${PORT}/test-resources/pages/MultiComboBox.html`);
228+
await browser.setWindowSize(1920, 1080);
229+
230+
const mcb = await $("#more-mcb");
231+
const icon = await mcb.shadow$("[input-icon]");
232+
const nMoreText = await mcb.shadow$("ui5-tokenizer").shadow$(".ui5-tokenizer-more-text");
233+
234+
await mcb.scrollIntoView();
235+
await nMoreText.click();
236+
237+
await browser.waitUntil(async () => mcb.getProperty("open"), {
238+
timeout: 500,
239+
timeoutMsg: "Popover is open"
240+
});
241+
242+
const staticAreaItemClassName = await browser.getStaticAreaItemClassName("#more-mcb")
243+
const popover = await $(`.${staticAreaItemClassName}`).shadow$(".ui5-multi-combobox-all-items-responsive-popover");
244+
const list = await popover.$(".ui5-multi-combobox-all-items-list");
245+
246+
assert.strictEqual((await list.getProperty("items")).length, 3, "3 items should be shown (all selected)");
247+
248+
await icon.click();
249+
250+
await browser.waitUntil(async () => !(await mcb.getProperty("open")), {
251+
timeout: 500,
252+
timeoutMsg: "Popover should be closed"
253+
});
254+
255+
await icon.click();
256+
257+
await browser.waitUntil(async () => await mcb.getProperty("open"), {
258+
timeout: 500,
259+
timeoutMsg: "Popover should be open"
260+
});
261+
262+
assert.strictEqual((await list.getProperty("items")).length, 4, "4 items should be shown");
263+
});
264+
265+
it("tests filtering of items when nmore popover is open and user types in the input fueld", async () => {
266+
await browser.url(`http://localhost:${PORT}/test-resources/pages/MultiComboBox.html`);
267+
await browser.setWindowSize(1920, 1080);
268+
269+
const mcb = await $("#more-mcb");
270+
const nMoreText = await mcb.shadow$("ui5-tokenizer").shadow$(".ui5-tokenizer-more-text");
271+
272+
await mcb.scrollIntoView();
273+
274+
const input = await mcb.shadow$("input");
275+
276+
await nMoreText.click();
277+
278+
const staticAreaItemClassName = await browser.getStaticAreaItemClassName("#more-mcb")
279+
const popover = await $(`.${staticAreaItemClassName}`).shadow$(".ui5-multi-combobox-all-items-responsive-popover");
280+
const list = await popover.$(".ui5-multi-combobox-all-items-list");
281+
const lastListItem = await list.$("ui5-li:last-child");
282+
283+
await input.click();
284+
await input.keys("c");
285+
286+
assert.strictEqual((await list.getProperty("items")).length, 3, "3 items should be shown (all selected)");
287+
assert.notOk(await lastListItem.getProperty("selected"), "last item should not be selected");
288+
})
225289
});
226290

227291
describe("keyboard handling", () => {

0 commit comments

Comments
 (0)