Skip to content
Merged
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
73 changes: 73 additions & 0 deletions packages/main/cypress/specs/Input.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,79 @@ describe("Input general interaction", () => {
cy.get("@onChange").should("have.been.calledOnce");
cy.get("@onSelectionChange").should("have.been.calledOnce");
});

it("Should control suggestions dynamically based on threshold", () => {
const THRESHOLD = 3;
const countries = [
"Argentina", "Albania", "Algeria", "Angola", "Austria", "Australia",
"Bulgaria", "Belgium", "Brazil", "Canada", "Colombia", "Croatia"
];

cy.mount(<Input id="threshold-input" showSuggestions />);

cy.document().then(doc => {
const input = doc.querySelector<Input>("#threshold-input")!;

input.addEventListener("input", () => {
const value = input.value;

while (input.lastChild) {
input.removeChild(input.lastChild);
}

if (value.length >= THRESHOLD) {
input.showSuggestions = true;

const filtered = countries.filter(country =>
country.toUpperCase().indexOf(value.toUpperCase()) === 0
);

filtered.forEach(country => {
const item = document.createElement("ui5-suggestion-item");
item.setAttribute("text", country);
input.appendChild(item);
});
} else {
input.showSuggestions = false;
}
});
});

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

cy.get("@input")
.should("be.focused");

cy.realType("B");

cy.get("@input")
.shadow()
.find<ResponsivePopover>("[ui5-responsive-popover]")
.should("not.exist", "true");

cy.realType("ul");

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

cy.get("@input")
.find("[ui5-suggestion-item]")
.should("have.length", 1)
.first()
.should("have.attr", "text", "Bulgaria");

cy.realPress("Backspace");
cy.realPress("Backspace");

cy.get("@input")
.shadow()
.find<ResponsivePopover>("[ui5-responsive-popover]")
.should("not.exist", "true");
});
});

describe("Input arrow navigation", () => {
Expand Down
53 changes: 52 additions & 1 deletion packages/main/cypress/specs/Input.mobile.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,57 @@ describe("Eventing", () => {

cy.get("@onSelectionChange").should("have.been.calledOnce");
});

it("Should control suggestions dynamically based on threshold on mobile", () => {
const THRESHOLD = 3;
const countries = [
"Argentina", "Albania", "Algeria", "Angola", "Austria", "Australia",
"Bulgaria", "Belgium", "Brazil", "Canada", "Colombia", "Croatia"
];

cy.mount(<Input id="mobile-threshold" showSuggestions />);

cy.document().then(doc => {
const input = doc.querySelector<Input>("#mobile-threshold")!;

input.addEventListener("input", () => {
const value = input.value;

while (input.lastChild) {
input.removeChild(input.lastChild);
}

if (value.length >= THRESHOLD) {
input.showSuggestions = true;

const filtered = countries.filter(country =>
country.toUpperCase().indexOf(value.toUpperCase()) === 0
);

filtered.forEach(country => {
const item = document.createElement("ui5-suggestion-item");
item.setAttribute("text", country);
input.appendChild(item);
});
} else {
input.showSuggestions = false;
}
});
});

cy.get("#mobile-threshold")
.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").shadow().find(".ui5-input-inner-phone").realType("Bu");
cy.get("@input").shadow().find("ui5-suggestion-item").should("have.length", 0);
});
});

