diff --git a/packages/main/src/Input.js b/packages/main/src/Input.js index 14c104087838..b583edd3b73e 100644 --- a/packages/main/src/Input.js +++ b/packages/main/src/Input.js @@ -11,6 +11,7 @@ import { isSpace, isEnter, isBackSpace, + isEscape, } from "@ui5/webcomponents-base/dist/Keys.js"; import Integer from "@ui5/webcomponents-base/dist/types/Integer.js"; import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; @@ -494,11 +495,20 @@ class Input extends UI5Element { // Indicates if there is selected suggestionItem. this.hasSuggestionItemSelected = false; - // Represents the value before user moves selection between the suggestion items. - // Used to register and fire "input" event upon [SPACE] or [ENTER]. - // Note: the property "value" is updated upon selection move and can`t be used. + // Represents the value before user moves selection from suggestion item to another + // and its value is updated after each move. + // Note: Used to register and fire "input" event upon [SPACE] or [ENTER]. + // Note: The property "value" is updated upon selection move and can`t be used. this.valueBeforeItemSelection = ""; + // Represents the value before user moves selection between the suggestion items + // and its value remains the same when the user navigates up or down the list. + // Note: Used to cancel selection upon [ESC]. + this.valueBeforeItemPreview = ""; + + // Indicates if the user selection has been canceled with [ESC]. + this.suggestionSelectionCanceled = false; + // tracks the value between focus in and focus out to detect that change event should be fired. this.previousValue = undefined; @@ -591,6 +601,10 @@ class Input extends UI5Element { return this._handleEnter(event); } + if (isEscape(event)) { + return this._handleEscape(event); + } + this._keyDown = true; } @@ -624,6 +638,20 @@ class Input extends UI5Element { } } + _handleEscape() { + if (this.showSuggestions && this.Suggestions && this.Suggestions._isItemOnTarget()) { + // Restore the value. + this.value = this.valueBeforeItemPreview; + + // Mark that the selection has been canceled, so the popover can close + // and not reopen, due to receiving focus. + this.suggestionSelectionCanceled = true; + + // Close suggestion popover + this._closeRespPopover(true); + } + } + async _onfocusin(event) { const inputDomRef = await this.getInputDOMRef(); @@ -633,6 +661,7 @@ class Input extends UI5Element { this.focused = true; // invalidating property this.previousValue = this.value; + this.valueBeforeItemPreview = this.value; this._inputIconFocused = event.target && event.target === this.querySelector("[ui5-icon]"); } @@ -682,6 +711,8 @@ class Input extends UI5Element { async _handleInput(event) { const inputDomRef = await this.getInputDOMRef(); + this.suggestionSelectionCanceled = false; + if (this.value && this.type === InputType.Number && !isBackSpace(event) && !inputDomRef.value) { // For input with type="Number", if the delimiter is entered second time, the inner input is firing event with empty value return; @@ -711,8 +742,8 @@ class Input extends UI5Element { this._inputWidth = this.offsetWidth; } - _closeRespPopover() { - this.Suggestions.close(); + _closeRespPopover(preventFocusRestore) { + this.Suggestions.close(preventFocusRestore); } async _afterOpenPopover() { @@ -786,7 +817,8 @@ class Input extends UI5Element { return !!(this.suggestionItems.length && this.focused && this.showSuggestions - && !this.hasSuggestionItemSelected); + && !this.hasSuggestionItemSelected + && !this.suggestionSelectionCanceled); } selectSuggestion(item, keyboardUsed) { @@ -807,6 +839,9 @@ class Input extends UI5Element { this.fireEvent(this.EVENT_CHANGE); } + this.valueBeforeItemPreview = ""; + this.suggestionSelectionCanceled = false; + this.fireEvent(this.EVENT_SUGGESTION_ITEM_SELECT, { item }); } @@ -857,6 +892,7 @@ class Input extends UI5Element { this.value = inputValue; this.highlightValue = inputValue; + this.valueBeforeItemPreview = inputValue; if (isSafari()) { // When setting the value by hand, Safari moves the cursor when typing in the middle of the text (See #1761) diff --git a/packages/main/test/pages/Input.html b/packages/main/test/pages/Input.html index 2fc6cb900cfa..a3e5541fdcda 100644 --- a/packages/main/test/pages/Input.html +++ b/packages/main/test/pages/Input.html @@ -134,6 +134,15 @@

Input test change

Input test change result

+

Input test ESC

+ + + + + + + +

Input readonly

diff --git a/packages/main/test/specs/Input.spec.js b/packages/main/test/specs/Input.spec.js index c1455e201373..d11f89ab87ae 100644 --- a/packages/main/test/specs/Input.spec.js +++ b/packages/main/test/specs/Input.spec.js @@ -215,6 +215,26 @@ describe("Input general interaction", () => { assert.strictEqual(inputResult.getValue(), "1", "suggestionItemSelect is not fired as item is 'Inactive'"); }); + it("handles suggestions selection cancel with ESC", () => { + const suggestionsInput = $("#myInputEsc").shadow$("input"); + + // act + suggestionsInput.click(); + suggestionsInput.keys("ch"); + suggestionsInput.keys("ArrowDown"); + + // assert + assert.strictEqual(suggestionsInput.getValue(), "Chromium", + "The value is updated as the item has been previewed."); + + // act + suggestionsInput.keys("Escape"); + + // assert + assert.strictEqual(suggestionsInput.getValue(), "ch", + "The value is restored as ESC has been pressed."); + }); + it("handles group suggestion item via keyboard", () => { const suggestionsInput = $("#myInputGrouping").shadow$("input"); const inputResult = $("#inputResultGrouping").shadow$("input");