Handsontable Editor Development
Editor state machine
Editors are stateful objects cycling through four states: VIRGIN (just created, never opened), EDITING (visible and accepting input), WAITING (editing finished, awaiting async validation), and FINISHED (validation complete, editor closed). Never skip states or transition backwards except through a full reset.
File structure
src/editors/{editorName}/
{editorName}.ts # Main class extending BaseEditor
index.ts # Re-exports
Registry: src/editors/registry.ts. Factory: src/editors/factory.ts.
Lifecycle methods (required overrides)
| Method |
Purpose |
init() |
Create DOM elements, set up event listeners. |
prepare() |
Called before editing starts. Receives row, col, prop, TD, cellProperties. |
getValue() |
Return the current editor value. |
setValue(newValue) |
Set the editor value (called before open). |
open() |
Show the editor, position it, capture focus. |
close() |
Hide the editor, release focus. |
focus() |
Set focus to the editor's input element. |
beginEditing() |
Start the editing process (calls prepare, setValue, open). |
finishEditing() |
End editing, trigger validation, close if valid. |
Positioning
Always use getEditedCellRect() for viewport-, scroll-, and overlay-aware positioning. Never calculate position manually - it will break with frozen rows/columns and scrolled viewports.
Key patterns
- All editors extend
BaseEditor from src/editors/baseEditor/baseEditor.ts.
- Support both full edit mode (Enter key opens editor, all keys go to the editor) and fast edit mode (typing a character immediately opens the editor with that character).
finishEditing() supports async validation - the editor enters WAITING state until the validator resolves.
- Use
this.hot.rootDocument instead of document for DOM creation (required for iframe support).
- Use
EventManager for event handling so listeners are cleaned up automatically.
Reference implementations
src/editors/textEditor/textEditor.ts - Standard text editing with a textarea.
src/editors/selectEditor/selectEditor.ts - Dropdown selection pattern.
src/editors/baseEditor/baseEditor.ts - Base class defining all lifecycle methods and state transitions.
IME (Input Method Editor) gotcha
For CJK languages, compositionstart/compositionend events have timing issues that affect when to read the editor value. Do not read or commit the value between compositionstart and compositionend - the composition is still in progress and the value is intermediate. The BaseEditor handles this, but custom editors that override key event handling must respect composition state.
Common mistakes
- Forgetting to clean up DOM elements and event listeners in
close().
- Not handling focus correctly, which breaks keyboard navigation after closing the editor.
- Positioning without
getEditedCellRect(), which breaks with overlays, scroll, and frozen rows/columns.
- Not supporting both LTR and RTL layouts - always use logical CSS properties or check
this.hot.isRtl().
- Using
document directly instead of this.hot.rootDocument.
- Not calling
super methods in lifecycle overrides (super.init(), super.close(), etc.).
1---2name: handsontable-editor-dev3description: Use when creating or modifying a Handsontable cell editor - covers the editor lifecycle state machine (VIRGIN/EDITING/WAITING/FINISHED), DOM management, focus handling, positioning with getEditedCellRect, and validation integration4---56# Handsontable Editor Development78## Editor state machine910Editors are stateful objects cycling through four states: **VIRGIN** (just created, never opened), **EDITING** (visible and accepting input), **WAITING** (editing finished, awaiting async validation), and **FINISHED** (validation complete, editor closed). Never skip states or transition backwards except through a full reset.1112## File structure1314```15src/editors/{editorName}/16 {editorName}.ts # Main class extending BaseEditor17 index.ts # Re-exports18```1920Registry: `src/editors/registry.ts`. Factory: `src/editors/factory.ts`.2122## Lifecycle methods (required overrides)2324| Method | Purpose |25|--------|---------|26| `init()` | Create DOM elements, set up event listeners. |27| `prepare()` | Called before editing starts. Receives row, col, prop, TD, cellProperties. |28| `getValue()` | Return the current editor value. |29| `setValue(newValue)` | Set the editor value (called before `open`). |30| `open()` | Show the editor, position it, capture focus. |31| `close()` | Hide the editor, release focus. |32| `focus()` | Set focus to the editor's input element. |33| `beginEditing()` | Start the editing process (calls prepare, setValue, open). |34| `finishEditing()` | End editing, trigger validation, close if valid. |3536## Positioning3738Always use `getEditedCellRect()` for viewport-, scroll-, and overlay-aware positioning. Never calculate position manually - it will break with frozen rows/columns and scrolled viewports.3940## Key patterns4142- All editors extend `BaseEditor` from `src/editors/baseEditor/baseEditor.ts`.43- Support both **full edit mode** (Enter key opens editor, all keys go to the editor) and **fast edit mode** (typing a character immediately opens the editor with that character).44- `finishEditing()` supports async validation - the editor enters WAITING state until the validator resolves.45- Use `this.hot.rootDocument` instead of `document` for DOM creation (required for iframe support).46- Use `EventManager` for event handling so listeners are cleaned up automatically.4748## Reference implementations4950- `src/editors/textEditor/textEditor.ts` - Standard text editing with a textarea.51- `src/editors/selectEditor/selectEditor.ts` - Dropdown selection pattern.52- `src/editors/baseEditor/baseEditor.ts` - Base class defining all lifecycle methods and state transitions.5354## IME (Input Method Editor) gotcha5556For CJK languages, `compositionstart`/`compositionend` events have timing issues that affect when to read the editor value. Do not read or commit the value between `compositionstart` and `compositionend` - the composition is still in progress and the value is intermediate. The BaseEditor handles this, but custom editors that override key event handling must respect composition state.5758## Common mistakes5960- Forgetting to clean up DOM elements and event listeners in `close()`.61- Not handling focus correctly, which breaks keyboard navigation after closing the editor.62- Positioning without `getEditedCellRect()`, which breaks with overlays, scroll, and frozen rows/columns.63- Not supporting both LTR and RTL layouts - always use logical CSS properties or check `this.hot.isRtl()`.64- Using `document` directly instead of `this.hot.rootDocument`.65- Not calling `super` methods in lifecycle overrides (`super.init()`, `super.close()`, etc.).