- fix: fix IME composition input causing character duplication

Handle onCompositionStart/End events to prevent repeated characters
  when typing Chinese in proxy group filter
This commit is contained in:
xmk23333 2026-01-19 13:04:34 +08:00
parent 3c148f2c01
commit 388581d75e
2 changed files with 44 additions and 4 deletions

View File

@ -1,20 +1,60 @@
import React, { useRef } from 'react'
import React, { useRef, useState, useCallback } from 'react'
import { Input, InputProps } from '@heroui/react'
import { FaSearch } from 'react-icons/fa'
interface CollapseInputProps extends InputProps {
interface CollapseInputProps extends Omit<InputProps, 'onValueChange'> {
title: string
onValueChange?: (value: string) => void
}
const CollapseInput: React.FC<CollapseInputProps> = (props) => {
const { title, ...inputProps } = props
const { title, value, onValueChange, ...inputProps } = props
const inputRef = useRef<HTMLInputElement>(null)
const isComposingRef = useRef(false)
const [localValue, setLocalValue] = useState(value || '')
// 同步外部 value 变化
React.useEffect(() => {
if (!isComposingRef.current) {
setLocalValue(value || '')
}
}, [value])
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value
setLocalValue(newValue)
// 只在非组合输入时触发外部更新
if (!isComposingRef.current) {
onValueChange?.(newValue)
}
},
[onValueChange]
)
const handleCompositionStart = useCallback(() => {
isComposingRef.current = true
}, [])
const handleCompositionEnd = useCallback(
(e: React.CompositionEvent<HTMLInputElement>) => {
isComposingRef.current = false
// 组合输入结束后,触发一次更新
onValueChange?.(e.currentTarget.value)
},
[onValueChange]
)
return (
<div className="flex">
<Input
size="sm"
ref={inputRef}
{...inputProps}
value={localValue as string}
onChange={handleChange}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
style={{ paddingInlineEnd: 0 }}
classNames={{
inputWrapper: 'cursor-pointer bg-transparent p-0 data-[hover=true]:bg-content2',

View File

@ -303,7 +303,7 @@ const drawSvg = async (
if (upload === currentUploadRef.current && download === currentDownloadRef.current) return
currentUploadRef.current = upload
currentDownloadRef.current = download
const svg = `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 36"><image height="36" width="36" href="${trayIconBase64}"/><text x="140" y="15" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end">${calcTraffic(upload)}/s</text><text x="140" y="34" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end">${calcTraffic(download)}/s</text></svg>`
const svg = `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 156 36"><image height="36" width="36" href="${trayIconBase64}"/><text x="156" y="15" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end" fill="black">${calcTraffic(upload)}/s</text><text x="156" y="34" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end" fill="black">${calcTraffic(download)}/s</text></svg>`
const image = await loadImage(svg)
window.electron.ipcRenderer.send('trayIconUpdate', image, true)
}