Skip to content
cccskills
FreePublished on GitHub

handsontable-plugin-dev

Create or modify Handsontable plugins using the project's plugin rules. Covers settings, hooks that respond to events, enable and disable behavior, conflicts between plugins, row index mapping, UI focus management, and required documentation and tests.

OriginalShow the original description

Use when creating a new Handsontable plugin, modifying an existing plugin's behavior, adding hooks or options to a plugin, or working with the plugin lifecycle (enablePlugin, disablePlugin, updatePlugin). Covers the full plugin contract, conflict registration, settings validation, and IndexMapper integration.

Works withPlaywright
See how to install

Good for

  • Creating a new Handsontable plugin
  • Adding plugin settings and event hooks
  • Reviewing enable, disable, and settings update behavior
  • Managing plugin conflicts and keyboard focus

Files included (1)

  • SKILL.md9.1 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Plugin file structure

src/plugins/{pluginName}/
├── index.ts              # Re-exports PLUGIN_KEY, PLUGIN_PRIORITY, ClassName
├── {pluginName}.ts       # Main class extending BasePlugin
├── AGENTS.md             # Plugin knowledge file, REQUIRED (see below)
├── CLAUDE.md             # symlink -> AGENTS.md, never a copy
├── types.ts              # (optional) exported plugin-local types
├── __tests__/            # *.unit.js unit tests (new E2E is Playwright, in tests/e2e/)
└── {submodules}/         # UI classes, strategies, etc.

Contract

Statics (PLUGIN_KEY, PLUGIN_PRIORITY, SETTING_KEYS, PLUGIN_DEPS, DEFAULT_SETTINGS, SETTINGS_VALIDATORS), lifecycle order, onUpdateSettings, hard conflicts, and the PLUGIN_PRIORITY table: src/plugins/base/AGENTS.md. Pick a free priority number from that table and add your row to it.

