JavaScript data grid component with spreadsheet-like functionality for React, Angular and Vue applications
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
User interface components and interaction features including context menus, selection handling, accessibility, and visual customization.
Right-click context menu system with predefined and custom menu items.
// Context menu configuration
interface ContextMenuSettings {
enabled?: boolean;
items?: ContextMenuItems;
callback?: (key: string, selection: ContextMenuSelection[], clickEvent: MouseEvent) => void;
}
type ContextMenuItems = Array<
| string // Predefined item key
| ContextMenuItemConfig
| ContextMenuSubmenuConfig
| '-' // Separator
>;
interface ContextMenuItemConfig {
key?: string;
name: string | (() => string);
callback?: (key: string, selection: ContextMenuSelection[], clickEvent: MouseEvent) => void;
disabled?: boolean | (() => boolean);
hidden?: boolean | (() => boolean);
renderer?: (hot: Core, wrapper: HTMLElement, row: number, col: number, prop: string | number, itemValue: string) => HTMLElement;
}
interface ContextMenuSubmenuConfig {
key?: string;
name: string | (() => string);
submenu: {
items: ContextMenuItems;
};
}
interface ContextMenuSelection {
start: CellCoords;
end: CellCoords;
}
// Plugin methods via hot.getPlugin('contextMenu')
class ContextMenu {
/**
* Open context menu at position
* @param event - Mouse event or coordinates
*/
open(event: MouseEvent | { pageX: number; pageY: number }): void;
/**
* Close context menu
*/
close(): void;
/**
* Execute menu command
* @param commandName - Command to execute
*/
executeCommand(commandName: string): void;
/**
* Check if menu is open
* @returns True if menu is open
*/
isOpened(): boolean;
}
// Predefined menu item keys
type PredefinedMenuItemKey =
| 'row_above' | 'row_below' | 'col_left' | 'col_right'
| 'remove_row' | 'remove_col' | 'undo' | 'redo'
| 'make_read_only' | 'alignment' | 'cut' | 'copy'
| 'freeze_column' | 'unfreeze_column' | 'borders'
| 'commentsAddEdit' | 'commentsRemove' | 'mergeCells' | 'add_child'
| 'detach_from_parent' | 'hidden_columns_hide' | 'hidden_columns_show'
| 'hidden_rows_hide' | 'hidden_rows_show' | 'filter_by_condition'
| 'filter_by_condition2' | 'filter_operators' | 'filter_by_value'
| 'filter_action_bar';Advanced selection handling with multiple selection modes and programmatic control.
/**
* Cell coordinates class for position management
*/
class CellCoords {
constructor(row: number, col: number);
row: number;
col: number;
/**
* Check if coordinates are equal
* @param coords - Coordinates to compare
* @returns True if equal
*/
isEqual(coords: CellCoords): boolean;
/**
* Check if this coordinate is southeast of another
* @param coords - Coordinates to compare
* @returns True if southeast
*/
isSouthEastOf(coords: CellCoords): boolean;
/**
* Check if this coordinate is northwest of another
* @param coords - Coordinates to compare
* @returns True if northwest
*/
isNorthWestOf(coords: CellCoords): boolean;
/**
* Clone coordinates
* @returns New CellCoords instance
*/
clone(): CellCoords;
}
/**
* Cell range class for selection management
*/
class CellRange {
constructor(highlight: CellCoords, from: CellCoords, to: CellCoords);
highlight: CellCoords; // Highlighted cell
from: CellCoords; // Top-left corner
to: CellCoords; // Bottom-right corner
/**
* Get all coordinates in the range
* @returns Array of all coordinates
*/
getAll(): CellCoords[];
/**
* Check if range contains coordinates
* @param coords - Coordinates to check
* @returns True if contains
*/
includes(coords: CellCoords): boolean;
/**
* Check if range includes only one cell
* @returns True if single cell
*/
isSingle(): boolean;
/**
* Get top-left corner
* @returns Top-left coordinates
*/
getTopLeftCorner(): CellCoords;
/**
* Get bottom-right corner
* @returns Bottom-right coordinates
*/
getBottomRightCorner(): CellCoords;
/**
* Clone range
* @returns New CellRange instance
*/
clone(): CellRange;
}
// Selection methods on Core instance
interface SelectionMethods {
/**
* Select single cell
* @param row - Row index
* @param col - Column index
* @param scrollToCell - Whether to scroll to cell
* @returns True if successful
*/
selectCell(row: number, col: number, scrollToCell?: boolean): boolean;
/**
* Select multiple cells or ranges
* @param coords - Array of [row, col, endRow, endCol] tuples
* @param scrollToCell - Whether to scroll to selection
* @returns True if successful
*/
selectCells(coords: [number, number, number, number][], scrollToCell?: boolean): boolean;
/**
* Select entire rows
* @param startRow - Starting row
* @param endRow - Ending row (optional)
* @returns True if successful
*/
selectRows(startRow: number, endRow?: number): boolean;
/**
* Select entire columns
* @param startCol - Starting column
* @param endCol - Ending column (optional)
* @returns True if successful
*/
selectColumns(startCol: number, endCol?: number): boolean;
/**
* Select all cells
*/
selectAll(): void;
/**
* Clear selection
*/
deselectCell(): void;
/**
* Get selected ranges
* @returns Array of selected ranges
*/
getSelectedRange(): CellRange[] | undefined;
/**
* Get last selected range
* @returns Last selected range
*/
getSelectedRangeLast(): CellRange | undefined;
}Keyboard interaction system with customizable shortcuts and navigation.
// Keyboard shortcuts via hot.getShortcutManager()
interface ShortcutManager {
/**
* Get context for current selection
* @returns Current context name
*/
getContext(): string;
/**
* Add custom shortcut
* @param keys - Key combination (e.g., 'ctrl+s')
* @param callback - Function to execute
* @param contextName - Context where shortcut is active
*/
addShortcut(keys: string, callback: Function, contextName?: string): void;
/**
* Remove shortcut
* @param keys - Key combination to remove
* @param contextName - Context to remove from
*/
removeShortcut(keys: string, contextName?: string): void;
}
// Default keyboard shortcuts
interface DefaultShortcuts {
'Enter': 'move down';
'Shift+Enter': 'move up';
'Tab': 'move right';
'Shift+Tab': 'move left';
'Arrow keys': 'navigate';
'Ctrl+A': 'select all';
'Ctrl+C': 'copy';
'Ctrl+X': 'cut';
'Ctrl+V': 'paste';
'Ctrl+Z': 'undo';
'Ctrl+Y': 'redo';
'Delete': 'clear cell';
'F2': 'edit cell';
'Escape': 'cancel edit';
}Advanced border styling for cells and ranges.
// Custom borders configuration
interface CustomBordersSettings {
enabled?: boolean;
}
interface BorderOptions {
width?: number;
color?: string;
style?: 'solid' | 'dashed' | 'dotted' | 'double';
}
interface BorderRange {
range: {
from: CellCoords;
to: CellCoords;
};
top?: BorderOptions;
right?: BorderOptions;
bottom?: BorderOptions;
left?: BorderOptions;
}
// Plugin methods via hot.getPlugin('customBorders')
class CustomBorders {
/**
* Set borders for range
* @param range - Cell range
* @param borderOptions - Border styling options
*/
setBorders(range: CellRange | [number, number, number, number], borderOptions: {
top?: BorderOptions;
right?: BorderOptions;
bottom?: BorderOptions;
left?: BorderOptions;
}): void;
/**
* Get borders for cell
* @param row - Row index
* @param col - Column index
* @returns Border options
*/
getBorders(row: number, col: number): BorderOptions | undefined;
/**
* Clear borders from range
* @param range - Cell range to clear
*/
clearBorders(range: CellRange | [number, number, number, number]): void;
}Touch interaction support for mobile devices.
// Touch scroll configuration
interface TouchScrollSettings {
enabled?: boolean;
}
// Touch scroll plugin methods via hot.getPlugin('touchScroll')
class TouchScroll {
/**
* Check if touch scrolling is enabled
* @returns True if enabled
*/
isEnabled(): boolean;
/**
* Enable touch scrolling
*/
enablePlugin(): void;
/**
* Disable touch scrolling
*/
disablePlugin(): void;
}Built-in accessibility support for screen readers and keyboard navigation.
// Accessibility configuration (automatically enabled)
interface AccessibilityFeatures {
// ARIA attributes automatically applied
'aria-label': 'Data grid';
'aria-rowcount': number;
'aria-colcount': number;
'role': 'grid';
// Keyboard navigation support
tabIndex: number;
// Screen reader announcements
announceSelectionChanges: boolean;
announceDataChanges: boolean;
}
// Focus management methods
interface FocusManager {
/**
* Set focus on specific cell
* @param coords - Cell coordinates
*/
setFocus(coords: CellCoords): void;
/**
* Get currently focused cell
* @returns Focused cell coordinates
*/
getFocusedCell(): CellCoords | null;
/**
* Check if grid has focus
* @returns True if focused
*/
isFocused(): boolean;
}Styling and theming options for grid appearance.
// CSS class configuration
interface StyleConfiguration {
className?: string; // Main container class
currentRowClassName?: string; // Active row highlight
currentColClassName?: string; // Active column highlight
currentHeaderClassName?: string; // Active header highlight
activeHeaderClassName?: string; // Selected header class
invalidCellClassName?: string; // Invalid cell styling
readOnlyCellClassName?: string; // Read-only cell styling
commentedCellClassName?: string; // Commented cell styling
noWordWrapClassName?: string; // No text wrap class
// Table styling
tableClassName?: string; // Table element class
stretchH?: 'none' | 'last' | 'all'; // Column stretching
// Cell styling
cells?: (row: number, col: number) => {
className?: string;
readOnly?: boolean;
type?: string;
[key: string]: any;
};
}Usage Examples:
// Context menu with custom items
const hot = new Handsontable(container, {
data: myData,
contextMenu: {
items: {
'row_above': {},
'row_below': {},
'separator': '-',
'custom_item': {
name: 'Custom Action',
callback: (key, selection, clickEvent) => {
console.log('Custom action executed', selection);
}
}
}
}
});
// Custom keyboard shortcuts
const shortcutManager = hot.getShortcutManager();
shortcutManager.addShortcut('ctrl+shift+s', () => {
console.log('Custom save shortcut');
});
// Custom borders
const customBorders = hot.getPlugin('customBorders');
customBorders.setBorders([0, 0, 2, 2], {
top: { width: 3, color: '#ff0000', style: 'solid' },
bottom: { width: 3, color: '#ff0000', style: 'solid' }
});
// Selection events
hot.addHook('afterSelection', (row, col, row2, col2) => {
console.log('Selection changed:', { row, col, row2, col2 });
});
hot.addHook('afterSelectionEnd', (row, col, row2, col2) => {
console.log('Selection ended:', { row, col, row2, col2 });
});