Skip to content

Commit 7b3bcdb

Browse files
feat(ui5-input,ui5-multi-input): add suggestions trigger
1 parent b4e8489 commit 7b3bcdb

File tree

11 files changed

+306
-12
lines changed

11 files changed

+306
-12
lines changed

packages/main/cypress/specs/Input.cy.tsx

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,7 +2208,7 @@ describe("Input general interaction", () => {
22082208

22092209
it("Should open suggestions popover if open is set on focusin", () => {
22102210
cy.mount(
2211-
<Input id="openPickerInput" showSuggestions={true} onFocus={(e) => (e.target as Input).open = true}>
2211+
<Input id="openPickerInput" showSuggestions={true} startSuggestion={0} onFocus={(e) => (e.target as Input).open = true}>
22122212
<SuggestionItem text="China" />
22132213
<SuggestionItem text="Chile" />
22142214
</Input>
@@ -2280,7 +2280,7 @@ describe("Input general interaction", () => {
22802280

22812281
it("Changes text if cleared in change event handler", () => {
22822282
cy.mount(
2283-
<Input id="change-event-value" showSuggestions={true} open={true} onChange={e => (e.target as Input).value = ""}>
2283+
<Input id="change-event-value" showSuggestions={true} startSuggestion={0} open={true} onChange={e => (e.target as Input).value = ""}>
22842284
<SuggestionItem text="China" />
22852285
<SuggestionItem text="Chile" />
22862286
</Input>
@@ -2601,7 +2601,7 @@ describe("Selection-change event", () => {
26012601
describe("Property open", () => {
26022602
it("Suggestions picker is open when attribute open is set to true", () => {
26032603
cy.mount(
2604-
<Input id="input-suggestions-open" showSuggestions open>
2604+
<Input id="input-suggestions-open" showSuggestions startSuggestion={0} open>
26052605
<SuggestionItem text="Item 1" />
26062606
<SuggestionItem text="Item 2" />
26072607
<SuggestionItem text="Item 3" />
@@ -2797,3 +2797,90 @@ describe("Input Composition", () => {
27972797
.should("have.attr", "value", "谢谢");
27982798
});
27992799
});
2800+
2801+
describe("startSuggestion threshold behavior", () => {
2802+
it("suggestions popover opens when startSuggestion threshold is met", () => {
2803+
cy.mount(
2804+
<Input showSuggestions startSuggestion={3}>
2805+
<SuggestionItem text="Apple"></SuggestionItem>
2806+
<SuggestionItem text="Apricot"></SuggestionItem>
2807+
<SuggestionItem text="Banana"></SuggestionItem>
2808+
</Input>
2809+
);
2810+
2811+
cy.get("[ui5-input]")
2812+
.as("input")
2813+
.shadow()
2814+
.find("input")
2815+
.as("innerInput");
2816+
2817+
cy.get("@innerInput").realClick();
2818+
2819+
cy.get("@innerInput").realType("App");
2820+
2821+
cy.get("@input")
2822+
.shadow()
2823+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2824+
.ui5ResponsivePopoverOpened();
2825+
2826+
cy.get("@innerInput").realPress("Backspace");
2827+
2828+
cy.get("@input")
2829+
.shadow()
2830+
.find("[ui5-responsive-popover]")
2831+
.should("not.be.visible");
2832+
});
2833+
2834+
it("suggestions popover remains closed when typing below startSuggestion threshold", () => {
2835+
cy.mount(
2836+
<Input showSuggestions startSuggestion={4}>
2837+
<SuggestionItem text="Apple"></SuggestionItem>
2838+
<SuggestionItem text="Application"></SuggestionItem>
2839+
<SuggestionItem text="Banana"></SuggestionItem>
2840+
</Input>
2841+
);
2842+
2843+
cy.get("[ui5-input]")
2844+
.as("input")
2845+
.shadow()
2846+
.find("input")
2847+
.as("innerInput");
2848+
2849+
cy.get("@innerInput").realClick();
2850+
2851+
cy.get("@innerInput").realType("A");
2852+
cy.get("@input")
2853+
.shadow()
2854+
.find("[ui5-responsive-popover]")
2855+
.should("not.be.visible");
2856+
2857+
cy.get("@innerInput").realType("p");
2858+
cy.get("@input")
2859+
.shadow()
2860+
.find("[ui5-responsive-popover]")
2861+
.should("not.be.visible");
2862+
});
2863+
2864+
it("suggestions popover is opened when startSuggestion is not set", () => {
2865+
cy.mount(
2866+
<Input showSuggestions>
2867+
<SuggestionItem text="Apple"></SuggestionItem>
2868+
<SuggestionItem text="Banana"></SuggestionItem>
2869+
</Input>
2870+
);
2871+
2872+
cy.get("[ui5-input]")
2873+
.as("input")
2874+
.shadow()
2875+
.find("input")
2876+
.as("innerInput");
2877+
2878+
cy.get("@innerInput").realClick();
2879+
2880+
cy.get("@innerInput").realType("A");
2881+
cy.get("@input")
2882+
.shadow()
2883+
.find<ResponsivePopover>("[ui5-responsive-popover]")
2884+
.ui5ResponsivePopoverOpened();
2885+
});
2886+
});

packages/main/cypress/specs/Input.mobile.cy.tsx

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ describe("Eventing", () => {
183183

184184
cy.mount(
185185
<>
186-
<Input id="input-custom-flat" showSuggestions onSelectionChange={onSelectionChange}>
186+
<Input id="input-custom-flat" showSuggestions startSuggestion={0} onSelectionChange={onSelectionChange}>
187187
<SuggestionItem text="Albania" />
188188
<SuggestionItem text="Argentina" />
189189
</Input>
@@ -390,4 +390,114 @@ describe("Property open", () => {
390390
.find<ResponsivePopover>("[ui5-responsive-popover]")
391391
.ui5ResponsivePopoverClosed();
392392
});
393-
});
393+
});
394+
395+
describe("startSuggestion threshold behavior on mobile", () => {
396+
beforeEach(() => {
397+
cy.ui5SimulateDevice("phone");
398+
});
399+
400+
it("suggestions list should not display when startSuggestion threshold is not met", () => {
401+
cy.mount(
402+
<Input id="input-suggestions" showSuggestions startSuggestion={3}>
403+
<SuggestionItem text="Apple"></SuggestionItem>
404+
<SuggestionItem text="Apricot"></SuggestionItem>
405+
<SuggestionItem text="Banana"></SuggestionItem>
406+
</Input>
407+
);
408+
409+
cy.get("#input-suggestions")
410+
.as("input")
411+
.realClick();
412+
413+
cy.get("@input")
414+
.shadow()
415+
.find<ResponsivePopover>("[ui5-responsive-popover]")
416+
.as("popover")
417+
.ui5ResponsivePopoverOpened();
418+
419+
cy.get("@input")
420+
.shadow()
421+
.find(".ui5-input-inner-phone")
422+
.as("innerInput")
423+
.should("be.focused");
424+
425+
cy.get("@innerInput").realType("Ap");
426+
427+
cy.get("@input")
428+
.find("[ui5-suggestion-item]")
429+
.should("not.be.visible");
430+
431+
cy.get<ResponsivePopover>("@popover").ui5ResponsivePopoverOpened();
432+
});
433+
434+
it("suggestion list should display when startSuggestion threshold is met", () => {
435+
cy.mount(
436+
<Input id="input-suggestions" showSuggestions startSuggestion={2}>
437+
<SuggestionItem text="Apple"></SuggestionItem>
438+
<SuggestionItem text="Apricot"></SuggestionItem>
439+
<SuggestionItem text="Banana"></SuggestionItem>
440+
</Input>
441+
);
442+
443+
cy.get("#input-suggestions")
444+
.as("input")
445+
.realClick();
446+
447+
cy.get("@input")
448+
.shadow()
449+
.find<ResponsivePopover>("[ui5-responsive-popover]")
450+
.as("popover")
451+
.ui5ResponsivePopoverOpened();
452+
453+
cy.get("@input")
454+
.shadow()
455+
.find(".ui5-input-inner-phone")
456+
.as("innerInput")
457+
.should("be.focused");
458+
459+
cy.get("@innerInput").realType("Ap");
460+
461+
cy.get("@input")
462+
.find("[ui5-suggestion-item]")
463+
.should("be.visible");
464+
465+
cy.get("@input")
466+
.find("[ui5-suggestion-item]")
467+
.should("have.length", 3);
468+
469+
cy.get<ResponsivePopover>("@popover").ui5ResponsivePopoverOpened();
470+
});
471+
472+
it("startSuggestion=0 shows suggestion list immediately on mobile", () => {
473+
cy.mount(
474+
<Input id="input-suggestions" showSuggestions startSuggestion={0}>
475+
<SuggestionItem text="Apple"></SuggestionItem>
476+
<SuggestionItem text="Apricot"></SuggestionItem>
477+
<SuggestionItem text="Banana"></SuggestionItem>
478+
</Input>
479+
);
480+
481+
cy.get("#input-suggestions")
482+
.as("input")
483+
.realClick();
484+
485+
cy.get("@input")
486+
.shadow()
487+
.find<ResponsivePopover>("[ui5-responsive-popover]")
488+
.ui5ResponsivePopoverOpened();
489+
490+
cy.get("@input")
491+
.shadow()
492+
.find(".ui5-input-inner-phone")
493+
.should("be.focused");
494+
495+
cy.get("@input")
496+
.find("[ui5-suggestion-item]")
497+
.should("be.visible");
498+
499+
cy.get("@input")
500+
.find("[ui5-suggestion-item]")
501+
.should("have.length", 3);
502+
});
503+
});

packages/main/cypress/specs/MultiInput.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ describe("MultiInput tokens", () => {
446446

447447
it("should empty the field when value is cleared in the change handler", () => {
448448
cy.mount(
449-
<MultiInput showSuggestions id="token-unique" showValueHelpIcon>
449+
<MultiInput showSuggestions startSuggestion={0} id="token-unique" showValueHelpIcon>
450450
<div slot="valueStateMessage" id="value-state-wrapper">Token is already in the list</div>
451451
<SuggestionItem text="Argentina"></SuggestionItem>
452452
</MultiInput>

packages/main/src/Input.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,16 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
426426
@property({ type: Boolean })
427427
showSuggestions = false;
428428

429+
/**
430+
* Defines the minimum number of typed characters required before suggestions become active
431+
*
432+
* @default 1
433+
* @public
434+
* @since 2.16.0
435+
*/
436+
@property({ type: Number })
437+
startSuggestion: number = 1;
438+
429439
/**
430440
* Sets the maximum number of characters available in the input field.
431441
*
@@ -773,7 +783,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
773783
if (preventOpenPicker) {
774784
this.open = false;
775785
} else if (!this._isPhone) {
776-
this.open = hasItems && (this.open || (hasValue && isFocused && this.isTyping));
786+
this.open = this._shouldTriggerSuggest && hasItems && (this.open || (hasValue && isFocused && this.isTyping));
777787
}
778788

779789
const value = this.value;
@@ -787,7 +797,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
787797

788798
// Typehead causes issues on Android devices, so we disable it for now
789799
// If there is already a selection the autocomplete has already been performed
790-
if (this._shouldAutocomplete && !isAndroid() && !autoCompletedChars && !this._isKeyNavigation) {
800+
if (this._shouldAutocomplete && this._shouldTriggerSuggest && !isAndroid() && !autoCompletedChars && !this._isKeyNavigation) {
791801
const item = this._getFirstMatchingItem(value);
792802
if (item) {
793803
if (!this._isComposing) {
@@ -801,14 +811,14 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
801811
onAfterRendering() {
802812
const innerInput = this.getInputDOMRefSync()!;
803813

804-
if (this.showSuggestions && this.Suggestions?._getPicker()) {
814+
if (this._shouldTriggerSuggest && this.showSuggestions && this.Suggestions?._getPicker()) {
805815
this._listWidth = this.Suggestions._getListWidth();
806816

807817
// disabled ItemNavigation from the list since we are not using it
808818
this.Suggestions._getList()._itemNavigation._getItems = () => [];
809819
}
810820

811-
if (this._performTextSelection) {
821+
if (this._shouldTriggerSuggest && this._performTextSelection) {
812822
// this is required to syncronize lit-html input's value and user's input
813823
// lit-html does not sync its stored value for the value property when the user is typing
814824
if (innerInput.value !== this._innerValue) {
@@ -1138,7 +1148,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
11381148
}
11391149

11401150
_clearPopoverFocusAndSelection() {
1141-
if (!this.showSuggestions || !this.Suggestions) {
1151+
if (!this.showSuggestions || !this.Suggestions || !this._shouldTriggerSuggest) {
11421152
return;
11431153
}
11441154

@@ -1939,6 +1949,10 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
19391949
return !this.focused && this.Suggestions?.isOpened();
19401950
}
19411951

1952+
get _shouldTriggerSuggest() {
1953+
return this.typedInValue.length >= this.startSuggestion;
1954+
}
1955+
19421956
/**
19431957
* Returns the placeholder value.
19441958
* @protected

packages/main/src/features/InputSuggestionsTemplate.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export default function InputSuggestionsTemplate(this: Input, hooks?: { suggesti
7272
</div>
7373
}
7474

75-
{ suggestionsList.call(this) }
75+
{ this._shouldTriggerSuggest && suggestionsList.call(this) }
7676

7777
{this._isPhone &&
7878
<div slot="footer" class="ui5-responsive-popover-footer">

packages/main/test/pages/Input.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ <h3>Input in Cozy</h3>
3434
<div id="myInput-open-picker"></div>
3535
</div>
3636

37+
<div>
38+
<h3>Input with <i>startSuggestion</i> trigger = 3</h3>
39+
<ui5-input id="myInputSuggestionTrigger" class="input2auto" show-suggestions start-suggestion="3" placeholder="Search for a country ...">
40+
</ui5-input>
41+
</div>
42+
3743
<div class="sapUiSizeCompact">
3844
<h3>Input in Compact</h3>
3945
<ui5-input id="inputCompact" class="input2auto" show-suggestions placeholder="Search for a country ...">
@@ -674,6 +680,7 @@ <h3>Input Composition</h3>
674680
];
675681

676682
var input = document.getElementById('myInput');
683+
var inputSuggestionTrigger = document.getElementById('myInputSuggestionTrigger');
677684
var inputChangeWithSuggestions = document.getElementById('inputChange-Suggestions');
678685
var inputGrouping = document.getElementById('myInputGrouping');
679686
var myInput2 = document.getElementById('myInput2');
@@ -831,6 +838,7 @@ <h3>Input Composition</h3>
831838
var selectionChangeCounter = 0;
832839

833840
input.addEventListener("ui5-input", suggest);
841+
myInputSuggestionTrigger.addEventListener("ui5-input", suggest);
834842
inputCompact.addEventListener("ui5-input", suggest);
835843
inputError.addEventListener("ui5-input", suggest);
836844
customSuggestionsInput.addEventListener("ui5-input", suggestCustom);

packages/main/test/pages/MultiInput.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,21 @@ <h1>Tokens + Suggestions</h1>
302302
</ui5-dialog>
303303
</div>
304304

305+
<div class="sample-container">
306+
<h1>Tokens + Suggestions + Start Suggestion Trigger = 3</h1>
307+
308+
<ui5-multi-input show-suggestions start-suggestion="3" id="suggestion-token">
309+
<ui5-suggestion-item text="Aute"></ui5-suggestion-item>
310+
<ui5-suggestion-item text="ad"></ui5-suggestion-item>
311+
<ui5-suggestion-item text="exercitation"></ui5-suggestion-item>
312+
<ui5-suggestion-item text="esse"></ui5-suggestion-item>
313+
<ui5-suggestion-item text="labore"></ui5-suggestion-item>
314+
<ui5-suggestion-item text="amet"></ui5-suggestion-item>
315+
<ui5-suggestion-item text="aute"></ui5-suggestion-item>
316+
<ui5-suggestion-item text="excepteur"></ui5-suggestion-item>
317+
</ui5-multi-input>
318+
</div>
319+
305320
<div class="sample-container">
306321
<h1>Composition</h1>
307322

0 commit comments

Comments
 (0)