Skip to content

Commit 5a9e4d3

Browse files
committed
fix: restore composition handling
1 parent 1343766 commit 5a9e4d3

File tree

1 file changed

+59
-1
lines changed

1 file changed

+59
-1
lines changed

packages/react/src/combobox/input/ComboboxInput.tsx

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ export const ComboboxInput = React.forwardRef(function ComboboxInput(
6161

6262
const disabled = fieldDisabled || comboboxDisabled || disabledProp;
6363

64+
const [composingValue, setComposingValue] = React.useState<string | null>(null);
65+
const isComposingRef = React.useRef(false);
66+
6467
const setInputElement = useEventCallback((element) => {
6568
store.apply({
6669
inputElement: element,
@@ -145,7 +148,7 @@ export const ComboboxInput = React.forwardRef(function ComboboxInput(
145148
triggerProps,
146149
{
147150
type: 'text',
148-
value: componentProps.value ?? inputValue,
151+
value: componentProps.value ?? composingValue ?? inputValue,
149152
'aria-readonly': readOnly || undefined,
150153
'aria-labelledby': labelId,
151154
disabled,
@@ -163,7 +166,62 @@ export const ComboboxInput = React.forwardRef(function ComboboxInput(
163166
fieldControlValidation?.commitValidation(valueToValidate);
164167
}
165168
},
169+
onCompositionStart(event) {
170+
isComposingRef.current = true;
171+
setComposingValue(event.currentTarget.value);
172+
},
173+
onCompositionEnd(event) {
174+
isComposingRef.current = false;
175+
const next = event.currentTarget.value;
176+
setComposingValue(null);
177+
store.state.setInputValue(
178+
next,
179+
createBaseUIEventDetails('input-change', event.nativeEvent),
180+
);
181+
},
166182
onChange(event: React.ChangeEvent<HTMLInputElement>) {
183+
// During IME composition, avoid propagating controlled updates to preserve
184+
// its state.
185+
if (isComposingRef.current) {
186+
const nextVal = event.currentTarget.value;
187+
setComposingValue(nextVal);
188+
189+
if (nextVal === '' && !openOnInputClick && !hasPositionerParent) {
190+
store.state.setOpen(
191+
false,
192+
createBaseUIEventDetails('input-clear', event.nativeEvent),
193+
);
194+
}
195+
196+
if (!readOnly && !disabled) {
197+
const trimmed = nextVal.trim();
198+
if (trimmed !== '') {
199+
store.state.setOpen(true, createBaseUIEventDetails('none', event.nativeEvent));
200+
if (!(selectionMode === 'none' && autoHighlight)) {
201+
store.state.setIndices({
202+
activeIndex: null,
203+
selectedIndex: null,
204+
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer',
205+
});
206+
}
207+
}
208+
}
209+
210+
if (
211+
open &&
212+
store.state.activeIndex !== null &&
213+
!(selectionMode === 'none' && autoHighlight && nextVal.trim() !== '')
214+
) {
215+
store.state.setIndices({
216+
activeIndex: null,
217+
selectedIndex: null,
218+
type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer',
219+
});
220+
}
221+
222+
return;
223+
}
224+
167225
store.state.setInputValue(
168226
event.currentTarget.value,
169227
createBaseUIEventDetails('input-change', event.nativeEvent),

0 commit comments

Comments
 (0)