71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
import { useDialogComposition } from "@/components/ui/dialog";
|
|
import { useComposition } from "@/hooks/useComposition";
|
|
import { cn } from "@/lib/utils";
|
|
import * as React from "react";
|
|
|
|
function Input({
|
|
className,
|
|
type,
|
|
onKeyDown,
|
|
onCompositionStart,
|
|
onCompositionEnd,
|
|
...props
|
|
}: React.ComponentProps<"input">) {
|
|
// Get dialog composition context if available (will be no-op if not inside Dialog)
|
|
const dialogComposition = useDialogComposition();
|
|
|
|
// Add composition event handlers to support input method editor (IME) for CJK languages.
|
|
const {
|
|
onCompositionStart: handleCompositionStart,
|
|
onCompositionEnd: handleCompositionEnd,
|
|
onKeyDown: handleKeyDown,
|
|
} = useComposition<HTMLInputElement>({
|
|
onKeyDown: (e) => {
|
|
// Check if this is an Enter key that should be blocked
|
|
const isComposing = (e.nativeEvent as any).isComposing || dialogComposition.justEndedComposing();
|
|
|
|
// If Enter key is pressed while composing or just after composition ended,
|
|
// don't call the user's onKeyDown (this blocks the business logic)
|
|
if (e.key === "Enter" && isComposing) {
|
|
return;
|
|
}
|
|
|
|
// Otherwise, call the user's onKeyDown
|
|
onKeyDown?.(e);
|
|
},
|
|
onCompositionStart: e => {
|
|
dialogComposition.setComposing(true);
|
|
onCompositionStart?.(e);
|
|
},
|
|
onCompositionEnd: e => {
|
|
// Mark that composition just ended - this helps handle the Enter key that confirms input
|
|
dialogComposition.markCompositionEnd();
|
|
// Delay setting composing to false to handle Safari's event order
|
|
// In Safari, compositionEnd fires before the ESC keydown event
|
|
setTimeout(() => {
|
|
dialogComposition.setComposing(false);
|
|
}, 100);
|
|
onCompositionEnd?.(e);
|
|
},
|
|
});
|
|
|
|
return (
|
|
<input
|
|
type={type}
|
|
data-slot="input"
|
|
className={cn(
|
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
className
|
|
)}
|
|
onCompositionStart={handleCompositionStart}
|
|
onCompositionEnd={handleCompositionEnd}
|
|
onKeyDown={handleKeyDown}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export { Input };
|