68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
import { useDialogComposition } from "@/components/ui/dialog";
|
|
import { useComposition } from "@/hooks/useComposition";
|
|
import { cn } from "@/lib/utils";
|
|
import * as React from "react";
|
|
|
|
function Textarea({
|
|
className,
|
|
onKeyDown,
|
|
onCompositionStart,
|
|
onCompositionEnd,
|
|
...props
|
|
}: React.ComponentProps<"textarea">) {
|
|
// 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<HTMLTextAreaElement>({
|
|
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)
|
|
// Note: For textarea, Shift+Enter should still work for newlines
|
|
if (e.key === "Enter" && !e.shiftKey && 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 (
|
|
<textarea
|
|
data-slot="textarea"
|
|
className={cn(
|
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
className
|
|
)}
|
|
onCompositionStart={handleCompositionStart}
|
|
onCompositionEnd={handleCompositionEnd}
|
|
onKeyDown={handleKeyDown}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export { Textarea };
|