mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
fix: add IME-safe onPressEnter prop to base Input component (#31757)
The base Input component lacked IME composition detection, causing Enter key presses during CJK input method candidate selection to mistakenly trigger form submissions. Add an `onPressEnter` prop with built-in IME safety using compositionStart/End tracking and nativeEvent.isComposing checks (with Safari 50ms delay workaround). Migrate all 5 call sites from manual onKeyDown Enter detection to onPressEnter.
This commit is contained in:
parent
f90fa2b186
commit
3997749867
@ -145,10 +145,7 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
id="password"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter')
|
||||
handleEmailPasswordLogin()
|
||||
}}
|
||||
onPressEnter={() => handleEmailPasswordLogin()}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
autoComplete="current-password"
|
||||
placeholder={t('passwordPlaceholder', { ns: 'login' }) || ''}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import type { ChangeEventHandler, CSSProperties, FocusEventHandler } from 'react'
|
||||
import type { ChangeEventHandler, CSSProperties, FocusEventHandler, KeyboardEventHandler } from 'react'
|
||||
import { RiCloseCircleFill, RiErrorWarningLine, RiSearchLine } from '@remixicon/react'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
@ -33,6 +33,7 @@ export type InputProps = {
|
||||
wrapperClassName?: string
|
||||
styleCss?: CSSProperties
|
||||
unit?: string
|
||||
onPressEnter?: KeyboardEventHandler<HTMLInputElement>
|
||||
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> & VariantProps<typeof inputVariants>
|
||||
|
||||
const removeLeadingZeros = (value: string) => value.replace(/^(-?)0+(?=\d)/, '$1')
|
||||
@ -52,10 +53,26 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
||||
placeholder,
|
||||
onChange = noop,
|
||||
onBlur = noop,
|
||||
onKeyDown,
|
||||
onPressEnter,
|
||||
unit,
|
||||
...props
|
||||
}, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const isComposingRef = React.useRef(false)
|
||||
const handleCompositionStart = () => {
|
||||
isComposingRef.current = true
|
||||
}
|
||||
const handleCompositionEnd = () => {
|
||||
setTimeout(() => {
|
||||
isComposingRef.current = false
|
||||
}, 50)
|
||||
}
|
||||
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
|
||||
if (onPressEnter && e.key === 'Enter' && !e.nativeEvent.isComposing && !isComposingRef.current)
|
||||
onPressEnter(e)
|
||||
onKeyDown?.(e)
|
||||
}
|
||||
const handleNumberChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
if (value === 0) {
|
||||
// remove leading zeros
|
||||
@ -108,6 +125,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
||||
onBlur={props.type === 'number' ? handleNumberBlur : onBlur}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
onKeyDown={handleKeyDown}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
/>
|
||||
{!!(showClearIcon && value && !disabled && !destructive) && (
|
||||
<div
|
||||
|
||||
@ -71,12 +71,12 @@ const CustomizedPagination: FC<Props> = ({
|
||||
setShowInput(false)
|
||||
}
|
||||
|
||||
const handleInputPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
handleInputConfirm()
|
||||
}
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
handleInputConfirm()
|
||||
}
|
||||
else if (e.key === 'Escape') {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
setInputValue(current + 1)
|
||||
setShowInput(false)
|
||||
@ -132,6 +132,7 @@ const CustomizedPagination: FC<Props> = ({
|
||||
autoFocus
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onPressEnter={handleInputPressEnter}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
|
||||
@ -319,22 +319,17 @@ const GotoAnything: FC<Props> = ({
|
||||
if (!e.target.value.startsWith('@') && !e.target.value.startsWith('/'))
|
||||
clearSelection()
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const query = searchQuery.trim()
|
||||
// Check if it's a complete slash command
|
||||
if (query.startsWith('/')) {
|
||||
const commandName = query.substring(1).split(' ')[0]
|
||||
const handler = slashCommandRegistry.findCommand(commandName)
|
||||
|
||||
// If it's a direct mode command, execute immediately
|
||||
const isAvailable = handler?.isAvailable?.() ?? true
|
||||
if (handler?.mode === 'direct' && handler.execute && isAvailable) {
|
||||
e.preventDefault()
|
||||
handler.execute()
|
||||
setShow(false)
|
||||
setSearchQuery('')
|
||||
}
|
||||
onPressEnter={(e) => {
|
||||
const query = searchQuery.trim()
|
||||
if (query.startsWith('/')) {
|
||||
const commandName = query.substring(1).split(' ')[0]
|
||||
const handler = slashCommandRegistry.findCommand(commandName)
|
||||
const isAvailable = handler?.isAvailable?.() ?? true
|
||||
if (handler?.mode === 'direct' && handler.execute && isAvailable) {
|
||||
e.preventDefault()
|
||||
handler.execute()
|
||||
setShow(false)
|
||||
setSearchQuery('')
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@ -139,10 +139,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter')
|
||||
handleEmailPasswordLogin()
|
||||
}}
|
||||
onPressEnter={() => handleEmailPasswordLogin()}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
autoComplete="current-password"
|
||||
placeholder={t('passwordPlaceholder', { ns: 'login' }) || ''}
|
||||
|
||||
@ -103,12 +103,10 @@ export default function InviteSettingsPage() {
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder={t('namePlaceholder', { ns: 'login' }) || ''}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleActivate()
|
||||
}
|
||||
onPressEnter={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleActivate()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1234,11 +1234,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/input/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/logo/dify-logo.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
|
||||
Loading…
Reference in New Issue
Block a user