Skip to content

Commit

Permalink
fix(ui5-input): fix typeahead on mobile devices (#5292)
Browse files Browse the repository at this point in the history
* fix(ui5-input): fix typeahead on mobile devices

The component no longer throws an exception when the input has suggestions
and the typeahead functionality is adjusted.

* fix(ui5-input): fix typeahead on mobile devices

add tests

* fix(ui5-input): fix typeahead on mobile devices

try different DOM selectors in the mobile tests

* fix(ui5-input): fix typeahead on mobile devices

adjust mobile tests

* fix(ui5-input): fix typeahead on mobile devices

adjust tests

* fix(ui5-input): fix typeahead on mobile devices

adjust mobile tests
  • Loading branch information
ndeshev authored Jun 1, 2022
1 parent ff044b0 commit edcdd24
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 20 deletions.
29 changes: 19 additions & 10 deletions packages/main/src/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ class Input extends UI5Element {

_handleEnter(event) {
const itemPressed = !!(this.Suggestions && this.Suggestions.onEnter(event));

const innerInput = this.getInputDOMRefSync();
// Check for autocompleted item
const matchingItem = this.suggestionItems.find(item => {
return (item.text && item.text === this.value) || (item.textContent === this.value);
Expand All @@ -820,8 +820,12 @@ class Input extends UI5Element {
}
}

if (this._isPhone && !this.suggestionItems.length) {
innerInput.setSelectionRange(this.value.length, this.value.length);
}

if (!itemPressed) {
this.fireEventByAction(this.ACTION_ENTER);
this.fireEventByAction(this.ACTION_ENTER, event);
this.lastConfirmedValue = this.value;

if (this.FormSupport) {
Expand Down Expand Up @@ -902,7 +906,6 @@ class Input extends UI5Element {
this.focused = true; // invalidating property
this.previousValue = this.value;
this.valueBeforeItemPreview = this.value;
this._shouldAutocomplete = false;

this._inputIconFocused = event.target && event.target === this.querySelector("[ui5-icon]");
}
Expand Down Expand Up @@ -995,7 +998,9 @@ class Input extends UI5Element {
_handleInput(event) {
const inputDomRef = this.getInputDOMRefSync();
const emptyValueFiredOnNumberInput = this.value && this.isTypeNumber && !inputDomRef.value;
const eventType = event.inputType || event.detail.inputType;

this._shouldAutocomplete = eventType !== "deleteContentBackward" && !this.noTypeahead;
this.suggestionSelectionCanceled = false;

if (emptyValueFiredOnNumberInput && !this._backspaceKeyDown) {
Expand Down Expand Up @@ -1030,7 +1035,7 @@ class Input extends UI5Element {
this.valueBeforeItemPreview = newValue;

// fire events
this.fireEvent(this.EVENT_INPUT);
this.fireEvent(this.EVENT_INPUT, { inputType: event.inputType });
this.fireEvent("value-changed");
return;
}
Expand All @@ -1049,7 +1054,7 @@ class Input extends UI5Element {
*/
const skipFiring = (inputDomRef.value === this.value) && isIE() && !this._keyDown && !!this.placeholder;

!skipFiring && this.fireEventByAction(this.ACTION_USER_INPUT);
!skipFiring && this.fireEventByAction(this.ACTION_USER_INPUT, event);

this.hasSuggestionItemSelected = false;
this._isValueStateFocused = false;
Expand Down Expand Up @@ -1090,7 +1095,11 @@ class Input extends UI5Element {
this.value = value;

innerInput.value = value;
innerInput.setSelectionRange(filterValue.length, value.length);
setTimeout(() => {
innerInput.setSelectionRange(filterValue.length, value.length);
}, 0);

this._shouldAutocomplete = false;
}

_handleResize() {
Expand Down Expand Up @@ -1242,7 +1251,7 @@ class Input extends UI5Element {
return this.getSuggestionByListItem(this._previewItem);
}

async fireEventByAction(action) {
async fireEventByAction(action, event) {
await this.getInputDOMRef();

if (this.disabled || this.readonly) {
Expand All @@ -1268,7 +1277,7 @@ class Input extends UI5Element {
}

if (isUserInput) { // input
this.fireEvent(this.EVENT_INPUT);
this.fireEvent(this.EVENT_INPUT, { inputType: event.inputType });
// Angular two way data binding
this.fireEvent("value-changed");
return;
Expand Down Expand Up @@ -1300,8 +1309,8 @@ class Input extends UI5Element {
}

getInputDOMRefSync() {
if (isPhone() && this.Suggestions) {
return this.Suggestions && this.Suggestions.responsivePopover.querySelector(".ui5-input-inner-phone");
if (isPhone() && this.Suggestions && this.Suggestions.responsivePopover) {
return this.Suggestions.responsivePopover.querySelector(".ui5-input-inner-phone").shadowRoot.querySelector("input");
}

return this.nativeInput;
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/InputPopover.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
.value="{{value}}"
?show-clear-icon={{showClearIcon}}
placeholder="{{placeholder}}"
@input="{{_handleInput}}"
@ui5-input="{{_handleInput}}"
@change="{{_handleChange}}"
></ui5-input>
</div>
Expand Down
37 changes: 37 additions & 0 deletions packages/main/test/specs/Input.mobile.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const assert = require("chai").assert;
const PORT = require("./_port.js");

describe("Typeahead", () => {
before(async () => {
await browser.url(`http://localhost:${PORT}/test-resources/pages/Input.html`);
await browser.emulateDevice('iPhone X');
});

it("Should autocomplete the first matched suggestion item", async () => {
const input = await browser.$("#myInput2");
const sExpected = "Cozy";
const staticAreaItemClassName = await browser.getStaticAreaItemClassName("#myInput2")

await input.scrollIntoView();
await input.click();

const dialogInput = await browser.$(`.${staticAreaItemClassName}`).shadow$("ui5-responsive-popover").$(".ui5-input-inner-phone");
await dialogInput.keys("c");
assert.strictEqual(await dialogInput.getProperty("value"), sExpected, "Value is autocompleted");
});

it("Should not perform typeahead when it is disabled", async () => {
await browser.url(`http://localhost:${PORT}/test-resources/pages/Input.html`);

const input = await browser.$("#input-disabled-autocomplete");
const staticAreaItemClassName = await browser.getStaticAreaItemClassName("#input-disabled-autocomplete")

await input.scrollIntoView();
await input.click();

const dialogInput = await browser.$(`.${staticAreaItemClassName}`).shadow$("ui5-responsive-popover").$(".ui5-input-inner-phone");
await dialogInput.keys("c");

assert.strictEqual(await dialogInput.getProperty("value"), "c", "Value is not autocompleted");
});
});
19 changes: 10 additions & 9 deletions packages/main/test/specs/Input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,11 +345,11 @@ describe("Input general interaction", () => {
it("handles suggestions selection cancel with ESC", async () => {
await browser.url(`http://localhost:${PORT}/test-resources/pages/Input.html`);

const suggestionsInput = await browser.$("#myInputEsc").shadow$("input");
const suggestionsInput = await browser.$("#myInputEsc");

// act
await suggestionsInput.click();
await suggestionsInput.keys("ch");
await suggestionsInput.keys("c");
await suggestionsInput.keys("ArrowDown");

// assert
Expand All @@ -360,7 +360,7 @@ describe("Input general interaction", () => {
await suggestionsInput.keys("Escape");

// assert
assert.strictEqual(await suggestionsInput.getValue(), "ch",
assert.strictEqual(await suggestionsInput.getProperty("value"), "c",
"The value is restored as ESC has been pressed.");
});

Expand All @@ -379,19 +379,20 @@ describe("Input general interaction", () => {

assert.strictEqual(await suggestionsInput.getValue(), "", "The value is restored as ESC has been pressed.");

await suggestionsInput.keys("Some value");
await suggestionsInput.keys(["a", "b", "c"]);
await suggestionsInput.keys("Enter");
await suggestionsInput.keys("Another value");
await suggestionsInput.keys(["c", "b", "a"]);

// Close sugggestions
await suggestionsInput.keys("Escape");
// Clear value
await suggestionsInput.keys("Escape");

assert.strictEqual(await suggestionsInput.getValue(), "Some value", "The value is restored to the last confirmed by 'ENTER' press one.");
assert.strictEqual(await suggestionsInput.getValue(), "abc", "The value is restored to the last confirmed by 'ENTER' press one.");
});

it("handles group suggestion item via keyboard", async () => {

const suggestionsInput = await browser.$("#myInputGrouping").shadow$("input");
const inputResult = await browser.$("#inputResultGrouping").shadow$("input");
const staticAreaItemClassName = await browser.getStaticAreaItemClassName("#myInputGrouping");
Expand Down Expand Up @@ -539,19 +540,19 @@ describe("Input general interaction", () => {
it("Tests suggestions highlighting", async () => {
await browser.url(`http://localhost:${PORT}/test-resources/pages/Input.html`);

const input = await browser.$("#myInputHighlighted").shadow$("input");
const input = await browser.$("#myInputHighlighted");
const staticAreaItemClassName = await browser.getStaticAreaItemClassName("#myInputHighlighted");
const EXPTECTED_TEXT = "<b>Ad</b>am";

await input.click();
await input.keys("ad");
await input.keys(["a", "d"]);

const respPopover = await browser.$(`.${staticAreaItemClassName}`).shadow$("ui5-responsive-popover");
const firstListItem = await respPopover.$("ui5-list").$("ui5-li-suggestion-item");

assert.ok(await respPopover.isDisplayedInViewport(), "The popover is visible");
const firstItemHtml = await firstListItem.getHTML();
assert.include(firstItemHtml, EXPTECTED_TEXT, "The suggestions is highlighted.");
assert.include(firstItemHtml, "<b>Ad</b>am", "The suggestions is highlighted.");
});

it("Doesn't remove value on number type input even if locale specific delimiter/multiple delimiters", async () => {
Expand Down

0 comments on commit edcdd24

Please sign in to comment.