or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

column-configuration.mdcustom-editors.mdindex.mdtable-component.md
tile.json

custom-editors.mddocs/

Custom Editors

The BaseEditorComponent class provides a foundation for creating React-based custom cell editors that integrate seamlessly with Handsontable's editing lifecycle. It implements the full Handsontable editor interface while allowing React component patterns.

Capabilities

BaseEditorComponent

Base class for creating custom React-based cell editors with full Handsontable editor lifecycle integration.

/**
 * Base class for React-based Handsontable editors
 * Implements the complete BaseEditor interface with React component lifecycle
 */
class BaseEditorComponent<P = {}, S = {}, SS = any> 
  extends React.Component<P & BaseEditorProps, S, SS> 
  implements Handsontable.editors.BaseEditor;

interface BaseEditorProps extends HotEditorProps {
  /** Editor scope identifier - column index or 'global' */
  editorColumnScope?: EditorScopeIdentifier;
  /** Callback to emit editor instance to parent */
  emitEditorInstance?: (editor: BaseEditorComponent, column: EditorScopeIdentifier) => void;
}

interface HotEditorProps {
  /** Editor marker property */
  "hot-editor": any;
  /** Editor DOM element ID */
  id?: string;
  /** CSS class name */
  className?: string;
  /** Inline styles */
  style?: React.CSSProperties;
}

type EditorScopeIdentifier = 'global' | number;

Usage Examples:

import React, { Component } from 'react';
import { BaseEditorComponent } from '@handsontable/react';

// Simple text editor with validation
class CustomTextEditor extends BaseEditorComponent {
  constructor(props) {
    super(props);
    this.state = {
      value: ''
    };
  }

  componentDidMount() {
    super.componentDidMount();
    this.setState({ value: this.originalValue || '' });
  }

  handleChange = (event) => {
    this.setState({ value: event.target.value });
  };

  handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      this.finishEditing();
    } else if (event.key === 'Escape') {
      this.cancelChanges();
    }
  };

  getValue() {
    return this.state.value;
  }

  setValue(value) {
    this.setState({ value: value || '' });
  }

  render() {
    return (
      <input
        type="text"
        value={this.state.value}
        onChange={this.handleChange}
        onKeyDown={this.handleKeyDown}
        autoFocus
        style={{ width: '100%', border: 'none', outline: 'none' }}
      />
    );
  }
}

// Dropdown editor with custom options
class CustomDropdownEditor extends BaseEditorComponent {
  constructor(props) {
    super(props);
    this.state = {
      value: '',
      options: ['Option 1', 'Option 2', 'Option 3']
    };
  }

  componentDidMount() {
    super.componentDidMount();
    this.setState({ value: this.originalValue || '' });
  }

  handleChange = (event) => {
    this.setState({ value: event.target.value });
    this.finishEditing();
  };

  getValue() {
    return this.state.value;
  }

  setValue(value) {
    this.setState({ value: value || '' });
  }

  render() {
    return (
      <select
        value={this.state.value}
        onChange={this.handleChange}
        autoFocus
        style={{ width: '100%', border: 'none' }}
      >
        <option value="">Select...</option>
        {this.state.options.map(option => (
          <option key={option} value={option}>
            {option}
          </option>
        ))}
      </select>
    );
  }
}

// Usage in HotTable
function TableWithCustomEditor() {
  return (
    <HotTable
      data={[['Sample', 'Data']]}
      colHeaders={true}
      licenseKey="non-commercial-and-evaluation"
    >
      <HotColumn data={0}>
        <CustomTextEditor hot-editor />
      </HotColumn>
      <HotColumn data={1}>
        <CustomDropdownEditor hot-editor />
      </HotColumn>
    </HotTable>
  );
}

Instance Properties

Properties available on BaseEditorComponent instances during the editing lifecycle:

/** Handsontable instance reference */
hotInstance: Handsontable | null;

/** Current row index being edited */
row: number | null;

/** Current column index being edited */
col: number | null;

/** Property name or column identifier */
prop: string | number | null;

