Canvas Editor provides a flexible context menu system that allows you to add custom menu items that appear on right-click.
Basic Usage
Register context menus using editor.register.contextMenuList():
import Editor , { Command , IContextMenuContext } from 'canvas-editor'
const editor = new Editor ( container , data , options )
editor . register . contextMenuList ([
{
key: 'myCustomMenu' ,
name: 'My Custom Action' ,
when : ( context ) => {
// Show only when text is selected
return context . editorHasSelection
},
callback : ( command , context ) => {
// Execute action
const text = command . getRangeText ()
console . log ( 'Selected text:' , text )
}
}
])
interface IRegisterContextMenu {
key ?: string // Unique identifier
i18nPath ?: string // Translation key
isDivider ?: boolean // Menu divider
icon ?: string // Icon class name
name ?: string // Display name
shortCut ?: string // Shortcut hint text
disable ?: boolean // Disable menu item
when ?: ( payload : IContextMenuContext ) => boolean // Show condition
callback ?: ( command : Command , context : IContextMenuContext ) => void
childMenus ?: IRegisterContextMenu [] // Submenu items
}
Context Information
The when and callback functions receive context about the current editor state:
interface IContextMenuContext {
startElement : IElement | null // Element at selection start
endElement : IElement | null // Element at selection end
isReadonly : boolean // Editor readonly state
editorHasSelection : boolean // Has selected text
editorTextFocus : boolean // Editor has focus
isInTable : boolean // Cursor in table
isCrossRowCol : boolean // Selection spans rows/cols
zone : EditorZone // Current zone (main/header/footer)
trIndex : number | null // Table row index
tdIndex : number | null // Table cell index
tableElement : IElement | null // Table element
options : DeepRequired < IEditorOption > // Editor options
}
Canvas Editor includes several built-in context menus:
Simple Menu Item
editor . register . contextMenuList ([
{
key: 'uppercase' ,
name: 'Convert to Uppercase' ,
icon: 'text-uppercase' ,
when : ( context ) => context . editorHasSelection ,
callback : ( command , context ) => {
const text = command . getRangeText ()
command . executeInsertElementList ([{
value: text . toUpperCase ()
}])
}
}
])
Conditional Menu
Show menu only in specific contexts: editor . register . contextMenuList ([
{
key: 'tableStats' ,
name: 'Table Statistics' ,
when : ( context ) => {
// Only show when in a table
return context . isInTable && ! context . isReadonly
},
callback : ( command , context ) => {
const { tableElement } = context
if ( tableElement ) {
const rows = tableElement . trList ?. length || 0
const cols = tableElement . tdList ?. length || 0
alert ( `Table: ${ rows } rows × ${ cols } columns` )
}
}
}
])
Menu with Submenu
Create nested menus: editor . register . contextMenuList ([
{
key: 'textTransform' ,
name: 'Text Transform' ,
when : ( context ) => context . editorHasSelection ,
childMenus: [
{
key: 'uppercase' ,
name: 'UPPERCASE' ,
when : () => true ,
callback : ( command ) => {
const text = command . getRangeText ()
command . executeInsertElementList ([{
value: text . toUpperCase ()
}])
}
},
{
key: 'lowercase' ,
name: 'lowercase' ,
when : () => true ,
callback : ( command ) => {
const text = command . getRangeText ()
command . executeInsertElementList ([{
value: text . toLowerCase ()
}])
}
},
{
key: 'titlecase' ,
name: 'Title Case' ,
when : () => true ,
callback : ( command ) => {
const text = command . getRangeText ()
const titleCase = text . replace ( / \w\S * / g , ( txt ) =>
txt . charAt ( 0 ). toUpperCase () + txt . substr ( 1 ). toLowerCase ()
)
command . executeInsertElementList ([{
value: titleCase
}])
}
}
]
}
])
Add visual separators between menu groups:
editor . register . contextMenuList ([
{
name: 'Action 1' ,
when : () => true ,
callback : ( command ) => { /* ... */ }
},
{
isDivider: true // Creates a horizontal line
},
{
name: 'Action 2' ,
when : () => true ,
callback : ( command ) => { /* ... */ }
}
])
Dividers at the beginning or end of a menu, or adjacent dividers, are automatically hidden.
Using Icons
Add icons to menu items using CSS classes:
editor . register . contextMenuList ([
{
name: 'Print' ,
icon: 'print' , // Adds class: canvas-editor-contextmenu-print
when : () => true ,
callback : ( command ) => command . executePrint ()
}
])
Define icon styles in your CSS:
.canvas-editor-contextmenu-print::before {
content : '🖨️' ;
margin-right : 8 px ;
}
Internationalization
Use i18nPath instead of name for translated menu items:
// Register translations first
editor . register . langMap ( 'en' , {
contextmenu: {
custom: {
myAction: 'My Action'
}
}
})
editor . register . langMap ( 'es' , {
contextmenu: {
custom: {
myAction: 'Mi Acción'
}
}
})
// Use i18nPath in menu
editor . register . contextMenuList ([
{
key: 'myAction' ,
i18nPath: 'contextmenu.custom.myAction' , // Uses current locale
when : () => true ,
callback : ( command ) => { /* ... */ }
}
])
Showing Keyboard Shortcuts
Display keyboard shortcuts as hints:
import { isApple } from 'canvas-editor'
editor . register . contextMenuList ([
{
name: 'Save' ,
shortCut: ` ${ isApple ? '⌘' : 'Ctrl' } + S` ,
when : () => true ,
callback : ( command ) => {
// Save logic
}
}
])
Disable built-in context menus using editor options:
import { INTERNAL_CONTEXT_MENU_KEY } from 'canvas-editor'
const editor = new Editor ( container , data , {
contextMenuDisableKeys: [
INTERNAL_CONTEXT_MENU_KEY . GLOBAL . CUT ,
INTERNAL_CONTEXT_MENU_KEY . GLOBAL . COPY ,
INTERNAL_CONTEXT_MENU_KEY . IMAGE . CHANGE
]
})
Available keys:
Global
Image
Table
Hyperlink
INTERNAL_CONTEXT_MENU_KEY . GLOBAL . CUT
INTERNAL_CONTEXT_MENU_KEY . GLOBAL . COPY
INTERNAL_CONTEXT_MENU_KEY . GLOBAL . PASTE
INTERNAL_CONTEXT_MENU_KEY . GLOBAL . SELECT_ALL
INTERNAL_CONTEXT_MENU_KEY . GLOBAL . PRINT
Advanced Examples
Word Count
Insert Template
Element Inspector
editor . register . contextMenuList ([
{
key: 'wordCount' ,
name: 'Word Count' ,
icon: 'calculator' ,
when : ( context ) => context . editorHasSelection ,
callback : ( command ) => {
const text = command . getRangeText ()
const words = text . trim (). split ( / \s + / ). length
const chars = text . length
alert ( `Words: ${ words } \n Characters: ${ chars } ` )
}
}
])
editor . register . contextMenuList ([
{
key: 'insertTemplate' ,
name: 'Insert Template' ,
when : ( context ) => ! context . isReadonly ,
childMenus: [
{
name: 'Meeting Notes' ,
when : () => true ,
callback : ( command ) => {
const template = [
{ value: 'Meeting Notes' , type: ElementType . TITLE , level: TitleLevel . FIRST },
{ value: 'Date: ' + new Date (). toLocaleDateString () },
{ value: ' \n ' },
{ value: 'Attendees:' , bold: true },
{ value: ' \n ' },
{ value: 'Agenda:' , bold: true },
{ value: ' \n ' }
]
command . executeInsertElementList ( template )
}
},
{
name: 'Code Block' ,
when : () => true ,
callback : ( command ) => {
command . executeInsertElementList ([
{ value: '```' , code: true },
{ value: ' \n ' },
{ value: '```' , code: true }
])
}
}
]
}
])
editor . register . contextMenuList ([
{
key: 'inspectElement' ,
name: 'Inspect Element' ,
when : ( context ) => {
// Show in development mode
return process . env . NODE_ENV === 'development'
},
callback : ( command , context ) => {
const { startElement } = context
console . group ( 'Element Inspector' )
console . log ( 'Type:' , startElement ?. type )
console . log ( 'Value:' , startElement ?. value )
console . log ( 'Full element:' , startElement )
console . groupEnd ()
}
}
])
Get the list of registered menus:
const menus = editor . register . getContextMenuList ()
console . log ( 'Registered context menus:' , menus )
Context menus are automatically filtered based on the when condition. Disabled menus and menus that don’t meet their condition won’t appear.
Best Practices
Use descriptive keys Always provide unique, descriptive keys for your menu items to avoid conflicts.
Implement proper conditions Use the when function to show menus only in relevant contexts.
Respect readonly mode Check context.isReadonly before showing edit actions.
Provide keyboard shortcuts Include shortCut hints for actions that have keyboard shortcuts.
Troubleshooting
Ensure CSS class is properly defined
Check that icon class name doesn’t conflict with built-in icons
Verify CSS is loaded after editor styles
Next Steps
Custom Shortcuts Register keyboard shortcuts
Internationalization Add multi-language support
Commands API Explore available commands
Plugin System Create custom plugins