diff --git a/packages/main/src/Input.js b/packages/main/src/Input.js index 7c873c662b7c..957b07a4472c 100644 --- a/packages/main/src/Input.js +++ b/packages/main/src/Input.js @@ -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); @@ -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) { @@ -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]"); } @@ -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) { @@ -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; } @@ -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; @@ -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() { @@ -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) { @@ -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; @@ -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; diff --git a/packages/main/src/InputPopover.hbs b/packages/main/src/InputPopover.hbs index 294abbc683cf..831219a13e23 100644 --- a/packages/main/src/InputPopover.hbs +++ b/packages/main/src/InputPopover.hbs @@ -30,7 +30,7 @@ .value="{{value}}" ?show-clear-icon={{showClearIcon}} placeholder="{{placeholder}}" - @input="{{_handleInput}}" + @ui5-input="{{_handleInput}}" @change="{{_handleChange}}" > diff --git a/packages/main/test/specs/Input.mobile.spec.js b/packages/main/test/specs/Input.mobile.spec.js new file mode 100644 index 000000000000..55788b50395d --- /dev/null +++ b/packages/main/test/specs/Input.mobile.spec.js @@ -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"); + }); +}); \ No newline at end of file diff --git a/packages/main/test/specs/Input.spec.js b/packages/main/test/specs/Input.spec.js index 9f4a1f823bed..56a5c68634dd 100644 --- a/packages/main/test/specs/Input.spec.js +++ b/packages/main/test/specs/Input.spec.js @@ -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 @@ -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."); }); @@ -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"); @@ -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 = "Adam"; 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, "Adam", "The suggestions is highlighted."); }); it("Doesn't remove value on number type input even if locale specific delimiter/multiple delimiters", async () => {