Component events & lifecycle
When Commander performs insert/delete text, line breaks, paste, apply format or attribute, or Selection changes the selection, the kernel dispatches hooks on Component (e.g. onContentInsert, onBreak). Register listeners in setup() with onXxx from @textbus/core—you can preventDefault to override defaults or only log / sync UI. Shortcuts are covered in Shortcuts & grammar.
Read Component basics, Query & operations, Selection, and Block styles first.
How to register
Inside setup() on the component class, call the imported helpers synchronously—do not register after await, Promise.then, or setTimeout.
import { Component, onBreak, onContentInsert } from '@textbus/core'
class MyBlock extends Component<MyState> {
override setup() {
onContentInsert(ev => {
// ...
})
onBreak(ev => {
ev.preventDefault()
})
}
}When setup runs
On first editor.render(root), the kernel calls setup() on the root, then walks child components already under child slots and runs setup on each. When a new Component is mounted under a Slot, setup runs for that subtree. Interaction with getSlots(), separate(), …: Advanced components.
For event.target below: when it is a Component, it is this component instance (the block whose setup registered the hook)—you do not receive another component’s events from here. When it is a Slot, it is one of this component’s child slots (the same Slot instances exposed via getSlots()), meaning the operation targets that slot.
Slot content
onContentInsert
When: before Commander.insert (and similar) actually writes into the slot.
Args: event: Event<Slot, InsertEventData>.
event.target: theSlotabout to receive content.event.data.index: insertion offset.event.data.content: string orComponentto insert.event.data.formats: formats carried with the insert (Formats).
event.preventDefault() cancels this insert; the command usually returns false. Root and paragraph blocks often use this to wrap typed text into blocks—Component basics.
import { onContentInsert } from '@textbus/core'
onContentInsert(event => {
const slot = event.target
const { index, content, formats } = event.data
// e.g. event.preventDefault() to forbid certain inserts
})onContentInserted
When: after content has been written to the Slot.
Args: event: Event<Slot, InsertEventData>—same fields as onContentInsert (describing the insert that just finished).
preventDefault() on onContentInserted affects whether the default pipeline collapses the selection near the insert (default insert behavior). Often used for observation or fine-tuning selection.
import { onContentInserted } from '@textbus/core'
onContentInserted(event => {
const slot = event.target
const { index, content } = event.data
// insert committed; sync UI. event.preventDefault() to avoid default selection collapse
})onContentDelete
When: before a span is removed or moved from the slot (e.g. Commander.delete after splitting the selection).
Args: event: Event<Slot, DeleteEventData>.
event.target:Slotbeing cut.event.data.index: delete start.event.data.count: length removed.event.data.toEnd: whether deletion heads toward the end of the document (forward vs backward).event.data.actionType:'delete'pure delete;'move'cut for cross-slot moves.
preventDefault() blocks this slice; command returns false.
import { onContentDelete } from '@textbus/core'
onContentDelete(event => {
const { index, count, toEnd, actionType } = event.data
// actionType is 'delete' | 'move'; event.preventDefault() to block
})onContentDeleted
When: after the slice has been cut from the Slot.
Args: event: Event<Slot>, event.data null—only event.target identifies the Slot.
preventDefault() may still be inspected in some delete flows: blocking can adjust focus/anchor and make delete return false (skip default post-delete cleanup). Often observation-only.
import { onContentDeleted } from '@textbus/core'
onContentDeleted(event => {
const slot = event.target
// post-delete observation; rarely event.preventDefault() to interrupt cleanup
})Line breaks
onBreak
When: Commander.break(). Non-collapsed selection is cleared first; collapsed caret fires directly.
Args: event: Event<Slot, BreakEventData>.
event.target:Slotwhere break fires (startSlot).event.data.index: offset at break.
If not prevented, the kernel may write a newline into the slot. Custom lists / todos usually preventDefault() then cut / insertAfter—Component basics.
import { onBreak } from '@textbus/core'
onBreak(event => {
const slot = event.target
const { index } = event.data
// custom split: ev.preventDefault() then Commander cut / insertAfter
})Paste
onPaste
When: before Commander.paste(pasteSlot, text) applies the default delta write.
Args: event: Event<Slot, PasteEventData>.
event.target:Slotused for dispatch (related to common-ancestor selection).event.data.index: intended paste offset.event.data.data: structured clipboardSlot(parsed fragment tree).event.data.text: parallel plain text.
preventDefault() → custom paste; otherwise the kernel **insert**s from pasteSlot.toDelta() with separate and multi-slot rules—Query & operations paste.
import { onPaste } from '@textbus/core'
onPaste(event => {
const { index, data, text } = event.data
// full custom paste: event.preventDefault(), then insert data tree or text yourself
})Formats & attributes (before write)
onSlotApplyFormat
When: before Commander.applyFormat writes the format onto the Slot.
Args: event: Event<Slot, SlotApplyFormatEventData>.
event.target:Slotreceiving the format.event.data.formatter:Formatterinstance.event.data.value: format value.
preventDefault() cancels this apply.
import { onSlotApplyFormat } from '@textbus/core'
onSlotApplyFormat(event => {
const { formatter, value } = event.data
// validation fails → event.preventDefault()
})onSlotSetAttribute
When: before slot.setAttribute, Commander.applyAttribute, … attach an attribute.
Args: event: Event<Slot, SlotSetAttributeEventData>.
event.target: targetSlot.event.data.attribute:Attributeinstance.event.data.value: attribute value.
preventDefault() cancels. Selection branching: Block styles.
import { onSlotSetAttribute } from '@textbus/core'
onSlotSetAttribute(event => {
const { attribute, value } = event.data
// disallow → event.preventDefault()
})IME
onCompositionStart
When: composition starts.
Args: event: Event<Slot, CompositionStartEventData>.
event.target:Slot.event.data.index: composition start offset.
import { onCompositionStart } from '@textbus/core'
onCompositionStart(event => {
const slot = event.target
const { index } = event.data
// composition starts at index in slot
})onCompositionUpdate
When: composition text updates.
Args: event: Event<Slot, CompositionUpdateEventData>.
event.data.index: current position.event.data.data: IME string this round.
import { onCompositionUpdate } from '@textbus/core'
onCompositionUpdate(event => {
const { index, data } = event.data
// IME updates at index to string data
})onCompositionEnd
When: composition ends.
Args: event: Event<Slot>—no data payload (null); event.target is the Slot.
import { onCompositionEnd } from '@textbus/core'
onCompositionEnd(event => {
const slot = event.target
// composition ends on slot
})Selection & ranges
onGetRanges
When: after selection changes, the kernel asks whether getRanges() should use custom multi-range selection.
Args: event: GetRangesEvent<Component>.
event.target: this component.- Call
event.useRanges([{ slot, startIndex, endIndex }, ...])withSlotRange[]; omit to keep the default continuous range. Table box selection: Selection.
import { onGetRanges } from '@textbus/core'
onGetRanges(event => {
event.useRanges([])
})onSelected
When: the selection becomes exactly one whole component (drag or selectComponent, …).
Args: none—() => void.
Use for block chrome, block-only actions.
import { onSelected } from '@textbus/core'
onSelected(() => {
// this component is fully selected
})onUnselect
When: whole-block selection ends or the selection no longer wraps only this block.
Args: none—() => void.
import { onUnselect } from '@textbus/core'
onUnselect(() => {
// whole-block selection ended
})onFocus
When: start Slot and end Slot share the same parent Component, and that Component differs from the previous round. Collapsed caret: parent of the caret’s Slot.
Args: none—() => void.
Often styles “current editing block”.
import { onFocus } from '@textbus/core'
onFocus(() => {
// anchor/focus sit in this component’s direct child slots
})onBlur
When: paired with onFocus—when parent switches to another component, onBlur fires on the previous parent.
Args: none—() => void.
import { onBlur } from '@textbus/core'
onBlur(() => {
// editing context left this component (paired with onFocus)
})onFocusIn
When: after common-ancestor component changes, fires once per Component on the path from the new ancestor toward the root—selection entered this subtree.
Args: none—() => void.
import { onFocusIn } from '@textbus/core'
onFocusIn(() => {
// selection common-ancestor chain includes this component
})onFocusOut
When: paired cleanup with onFocusIn—fires on components along the previous chain.
Args: none—() => void.
import { onFocusOut } from '@textbus/core'
onFocusOut(() => {
// previous focus-in context ends
})onSelectionFromFront
When: e.g. Selection.toNext()—caret crosses into a Component from its front edge; the component may handle it first.
Args: event: Event<Component>, no data (null). event.target is that component.
preventDefault() can skip default behavior (e.g. selectComponent when there are no child slots); selection may stay where it was.
import { onSelectionFromFront } from '@textbus/core'
onSelectionFromFront(event => {
// event.target: entered component; event.preventDefault() to skip default “select whole block on entry”
})onSelectionFromEnd
When: e.g. Selection.toPrevious()—symmetric entry from the back edge.
Args: event: Event<Component>, event.target the entered component. preventDefault() mirrors onSelectionFromFront.
import { onSelectionFromEnd } from '@textbus/core'
onSelectionFromEnd(event => {
// symmetric to onSelectionFromFront
})If the caret “jumps back”, check for accidental preventDefault() in these hooks.
Context menu & model notifications
onContextMenu
When: app calls triggerContextMenu(component) (from @textbus/core); walks parentComponent upward from component.
Args: event: ContextMenuEvent<Component>.
event.target: component at this layer.event.useMenus(menuConfigs): contributesContextMenuConfig[]—item types includeContextMenuItem,ContextMenuGroup, … (see typings).event.stopPropagation(): stop bubbling to parents.
import { onContextMenu } from '@textbus/core'
onContextMenu(event => {
event.useMenus([])
// event.stopPropagation()
})onParentSlotUpdated
When: parent Slot notifies the subtree after an update (e.g. after adapter sync).
Args: none—() => void. Refresh local view state that depends only on the parent slot.
import { onParentSlotUpdated } from '@textbus/core'
onParentSlotUpdated(() => {
// parent slot updated
})onDetach
When: this component instance is about to be removed from the document (delete, replace whole block, …).
Args: none—() => void. Unsubscribe timers, etc.; the kernel clears hook registrations after.
import { onDetach } from '@textbus/core'
onDetach(() => {
// cleanup side effects
})What's next
- Component basics (
onBreak,onContentInsert) - Query & operations (commands vs hooks)
- Advanced components (
separate, multi-slot) - Shortcuts & grammar
- Concepts