describe("Typeahead", () => {
Expand Down Expand Up @@ -390,4 +441,4 @@ describe("Property open", () => {
.find<ResponsivePopover>("[ui5-responsive-popover]")
.ui5ResponsivePopoverClosed();
});
});
});
16 changes: 11 additions & 5 deletions packages/main/src/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
_onfocusout(e: FocusEvent) {
const toBeFocused = e.relatedTarget as HTMLElement;

if (this.Suggestions?._getPicker().contains(toBeFocused) || this.contains(toBeFocused) || this.getSlottedNodes("valueStateMessage").some(el => el.contains(toBeFocused))) {
if (this.Suggestions?._getPicker()?.contains(toBeFocused) || this.contains(toBeFocused) || this.getSlottedNodes("valueStateMessage").some(el => el.contains(toBeFocused))) {
return;
}

Expand Down Expand Up @@ -1174,7 +1174,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement

if (this.previousValue !== this.getInputDOMRefSync()!.value) {
// if picker is open there might be a selected item, wait next tick to get the value applied
if (this.Suggestions?._getPicker().open && this._flattenItems.some(item => item.hasAttribute("ui5-suggestion-item") && (item as SuggestionItem).selected)) {
if (this.Suggestions?._getPicker()?.open && this._flattenItems.some(item => item.hasAttribute("ui5-suggestion-item") && (item as SuggestionItem).selected)) {
this._changeToBeFired = true;
} else {
fireChange();
Expand Down Expand Up @@ -1566,15 +1566,21 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement

getInputDOMRef() {
if (isPhone() && this.Suggestions) {
return this.Suggestions._getPicker()!.querySelector<Input>(".ui5-input-inner-phone")!;
const picker = this.Suggestions._getPicker();
if (picker) {
return picker.querySelector<Input>(".ui5-input-inner-phone")!;
}
}

return this.nativeInput;
}

getInputDOMRefSync() {
if (isPhone() && this.Suggestions?._getPicker()) {
return this.Suggestions._getPicker().querySelector(".ui5-input-inner-phone")!.shadowRoot!.querySelector<HTMLInputElement>("input")!;
if (isPhone() && this.Suggestions) {
const picker = this.Suggestions._getPicker();
if (picker) {
return picker.querySelector(".ui5-input-inner-phone")!.shadowRoot!.querySelector<HTMLInputElement>("input")!;
}
}

return this.nativeInput;
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.showSuggestions && suggestionsList.call(this) }

{this._isPhone &&
<div slot="footer" class="ui5-responsive-popover-footer">
Expand Down
49 changes: 49 additions & 0 deletions packages/main/test/pages/Input.html
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,15 @@ <h3>Input with open suggestions on focusin</h3>
<br>
<br>

<h3>Input with Dynamic Suggestions Triggering (Threshold = 3)</h3>
<ui5-input id="dynamicTriggerInput" placeholder="Type at least 3 characters to see suggestions..." show-suggestions></ui5-input>
<div>
<small>Suggestions will appear after typing 3 or more characters</small>
</div>

<br>
<br>

<h3>Input - showing wrapping</h3>
<ui5-input show-suggestions id="input-change-wrapping">
<ui5-suggestion-item text="Item that does not wrap"></ui5-suggestion-item>
Expand Down Expand Up @@ -1113,6 +1122,46 @@ <h3>Input Composition</h3>
document.getElementById("prevent-input-event-clear-icon").addEventListener("ui5-input", event => {
event.preventDefault();
});

// Dynamic Suggestions Triggering with Threshold
const dynamicTriggerInput = document.getElementById("dynamicTriggerInput");
const THRESHOLD = 3;

const countriesForDynamicTrigger = [
"Albania", "Andorra", "Armenia", "Austria", "Azerbaijan",
"Belarus", "Belgium", "Bosnia and Herzegovina", "Bulgaria",
"Croatia", "Cyprus", "Czech Republic", "Denmark",
"Estonia", "Finland", "France", "Georgia", "Germany", "Greece",
"Hungary", "Iceland", "Ireland", "Italy", "Kazakhstan",
"Kosovo", "Latvia", "Liechtenstein", "Lithuania", "Luxembourg",
"Malta", "Moldova", "Monaco", "Montenegro", "Netherlands",
"North Macedonia", "Norway", "Poland", "Portugal", "Romania",
"Russia", "San Marino", "Serbia", "Slovakia", "Slovenia",
"Spain", "Sweden", "Switzerland", "Turkey", "Ukraine",
"United Kingdom", "Vatican City"
];

dynamicTriggerInput.addEventListener("input", () => {
const value = dynamicTriggerInput.value;

while (dynamicTriggerInput.lastChild) {
dynamicTriggerInput.removeChild(dynamicTriggerInput.lastChild);
}

if (value.length >= THRESHOLD) {
dynamicTriggerInput.showSuggestions = true;

const filteredCountries = countriesForDynamicTrigger.filter(country =>
country.toLowerCase().startsWith(value.toLowerCase())
);

filteredCountries.forEach(country => {
const suggestion = document.createElement("ui5-suggestion-item");
suggestion.text = country;
dynamicTriggerInput.appendChild(suggestion);
});
}
});
</script>
</body>

Expand Down
7 changes: 7 additions & 0 deletions packages/website/docs/_components_pages/main/Input/Input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Basic from "../../../_samples/main/Input/Basic/Basic.md";
import Suggestions from "../../../_samples/main/Input/Suggestions/Suggestions.md";
import ClearIcon from "../../../_samples/main/Input/ClearIcon/ClearIcon.md";
import SuggestionsWrapping from "../../../_samples/main/Input/SuggestionsWrapping/SuggestionsWrapping.md";
import DynamicSuggestions from "../../../_samples/main/Input/DynamicSuggestions/DynamicSuggestions.md";
import ValueStateMessage from "../../../_samples/main/Input/ValueStateMessage/ValueStateMessage.md";
import Label from "../../../_samples/main/Input/Label/Label.md";
import ValueHelpDialog from "../../../_samples/main/Input/ValueHelpDialog/ValueHelpDialog.md";
Expand Down Expand Up @@ -37,6 +38,12 @@ The sample demonstrates how the text of the suggestions wrap when too long.

<SuggestionsWrapping />

### Dynamic Suggestion Control
This sample demonstrates how applications can control when suggestions appear by dynamically toggling the <b>showSuggestions</b> property.
In this example, suggestions are only shown when the user has typed 3 or more characters.

<DynamicSuggestions />

### Input and Label
<Label />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import html from '!!raw-loader!./sample.html';
import js from '!!raw-loader!./main.js';

<Editor html={html} js={js} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import "@ui5/webcomponents/dist/Input.js";
import "@ui5/webcomponents/dist/SuggestionItem.js";
import "@ui5/webcomponents/dist/features/InputSuggestions.js";

const THRESHOLD = 3;

const countries = [
"Argentina", "Albania", "Algeria", "Angola", "Austria", "Australia",
"Bulgaria", "Belgium", "Brazil", "Canada", "Columbia", "Croatia",
"Denmark", "England", "Finland", "France", "Germany", "Greece",
"Hungary", "Ireland", "Italy", "Japan", "Kuwait", "Luxembourg",
"Mexico", "Morocco", "Netherlands", "Norway", "Paraguay", "Philippines",
"Portugal", "Romania", "Spain", "Sweden", "Switzerland", "Sri Lanka",
"Senegal", "Thailand", "The United Kingdom of Great Britain and Northern Ireland",
"USA", "Ukraine", "Vietnam"
];

let suggestionItems = [];

const input = document.getElementById("input-threshold-3");

input.addEventListener("input", () => {
const value = input.value;

// Clear existing suggestions
while (input.lastChild) {
input.removeChild(input.lastChild);
}

if (value.length >= THRESHOLD) {
// Enable suggestions and typeahead when threshold is met
input.showSuggestions = true;

// Filter and add matching suggestions
suggestionItems = countries.filter((item) => {
return item.toUpperCase().indexOf(value.toUpperCase()) === 0;
});

suggestionItems.forEach((item) => {
const li = document.createElement("ui5-suggestion-item");
li.text = item;
input.appendChild(li);
});
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- playground-fold -->
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sample</title>
</head>

<body style="background-color: var(--sapBackgroundColor); height: 400px;">
<!-- playground-fold-end -->

<ui5-input id="input-threshold-3" placeholder="Start typing (threshold: 3 chars)" show-suggestions></ui5-input>

<!-- playground-fold -->
<script type="module" src="main.js"></script>
</body>

</html>
<!-- playground-fold-end -->
Loading