Documentation Index
Fetch the complete documentation index at: https://docs.superdoc.dev/llms.txt
Use this file to discover all available pages before exploring further.
Quick start
Three pieces: a registration that contributes an item, acontextmenu listener that opens the menu, and a <SuperDocEditor disableContextMenu> to keep the built-in out of the way.
disableContextMenu switches off SuperDoc’s own menu UI and lets the browser’s native contextmenu event proceed. When getContextMenuItems(context) returns nothing for a click, the listener returns without preventDefault and the browser native menu falls through (Copy / Paste / Inspect). No dead right-click.
The bundle
ui.viewport.contextAt({ x, y }) always returns an object, never null. Empty defaults make destructuring safe.
| Field | Type | Meaning |
|---|---|---|
point | { x, y } | Echoes the input. Useful for anchoring floating UI. |
entities | ViewportEntityHit[] | Tracked changes / comments under the click, innermost first. Empty when none. |
position | ViewportPositionHit | null | Resolved caret position at the click. null when the click is outside the painted host. |
selection | SelectionSlice | Mirrors the live state.selection slice. |
insideSelection | boolean | True when the click lands inside the rects the live selection currently paints. |
position.target is a collapsed SelectionTarget at the click, story-aware when the click landed inside a header / footer / footnote. Pass it straight to editor.doc.insert for “Paste here” / “Insert clause here” actions.
Contribute an item
Add acontextMenu field to your registration. The when predicate filters on the same bundle the handler will receive.
format, clipboard, review, comment, link, then customs in registration order). Items inside a group sort by order. Predicates that throw are caught and the item is hidden for that menu.
Predicate examples
The bundle’s optional fields make scope rules direct.entities, selection, point, position, insideSelection. Old predicates that only destructure { entities, selection } keep working.
item.invoke()
Items returned fromgetContextMenuItems(context) carry an invoke() closure that fires the registered execute with the bundle bound to context. Your menu component dispatches without re-threading the click target through a payload.
execute, the same bundle the predicate filtered on is available as context:
context is undefined when the command is dispatched directly (ui.commands.get(id)?.execute(payload), ui.commands.require(id).execute(...), or ui.toolbar.execute(id, payload) for built-ins). Handlers that only depend on payload keep working unchanged.
Falling through to the native menu
WhengetContextMenuItems(context) returns no items, your listener returns early without calling event.preventDefault(). The browser shows its native menu (Copy / Paste / Inspect) instead of producing a dead right-click. This relies on disableContextMenu: true on the editor: with the built-in menu suppressed, no other listener swallows the event.
If you’d rather suppress the native menu in the empty case too, call event.preventDefault() regardless of items length and render nothing.
Worked example
The reference workspace atdemos/custom-ui wires the full pattern end-to-end. The four registrations below mirror the demo’s ContextMenuRegistrations.tsx. They cover the three subjects the menu can act on: an entity, the selection, or the click point.
Trade-offs
- The bundle is computed once when the menu opens. If your registration’s
executeruns much later (popover, multi-step picker),context.selectionreflects the open-time selection, not the current one. Re-readui.selection.getSnapshot()when you need fresh selection. item.invoke?.()isundefinedfor items returned from the legacygetContextMenuItems({ entities })shape. Always call asitem.invoke?.(). The full bundle path always populates it.- Scope your
contextmenulistener toui.viewport.getHost(). An empty bundle alone isn’t a scope signal: it can mean “outside the editor” or “inside plain text with no selection and no entities”. positionisnullwhen the click is outside the painted host. Predicates that act on the click point should checkposition !== nullfirst.

