From b98e642a67db281dffe75a338c77b0d6ac98a4fb Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 17 Apr 2023 10:17:28 -0400 Subject: [PATCH] Correctly handle IME composition in `` (#2426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don’t try to open combobox when composing characters * wip * Delay IME composition end until after keydown events * Use `d.nextFrame` to handle `compositionend` event * Update changelog --- packages/@headlessui-react/CHANGELOG.md | 1 + .../src/components/combobox/combobox.tsx | 13 ++++++++++++- packages/@headlessui-vue/CHANGELOG.md | 1 + .../src/components/combobox/combobox.ts | 14 +++++++++++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index ee8b6aab4d..a27e3fc72e 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix `className` hydration for `` ([#2390](https://github.com/tailwindlabs/headlessui/pull/2390)) - Improve `Combobox` types to improve false positives ([#2411](https://github.com/tailwindlabs/headlessui/pull/2411)) - Merge `className` correctly when it’s a function ([#2412](https://github.com/tailwindlabs/headlessui/pull/2412)) +- Correctly handle IME composition in `` ([#2426](https://github.com/tailwindlabs/headlessui/pull/2426)) ### Added diff --git a/packages/@headlessui-react/src/components/combobox/combobox.tsx b/packages/@headlessui-react/src/components/combobox/combobox.tsx index 60b922f5aa..d7a508ea14 100644 --- a/packages/@headlessui-react/src/components/combobox/combobox.tsx +++ b/packages/@headlessui-react/src/components/combobox/combobox.tsx @@ -821,12 +821,19 @@ function InputFn< ) let isComposing = useRef(false) + let composedChangeEvent = useRef | null>(null) let handleCompositionStart = useEvent(() => { isComposing.current = true }) let handleCompositionEnd = useEvent(() => { - setTimeout(() => { + d.nextFrame(() => { isComposing.current = false + + if (composedChangeEvent.current) { + actions.openCombobox() + onChange?.(composedChangeEvent.current) + composedChangeEvent.current = null + } }) }) @@ -953,6 +960,10 @@ function InputFn< }) let handleChange = useEvent((event: React.ChangeEvent) => { + if (isComposing.current) { + composedChangeEvent.current = event + return + } actions.openCombobox() onChange?.(event) }) diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index c6a6f6aa72..a96b2fabcd 100644 --- a/packages/@headlessui-vue/CHANGELOG.md +++ b/packages/@headlessui-vue/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `FocusTrap` event listeners once document has loaded ([#2389](https://github.com/tailwindlabs/headlessui/pull/2389)) - Don't scroll-lock `` when wrapping transition isn't showing ([#2422](https://github.com/tailwindlabs/headlessui/pull/2422)) - Ensure DOM `ref` is properly handled in the `RadioGroup` component ([#2424](https://github.com/tailwindlabs/headlessui/pull/2424)) +- Correctly handle IME composition in `` ([#2426](https://github.com/tailwindlabs/headlessui/pull/2426)) ### Added diff --git a/packages/@headlessui-vue/src/components/combobox/combobox.ts b/packages/@headlessui-vue/src/components/combobox/combobox.ts index b5ec3e7cbf..c0f26aa4ec 100644 --- a/packages/@headlessui-vue/src/components/combobox/combobox.ts +++ b/packages/@headlessui-vue/src/components/combobox/combobox.ts @@ -37,6 +37,7 @@ import { objectToFormEntries } from '../../utils/form' import { useControllable } from '../../hooks/use-controllable' import { useTrackedPointer } from '../../hooks/use-tracked-pointer' import { isMobile } from '../../utils/platform' +import { disposables } from '../../utils/disposables' function defaultComparator(a: T, z: T): boolean { return a === z @@ -763,12 +764,19 @@ export let ComboboxInput = defineComponent({ }) let isComposing = ref(false) + let composedChangeEvent = ref<(Event & { target: HTMLInputElement }) | null>(null) function handleCompositionstart() { isComposing.value = true } function handleCompositionend() { - setTimeout(() => { + disposables().nextFrame(() => { isComposing.value = false + + if (composedChangeEvent.value) { + api.openCombobox() + emit('change', composedChangeEvent.value) + composedChangeEvent.value = null + } }) } @@ -891,6 +899,10 @@ export let ComboboxInput = defineComponent({ } function handleInput(event: Event & { target: HTMLInputElement }) { + if (isComposing.value) { + composedChangeEvent.value = event + return + } api.openCombobox() emit('change', event) }