Skip to content

fix(ui5-input): prevent typeahead during IME composition #11976

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
39 changes: 38 additions & 1 deletion packages/main/src/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ type InputSuggestionScrollEventDetail = {
scrollContainer: HTMLElement;
}

type CompositionEventHandler = (e?: CompositionEvent) => void;

/**
* @class
* ### Overview
Expand Down Expand Up @@ -566,6 +568,13 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
@property({ type: Array })
_linksListenersArray: Array<(args: any) => void> = [];

/**
* Indicates whether the input is currently being composed (e.g. during IME input).
* @private
*/
@property({ type: Boolean, noAttribute: true })
_isComposing = false;

/**
* Defines the suggestion items.
*
Expand Down Expand Up @@ -606,6 +615,8 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
})
valueStateMessage!: Array<HTMLElement>;

_onCompositionStartBound: CompositionEventHandler;
_onCompositionEndBound: CompositionEventHandler;
hasSuggestionItemSelected: boolean;
valueBeforeItemSelection: string;
valueBeforeSelectionStart: string;
Expand Down Expand Up @@ -702,17 +713,43 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
this._keepInnerValue = false;
this._focusedAfterClear = false;
this._valueStateLinks = [];

this._onCompositionStartBound = this._onCompositionStart.bind(this);
this._onCompositionEndBound = this._onCompositionEnd.bind(this);
}

onEnterDOM() {
ResizeHandler.register(this, this._handleResizeBound);
registerUI5Element(this, this._updateAssociatedLabelsTexts.bind(this));
const input = this.nativeInput;
if (input) {
// Update to JSX bindings when Preact resolves bug with:
// https://github.com/preactjs/preact/issues/1978
input.addEventListener("compositionstart", this._onCompositionStartBound);
input.addEventListener("compositionend", this._onCompositionEndBound);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

place this in the template file

Copy link
Contributor Author

@aleksandar-terziev aleksandar-terziev Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed, Preact does not support composition events so we need this workaround until Preact resolves the bug

}

onExitDOM() {
ResizeHandler.deregister(this, this._handleResizeBound);
deregisterUI5Element(this);
this._removeLinksEventListeners();
const input = this.nativeInput;
if (input) {
input.removeEventListener("compositionstart", this._onCompositionStartBound);
input.removeEventListener("compositionend", this._onCompositionEndBound);
}
}

_onCompositionStart() {
this._isComposing = true;
}

_onCompositionEnd() {
//use requestAnimationFrame to defer the typeahead and autocomplete logic inside onBeforeRendering to ensure the input value is up-to-date after IME composition.
requestAnimationFrame(() => {
this._isComposing = false;
});
}

_highlightSuggestionItem(item: SuggestionItem) {
Expand Down Expand Up @@ -773,7 +810,7 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement

// 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) {
if (this._shouldAutocomplete && !isAndroid() && !autoCompletedChars && !this._isKeyNavigation && !this._isComposing) {
const item = this._getFirstMatchingItem(value);
if (item) {
this._handleTypeAhead(item);
Expand Down
Loading