mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
fix(core): handle composition event for Input component (#8351)
close AF-1065
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
export * from './input';
|
||||
export * from './row-input';
|
||||
import { Input } from './input';
|
||||
export default Input;
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import clsx from 'clsx';
|
||||
import type {
|
||||
ChangeEvent,
|
||||
CSSProperties,
|
||||
ForwardedRef,
|
||||
InputHTMLAttributes,
|
||||
KeyboardEvent,
|
||||
KeyboardEventHandler,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { forwardRef, useCallback, useEffect } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { useAutoFocus, useAutoSelect } from '../../hooks';
|
||||
import { RowInput } from './row-input';
|
||||
import { input, inputWrapper } from './style.css';
|
||||
|
||||
export type InputProps = {
|
||||
@@ -50,32 +48,6 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
}: InputProps,
|
||||
upstreamRef: ForwardedRef<HTMLInputElement>
|
||||
) {
|
||||
const focusRef = useAutoFocus<HTMLInputElement>(autoFocus);
|
||||
const selectRef = useAutoSelect<HTMLInputElement>(autoSelect);
|
||||
|
||||
const inputRef = (el: HTMLInputElement | null) => {
|
||||
focusRef.current = el;
|
||||
selectRef.current = el;
|
||||
if (upstreamRef) {
|
||||
if (typeof upstreamRef === 'function') {
|
||||
upstreamRef(el);
|
||||
} else {
|
||||
upstreamRef.current = el;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// use native blur event to get event after unmount
|
||||
// don't use useLayoutEffect here, because the cleanup function will be called before unmount
|
||||
useEffect(() => {
|
||||
if (!onBlur) return;
|
||||
selectRef.current?.addEventListener('blur', onBlur as any);
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
selectRef.current?.removeEventListener('blur', onBlur as any);
|
||||
};
|
||||
}, [onBlur, selectRef]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(inputWrapper, className, {
|
||||
@@ -96,29 +68,20 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
}}
|
||||
>
|
||||
{preFix}
|
||||
<input
|
||||
<RowInput
|
||||
className={clsx(input, {
|
||||
large: size === 'large',
|
||||
'extra-large': size === 'extraLarge',
|
||||
})}
|
||||
ref={inputRef}
|
||||
ref={upstreamRef}
|
||||
disabled={disabled}
|
||||
style={inputStyle}
|
||||
onChange={useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
propsOnChange?.(e.target.value);
|
||||
},
|
||||
[propsOnChange]
|
||||
)}
|
||||
onKeyDown={useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
onEnter?.();
|
||||
}
|
||||
onKeyDown?.(e);
|
||||
},
|
||||
[onKeyDown, onEnter]
|
||||
)}
|
||||
onChange={propsOnChange}
|
||||
onEnter={onEnter}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={onBlur}
|
||||
autoFocus={autoFocus}
|
||||
autoSelect={autoSelect}
|
||||
{...otherProps}
|
||||
/>
|
||||
{endFix}
|
||||
|
||||
112
packages/frontend/component/src/ui/input/row-input.tsx
Normal file
112
packages/frontend/component/src/ui/input/row-input.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import type {
|
||||
ChangeEvent,
|
||||
CompositionEventHandler,
|
||||
CSSProperties,
|
||||
ForwardedRef,
|
||||
InputHTMLAttributes,
|
||||
KeyboardEvent,
|
||||
KeyboardEventHandler,
|
||||
} from 'react';
|
||||
import { forwardRef, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { useAutoFocus, useAutoSelect } from '../../hooks';
|
||||
|
||||
export type RowInputProps = {
|
||||
disabled?: boolean;
|
||||
onChange?: (value: string) => void;
|
||||
onBlur?: (ev: FocusEvent & { currentTarget: HTMLInputElement }) => void;
|
||||
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
||||
autoSelect?: boolean;
|
||||
type?: HTMLInputElement['type'];
|
||||
style?: CSSProperties;
|
||||
onEnter?: () => void;
|
||||
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size' | 'onBlur'>;
|
||||
|
||||
// RowInput component that is used in the selector layout for search input
|
||||
// handles composition events and enter key press
|
||||
export const RowInput = forwardRef<HTMLInputElement, RowInputProps>(
|
||||
function RowInput(
|
||||
{
|
||||
disabled,
|
||||
onChange: propsOnChange,
|
||||
className,
|
||||
style = {},
|
||||
onEnter,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
autoFocus,
|
||||
autoSelect,
|
||||
...otherProps
|
||||
}: RowInputProps,
|
||||
upstreamRef: ForwardedRef<HTMLInputElement>
|
||||
) {
|
||||
const [composing, setComposing] = useState(false);
|
||||
const focusRef = useAutoFocus<HTMLInputElement>(autoFocus);
|
||||
const selectRef = useAutoSelect<HTMLInputElement>(autoSelect);
|
||||
|
||||
const inputRef = (el: HTMLInputElement | null) => {
|
||||
focusRef.current = el;
|
||||
selectRef.current = el;
|
||||
if (upstreamRef) {
|
||||
if (typeof upstreamRef === 'function') {
|
||||
upstreamRef(el);
|
||||
} else {
|
||||
upstreamRef.current = el;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// use native blur event to get event after unmount
|
||||
// don't use useLayoutEffect here, because the cleanup function will be called before unmount
|
||||
useEffect(() => {
|
||||
if (!onBlur) return;
|
||||
selectRef.current?.addEventListener('blur', onBlur as any);
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
selectRef.current?.removeEventListener('blur', onBlur as any);
|
||||
};
|
||||
}, [onBlur, selectRef]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
propsOnChange?.(e.target.value);
|
||||
},
|
||||
[propsOnChange]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
onKeyDown?.(e);
|
||||
if (e.key !== 'Enter' || composing) {
|
||||
return;
|
||||
}
|
||||
onEnter?.();
|
||||
},
|
||||
[onKeyDown, composing, onEnter]
|
||||
);
|
||||
|
||||
const handleCompositionStart: CompositionEventHandler<HTMLInputElement> =
|
||||
useCallback(() => {
|
||||
setComposing(true);
|
||||
}, []);
|
||||
|
||||
const handleCompositionEnd: CompositionEventHandler<HTMLInputElement> =
|
||||
useCallback(() => {
|
||||
setComposing(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<input
|
||||
className={className}
|
||||
ref={inputRef}
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user