Rich text / foldkit-mixins-richtext

The slash menu

What opens a menu, what it offers, and what Enter sends are the editor's (foldkit-richtext-dom's README explains slashQuery, slashEntries, and slashMenu), and they are re-exported here so the chrome and the editor share one vocabulary. This package adds the two things the editor cannot: the keys that move the highlight, and the view.

ts
import { slashEntries, slashMenuView, slashMove } from 'foldkit-mixins-richtext'

// The catalogue as the caller's Messages.
const entries = slashEntries(message => edited(message))

// Where ArrowDown moves the highlight: the primitive's rule, shared with any other list.
const next = slashMove(entries, 'see /head', model.editor.menuIndex, 'ArrowDown', modifiers)

// The view, placed only while `slashMenu` says a query is open.
slashMenuView<Message>()(
  { entries, textBefore: RichText.textBefore(document, caret), index: model.editor.menuIndex },
  h,
)

slashMove returns the index a key moves the highlight to, or undefined for a key that moves nothing, and it starts from what the menu highlights now rather than a remembered index, so a query that narrowed past the old one does not move from a position no one can see. The keys are the application's to handle, because only it knows the caret: ArrowUp/Down, Home/End, and Escape in OnKeyDownPreventDefault while a query is open, writing the index to editor.menuIndex. Enter is not: the editor's update resolves it against that index.

Each item carries data-entry (its id), role="menuitem", aria-current="true" on the highlighted one, and its entry's Message on click, and draws the entry's icon (when the set below has it), label and description. Under / alone the entries sit under their groups' headings; a query lists the best matches first, without headings, and one that matches nothing says "No matches". Outside a query the view draws nothing.

SlotCapabilityRenders
rootContainerthe menu's wrapper
listCollectionthe list of matches
headingContainera group's name, before its first entry
itemInteractiveone entry, per match
iconContainerthe entry's icon
labelContainerthe entry's label
descriptionContainerthe line under the label
emptyContainer"No matches"