@@ -22,7 +22,7 @@ import fakeRestDataProvider from 'ra-data-fakerest';
2222import { Routes , Route } from 'react-router-dom' ;
2323import Mention from '@tiptap/extension-mention' ;
2424import { Editor , ReactRenderer } from '@tiptap/react' ;
25- import tippy , { Instance as TippyInstance } from 'tippy.js ' ;
25+ import { computePosition , flip , shift , offset } from '@floating-ui/dom ' ;
2626import {
2727 DefaultEditorOptions ,
2828 RichTextInput ,
@@ -375,59 +375,48 @@ export const CustomOptions = () => (
375375 </ TestMemoryRouter >
376376) ;
377377
378- const MentionList = React . forwardRef <
379- MentionListRef ,
380- {
381- items : string [ ] ;
382- command : ( props : { id : string } ) => void ;
383- }
384- > ( ( props , ref ) => {
378+ const MentionList = ( props : {
379+ items : string [ ] ;
380+ command : ( props : { id : string } ) => void ;
381+ onKeyDownRef : React . MutableRefObject <
382+ ( ( props : { event : KeyboardEvent } ) => boolean ) | null
383+ > ;
384+ } ) => {
385385 const [ selectedIndex , setSelectedIndex ] = React . useState ( 0 ) ;
386386
387387 const selectItem = index => {
388388 const item = props . items [ index ] ;
389+ console . log ( 'selectItem' , index , item , props . command ) ;
389390
390391 if ( item ) {
391392 props . command ( { id : item } ) ;
392393 }
393394 } ;
394395
395- const upHandler = ( ) => {
396- setSelectedIndex (
397- ( selectedIndex + props . items . length - 1 ) % props . items . length
398- ) ;
399- } ;
400-
401- const downHandler = ( ) => {
402- setSelectedIndex ( ( selectedIndex + 1 ) % props . items . length ) ;
403- } ;
404-
405- const enterHandler = ( ) => {
406- selectItem ( selectedIndex ) ;
407- } ;
408-
409396 React . useEffect ( ( ) => setSelectedIndex ( 0 ) , [ props . items ] ) ;
410397
411- React . useImperativeHandle ( ref , ( ) => ( {
412- onKeyDown : ( { event } ) => {
398+ React . useEffect ( ( ) => {
399+ props . onKeyDownRef . current = ( { event } ) => {
413400 if ( event . key === 'ArrowUp' ) {
414- upHandler ( ) ;
401+ setSelectedIndex (
402+ i => ( i + props . items . length - 1 ) % props . items . length
403+ ) ;
415404 return true ;
416405 }
417406
418407 if ( event . key === 'ArrowDown' ) {
419- downHandler ( ) ;
408+ setSelectedIndex ( i => ( i + 1 ) % props . items . length ) ;
420409 return true ;
421410 }
422411
423412 if ( event . key === 'Enter' ) {
424- enterHandler ( ) ;
413+ selectItem ( selectedIndex ) ;
425414 return true ;
426415 }
427416
428417 return false ;
429- } ,
430- } ) ) ;
418+ } ;
419+ } ) ;
431420
432421 return (
433422 < Paper >
@@ -438,7 +427,11 @@ const MentionList = React.forwardRef<
438427 dense
439428 selected = { index === selectedIndex }
440429 key = { index }
441- onClick = { ( ) => selectItem ( index ) }
430+ onMouseDown = { e => {
431+ console . log ( 'onMouseDown' , index ) ;
432+ e . preventDefault ( ) ;
433+ selectItem ( index ) ;
434+ } }
442435 >
443436 { item }
444437 </ ListItemButton >
@@ -451,10 +444,6 @@ const MentionList = React.forwardRef<
451444 </ List >
452445 </ Paper >
453446 ) ;
454- } ) ;
455-
456- type MentionListRef = {
457- onKeyDown : ( props : { event : React . KeyboardEvent } ) => boolean ;
458447} ;
459448const suggestions = tags => {
460449 return {
@@ -467,75 +456,90 @@ const suggestions = tags => {
467456 } ,
468457
469458 render : ( ) => {
470- let component : ReactRenderer < MentionListRef > ;
471- let popup : TippyInstance [ ] ;
459+ let component : ReactRenderer ;
460+ let floatingEl : HTMLElement ;
461+ const onKeyDownRef : React . MutableRefObject <
462+ ( ( props : { event : KeyboardEvent } ) => boolean ) | null
463+ > = { current : null } ;
464+
465+ const updatePosition = ( clientRect : ( ) => DOMRect ) => {
466+ if ( ! floatingEl ) return ;
467+ const virtualEl = {
468+ getBoundingClientRect : clientRect ,
469+ } ;
470+ computePosition ( virtualEl , floatingEl , {
471+ placement : 'bottom-start' ,
472+ middleware : [ offset ( 8 ) , flip ( ) , shift ( ) ] ,
473+ } ) . then ( ( { x, y } ) => {
474+ Object . assign ( floatingEl . style , {
475+ left : `${ x } px` ,
476+ top : `${ y } px` ,
477+ } ) ;
478+ } ) ;
479+ } ;
472480
473481 return {
474482 onStart : props => {
475483 component = new ReactRenderer ( MentionList , {
476- props,
484+ props : { ... props , onKeyDownRef } ,
477485 editor : props . editor ,
478486 } ) ;
479487
480- if ( ! props . clientRect ) {
481- return ;
482- }
488+ floatingEl = document . createElement ( 'div' ) ;
489+ floatingEl . style . position = 'absolute' ;
490+ floatingEl . style . zIndex = '1300' ;
491+ floatingEl . addEventListener ( 'mousedown' , e =>
492+ e . preventDefault ( )
493+ ) ;
494+ floatingEl . appendChild ( component . element ) ;
495+ props . editor . view . dom . parentElement . appendChild ( floatingEl ) ;
483496
484- popup = tippy ( 'body' , {
485- getReferenceClientRect : props . clientRect ,
486- appendTo : ( ) => document . body ,
487- content : component . element ,
488- showOnCreate : true ,
489- interactive : true ,
490- trigger : 'manual' ,
491- placement : 'bottom-start' ,
492- } ) ;
497+ if ( props . clientRect ) {
498+ updatePosition ( props . clientRect ) ;
499+ }
493500 } ,
494501
495502 onUpdate ( props ) {
496503 if ( component ) {
497- component . updateProps ( props ) ;
498- }
499-
500- if ( ! props . clientRect ) {
501- return ;
504+ component . updateProps ( { ...props , onKeyDownRef } ) ;
502505 }
503506
504- if ( popup && popup [ 0 ] ) {
505- popup [ 0 ] . setProps ( {
506- getReferenceClientRect : props . clientRect ,
507- } ) ;
507+ if ( props . clientRect ) {
508+ updatePosition ( props . clientRect ) ;
508509 }
509510 } ,
510511
511512 onKeyDown ( props ) {
512- if ( popup && popup [ 0 ] && props . event . key === 'Escape' ) {
513- popup [ 0 ] . hide ( ) ;
514-
513+ console . log (
514+ 'suggestion onKeyDown' ,
515+ props . event . key ,
516+ 'ref:' ,
517+ ! ! onKeyDownRef . current
518+ ) ;
519+ if ( props . event . key === 'Escape' ) {
520+ if ( floatingEl ) {
521+ floatingEl . style . display = 'none' ;
522+ }
515523 return true ;
516524 }
517525
518- if ( ! component . ref ) {
526+ if ( ! onKeyDownRef . current ) {
519527 return false ;
520528 }
521529
522- return component . ref . onKeyDown ( props ) ;
530+ return onKeyDownRef . current ( props ) ;
523531 } ,
524532
525533 onExit ( ) {
534+ onKeyDownRef . current = null ;
526535 queueMicrotask ( ( ) => {
527- if ( popup && popup [ 0 ] && ! popup [ 0 ] . state . isDestroyed ) {
528- popup [ 0 ] . destroy ( ) ;
529- }
530536 if ( component ) {
531537 component . destroy ( ) ;
532538 }
533- // Remove references to the old popup and component upon destruction/exit.
534- // (This should prevent redundant calls to `popup.destroy()`, which Tippy
535- // warns in the console is a sign of a memory leak, as the `suggestion`
536- // plugin seems to call `onExit` both when a suggestion menu is closed after
537- // a user chooses an option, *and* when the editor itself is destroyed.)
538- popup = undefined ;
539+ if ( floatingEl && floatingEl . parentNode ) {
540+ floatingEl . parentNode . removeChild ( floatingEl ) ;
541+ }
542+ floatingEl = undefined ;
539543 component = undefined ;
540544 } ) ;
541545 } ,
0 commit comments