-
Notifications
You must be signed in to change notification settings - Fork 273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ui5-input): implement type ahead (autocomplete) #5211
Merged
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
8fd5d3d
feat(ui5-input): implement type ahead (autocomplete)
ndeshev 0dfcbd0
feat(ui5-input): typeahead
ndeshev d1341f9
feat(ui5-input): typeahead
ndeshev f487f04
Merge branch 'master' into input-typeahead
ndeshev 65f578e
feat(ui5-input): typeahead
ndeshev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,12 @@ | ||
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; | ||
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; | ||
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; | ||
import { isIE, isPhone, isSafari } from "@ui5/webcomponents-base/dist/Device.js"; | ||
import { | ||
isIE, | ||
isPhone, | ||
isSafari, | ||
isAndroid, | ||
} from "@ui5/webcomponents-base/dist/Device.js"; | ||
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; | ||
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js"; | ||
import { | ||
|
@@ -35,6 +40,7 @@ import Icon from "./Icon.js"; | |
// Templates | ||
import InputTemplate from "./generated/templates/InputTemplate.lit.js"; | ||
import InputPopoverTemplate from "./generated/templates/InputPopoverTemplate.lit.js"; | ||
import * as Filters from "./Filters.js"; | ||
|
||
import { | ||
VALUE_STATE_SUCCESS, | ||
|
@@ -212,6 +218,18 @@ const metadata = { | |
type: Boolean, | ||
}, | ||
|
||
/** | ||
* Defines whether the value will be autcompleted to match an item | ||
* | ||
* @type {boolean} | ||
* @defaultvalue true | ||
* @public | ||
* @since 1.4.0 | ||
*/ | ||
disableAutocomplete: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dont like that naming @ilhan007 ideas? :D |
||
type: Boolean, | ||
}, | ||
|
||
/** | ||
* Defines the HTML type of the component. | ||
* Available options are: <code>Text</code>, <code>Email</code>, | ||
|
@@ -596,6 +614,9 @@ class Input extends UI5Element { | |
// The last value confirmed by the user with "ENTER" | ||
this.lastConfirmedValue = ""; | ||
|
||
// The value that the user is typed in the input | ||
this.valueBeforeAutoComplete = ""; | ||
|
||
// Indicates, if the user pressed the BACKSPACE key. | ||
this._backspaceKeyDown = false; | ||
|
||
|
@@ -651,6 +672,25 @@ class Input extends UI5Element { | |
} else if (this.name) { | ||
console.warn(`In order for the "name" property to have effect, you should also: import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";`); // eslint-disable-line | ||
} | ||
|
||
const value = this.value; | ||
const innerInput = this.getInputDOMRefSync(); | ||
|
||
if (!innerInput || !value) { | ||
return; | ||
} | ||
|
||
const autoCompletedChars = innerInput.selectionEnd - innerInput.selectionStart; | ||
|
||
// 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) { | ||
const item = this._getFirstMatchingItem(value); | ||
|
||
// Keep the original typed in text intact | ||
this.valueBeforeAutoComplete += value.slice(this.valueBeforeAutoComplete.length, value.length); | ||
this._autocomplete(item, value); | ||
} | ||
} | ||
|
||
async onAfterRendering() { | ||
|
@@ -670,6 +710,9 @@ class Input extends UI5Element { | |
} | ||
|
||
_onkeydown(event) { | ||
this._isKeyNavigation = true; | ||
this._shouldAutocomplete = !this.disableAutocomplete && !(isBackSpace(event) || isDelete(event) || isEscape(event)); | ||
|
||
if (isUp(event)) { | ||
return this._handleUp(event); | ||
} | ||
|
@@ -720,6 +763,7 @@ class Input extends UI5Element { | |
} | ||
|
||
this._keyDown = true; | ||
this._isKeyNavigation = false; | ||
} | ||
|
||
_onkeyup(event) { | ||
|
@@ -760,6 +804,21 @@ class Input extends UI5Element { | |
_handleEnter(event) { | ||
const itemPressed = !!(this.Suggestions && this.Suggestions.onEnter(event)); | ||
|
||
// Check for autocompleted item | ||
const matchingItem = this.suggestionItems.find(item => { | ||
return (item.text && item.text === this.value) || (item.textContent === this.value); | ||
}); | ||
|
||
if (matchingItem) { | ||
const itemText = matchingItem.text ? matchingItem.text : matchingItem.textContent; | ||
|
||
this.getInputDOMRefSync().setSelectionRange(itemText.length, itemText.length); | ||
if (!itemPressed) { | ||
this.selectSuggestion(matchingItem, true); | ||
this.open = false; | ||
} | ||
} | ||
|
||
if (!itemPressed) { | ||
this.fireEventByAction(this.ACTION_ENTER); | ||
this.lastConfirmedValue = this.value; | ||
|
@@ -805,6 +864,8 @@ class Input extends UI5Element { | |
_handleEscape() { | ||
const hasSuggestions = this.showSuggestions && !!this.Suggestions; | ||
const isOpen = hasSuggestions && this.open; | ||
const innerInput = this.getInputDOMRefSync(); | ||
const isAutoCompleted = innerInput.selectionEnd - innerInput.selectionStart > 0; | ||
|
||
if (!isOpen) { | ||
this.value = this.lastConfirmedValue ? this.lastConfirmedValue : this.previousValue; | ||
|
@@ -813,12 +874,18 @@ class Input extends UI5Element { | |
|
||
if (hasSuggestions && isOpen && this.Suggestions._isItemOnTarget()) { | ||
// Restore the value. | ||
this.value = this.valueBeforeItemPreview; | ||
this.value = this.valueBeforeAutoComplete || this.valueBeforeItemPreview; | ||
|
||
// Mark that the selection has been canceled, so the popover can close | ||
// and not reopen, due to receiving focus. | ||
this.suggestionSelectionCanceled = true; | ||
this.focused = true; | ||
|
||
return; | ||
} | ||
|
||
if (isAutoCompleted) { | ||
this.value = this.valueBeforeAutoComplete; | ||
} | ||
|
||
if (this._isValueStateFocused) { | ||
|
@@ -830,9 +897,11 @@ class Input extends UI5Element { | |
async _onfocusin(event) { | ||
await this.getInputDOMRef(); | ||
|
||
this.valueBeforeAutoComplete = ""; | ||
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]"); | ||
} | ||
|
@@ -991,6 +1060,33 @@ class Input extends UI5Element { | |
this.isTyping = true; | ||
} | ||
|
||
_startsWithMatchingItems(str) { | ||
return Filters.StartsWith(str, this.suggestionItems); | ||
} | ||
|
||
_getFirstMatchingItem(current) { | ||
const matchingItems = this._startsWithMatchingItems(current).filter(item => !item.groupItem); | ||
|
||
if (matchingItems.length) { | ||
return matchingItems[0]; | ||
} | ||
} | ||
|
||
_autocomplete(item, filterValue) { | ||
if (!item) { | ||
return; | ||
} | ||
|
||
const value = item.text ? item.text : item.textContent || ""; | ||
const innerInput = this.getInputDOMRefSync(); | ||
|
||
filterValue = filterValue || ""; | ||
this.value = value; | ||
|
||
innerInput.value = value; | ||
innerInput.setSelectionRange(filterValue.length, value.length); | ||
} | ||
|
||
_handleResize() { | ||
this._inputWidth = this.offsetWidth; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens if a new poperty pops up next week, should we add another || ? Can we add the
propName
as parameter to the filter function? not big deal, just an idea