Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 90 additions & 3 deletions packages/main/cypress/specs/Input.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2208,7 +2208,7 @@ describe("Input general interaction", () => {

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

it("Changes text if cleared in change event handler", () => {
cy.mount(
<Input id="change-event-value" showSuggestions={true} open={true} onChange={e => (e.target as Input).value = ""}>
<Input id="change-event-value" showSuggestions={true} startSuggestion={0} open={true} onChange={e => (e.target as Input).value = ""}>
<SuggestionItem text="China" />
<SuggestionItem text="Chile" />
</Input>
Expand Down Expand Up @@ -2601,7 +2601,7 @@ describe("Selection-change event", () => {
describe("Property open", () => {
it("Suggestions picker is open when attribute open is set to true", () => {
cy.mount(
<Input id="input-suggestions-open" showSuggestions open>
<Input id="input-suggestions-open" showSuggestions startSuggestion={0} open>
<SuggestionItem text="Item 1" />
<SuggestionItem text="Item 2" />
<SuggestionItem text="Item 3" />
Expand Down Expand Up @@ -2797,3 +2797,90 @@ describe("Input Composition", () => {
.should("have.attr", "value", "谢谢");
});
});

describe("startSuggestion threshold behavior", () => {
it("suggestions popover opens when startSuggestion threshold is met", () => {
cy.mount(
<Input showSuggestions startSuggestion={3}>
<SuggestionItem text="Apple"></SuggestionItem>
<SuggestionItem text="Apricot"></SuggestionItem>
<SuggestionItem text="Banana"></SuggestionItem>
</Input>
);

cy.get("[ui5-input]")
.as("input")
.shadow()
.find("input")
.as("innerInput");

cy.get("@innerInput").realClick();

cy.get("@innerInput").realType("App");

cy.get("@input")
.shadow()
.find<ResponsivePopover>("[ui5-responsive-popover]")
.ui5ResponsivePopoverOpened();

cy.get("@innerInput").realPress("Backspace");

cy.get("@input")
.shadow()
.find("[ui5-responsive-popover]")
.should("not.be.visible");
});

it("suggestions popover remains closed when typing below startSuggestion threshold", () => {
cy.mount(
<Input showSuggestions startSuggestion={4}>
<SuggestionItem text="Apple"></SuggestionItem>
<SuggestionItem text="Application"></SuggestionItem>
<SuggestionItem text="Banana"></SuggestionItem>
</Input>
);

cy.get("[ui5-input]")
.as("input")
.shadow()
.find("input")
.as("innerInput");

cy.get("@innerInput").realClick();

cy.get("@innerInput").realType("A");
cy.get("@input")
.shadow()
.find("[ui5-responsive-popover]")
.should("not.be.visible");

cy.get("@innerInput").realType("p");
cy.get("@input")
.shadow()
.find("[ui5-responsive-popover]")
.should("not.be.visible");
});

it("suggestions popover is opened when startSuggestion is not set", () => {
cy.mount(
<Input showSuggestions>
<SuggestionItem text="Apple"></SuggestionItem>
<SuggestionItem text="Banana"></SuggestionItem>
</Input>
);

cy.get("[ui5-input]")
.as("input")
.shadow()
.find("input")
.as("innerInput");

cy.get("@innerInput").realClick();

cy.get("@innerInput").realType("A");
cy.get("@input")
.shadow()
.find<ResponsivePopover>("[ui5-responsive-popover]")
.ui5ResponsivePopoverOpened();
});
});
114 changes: 112 additions & 2 deletions packages/main/cypress/specs/Input.mobile.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ describe("Eventing", () => {

cy.mount(
<>
<Input id="input-custom-flat" showSuggestions onSelectionChange={onSelectionChange}>
<Input id="input-custom-flat" showSuggestions startSuggestion={0} onSelectionChange={onSelectionChange}>
<SuggestionItem text="Albania" />
<SuggestionItem text="Argentina" />
</Input>
Expand Down Expand Up @@ -390,4 +390,114 @@ describe("Property open", () => {
.find<ResponsivePopover>("[ui5-responsive-popover]")
.ui5ResponsivePopoverClosed();
});
});
});

describe("startSuggestion threshold behavior on mobile", () => {
beforeEach(() => {
cy.ui5SimulateDevice("phone");
});

it("suggestions list should not display when startSuggestion threshold is not met", () => {
cy.mount(
<Input id="input-suggestions" showSuggestions startSuggestion={3}>
<SuggestionItem text="Apple"></SuggestionItem>
<SuggestionItem text="Apricot"></SuggestionItem>
<SuggestionItem text="Banana"></SuggestionItem>
</Input>
);

cy.get("#input-suggestions")
.as("input")
.realClick();

cy.get("@input")
.shadow()
.find<ResponsivePopover>("[ui5-responsive-popover]")
.as("popover")
.ui5ResponsivePopoverOpened();

cy.get("@input")
.shadow()
.find(".ui5-input-inner-phone")
.as("innerInput")
.should("be.focused");

cy.get("@innerInput").realType("Ap");

cy.get("@input")
.find("[ui5-suggestion-item]")
.should("not.be.visible");

cy.get<ResponsivePopover>("@popover").ui5ResponsivePopoverOpened();
});

it("suggestion list should display when startSuggestion threshold is met", () => {
cy.mount(
<Input id="input-suggestions" showSuggestions startSuggestion={2}>
<SuggestionItem text="Apple"></SuggestionItem>
<SuggestionItem text="Apricot"></SuggestionItem>
<SuggestionItem text="Banana"></SuggestionItem>
</Input>
);

cy.get("#input-suggestions")
.as("input")
.realClick();

cy.get("@input")
.shadow()
.find<ResponsivePopover>("[ui5-responsive-popover]")
.as("popover")
.ui5ResponsivePopoverOpened();

cy.get("@input")
.shadow()
.find(".ui5-input-inner-phone")
.as("innerInput")
.should("be.focused");

cy.get("@innerInput").realType("Ap");

cy.get("@input")
.find("[ui5-suggestion-item]")
.should("be.visible");

cy.get("@input")
.find("[ui5-suggestion-item]")
.should("have.length", 3);

cy.get<ResponsivePopover>("@popover").ui5ResponsivePopoverOpened();
});

it("startSuggestion=0 shows suggestion list immediately on mobile", () => {
cy.mount(
<Input id="input-suggestions" showSuggestions startSuggestion={0}>
<SuggestionItem text="Apple"></SuggestionItem>
<SuggestionItem text="Apricot"></SuggestionItem>
<SuggestionItem text="Banana"></SuggestionItem>
</Input>
);

cy.get("#input-suggestions")
.as("input")
.realClick();

cy.get("@input")
.shadow()
.find<ResponsivePopover>("[ui5-responsive-popover]")
.ui5ResponsivePopoverOpened();

cy.get("@input")
.shadow()
.find(".ui5-input-inner-phone")
.should("be.focused");

cy.get("@input")
.find("[ui5-suggestion-item]")
.should("be.visible");

cy.get("@input")
.find("[ui5-suggestion-item]")
.should("have.length", 3);
});
});
2 changes: 1 addition & 1 deletion packages/main/cypress/specs/MultiInput.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ describe("MultiInput tokens", () => {

it("should empty the field when value is cleared in the change handler", () => {
cy.mount(
<MultiInput showSuggestions id="token-unique" showValueHelpIcon>
<MultiInput showSuggestions startSuggestion={0} id="token-unique" showValueHelpIcon>
<div slot="valueStateMessage" id="value-state-wrapper">Token is already in the list</div>
<SuggestionItem text="Argentina"></SuggestionItem>
</MultiInput>
Expand Down
24 changes: 19 additions & 5 deletions packages/main/src/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,16 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
@property({ type: Boolean })
showSuggestions = false;

/**
* Defines the minimum number of typed characters required before suggestions become active
*
* @default 1
* @public
* @since 2.16.0
*/
@property({ type: Number })
startSuggestion: number = 1;

/**
* Sets the maximum number of characters available in the input field.
*
Expand Down Expand Up @@ -773,7 +783,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
if (preventOpenPicker) {
this.open = false;
} else if (!this._isPhone) {
this.open = hasItems && (this.open || (hasValue && isFocused && this.isTyping));
this.open = this._shouldTriggerSuggest && hasItems && (this.open || (hasValue && isFocused && this.isTyping));
}

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

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

if (this.showSuggestions && this.Suggestions?._getPicker()) {
if (this._shouldTriggerSuggest && this.showSuggestions && this.Suggestions?._getPicker()) {
this._listWidth = this.Suggestions._getListWidth();

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

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

_clearPopoverFocusAndSelection() {
if (!this.showSuggestions || !this.Suggestions) {
if (!this.showSuggestions || !this.Suggestions || !this._shouldTriggerSuggest) {
return;
}

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

get _shouldTriggerSuggest() {
return this.typedInValue.length >= this.startSuggestion;
}

/**
* Returns the placeholder value.
* @protected
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/features/InputSuggestionsTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default function InputSuggestionsTemplate(this: Input, hooks?: { suggesti
</div>
}

{ suggestionsList.call(this) }
{ this._shouldTriggerSuggest && suggestionsList.call(this) }

{this._isPhone &&
<div slot="footer" class="ui5-responsive-popover-footer">
Expand Down
8 changes: 8 additions & 0 deletions packages/main/test/pages/Input.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ <h3>Input in Cozy</h3>
<div id="myInput-open-picker"></div>
</div>

<div>
<h3>Input with <i>startSuggestion</i> trigger = 3</h3>
<ui5-input id="myInputSuggestionTrigger" class="input2auto" show-suggestions start-suggestion="3" placeholder="Search for a country ...">
</ui5-input>
</div>

<div class="sapUiSizeCompact">
<h3>Input in Compact</h3>
<ui5-input id="inputCompact" class="input2auto" show-suggestions placeholder="Search for a country ...">
Expand Down Expand Up @@ -674,6 +680,7 @@ <h3>Input Composition</h3>
];

var input = document.getElementById('myInput');
var inputSuggestionTrigger = document.getElementById('myInputSuggestionTrigger');
var inputChangeWithSuggestions = document.getElementById('inputChange-Suggestions');
var inputGrouping = document.getElementById('myInputGrouping');
var myInput2 = document.getElementById('myInput2');
Expand Down Expand Up @@ -831,6 +838,7 @@ <h3>Input Composition</h3>
var selectionChangeCounter = 0;

input.addEventListener("ui5-input", suggest);
myInputSuggestionTrigger.addEventListener("ui5-input", suggest);
inputCompact.addEventListener("ui5-input", suggest);
inputError.addEventListener("ui5-input", suggest);
customSuggestionsInput.addEventListener("ui5-input", suggestCustom);
Expand Down
15 changes: 15 additions & 0 deletions packages/main/test/pages/MultiInput.html
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,21 @@ <h1>Tokens + Suggestions</h1>
</ui5-dialog>
</div>

<div class="sample-container">
<h1>Tokens + Suggestions + Start Suggestion Trigger = 3</h1>

<ui5-multi-input show-suggestions start-suggestion="3" id="suggestion-token">
<ui5-suggestion-item text="Aute"></ui5-suggestion-item>
<ui5-suggestion-item text="ad"></ui5-suggestion-item>
<ui5-suggestion-item text="exercitation"></ui5-suggestion-item>
<ui5-suggestion-item text="esse"></ui5-suggestion-item>
<ui5-suggestion-item text="labore"></ui5-suggestion-item>
<ui5-suggestion-item text="amet"></ui5-suggestion-item>
<ui5-suggestion-item text="aute"></ui5-suggestion-item>
<ui5-suggestion-item text="excepteur"></ui5-suggestion-item>
</ui5-multi-input>
</div>

<div class="sample-container">
<h1>Composition</h1>

Expand Down
Loading
Loading