/** Cell DOM element being edited */
TD: HTMLTableCellElement | null;

/** Original cell value before editing */
originalValue: any;

/** Cell properties object with metadata */
cellProperties: Handsontable.CellProperties | null;

/** Editor state (inherited from BaseEditor) */
state: any;

/** Reference to custom editor instance wrapper */
hotCustomEditorInstance: any;

Editor Lifecycle Methods

Methods inherited from Handsontable.editors.BaseEditor that handle the editing lifecycle:

/**
 * Prepare the editor for editing a specific cell
 * @param row - Row index
 * @param col - Column index  
 * @param prop - Property name
 * @param TD - Cell DOM element
 * @param originalValue - Current cell value
 * @param cellProperties - Cell metadata
 */
prepare(
  row: number, 
  col: number, 
  prop: string | number, 
  TD: HTMLTableCellElement, 
  originalValue: any, 
  cellProperties: Handsontable.CellProperties
): any;

/**
 * Initialize the editor (called once)
 */
init(...args: any[]): any;

/**
 * Open the editor for editing
 */
open(...args: any[]): any;

/**
 * Close the editor
 */
close(...args: any[]): any;

/**
 * Focus the editor input
 */
focus(...args: any[]): any;

/**
 * Get the current editor value
 * @returns Current editor value
 */
getValue(...args: any[]): any;

/**
 * Set the editor value
 * @param value - Value to set
 */
setValue(...args: any[]): any;

/**
 * Begin the editing process
 */
beginEditing(...args: any[]): any;

/**
 * Finish editing and save the value
 */
finishEditing(...args: any[]): any;

/**
 * Cancel editing and revert changes
 */
cancelChanges(...args: any[]): any;

/**
 * Save the current editor value to the cell
 */
saveValue(...args: any[]): any;

/**
 * Discard the editor without saving
 */
discardEditor(...args: any[]): any;

/**
 * Enable full edit mode
 */
enableFullEditMode(...args: any[]): any;

/**
 * Check if editor is in full edit mode
 * @returns True if in full edit mode
 */
isInFullEditMode(...args: any[]): boolean;

/**
 * Check if editor is open
 * @returns True if editor is open
 */
isOpened(...args: any[]): boolean;

/**
 * Check if editor is waiting
 * @returns True if editor is waiting
 */
isWaiting(...args: any[]): boolean;

Hook System

Methods for managing Handsontable hooks within custom editors:

/**
 * Add a hook callback
 * @param key - Hook name
 * @param callback - Hook callback function
 */
addHook(...args: any[]): any;

/**
 * Remove hooks by key
 * @param key - Hook name to remove
 */
removeHooksByKey(...args: any[]): any;

/**
 * Clear all hooks
 */
clearHooks(...args: any[]): any;

Cell Information Methods

Methods for getting information about the cell being edited:

/**
 * Get the edited cell element
 * @returns Cell DOM element
 */
getEditedCell(...args: any[]): HTMLTableCellElement;

/**
 * Get the edited cell rectangle
 * @returns Cell bounding rectangle
 */
getEditedCellRect(...args: any[]): DOMRect;

/**
 * Get the edited cell's z-index
 * @returns Z-index value
 */
getEditedCellsZIndex(...args: any[]): number;

Editor Registration

To use custom editors, they must be provided as children of HotColumn or HotTable components:

// Column-specific editor
<HotColumn data="description">
  <CustomEditor hot-editor />
</HotColumn>

// Global editor for entire table
<HotTable data={data}>
  <CustomEditor hot-editor />
  <HotColumn data={0} />
  <HotColumn data={1} />
</HotTable>

The

hot-editor
prop is required to identify React components as editor components. The component will be automatically integrated into Handsontable's editor system.

Editor Scope

Editors can be scoped to specific columns or applied globally:

  • Column-scoped: Editor defined as child of HotColumn affects only that column
  • Global-scoped: Editor defined as child of HotTable affects all cells unless overridden

The

editorColumnScope
prop identifies the scope and is managed automatically by the parent components.