@@ -382,13 +382,14 @@ class WikilinkMenu {
382382
383383const wikilinkPluginKey = new PluginKey ( 'wikilink-autocomplete' )
384384
385- const Editor = forwardRef ( function Editor ( { defaultValue = '' , onChange, onDrawioOpen, onMediaPickerOpen } , ref ) {
385+ const Editor = forwardRef ( function Editor ( { defaultValue = '' , onChange, onDrawioOpen, onMediaPickerOpen, onTabOut } , ref ) {
386386 const { t } = useTranslation ( )
387387 const editorRef = useRef ( null )
388388 const containerRef = useRef ( null )
389389 const onChangeRef = useRef ( onChange )
390390 const drawioHandlerRef = useRef ( onDrawioOpen )
391391 const mediaHandlerRef = useRef ( onMediaPickerOpen )
392+ const onTabOutRef = useRef ( onTabOut )
392393 const editorViewRef = useRef ( null )
393394 // The slash menu and wikilink menu both render plain DOM (not React),
394395 // so they cannot read t() from context. Snapshot translated strings into
@@ -412,13 +413,22 @@ const Editor = forwardRef(function Editor({ defaultValue = '', onChange, onDrawi
412413 mediaHandlerRef . current = onMediaPickerOpen || null
413414 } , [ onMediaPickerOpen ] )
414415
416+ useEffect ( ( ) => {
417+ onTabOutRef . current = onTabOut || null
418+ } , [ onTabOut ] )
419+
415420 useImperativeHandle ( ref , ( ) => ( {
416421 insertText ( text ) {
417422 const view = editorViewRef . current
418423 if ( ! view ) return
419424 const { state, dispatch } = view
420425 const pos = state . selection . from
421426 dispatch ( state . tr . insertText ( text , pos ) )
427+ } ,
428+ focus ( ) {
429+ const view = editorViewRef . current
430+ if ( ! view ) return
431+ view . focus ( )
422432 }
423433 } ) , [ ] )
424434
@@ -478,6 +488,28 @@ const Editor = forwardRef(function Editor({ defaultValue = '', onChange, onDrawi
478488 } )
479489 } )
480490
491+ const tabOutPlugin = $prose ( ( ) => {
492+ return new Plugin ( {
493+ props : {
494+ handleKeyDown ( view , event ) {
495+ if ( event . key !== 'Tab' || event . shiftKey ) return false
496+ if ( event . isComposing || event . keyCode === 229 ) return false
497+ if ( ! onTabOutRef . current ) return false
498+ const { $from } = view . state . selection
499+ for ( let depth = $from . depth ; depth >= 0 ; depth -- ) {
500+ const name = $from . node ( depth ) . type . name
501+ if ( [ 'list_item' , 'bullet_list' , 'ordered_list' , 'code_block' , 'table' ] . includes ( name ) ) {
502+ return false
503+ }
504+ }
505+ event . preventDefault ( )
506+ onTabOutRef . current ( )
507+ return true
508+ } ,
509+ } ,
510+ } )
511+ } )
512+
481513 const init = async ( ) => {
482514 const editor = await MilkdownEditor . make ( )
483515 . config ( ( ctx ) => {
@@ -508,6 +540,7 @@ const Editor = forwardRef(function Editor({ defaultValue = '', onChange, onDrawi
508540 . use ( slash )
509541 . use ( wikilink )
510542 . use ( wikilinkPlugin )
543+ . use ( tabOutPlugin )
511544 . create ( )
512545
513546 // StrictMode: if cleanup ran while we were awaiting, destroy immediately
0 commit comments