1

What I need

Let's start with The mentions plugin taken from the docs.

I would like to enhance if with the following functionality:

  1. Whenever I click on an existing MentionNode, the menu gets rendered (like it does when menuRenderFunction gets called), with the full list of options, regardless of queryString matching
  2. Selecting an option from menu replaces said mention with the newly selected one

Is there a way to implement this while leaving LexicalTypeaheadMenuPlugin in control of the menu?

Thank you for your time


What I've tried

I figured that maybe I could achieve my desired behaviour simply by returning the right QueryMatch from triggerFn. Something like this:

const x: FC = () => {
  const nodeAtSelection = useNodeAtSelection() // Returns LexicalNode at selection

  return (
    <LexicalTypeaheadMenuPlugin<VariableTypeaheadOption>
      triggerFn={(text, editor) => {
        if ($isMentionsNode(nodeAtSelection)) {
          // No idea how to implement `getQueryMatchForMentionsNode`, 
          // or whether it's even possible
          return getQueryMatchForMentionsNode(nodeAtSelection, text, editor)
        }

        return checkForVariableBeforeCaret(text, editor)
      }}
    />
  )
}

I played around with it for about half an hour, unfortunately I couldn't really find any documentation for triggerFn or QueryMatch, and haven't really made any progress just by messing around.

I also thought of a potential solution the I think would work, but feels very hacky and I would prefer not to use it. I'll post it as an answer.

Michal Kurz
  • 1,592
  • 13
  • 41

1 Answers1

1

So here is my "dirty" solution that should work, but feels very hacky:

I could basically take the function which I provide to menuRenderFn prop and call it manually.

Let's say I render the plugin like this:

const menuRenderer = (
   anchorElementRef,
   { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }
) => { /* ... */}
    
return (
   <LexicalTypeaheadMenuPlugin menuRenderFn={menuRenderer}  /* ... other props */ />
)

I could then create a parallel environment for rendering menuRenderer, something like this:

const useParallelMenu = (
  menuRenderer: MenuRenderFn<any>,
  allOptions: TypeaheadOption[],
  queryString: string
) => {
  // I could get anchor element:
  // 1. either by using document.querySelector("." + anchorClassName)
  // 2. or by extracting it from inside `menuRenderFn`:
  //    menuRenderFn={(...params) => {
  //       extractedRef.current = params[0].current;
  //       return menuRenderer(...params)
  //    }}
  const anchorEl = x
  const [selectedIndex, setHighlightedIndex] = useState(0)

  const nodeAtSelection = useNodeAtSelection() // Returns LexicalNode at selection

  const selectOptionAndCleanUp = (option: TypeaheadOption) => {
    // Replace nodeAtSelection with new MentionsNode from `option`
  }

  return () =>
    $isMentionsNode(nodeAtSelection) &&
    menuRenderer(
      anchorEl,
      {
        selectedIndex,
        setHighlightedIndex,
        selectOptionAndCleanUp,
        options: allOptions
      },
      queryString
    )
}

On paper, this seems like a viable approach to me... but I would really prefer not to have to do this and instead let LexicalTypeaheadMenuPlugin manage the state of my menu, as it is intended to do.

Michal Kurz
  • 1,592
  • 13
  • 41