Skip to content

Commit

Permalink
Correctly handle IME composition in <Combobox.Input> (#2426)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
thecrypticace committed Apr 17, 2023
1 parent 3536745 commit b98e642
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix `className` hydration for `<Transition appear>` ([#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 `<Combobox.Input>` ([#2426](https://github.com/tailwindlabs/headlessui/pull/2426))

### Added

Expand Down
13 changes: 12 additions & 1 deletion packages/@headlessui-react/src/components/combobox/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -821,12 +821,19 @@ function InputFn<
)

let isComposing = useRef(false)
let composedChangeEvent = useRef<React.ChangeEvent<HTMLInputElement> | 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
}
})
})

Expand Down Expand Up @@ -953,6 +960,10 @@ function InputFn<
})

let handleChange = useEvent((event: React.ChangeEvent<HTMLInputElement>) => {
if (isComposing.current) {
composedChangeEvent.current = event
return
}
actions.openCombobox()
onChange?.(event)
})
Expand Down
1 change: 1 addition & 0 deletions packages/@headlessui-vue/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Dialog>` 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 `<Combobox.Input>` ([#2426](https://github.com/tailwindlabs/headlessui/pull/2426))

### Added

Expand Down
14 changes: 13 additions & 1 deletion packages/@headlessui-vue/src/components/combobox/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(a: T, z: T): boolean {
return a === z
Expand Down Expand Up @@ -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
}
})
}

Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit b98e642

Please sign in to comment.