@@ -11,7 +11,6 @@ import { LexicalComposer } from "@lexical/react/LexicalComposer"
1111import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"
1212import { ContentEditable } from "@lexical/react/LexicalContentEditable"
1313import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"
14- import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin"
1514import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"
1615import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary"
1716import {
@@ -24,8 +23,6 @@ import {
2423 CLEAR_HISTORY_COMMAND ,
2524 COMMAND_PRIORITY_EDITOR ,
2625 createCommand ,
27- EditorState ,
28- LexicalEditor ,
2926 LexicalNode ,
3027 LexicalCommand ,
3128 TextNode ,
@@ -197,31 +194,38 @@ const LexicalTextArea = forwardRef<LexicalTextAreaHandle, LexicalTextAreaProps>(
197194 }
198195 } , [ value , editor ] )
199196
200- // --- OnChange Handler ---
201- const handleOnChange = ( editorState : EditorState , currentEditor : LexicalEditor , tags : Set < string > ) => {
202- if ( isFirstRender . current ) return
197+ // --- Event Handlers ---
198+ const handleInternalFocus = ( event : React . FocusEvent < HTMLDivElement > ) => {
199+ setIsFocused ( true )
200+ onFocus ( event )
201+ }
203202
204- editorState . read ( ( ) => {
205- const plainText = $getRoot ( ) . getTextContent ( )
203+ const handleInternalBlur = ( event : React . FocusEvent < HTMLDivElement > ) => {
204+ setTimeout ( ( ) => {
205+ setIsFocused ( false )
206+ onBlur ( event )
207+ } , 100 )
208+ }
206209
207- if ( plainText !== value ) {
208- onChange ( plainText )
209- }
210+ const handleKeyDown = async ( event : React . KeyboardEvent < HTMLDivElement > ) => {
211+ // Context Menu Logic - Check before Lexical update
212+ let showMenu = false
213+ let menuType : "mention" | "command" | null = null
214+ let query = ""
210215
211- // Context Menu Logic
216+ editor . getEditorState ( ) . read ( ( ) => {
212217 const selection = $getSelection ( )
213- let showMenu = false
214- let menuType : "mention" | "command" | null = null
215- let query = ""
216218
217219 if ( $isRangeSelection ( selection ) && selection . isCollapsed ( ) ) {
218220 const anchor = selection . anchor
219221 const anchorNode = anchor . getNode ( )
220222 const anchorOffset = anchor . offset
221223
222- if ( anchorNode instanceof TextNode ) {
224+ let textBeforeCursor = ""
225+
226+ if ( $isRangeSelection ( selection ) && selection . isCollapsed ( ) && anchorNode instanceof TextNode ) {
223227 const textContent = anchorNode . getTextContent ( )
224- const textBeforeCursor = textContent . substring ( 0 , anchorOffset )
228+ textBeforeCursor = textContent . substring ( 0 , anchorOffset )
225229
226230 const root = $getRoot ( )
227231 const firstParagraph = root . getFirstChild ( )
@@ -235,9 +239,25 @@ const LexicalTextArea = forwardRef<LexicalTextAreaHandle, LexicalTextAreaProps>(
235239 query = textBeforeCursor . substring ( 1 )
236240 }
237241 }
242+ }
238243
239- // handles mention
240- if ( ! showMenu ) {
244+ // handles mention
245+ if ( ! showMenu ) {
246+ // Check if the pressed key is '@'
247+ if ( event . key === "@" ) {
248+ // Check if '@' is at the start or preceded by whitespace in the current text before cursor
249+ const charBeforeCursor =
250+ textBeforeCursor . length > 0 ? textBeforeCursor [ textBeforeCursor . length - 1 ] : null
251+ if (
252+ textBeforeCursor . length === 0 ||
253+ ( charBeforeCursor !== null && / \s / . test ( charBeforeCursor ) )
254+ ) {
255+ showMenu = true
256+ menuType = "mention"
257+ query = "" // Initial query is empty after typing '@'
258+ }
259+ } else {
260+ // For other keys, check if we are currently in a mention query
241261 const mentionMatch = textBeforeCursor . match ( / @ ( [ ^ \s @ ] * ) $ / )
242262 if ( mentionMatch ) {
243263 const charBeforeMention = textBeforeCursor [ mentionMatch . index ! - 1 ]
@@ -250,32 +270,46 @@ const LexicalTextArea = forwardRef<LexicalTextAreaHandle, LexicalTextAreaProps>(
250270 }
251271 }
252272 }
273+ } )
274+
275+ // If Enter is pressed and a menu is expected, prevent default behavior
276+ if ( event . key === "Enter" && showMenu ) {
277+ event . preventDefault ( )
278+ event . stopPropagation ( )
279+ // Do not return here, allow the rest of the handler to update menu state
280+ }
281+
282+ console . log ( { showMenu, menuType } )
283+
284+ // Call context menu handlers immediately
285+ onShowContextMenu ( showMenu , menuType )
286+ if ( menuType === "mention" ) {
287+ onMentionQueryChange ( query )
288+ onCommandQueryChange ( "" )
289+ } else if ( menuType === "command" ) {
290+ onCommandQueryChange ( query )
291+ onMentionQueryChange ( "" )
292+ } else {
293+ onCommandQueryChange ( "" )
294+ onMentionQueryChange ( "" )
295+ }
296+
297+ // Continue with Lexical update for text content changes
298+ editor . update ( ( ) => {
299+ if ( isFirstRender . current ) {
300+ isFirstRender . current = false
301+ return
302+ }
253303
254- onShowContextMenu ( showMenu , menuType )
255- if ( menuType === "mention" ) {
256- onMentionQueryChange ( query )
257- onCommandQueryChange ( "" )
258- } else if ( menuType === "command" ) {
259- onCommandQueryChange ( query )
260- onMentionQueryChange ( "" )
261- } else {
262- onCommandQueryChange ( "" )
263- onMentionQueryChange ( "" )
304+ const plainText = $getRoot ( ) . getTextContent ( )
305+
306+ // if the text content is not equal to the value, update the value
307+ if ( plainText !== value ) {
308+ onChange ( plainText )
264309 }
265310 } )
266- }
267311
268- // --- Event Handlers ---
269- const handleInternalFocus = ( event : React . FocusEvent < HTMLDivElement > ) => {
270- setIsFocused ( true )
271- onFocus ( event )
272- }
273-
274- const handleInternalBlur = ( event : React . FocusEvent < HTMLDivElement > ) => {
275- setTimeout ( ( ) => {
276- setIsFocused ( false )
277- onBlur ( event )
278- } , 100 )
312+ onKeyDown ( event )
279313 }
280314
281315 // --- Drop/Drag Handlers ---
@@ -603,9 +637,9 @@ const LexicalTextArea = forwardRef<LexicalTextAreaHandle, LexicalTextAreaProps>(
603637 outline : "none" , // Remove default browser outline
604638 tabSize : 4 , // Standard tab size
605639 } }
606- onKeyDown = { onKeyDown }
607640 onFocus = { handleInternalFocus }
608641 onBlur = { handleInternalBlur }
642+ onKeyDown = { handleKeyDown }
609643 />
610644 }
611645 placeholder = {
@@ -628,7 +662,6 @@ const LexicalTextArea = forwardRef<LexicalTextAreaHandle, LexicalTextAreaProps>(
628662 }
629663 ErrorBoundary = { LexicalErrorBoundary } // Basic error boundary
630664 />
631- < OnChangePlugin onChange = { handleOnChange } ignoreSelectionChange = { true } />
632665 < HistoryPlugin />
633666 < AutosizePlugin contentEditableRef = { contentEditableRef } onHeightChange = { onHeightChange } />
634667 </ div >
0 commit comments