diff --git a/src/components/room/MentionPopover.tsx b/src/components/room/MentionPopover.tsx index 734849c..b9d28aa 100644 --- a/src/components/room/MentionPopover.tsx +++ b/src/components/room/MentionPopover.tsx @@ -61,6 +61,8 @@ interface MentionPopoverProps { onSelect: (newValue: string, newCursorPosition: number) => void; textareaRef: React.RefObject; onOpenChange: (open: boolean) => void; + /** Mutable ref: write the confirm fn here so ChatInputArea can call it on Enter */ + onMentionConfirmRef?: React.MutableRefObject<() => void>; } // Category configuration with icons and colors @@ -372,6 +374,7 @@ export function MentionPopover({ onSelect, textareaRef, onOpenChange, + onMentionConfirmRef, }: MentionPopoverProps) { const [position, setPosition] = useState({ top: 0, left: 0 }); const [selectedIndex, setSelectedIndex] = useState(0); @@ -521,6 +524,14 @@ export function MentionPopover({ handleSelectRef.current = handleSelect; + // Register confirm fn so ChatInputArea can call it on Enter + if (onMentionConfirmRef) { + onMentionConfirmRef.current = () => { + const item = visibleSuggestionsRef.current[selectedIndexRef.current]; + if (item) handleSelectRef.current(item); + }; + } + // Position calculation useEffect(() => { if (!mentionState || !textareaRef.current) return; @@ -723,6 +734,11 @@ export function MentionPopover({ ↑↓ navigate + | + + ↵ + + select @@ -779,7 +795,7 @@ export function MentionPopover({
- Enter + Ctrl+Enter to insert
diff --git a/src/components/room/RoomChatPanel.tsx b/src/components/room/RoomChatPanel.tsx index a0d7dba..3976829 100644 --- a/src/components/room/RoomChatPanel.tsx +++ b/src/components/room/RoomChatPanel.tsx @@ -48,6 +48,8 @@ interface ChatInputAreaProps { draft: string; onDraftChange: (content: string) => void; onClearDraft: () => void; + /** Called when Enter is pressed with mention popover open — parent triggers mention selection */ + onMentionConfirm?: () => void; ref?: React.Ref; } @@ -65,6 +67,7 @@ const ChatInputArea = memo(function ChatInputArea({ draft, onDraftChange, onClearDraft, + onMentionConfirm, ref, }: ChatInputAreaProps) { const textareaRef = useRef(null); @@ -88,7 +91,6 @@ const ChatInputArea = memo(function ChatInputArea({ if (!textareaRef.current) return; const value = textareaRef.current.value; const cursorPos = textareaRef.current.selectionStart; - // Build new HTML mention: label const escapedLabel = label.replace(//g, '>'); const escapedId = id.replace(/"/g, '"'); const mentionText = `${escapedLabel} `; @@ -122,12 +124,18 @@ const ChatInputArea = memo(function ChatInputArea({ }; const handleKeyDown = (e: React.KeyboardEvent) => { - if (showMentionPopover && MENTION_POPOVER_KEYS.includes(e.key)) { + if (showMentionPopover && MENTION_POPOVER_KEYS.includes(e.key) && !e.ctrlKey && !e.shiftKey) { e.preventDefault(); + if ((e.key === 'Enter' || e.key === 'Tab') && onMentionConfirm) { + onMentionConfirm(); + } return; } - if (e.key === 'Enter' && !e.shiftKey) { + // Shift+Enter → let textarea naturally insert newline (pass through) + + // Ctrl+Enter → send message + if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); const content = e.currentTarget.value.trim(); if (content && !isSending) { @@ -135,6 +143,12 @@ const ChatInputArea = memo(function ChatInputArea({ onClearDraft(); } } + + // Plain Enter (no modifiers) → only trigger mention select; otherwise do nothing + if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey) { + e.preventDefault(); + // Do nothing — Shift+Enter falls through to let textarea insert newline + } }; return ( @@ -190,6 +204,7 @@ const ChatInputArea = memo(function ChatInputArea({ onSelect={handleMentionSelect} textareaRef={textareaRef} onOpenChange={setShowMentionPopover} + onMentionConfirmRef={mentionConfirmRef} /> )} @@ -268,6 +283,8 @@ export function RoomChatPanel({ room, isAdmin, onClose, onDelete }: RoomChatPane const messagesEndRef = useRef(null); const chatInputRef = useRef(null); + /** Shared ref: MentionPopover writes the confirm fn here; ChatInputArea reads and calls it */ + const mentionConfirmRef = useRef<() => void>(() => {}); const [replyingTo, setReplyingTo] = useState(null); const [editingMessage, setEditingMessage] = useState(null); @@ -559,6 +576,7 @@ export function RoomChatPanel({ room, isAdmin, onClose, onDelete }: RoomChatPane draft={draft} onDraftChange={setDraft} onClearDraft={clearDraft} + onMentionConfirm={() => mentionConfirmRef.current()} />