Skip to main content
Canvas Editor provides comprehensive event handling through both the Listener (callback-based) and EventBus (pub-sub) systems.

Event Categories

Content & Document Events

EventPayloadDescription
contentChangevoidDocument content changed
savedIEditorResultDocument saved (Ctrl+S or command)

Selection & Style Events

EventPayloadDescription
rangeStyleChangeIRangeStyleSelection formatting changed
positionContextChangeIPositionContextChangePayloadCursor position context changed

Page Events

EventPayloadDescription
pageSizeChangenumberTotal page count changed
pageScaleChangenumberZoom level changed
pageModeChangePageModePage mode changed (paging/continuous)
visiblePageNoListChangenumber[]Visible pages during scroll
intersectionPageNoChangenumberPrimary visible page changed

Control (Form) Events

EventPayloadDescription
controlChangeIControlChangeResultControl state changed (active/inactive)
controlContentChangeIControlContentChangeResultControl value/content changed

Zone Events

EventPayloadDescription
zoneChangeEditorZoneCursor moved between zones (header/main/footer)

Mouse Events

EventPayloadDescription
clickMouseEventEditor clicked
mousedownMouseEventMouse button pressed
mouseupMouseEventMouse button released
mousemoveMouseEventMouse moved over editor
mouseenterMouseEventMouse entered editor area
mouseleaveMouseEventMouse left editor area

Input Events

EventPayloadDescription
inputEventInput/text entry occurred

Image Events

EventPayloadDescription
imageSizeChange{ element: IElement }Image resized
imageMousedown{ evt: MouseEvent, element: IElement }Image clicked
imageDblclick{ evt: MouseEvent, element: IElement }Image double-clicked

Label Events

EventPayloadDescription
labelMousedown{ evt: MouseEvent, element: IElement }Label clicked

Usage Examples

Using Listener (Callback)

import Editor from '@hufe921/canvas-editor'

const editor = new Editor(container, data)

// Single callback per event
editor.listener.contentChange = () => {
  console.log('Content changed')
}

editor.listener.rangeStyleChange = (style) => {
  console.log('Selection style:', style.bold, style.italic)
}

// Remove listener
editor.listener.contentChange = null

Using EventBus (Pub-Sub)

import Editor from '@hufe921/canvas-editor'

const editor = new Editor(container, data)

// Multiple subscribers for same event
const handler1 = () => console.log('Handler 1')
const handler2 = () => console.log('Handler 2')

editor.eventBus.on('contentChange', handler1)
editor.eventBus.on('contentChange', handler2)

// Unsubscribe
editor.eventBus.off('contentChange', handler1)

Event Payload Types

IRangeStyle

src/editor/interface/Listener.ts
{
  type: ElementType | null
  undo: boolean
  redo: boolean
  painter: boolean
  font: string
  size: number
  bold: boolean
  italic: boolean
  underline: boolean
  strikeout: boolean
  color: string | null
  highlight: string | null
  rowFlex: RowFlex | null
  rowMargin: number
  dashArray: number[]
  level: TitleLevel | null
  listType: ListType | null
  listStyle: ListStyle | null
  groupIds: string[] | null
  textDecoration: ITextDecoration | null
  extension?: unknown | null
}

IEditorResult

src/editor/interface/Editor.ts
{
  version: string
  data: IEditorData
  options: IEditorOption
}

IControlChangeResult

src/editor/interface/Control.ts
{
  state: ControlState      // 'active' | 'inactive'
  control: IControl
  controlId: string
}

IControlContentChangeResult

src/editor/interface/Control.ts
{
  control: IControl
  controlId: string
}

IPositionContextChangePayload

src/editor/interface/Listener.ts
{
  value: IPositionContext
  oldValue: IPositionContext
}

Common Patterns

Auto-Save

let saveTimer: NodeJS.Timeout

editor.eventBus.on('contentChange', () => {
  clearTimeout(saveTimer)
  saveTimer = setTimeout(() => {
    const data = editor.command.getValue()
    saveToServer(data)
  }, 2000)
})

Toolbar Updates

editor.listener.rangeStyleChange = (style) => {
  // Update toolbar buttons
  document.querySelector('#bold')?.classList.toggle('active', style.bold)
  document.querySelector('#italic')?.classList.toggle('active', style.italic)
  document.querySelector('#underline')?.classList.toggle('active', style.underline)
  
  // Update undo/redo
  document.querySelector('#undo').disabled = !style.undo
  document.querySelector('#redo').disabled = !style.redo
  
  // Update font size dropdown
  document.querySelector('#font-size').value = style.size.toString()
}
editor.eventBus.on('intersectionPageNoChange', (pageNo) => {
  document.querySelector('#current-page').textContent = `${pageNo + 1}`
})

editor.eventBus.on('pageSizeChange', (total) => {
  document.querySelector('#total-pages').textContent = ` / ${total}`
})

Form Validation

editor.eventBus.on('controlContentChange', ({ control }) => {
  switch (control.conceptId) {
    case 'email':
      validateEmail(control.value)
      break
    case 'phone':
      validatePhone(control.value)
      break
    case 'zipcode':
      validateZipCode(control.value)
      break
  }
})

Image Interaction

editor.eventBus.on('imageMousedown', ({ element }) => {
  // Show image toolbar
  showImageToolbar(element)
})

editor.eventBus.on('imageDblclick', ({ element }) => {
  // Open image editor
  openImageEditor(element)
})

editor.eventBus.on('imageSizeChange', ({ element }) => {
  // Log size changes
  console.log(`Image resized to ${element.width}x${element.height}`)
})

Activity Tracking

let lastActivity = Date.now()
let idleTimer: NodeJS.Timeout

const resetIdleTimer = () => {
  lastActivity = Date.now()
  clearTimeout(idleTimer)
  
  idleTimer = setTimeout(() => {
    console.log('User idle for 5 minutes')
    showIdleWarning()
  }, 5 * 60 * 1000)
}

editor.eventBus.on('input', resetIdleTimer)
editor.eventBus.on('click', resetIdleTimer)
editor.eventBus.on('mousedown', resetIdleTimer)

Event Timing

Events fire in the following order during typical operations: Text Input:
  1. input
  2. contentChange
  3. rangeStyleChange (if style context changes)
  4. positionContextChange (if position context changes)
Page Navigation:
  1. visiblePageNoListChange
  2. intersectionPageNoChange
Zoom:
  1. pageScaleChange
  2. visiblePageNoListChange (if visible pages change)
Control Focus:
  1. controlChange (state: ‘active’)
  2. positionContextChange

Best Practices

  1. Debounce expensive operations: For contentChange, mousemove, etc.
    let timer: NodeJS.Timeout
    editor.eventBus.on('contentChange', () => {
      clearTimeout(timer)
      timer = setTimeout(expensiveOperation, 300)
    })
    
  2. Clean up event listeners: Always unsubscribe when component unmounts
    // Store references
    const handler = () => {}
    editor.eventBus.on('contentChange', handler)
    
    // Clean up
    editor.eventBus.off('contentChange', handler)
    // Or destroy editor entirely
    editor.destroy()
    
  3. Use Listener for primary handlers: Use EventBus for plugins/modules
    // Primary handler - use Listener
    editor.listener.contentChange = () => { /* main logic */ }
    
    // Additional handlers - use EventBus
    editor.eventBus.on('contentChange', () => { /* plugin logic */ })
    
  4. Type your event handlers:
    import { EventBusMap } from '@hufe921/canvas-editor'
    
    const handler: EventBusMap['contentChange'] = () => {
      // Fully typed
    }
    

Build docs developers (and LLMs) love