Key patterns (gold standard: src/plugins/pagination/pagination.ts)

  • Private fields: # prefix for all internal state, no @private JSDoc.
  • Hook callbacks (required for new code): pass #on* handlers to addHook as arrow function class fields, directly: this.addHook('afterLoadData', this.#onAfterLoadData) (priority stays the 3rd arg). .bind(this) builds a new function per call, so the registered reference can never be removed. Existing inline-wrapper sites work; leave them as they are.
  • Hook registration: this.addHook() auto-cleans on disablePlugin(); this.hot.addHook() does not. Register new hook names at module level: Hooks.getSingleton().register('beforeMyAction'); (import Hooks from '../../core/hooks').
  • Settings: read via this.getSetting('key') (dot notation supported); defaults come from DEFAULT_SETTINGS.
  • Conflict registration (module level, before the class), then check this.isHardConflictBlocked() in enablePlugin():
    import { registerConflict } from '../base/conflictRegistry';
    registerConflict(PLUGIN_KEY, ['nestedRows', 'mergeCells']);
    
  • IndexMapper: create maps in enablePlugin(), unregister in disablePlugin():
    this.#map = this.hot.rowIndexMapper.createAndRegisterIndexMap(this.pluginName, 'hiding', false);
    // 'hiding' = HidingMap (not rendered, stays in DataMap); 'trimming' = TrimmingMap (removed from DataMap)
    
  • UI: extract it into its own class with dependency injection (no direct hot reference). Swappable logic uses a strategy (e.g. autoPageSize vs fixedPageSize).
  • Batch rendering: wrap multiple data/render changes in this.hot.batch(() => { ... }) for one render, but never around code that can run host code (updateSettings, loadData, another plugin's hooks). Core#batch/batchRender do not resume in a finally, so a listener that throws mid-batch leaves the grid render-suspended for good. For such code, call this.hot.suspendRender() and put this.hot.resumeRender() in a finally.
  • Sliced per-unit settings (one plugin managing several units, e.g. sheets): treat each unit's settings as a partial slice, applying only the declared keys. Apply the slice and its data together in one guarded batch (suspendRender() + try/finally resumeRender()), and run the swap directly when this.hot.view does not exist yet (initial setup). Reference: src/plugins/sheetsBar/sheetsBar.ts (#applySheet). A plugin that also owns a layout slot (handsontable/AGENTS.md "Wrapper UI placement") registers its UI once in enablePlugin(); this method owns only the settings/data swap.

Decoupling rules

  • Cross-plugin access goes through hooks or hot.getPlugin('{Name}'), never imports; no circular dependencies between plugins.
  • The plugin introducing the incompatibility owns the blocking logic.
  • For custom error UI when Notification is off, use the hooks afterDataProviderFetchError and afterRowsMutationError. Built-in DataProvider toasts: src/plugins/dataProvider/AGENTS.md.

Registration checklist

  1. index.ts: export { PLUGIN_KEY, PLUGIN_PRIORITY, ClassName } from './pluginName';
  2. Wire into src/plugins/index.ts.
  3. Add the default option (disabled) in src/dataMap/metaManager/metaSchema.ts.
  4. For new hook signatures or settings, add them to src/core/settings.ts (GridSettings); npm run build:types regenerates the public .d.ts files into tmp/.
  5. Write AGENTS.md and symlink CLAUDE.md to it with the relative target, so the link resolves in a git worktree:
    cd handsontable/src/plugins/myPlugin && ln -s AGENTS.md CLAUDE.md
    
    Git stores it as mode 120000; a regular file in git status means you copied.
  6. Add your PLUGIN_PRIORITY row to the table in src/plugins/base/AGENTS.md, in the same change: nothing enforces it, so it drifts silently.

Focus management

A plugin with UI (buttons, inputs, navigation bars) integrates with src/focusManager/. Reference: Pagination (#registerFocusScope / #unregisterFocusScope).

  • Register a focus scope with a unique name for the plugin's UI region.
  • On scope activation, focus the first element for Tab and the last for Shift+Tab.
  • Decide whether your scope COVERS the grid or REPLACES it. Activating a scope switches the shortcut manager to the scope's shortcutsContextName, and only that context runs, so an empty one kills every grid shortcut while your UI is up (emptyDataState shipped that way; DEV-53). An overlay the user still thinks of as "the grid underneath" adds fallbackShortcutsContextName: 'grid' to registerScope() and inherits every grid shortcut, including ones added later (reference: emptyDataState). A modal scope leaves it unset: letting grid shortcuts through a modal is the bug. Register a shortcut in your own context only to OVERRIDE one, and record in your AGENTS.md why the grid's version does not fit.
  • UI painted OVER the grid body also adds coversGridBody: true. Inheriting and covering are separate questions. Without the flag, a content-writing shortcut sees that the grid still draws cells and lets Delete reach data the user cannot touch (DEV-2917: emptyDataState covered a rendered grid during a DataProvider fetch and the dataset was wiped). The flag is read from every ENABLED scope, not only the active one.

Gotchas

  • Merged cells: read colspan/rowspan from hot.getCellMeta(row, col) (set by MergeCells via afterGetCellMeta), not from DOM attributes; the meta is available regardless of viewport state.

Knowledge file (required)

Every plugin directory carries an AGENTS.md with CLAUDE.md symlinked to it; a new plugin without one is incomplete. Edit AGENTS.md, never CLAUDE.md.

It answers "what must I never get wrong here, and where do I look next": the reason a guard exists, the load-bearing ordering, the shortcut that looks right and is wrong. Required sections:

  1. # {PluginName} plugin – {one-line focus}, then one or two sentences naming the files covered and saying "Read this before touching X." (10 pre-existing files lack ## Where to look next or ## Testing; new files carry both, and fixing those 10 belongs in its own change.)
  2. What it owns and what it does not. Most plugin bugs are ownership confusion.
  3. The traps, each with the reason it exists (and the issue or DEV id where you have one). "Do not reorder these calls, because Formulas syncs the data source in that hook and selecting first made afterSelection read the stale value" survives; the bare rule gets undone.
  4. ## Where to look next: sibling plugins, the .ai/ reference, ../base/AGENTS.md for the contract, and this skill for the workflow.
  5. ## Testing: targeted commands and anything non-obvious about the suite (a __tests__/ split, a spec failing for an unrelated reason, a case needing a real device).

Length follows the plugin: stretchColumns/AGENTS.md (three bullets) and base/AGENTS.md (the contract for every plugin) are the two ends; read one of each before writing yours.

Where a fact belongs. A trap found while changing an existing plugin goes in that plugin's AGENTS.md. handsontable/AGENTS.md holds rules that span plugins; for a core-wide rule with one worked example, state the rule there and put the example in the plugin file with a pointer back.

Testing requirements

  • New E2E tests are Playwright in tests/e2e/ (skill handsontable-playwright-e2e) for a plugin's UI, interaction, and rendering. The presence gate blocks new legacy *.spec.js; editing an existing one (async it() callbacks) is fine.
  • Unit tests (__tests__/*.unit.js): strategies and helpers in isolation.
  • Cover updateSettings(), enablePlugin()/disablePlugin() toggling, and interactions with other plugins (sorting, filters, hidden rows).

Base class: src/plugins/base/base.ts. Deeper context: handsontable/.ai/ARCHITECTURE.md, handsontable/.ai/CONVENTIONS.md.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Develop and modify the Handsontable Angular wrapper with guidance on component lifecycle, settings updates, and NgZone, which controls change detection. It also covers standalone examples, global configuration, and the build and test workflow.

  • Fix settings updates in the Angular wrapper
  • Review change detection around grid operations
  • Update examples to standalone components
handsontable/handsontable22.1KUpdated Oct 19, 2026

Open local or public web pages in BrowserStack Live for checks on real mobile devices or desktop browsers. The skill prepares temporary access to a local server, launches the requested session, and shares its dashboard link.

  • Check a local page on an Android device
  • Open a page in Safari on an iPhone or iPad
  • Inspect behavior in a specific desktop browser
handsontable/handsontable22.1KUpdated Oct 19, 2026

Prepare user-facing changelog entries for source code changes. The skill determines whether an entry is needed, classifies the change, writes a clear title, and uses the project CLI to create a JSON file tied to the pull request.

  • Add a changelog entry for a bug fix
  • Classify a new feature or behavior change
  • Explain a breaking change and its replacement
handsontable/handsontable22.1KUpdated Oct 19, 2026

code-graph

FreeSummary

Explore relationships between functions and files using a prebuilt code graph, a map of code dependencies. Trace bugs, inspect callers and execution paths, and assess the impact of refactoring or proposed changes.

  • Explore callers and dependencies in unfamiliar code
  • Trace a bug across multiple files
  • Assess the impact of renaming or removing code
handsontable/handsontable22.1KUpdated Oct 19, 2026

Work with Handsontable row and column positions by distinguishing source data, displayed order, and rendered cells. It guides index conversion and helps investigate misplaced data when sorting, filtering, hiding, or moving rows and columns.

  • Investigating misplaced rows or columns
  • Converting between source and displayed indexes
  • Combining filters with manual column movement
handsontable/handsontable22.1KUpdated Oct 19, 2026

Create Handsontable documentation examples for JavaScript, TypeScript, React, Angular, and Vue. The skill explains source files, generated JavaScript, required imports and license settings, embedding examples in guides, and checking that each framework version displays correctly.

  • Adding a code example to a feature guide
  • Preparing examples for multiple frameworks
  • Generating JavaScript examples from TypeScript
handsontable/handsontable22.1KUpdated Oct 19, 2026

See all skills by handsontable

Report a problem with this skill