Plugin Architecture
Plugin Architecture
Section titled “Plugin Architecture”@witqq/spreadsheet uses a lightweight plugin system that lets you extend the engine without modifying core code. Plugins can add render layers, listen to events, store isolated state, and integrate with undo/redo.
View source code
import { useRef, useEffect, useState, useCallback } from 'react';import { Spreadsheet } from '@witqq/spreadsheet-react';import type { SpreadsheetRef } from '@witqq/spreadsheet-react';import type { ColumnDef } from '@witqq/spreadsheet';import { FormulaPlugin, ConditionalFormattingPlugin } from '@witqq/spreadsheet-plugins';import { DemoWrapper } from './DemoWrapper';import { DemoButton } from './DemoButton';import { DemoToolbar } from './DemoToolbar';import { useSiteTheme } from './useSiteTheme';
const columns: ColumnDef[] = [ { key: 'a', title: 'Value A', width: 100, type: 'number' }, { key: 'b', title: 'Value B', width: 100, type: 'number' }, { key: 'c', title: 'Sum (A+B)', width: 120 }, { key: 'score', title: 'Score', width: 100, type: 'number' },];
const initialData = [ { a: 10, b: 20, c: '', score: 85 }, { a: 25, b: 15, c: '', score: 42 }, { a: 30, b: 10, c: '', score: 95 }, { a: 5, b: 45, c: '', score: 28 }, { a: 15, b: 35, c: '', score: 73 }, { a: 40, b: 20, c: '', score: 58 }, { a: 20, b: 30, c: '', score: 91 }, { a: 35, b: 5, c: '', score: 15 }, { a: 8, b: 42, c: '', score: 67 }, { a: 50, b: 10, c: '', score: 100 },];
export function PluginShowcaseDemo() { const { witTheme } = useSiteTheme(); const tableRef = useRef<SpreadsheetRef>(null); const [formulaEnabled, setFormulaEnabled] = useState(false); const [condFormatEnabled, setCondFormatEnabled] = useState(false); const formulaPluginRef = useRef<FormulaPlugin | null>(null); const condFormatPluginRef = useRef<ConditionalFormattingPlugin | null>(null);
const toggleFormula = useCallback(() => { const engine = tableRef.current?.getInstance(); if (!engine) return;
if (formulaEnabled) { engine.removePlugin('formula'); formulaPluginRef.current = null; for (let row = 0; row < 10; row++) { engine.setCell(row, 2, ''); } engine.requestRender(); } else { const plugin = new FormulaPlugin({ syncOnly: true }); engine.installPlugin(plugin); formulaPluginRef.current = plugin; for (let row = 0; row < 10; row++) { const formula = `=A${row + 1}+B${row + 1}`; engine.setCell(row, 2, formula); engine.getEventBus().emit('cellChange', { row, col: 2, value: formula, column: columns[2], oldValue: '', newValue: formula, source: 'edit' as const, }); } engine.requestRender(); } setFormulaEnabled(!formulaEnabled); }, [formulaEnabled]);
const toggleCondFormat = useCallback(() => { const engine = tableRef.current?.getInstance(); if (!engine) return;
if (condFormatEnabled) { engine.removePlugin('conditional-format'); condFormatPluginRef.current = null; engine.requestRender(); } else { const plugin = new ConditionalFormattingPlugin(); engine.installPlugin(plugin); plugin.addRule( ConditionalFormattingPlugin.createGradientScale( { startRow: 0, startCol: 3, endRow: 9, endCol: 3 }, [ { value: 0, color: '#ef4444' }, { value: 50, color: '#eab308' }, { value: 100, color: '#22c55e' }, ] ) ); condFormatPluginRef.current = plugin; engine.requestRender(); } setCondFormatEnabled(!condFormatEnabled); }, [condFormatEnabled]);
return ( <DemoWrapper height={440} title="Live Demo" description="Toggle plugins on and off to see their effects. Formula calculates Sum column. Conditional formatting applies gradient to Score." > <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> <DemoToolbar> <DemoButton variant="toggle" active={formulaEnabled} onClick={toggleFormula} > Formula Plugin: {formulaEnabled ? 'ON' : 'OFF'} </DemoButton> <DemoButton variant="toggle" active={condFormatEnabled} onClick={toggleCondFormat} > Conditional Formatting: {condFormatEnabled ? 'ON' : 'OFF'} </DemoButton> </DemoToolbar> <div style={{ flex: 1 }}> <Spreadsheet theme={witTheme} ref={tableRef} columns={columns} data={initialData} editable={true} showRowNumbers={true} style={{ width: '100%', height: '100%' }} /> </div> </div> </DemoWrapper> );}SpreadsheetPlugin Interface
Section titled “SpreadsheetPlugin Interface”Every plugin implements SpreadsheetPlugin:
interface SpreadsheetPlugin { readonly name: string; readonly version: string; readonly dependencies?: string[]; install(api: PluginAPI): void; destroy?(): void;}| Field | Description |
|---|---|
name | Unique identifier (e.g. 'formula', 'conditional-format') |
version | Semver string |
dependencies | Optional array of plugin names that must be installed first |
install(api) | Called when the plugin is added to the engine |
destroy() | Optional cleanup — unbind events, remove layers |
PluginAPI
Section titled “PluginAPI”The install method receives a PluginAPI object:
interface PluginAPI { readonly engine: SpreadsheetEngine; getPluginState<T>(key: string): T | undefined; setPluginState<T>(key: string, value: T): void;}engine— full access toSpreadsheetEngine(event bus, cell store, render pipeline, etc.)getPluginState / setPluginState— isolated key-value storage per plugin, managed by the engine
Installing and Removing Plugins
Section titled “Installing and Removing Plugins”Direct engine access
Section titled “Direct engine access”import { SpreadsheetEngine } from '@witqq/spreadsheet';
const engine = new SpreadsheetEngine(config);engine.installPlugin(myPlugin);engine.removePlugin('my-plugin');Via React ref
Section titled “Via React ref”const tableRef = useRef<SpreadsheetRef>(null);
// After mounttableRef.current?.installPlugin(myPlugin);tableRef.current?.removePlugin('my-plugin');Official Plugins
Section titled “Official Plugins”| Plugin | Package | Description |
|---|---|---|
FormulaPlugin | @witqq/spreadsheet-plugins | Spreadsheet formulas with dependency graph |
ConditionalFormattingPlugin | @witqq/spreadsheet-plugins | Color scales, data bars, icon sets |
ExcelPlugin | @witqq/spreadsheet-plugins | Import/export .xlsx via lazy-loaded SheetJS |
createContextMenuPlugin | @witqq/spreadsheet-plugins | Right-click context menu with custom items |
CollaborationPlugin | @witqq/spreadsheet-plugins | Real-time OT collaboration with remote cursors |
ProgressiveLoaderPlugin | @witqq/spreadsheet-plugins | Non-blocking large dataset loading with progress overlay |
Custom Plugin Example
Section titled “Custom Plugin Example”A plugin that highlights the active row:
import type { SpreadsheetPlugin, PluginAPI } from '@witqq/spreadsheet';
const activeRowPlugin: SpreadsheetPlugin = { name: 'active-row-highlight', version: '1.0.0',
install(api: PluginAPI) { const { engine } = api;
const handler = (event: { selection: Selection }) => { const row = event.selection.activeCell.row; api.setPluginState('activeRow', row); engine.requestRender(); };
engine.on('selectionChange', handler); api.setPluginState('handler', handler); },
destroy() { // Cleanup handled by engine when plugin is removed },};Installing Plugins in React
Section titled “Installing Plugins in React”import { Spreadsheet, SpreadsheetRef } from '@witqq/spreadsheet-react';import { FormulaPlugin, ConditionalFormattingPlugin } from '@witqq/spreadsheet-plugins';
function App() { const ref = useRef<SpreadsheetRef>(null);
useEffect(() => { ref.current?.installPlugin(new FormulaPlugin()); ref.current?.installPlugin(new ConditionalFormattingPlugin()); return () => { ref.current?.removePlugin('formula'); ref.current?.removePlugin('conditional-format'); }; }, []);
return <Spreadsheet ref={ref} columns={columns} data={data} />;}