Events
Events
Section titled “Events”EventBus
Section titled “EventBus”All inter-subsystem communication in witqq spreadsheet goes through a typed EventBus. It implements a publish/subscribe pattern with type-safe event names and payloads.
View source code
import { useRef, useEffect, useState, useCallback } from 'react';import { WitTable } from '@witqq/spreadsheet-react';import type { WitTableRef } from '@witqq/spreadsheet-react';import { DemoWrapper } from './DemoWrapper';import { generateEmployees, employeeColumns } from './generate-data';import { useSiteTheme } from './useSiteTheme';
const data = generateEmployees(30);const sortableColumns = employeeColumns.map(col => ({ ...col, sortable: true }));
const EVENT_COLORS: Record<string, string> = { cellClick: '#2563eb', cellChange: '#16a34a', selectionChange: '#9333ea', scroll: '#64748b', sortChange: '#ea580c',};
interface EventEntry { time: string; name: string; detail: string;}
export function EventBusDemo() { const { witTheme } = useSiteTheme(); const tableRef = useRef<WitTableRef>(null); const logRef = useRef<HTMLDivElement>(null); const [events, setEvents] = useState<EventEntry[]>([]);
const clearLog = useCallback(() => setEvents([]), []);
useEffect(() => { const engine = tableRef.current?.getInstance(); if (!engine) return; const bus = engine.getEventBus();
const logEvent = (name: string, detail: string) => { const time = new Date().toLocaleTimeString('en', { hour12: false }); setEvents(prev => [...prev.slice(-49), { time, name, detail }]); };
const unsubs = [ bus.on('cellClick', (e: any) => logEvent('cellClick', `row:${e.row} col:${e.col}`)), bus.on('cellChange', (e: any) => logEvent('cellChange', `[${e.row},${e.col}] "${e.oldValue}" → "${e.newValue}"`)), bus.on('selectionChange', (e: any) => logEvent('selectionChange', `row:${e.selection.activeRow} col:${e.selection.activeCol}`)), bus.on('scroll', (e: any) => logEvent('scroll', `top:${Math.round(e.scrollTop)} left:${Math.round(e.scrollLeft)}`)), bus.on('sortChange', (e: any) => logEvent('sortChange', `${e.sortColumns.length} column(s)`)), ];
return () => unsubs.forEach(fn => fn()); }, []);
useEffect(() => { if (logRef.current) { logRef.current.scrollTop = logRef.current.scrollHeight; } }, [events]);
return ( <DemoWrapper height={500} title="Live Demo" description="Interact with the table — click cells, edit values, sort columns, scroll. Events appear in the log below."> <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> <div style={{ flex: 1, minHeight: 0 }}> <WitTable theme={witTheme} ref={tableRef} columns={sortableColumns} data={data} showRowNumbers editable style={{ width: '100%', height: '100%' }} /> </div> <div style={{ borderTop: '1px solid #e2e8f0' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '4px 8px', background: '#f1f5f9', fontSize: 11 }}> <span style={{ fontWeight: 600 }}>Event Log ({events.length})</span> <button onClick={clearLog} style={{ border: '1px solid #cbd5e1', borderRadius: 4, background: '#fff', padding: '2px 8px', cursor: 'pointer', fontSize: 11 }} > Clear </button> </div> <div ref={logRef} style={{ height: 120, overflowY: 'auto', background: '#f8fafc', padding: 8, fontFamily: 'monospace', fontSize: 11 }} > {events.length === 0 && ( <div style={{ color: '#94a3b8', fontStyle: 'italic' }}>No events yet. Click a cell, edit a value, or scroll the table.</div> )} {events.map((evt, i) => ( <div key={i} style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', lineHeight: '18px' }}> <span style={{ color: '#94a3b8' }}>[{evt.time}]</span>{' '} <span style={{ color: EVENT_COLORS[evt.name] || '#334155', fontWeight: 600 }}>{evt.name}</span> {': '} <span style={{ color: '#334155' }}>{evt.detail}</span> </div> ))} </div> </div> </div> </DemoWrapper> );}const engine = tableRef.current?.getInstance();
// Subscribe to an eventconst unsubscribe = engine.on('cellChange', (event) => { console.log(`Cell [${event.row}, ${event.col}] changed`); console.log(`Old: ${event.oldValue}, New: ${event.newValue}`);});
// Unsubscribe when doneunsubscribe();Event Reference
Section titled “Event Reference”Cell Events
Section titled “Cell Events”| Event | Payload | Description |
|---|---|---|
cellClick | { row, col, originalEvent } | Single click on a cell |
cellDoubleClick | { row, col, originalEvent } | Double-click on a cell (opens editor) |
cellChange | { row, col, oldValue, newValue } | Cell value changed after edit commit |
cellStatusChange | { row, col, status } | Cell status lifecycle: changed → saving → saved, error |
cellValidation | { row, col, valid, errors } | Validation result after cell edit |
Selection Events
Section titled “Selection Events”| Event | Payload | Description |
|---|---|---|
selectionChange | { activeCell, ranges, anchor } | Selection changed (click, keyboard, or programmatic) |
Scroll Events
Section titled “Scroll Events”| Event | Payload | Description |
|---|---|---|
scroll | { scrollTop, scrollLeft, viewport } | Scroll position changed |
Lifecycle Events
Section titled “Lifecycle Events”| Event | Payload | Description |
|---|---|---|
ready | { engine } | Engine initialization complete |
destroy | {} | Engine destroyed and resources cleaned up |
Command Events
Section titled “Command Events”| Event | Payload | Description |
|---|---|---|
commandExecute | { command } | A command was executed |
commandUndo | { command } | A command was undone |
commandRedo | { command } | A command was redone |
Clipboard Events
Section titled “Clipboard Events”| Event | Payload | Description |
|---|---|---|
clipboardCopy | { cells, text } | Cells copied to clipboard |
clipboardCut | { cells, text } | Cells cut to clipboard |
clipboardPaste | { cells, target } | Data pasted from clipboard |
Resize Events
Section titled “Resize Events”| Event | Payload | Description |
|---|---|---|
columnResizeStart | { col, width } | Column resize drag started |
columnResize | { col, width } | Column width changing during drag |
columnResizeEnd | { col, oldWidth, newWidth } | Column resize drag completed |
rowResizeStart | { row, height } | Row resize drag started |
rowResize | { row, height } | Row height changing during drag |
rowResizeEnd | { row, oldHeight, newHeight } | Row resize drag completed |
Autofill Events
Section titled “Autofill Events”| Event | Payload | Description |
|---|---|---|
autofillStart | { startCell, direction } | Fill handle drag started |
autofillPreview | { range, values } | Preview values during drag |
autofillComplete | { range, values } | Fill operation completed |
Sort & Filter Events
Section titled “Sort & Filter Events”| Event | Payload | Description |
|---|---|---|
sortChange | { columns } | Sort configuration changed |
sortRejected | { column, reason } | Sort request rejected |
filterChange | { filters } | Filter configuration changed |
Group Events
Section titled “Group Events”| Event | Payload | Description |
|---|---|---|
rowGroupToggle | { groupKey, expanded } | Row group expanded/collapsed |
rowGroupChange | { groups } | Row grouping configuration changed |
Theme Events
Section titled “Theme Events”| Event | Payload | Description |
|---|---|---|
themeChange | { theme } | Theme changed via setTheme() |
EventTranslator
Section titled “EventTranslator”The EventTranslator converts raw DOM events (mouse clicks, touch, keyboard) into cell-level events. It performs hit-testing to determine which cell or region was interacted with.
Regions identified by EventTranslator:
| Region | Area | Actions |
|---|---|---|
cell | Data cells in the grid body | Click, edit, select |
header | Column header row | Sort toggle, filter open, resize |
row-number | Row number column on the left | Row selection |
corner | Top-left corner (row numbers × header) | Select all |
Touch Events
Section titled “Touch Events”On touch devices, the EventTranslator maps gestures:
| Gesture | Action |
|---|---|
| Tap | Select cell |
| Double-tap | Open inline editor |
| Scroll | Native scroll (CSS touch-action: pan-x pan-y) |
React Event Callbacks
Section titled “React Event Callbacks”In the React wrapper, events are exposed as props:
<WitTable<Row> columns={columns} data={data} onCellChange={(row, col, oldVal, newVal) => { // Persist to server }} onSelectionChange={(selection) => { // Update status bar }} onSortChange={(columns) => { // Server-side sort }} onFilterChange={(filters) => { // Server-side filter }} onReady={(engine) => { // Store engine reference, install plugins }}/>