# BaseGridPlugin

> _Since v0.1.1_

Abstract base class for all grid plugins.

## Constructors

### constructor

```ts
new BaseGridPlugin(config: Partial<TConfig>)
```

## Properties

| Property | Type | Description |
| -------- | ---- | ----------- |
| `dependencies?` | <code><a href="/grid/api/plugin-development/interfaces/plugindependency/">PluginDependency</a>[]</code> | Plugin dependencies - declare other plugins this one requires. |
| `manifest?` | <code><a href="/grid/api/plugin-development/interfaces/pluginmanifest/">PluginManifest</a>&lt;any&gt;</code> | Plugin manifest - declares owned properties, config rules, and hook priorities. |
| `name` | <code>string</code> | Unique plugin identifier (derived from class name by default) |
| `aliases?` | <code>readonly string[]</code> | Alternative names for backward compatibility. `getPluginByName()` matches against both `name` and `aliases`. |
| `version` | <code>string</code> | Plugin version - defaults to grid version for built-in plugins. Third-party plugins can override with their own semver. |
| `styles?` | <code>string</code> | CSS styles to inject via `document.adoptedStyleSheets` |
| `cellRenderers?` | <code>Record&lt;string, CellRenderer&gt;</code> | Custom cell renderers keyed by type name |
| `headerRenderers?` | <code>Record&lt;string, <a href="/grid/api/core/types/headerrenderer/">HeaderRenderer</a>&gt;</code> | Custom header renderers keyed by type name |
| `cellEditors?` | <code>Record&lt;string, CellEditor&gt;</code> | Custom cell editors keyed by type name |
| `grid` | <code>GridElement</code> | The grid instance this plugin is attached to |
| `config` | <code>TConfig</code> | Plugin configuration - merged with defaults in attach() |
| `userConfig` | <code>Partial&lt;TConfig&gt;</code> | User-provided configuration from constructor |

### Property Details

#### dependencies

Plugin dependencies - declare other plugins this one requires.

Dependencies are validated when the plugin is attached.
Required dependencies throw an error if missing. Optional dependencies are
silent by default; set a PluginDependency.severity of `'warn'` or
`'info'` to opt into a development-only log.

```typescript
static readonly dependencies: PluginDependency[] = [
  { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
  { name: 'selection', required: false, reason: 'Enables selection-based undo' },
];
```

---

#### manifest

Plugin manifest - declares owned properties, config rules, and hook priorities.

This is read by the configuration validator to:
- Validate that required plugins are loaded when their properties are used
- Execute configRules to detect invalid/conflicting settings
- Order hook execution based on priority

```typescript
static override readonly manifest: PluginManifest<MyConfig> = {
  ownedProperties: [
    { property: 'myProp', level: 'column', description: 'the "myProp" column property' },
  ],
  configRules: [
    { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },
  ],
};
```

---

## Accessors

### defaultConfig

Default configuration - subclasses should override this getter.
Note: This must be a getter (not property initializer) for proper inheritance
since property initializers run after parent constructor.

```ts
readonly defaultConfig: Partial<TConfig>
```

***

### resolvedConfig

Effective configuration (defaults merged with user config), resolved
eagerly so it is available **before** attach runs. Dependency
validation happens before `attach()` merges config, so config
-conditional dependency `when` predicates evaluate against this getter.

While attached, returns the already-merged config; otherwise
recomputes `defaultConfig` + `userConfig` fresh. `config` is intentionally
**not** trusted when detached because detach does not clear it — a
re-validated plugin would otherwise see stale config from a prior
attachment. The attach-managed abort controller is the
attached/detached signal.
 Plugin infrastructure (used by `validatePluginDependencies`).

```ts
readonly resolvedConfig: TConfig
```

***

### rows

Get the current rows from the grid.

```ts
readonly rows: any[]
```

***

### sourceRows

Get the original unfiltered/unprocessed rows from the grid.
Use this when you need all source data regardless of active filters.

```ts
readonly sourceRows: any[]
```

***

### columns

Get the current columns from the grid.

```ts
readonly columns: ColumnConfig<any>[]
```

***

### visibleColumns

Get only visible columns from the grid (excludes hidden).
Use this for rendering that needs to match the grid template.

```ts
readonly visibleColumns: ColumnConfig<any>[]
```

***

### gridElement

Get the grid as an HTMLElement for direct DOM operations.
Use sparingly - prefer the typed GridElementRef API when possible.

```ts
readonly gridElement: HTMLElement
```

#### Example

```ts
const width = this.gridElement.clientWidth;
this.gridElement.classList.add('my-plugin-active');
```

***

### disconnectSignal

Get the disconnect signal for event listener cleanup.
This signal is aborted when the grid disconnects from the DOM.
Use this when adding event listeners that should be cleaned up automatically.

Best for:
- Document/window-level listeners added in attach()
- Listeners on the grid element itself
- Any listener that should persist across renders

Not needed for:
- Listeners on elements created in afterRender() (removed with element)

```ts
readonly disconnectSignal: AbortSignal
```

#### Example

```ts
element.addEventListener('click', handler, { signal: this.disconnectSignal });
document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
```

***

### gridIcons

Get the grid-level icons configuration.
Returns merged icons (user config + defaults).

```ts
readonly gridIcons: Required<GridIcons>
```

***

### isAnimationEnabled

Check if animations are enabled at the grid level.
Respects gridConfig.animation.mode and the CSS variable set by the grid.

Plugins should use this to skip animations when:
- Animation mode is 'off' or `false`
- User prefers reduced motion and mode is 'reduced-motion' (default)

```ts
readonly isAnimationEnabled: boolean
```

#### Example

```ts
private get animationStyle(): 'slide' | 'fade' | false {
  if (!this.isAnimationEnabled) return false;
  return this.config.animation ?? 'slide';
}
```

***

### animationDuration

Get the animation duration in milliseconds from CSS variable.
Falls back to 200ms if not set.

Plugins can use this for their animation timing to stay consistent
with the grid-level animation.duration setting.

```ts
readonly animationDuration: number
```

#### Example

```ts
element.animate(keyframes, { duration: this.animationDuration });
```

***

## Methods

### mergeConfigsFrom()

Merge user-supplied configuration from other plugin instances into this
plugin's `userConfig`. Used by `PluginManager.attachAll()`'s alias-collapse
pre-pass: when a consumer instantiates the same plugin under multiple
names (e.g. `RowReorderPlugin` and `RowDragDropPlugin`, which are aliases
after V2.x), only one instance is attached, and the other instances'
configs are folded in via this method.

Merge rules (shallow):

- Same key, **same value** → silent.
- Same key, **different values** (incl. function vs function) → throws
  a diagnostic naming the conflicting key. Conflicting callbacks
  (`canDrag`, `canDrop`, etc.) cannot be reconciled automatically.
- **Disjoint keys** → merged cleanly.

Subclasses may override to implement custom deep-merge logic, but should
call `super.mergeConfigsFrom(others)` to inherit the conflict-detection.

 Plugin infrastructure (used by `PluginManager.attachAll`).

```ts
mergeConfigsFrom(others: readonly BaseGridPlugin<TConfig>[]): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `others` | <code>readonly <a href="/grid/api/plugin-development/classes/basegridplugin/">BaseGridPlugin</a>&lt;TConfig&gt;[]</code> |  |

***

### refreshUserConfigFrom()

Replace this plugin's `userConfig` with a freshly-resolved sibling
instance's config. Used by the grid's per-grid feature-instance gate
(`#initializePlugins`): when a feature is re-resolved across a config
change, the **existing** instance is reused — so its accumulated state
survives — but it must adopt the latest `gridConfig.features.<name>`
value. Unlike mergeConfigsFrom, this is last-write-wins with no
conflict detection: the re-resolved instance is authoritative.

`attach()` recomputes config from `userConfig`, so updating
`userConfig` here is enough for the reused instance to pick up the new
config on its next attach during the same `#initializePlugins` pass.

 Plugin infrastructure (used by the grid feature gate).

```ts
refreshUserConfigFrom(other: BaseGridPlugin<TConfig>): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `other` | <code><a href="/grid/api/plugin-development/classes/basegridplugin/">BaseGridPlugin</a>&lt;TConfig&gt;</code> |  |

***

### attach()

Called when the plugin is attached to a grid.
Override to set up event listeners, initialize state, etc.

```ts
attach(grid: GridElement): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `grid` | <code>GridElement</code> |  |

#### Example

```ts
attach(grid: GridElement): void {
  super.attach(grid);
  // Set up document-level listeners with auto-cleanup
  document.addEventListener('keydown', this.handleEscape, {
    signal: this.disconnectSignal
  });
}
```

***

### detach()

Called when the plugin is detached from a grid.
Override to clean up event listeners, timers, etc.

```ts
detach(): void
```

#### Example

```ts
detach(): void {
  // Clean up any state not handled by disconnectSignal
  this.selectedRows.clear();
  this.cache = null;
}
```

***

### onPluginAttached()

Called when another plugin is attached to the same grid.
Use for inter-plugin coordination, e.g., to integrate with new plugins.

```ts
onPluginAttached(name: string, plugin: BaseGridPlugin): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `name` | <code>string</code> | The name of the plugin that was attached |
| `plugin` | <code><a href="/grid/api/plugin-development/classes/basegridplugin/">BaseGridPlugin</a></code> | The plugin instance (for direct access if needed) |

#### Example

```ts
onPluginAttached(name: string, plugin: BaseGridPlugin): void {
  if (name === 'selection') {
    // Integrate with selection plugin
    this.selectionPlugin = plugin as SelectionPlugin;
  }
}
```

***

### onPluginDetached()

Called when another plugin is detached from the same grid.
Use to clean up inter-plugin references.

```ts
onPluginDetached(name: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `name` | <code>string</code> | The name of the plugin that was detached |

#### Example

```ts
onPluginDetached(name: string): void {
  if (name === 'selection') {
    this.selectionPlugin = undefined;
  }
}
```

***

### getPlugin()

Get another plugin instance from the same grid.
Use for inter-plugin communication.

**Prefer grid.getPluginByName()** when you don't need the class import.

```ts
getPlugin(PluginClass: (args: any[]) => T): T | undefined
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `PluginClass` | <code>(args: any[]) =&gt; T</code> |  |

#### Example

```ts
// Preferred: by name
const selection = this.grid?.getPluginByName('selection');

// Alternative: by class
const selection = this.getPlugin(SelectionPlugin);
if (selection) {
  const selectedRows = selection.getSelectedRows();
}
```

***

### emit()

Emit a custom event from the grid.

```ts
emit(eventName: string, detail: T): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `eventName` | <code>string</code> |  |
| `detail` | <code>T</code> |  |

***

### emitCancelable()

Emit a cancelable custom event from the grid.

```ts
emitCancelable(eventName: string, detail: T): boolean
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `eventName` | <code>string</code> |  |
| `detail` | <code>T</code> |  |

#### Returns

`boolean` - `true` if the event was cancelled (preventDefault called), `false` otherwise

***

### on()

Subscribe to an event from another plugin.
The subscription is automatically cleaned up when this plugin is detached.

```ts
on(eventType: string, callback: (detail: T) => void): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `eventType` | <code>string</code> | The event type to listen for (e.g., 'filter-change') |
| `callback` | <code>(detail: T) =&gt; void</code> | The callback to invoke when the event is emitted |

#### Example

```typescript
// In attach() or other initialization
this.on('filter-change', (detail) => {
  console.log('Filter changed:', detail);
});
```

***

### off()

Unsubscribe from a plugin event.

```ts
off(eventType: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `eventType` | <code>string</code> | The event type to stop listening for |

#### Example

```typescript
this.off('filter-change');
```

***

### emitPluginEvent()

Emit an event to other plugins via the Event Bus.
This is for inter-plugin communication only; it does NOT dispatch DOM events.
Use `emit()` to dispatch DOM events that external consumers can listen to.

```ts
emitPluginEvent(eventType: string, detail: T): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `eventType` | <code>string</code> | The event type to emit (should be declared in manifest.events) |
| `detail` | <code>T</code> | The event payload |

#### Example

```typescript
// Emit to other plugins (not DOM)
this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });

// For DOM events that consumers can addEventListener to:
this.emit('filter-change', { field: 'name', value: 'Alice' });
```

***

### broadcast()

Emit an event to **both** the plugin Event Bus (for inter-plugin communication)
**and** the DOM (for external consumers via `addEventListener`).

Use this when a state change is relevant to both other plugins and external
consumers. For example, `sort-change` needs to invalidate Selection (plugin bus)
and notify the host application (DOM event).

```ts
broadcast(eventType: string, detail: T): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `eventType` | <code>string</code> | The event type to broadcast |
| `detail` | <code>T</code> | The event payload |

#### Example

```typescript
// Notify both plugins and consumers of a sort change
this.broadcast('sort-change', { sortModel: [...this.sortModel] });
```

***

### requestRender()

Request a re-render of the grid.
Uses ROWS phase - does NOT trigger processColumns hooks.

```ts
requestRender(): void
```

***

### requestColumnsRender()

Request a columns re-render of the grid.
Uses COLUMNS phase - triggers processColumns hooks.
Use this when your plugin needs to reprocess column configuration.

```ts
requestColumnsRender(): void
```

***

### requestRenderWithFocus()

Request a re-render and restore focus styling afterward.
Use this when a plugin action (like expand/collapse) triggers a render
but needs to maintain keyboard navigation focus.

```ts
requestRenderWithFocus(): void
```

***

### requestAfterRender()

Request a lightweight style update without rebuilding DOM.
Use this instead of requestRender() when only CSS classes need updating.

```ts
requestAfterRender(): void
```

***

### requestVirtualRefresh()

Re-render visible rows without rebuilding the row model or recalculating geometry.
Use this when row data has been updated in-place (e.g., server-side block loads)
and only the visible viewport needs to re-render.

```ts
requestVirtualRefresh(): void
```

***

### setIcon()

Set an icon on an element using the CSS-first hybrid approach.

Sets `data-icon` for CSS pseudo-element rendering and `data-expanded` for
expand/collapse toggle elements. Only injects DOM content when a JS override
is present (via `gridConfig.icons` or `pluginOverride`), which naturally
suppresses the CSS `::before` via the `:empty` selector.

```ts
setIcon(element: HTMLElement, iconKey: keyof GridIcons, pluginOverride: IconValue): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `element` | <code>HTMLElement</code> | The element to set the icon on |
| `iconKey` | <code>keyof <a href="/grid/api/core/interfaces/gridicons/">GridIcons</a></code> | The icon key in GridIcons (e.g., 'expand', 'collapse') |
| `pluginOverride` | <code><a href="/grid/api/core/types/iconvalue/">IconValue</a></code> | Optional plugin-level override (string or HTMLElement) |

***

### updateSortIndicator()

Create or replace a sort indicator on a header cell.

Handles the full lifecycle: removes any existing indicator, creates a new
`<span part="sort-indicator" class="sort-indicator">` with the appropriate
icon, sets `aria-sort` / `data-sort` attributes, and inserts it before the
filter button or resize handle.

Plugins decide **which** cells and **when** — this method handles **how**.

```ts
updateSortIndicator(cell: Element, direction: "desc" | "asc" | null): HTMLElement
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `cell` | <code>Element</code> | The header cell element |
| `direction` | <code>desc &#124; asc &#124; unknown</code> | `'asc'` | `'desc'` for sorted, `null` for unsorted idle state |

#### Returns

`HTMLElement` - The created indicator element (useful for appending badges, etc.)

***

### warn()

Log a warning with an optional diagnostic code.

When a diagnostic code is provided, the message is formatted with the code
and a link to the troubleshooting docs.

```ts
warn(message: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `message` | <code>string</code> |  |

#### Example

```ts
this.warn('Something went wrong');                          // plain
this.warn(MISSING_BREAKPOINT, 'Set a breakpoint'); // with code + docs link
```

***

### throwDiagnostic()

Throw an error with a diagnostic code and docs link.
Use for configuration errors and API misuse that should halt execution.

```ts
throwDiagnostic(code: DiagnosticCode, message: string): never
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `code` | <code>DiagnosticCode</code> |  |
| `message` | <code>string</code> |  |

***

### processRows()

Transform rows before rendering.
Called during each render cycle before rows are rendered to the DOM.
Use this to filter, sort, or add computed properties to rows.

```ts
processRows(rows: readonly any[]): any[]
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rows` | <code>readonly any[]</code> | The current rows array (readonly to encourage returning a new array) |

#### Returns

`any[]` - The modified rows array to render

#### Example

```ts
processRows(rows: readonly any[]): any[] {
  // Filter out hidden rows
  return rows.filter(row => !row._hidden);
}
```

***

### processConfig()

Contribute to the grid's effective configuration during each config merge.
Called by ConfigManager.merge after the base config is assembled,
before it becomes `effectiveConfig`. Mutate `config` in place to inject
plugin-derived settings (e.g. the shell plugin folds its light-DOM /
API-registered state into `config.shell`).

This is the config-pipeline counterpart to processColumns /
processRows: it keeps the core config merge free of any plugin
knowledge — each plugin owns the shape it contributes.

```ts
processConfig(config: GridConfig): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `config` | <code><a href="/grid/api/core/interfaces/gridconfig/">GridConfig</a></code> | The mutable base config being assembled into effectiveConfig. |

#### Example

```ts
processConfig(config: GridConfig): void {
  config.shell = { ...config.shell, header: { title: this.#title } };
}
```

***

### processColumns()

Transform columns before rendering.
Called during each render cycle before column headers and cells are rendered.
Use this to add, remove, or modify column definitions.

```ts
processColumns(columns: readonly ColumnConfig<any>[]): ColumnConfig<any>[]
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `columns` | <code>readonly <a href="/grid/api/core/interfaces/columnconfig/">ColumnConfig</a>&lt;any&gt;[]</code> | The current columns array (readonly to encourage returning a new array) |

#### Returns

`ColumnConfig<any>[]` - The modified columns array to render

#### Example

```ts
processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {
  // Add a selection checkbox column
  return [
    { field: '_select', header: '', width: 40 },
    ...columns
  ];
}
```

***

### beforeRender()

Called before each render cycle begins.
Use this to prepare state or cache values needed during rendering.

**Note:** This hook is currently a placeholder for future implementation.
It is defined in the interface but not yet invoked by the grid's render pipeline.
If you need this functionality, please open an issue or contribute an implementation.

```ts
beforeRender(): void
```

#### Example

```ts
beforeRender(): void {
  this.visibleRowCount = this.calculateVisibleRows();
}
```

***

### afterRender()

Called after each render cycle completes.
Use this for DOM manipulation, adding event listeners to rendered elements,
or applying visual effects like selection highlights.

```ts
afterRender(): void
```

#### Example

```ts
afterRender(): void {
  // Apply selection styling to rendered rows
  const rows = this.gridElement?.querySelectorAll('.data-grid-row');
  rows?.forEach((row, i) => {
    row.classList.toggle('selected', this.selectedRows.has(i));
  });
}
```

***

### afterStructuralRender()

Called synchronously immediately after the grid (re)builds its root DOM
structure, before the browser has a chance to paint.

Unlike afterRender (which runs in the scheduler's STYLE phase and
may be deferred), this hook fires inside the same task as the structural
DOM build. It is the correct place for plugins that need to wrap, relocate,
or augment the freshly built grid DOM without a flash of unstyled/unwrapped
content — e.g. a shell that moves the grid content into a chrome wrapper.

The grid emits this hook every time it performs a full structural rebuild
(initial connect and structural changes), but NOT on the scroll/data hot
path. Implementations MUST be idempotent: a full rebuild discards any DOM a
previous invocation produced, so re-create it, and no-op when the desired
structure is already present.

```ts
afterStructuralRender(): void
```

#### Example

```ts
afterStructuralRender(): void {
  const root = this.grid?._renderRoot;
  if (!root || root.querySelector('.my-wrapper > .tbw-grid-content')) return;
  // build wrapper, move .tbw-grid-content inside it
}
```

***

### afterCellRender()

Called after each cell is rendered.
This hook is more efficient than `afterRender()` for cell-level modifications
because you receive the cell context directly - no DOM queries needed.

Use cases:
- Adding selection/highlight classes to specific cells
- Injecting badges or decorations
- Applying conditional styling based on cell value

Performance note: Called for every visible cell during render. Keep implementation fast.
This hook is also called during scroll when cells are recycled.

```ts
afterCellRender(context: AfterCellRenderContext): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `context` | <code><a href="/grid/api/plugin-development/interfaces/aftercellrendercontext/">AfterCellRenderContext</a></code> | The cell render context with row, column, value, and elements |

#### Example

```ts
afterCellRender(context: AfterCellRenderContext): void {
  // Add selection class without DOM queries
  if (this.isSelected(context.rowIndex, context.colIndex)) {
    context.cellElement.classList.add('selected');
  }

  // Add validation error styling
  if (this.hasError(context.row, context.column.field)) {
    context.cellElement.classList.add('has-error');
  }
}
```

***

### afterRowRender()

Called after a row is fully rendered (all cells complete).
Use this for row-level decorations, styling, or ARIA attributes.

Common use cases:
- Adding selection classes to entire rows (row-focus, selected)
- Setting row-level ARIA attributes
- Applying row validation highlighting
- Tree indentation styling

Performance note: Called for every visible row during render. Keep implementation fast.
This hook is also called during scroll when rows are recycled.

```ts
afterRowRender(context: AfterRowRenderContext): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `context` | <code><a href="/grid/api/plugin-development/interfaces/afterrowrendercontext/">AfterRowRenderContext</a></code> | The row render context with row data and element |

#### Example

```ts
afterRowRender(context: AfterRowRenderContext): void {
  // Add row selection class without DOM queries
  if (this.isRowSelected(context.rowIndex)) {
    context.rowElement.classList.add('selected', 'row-focus');
  }

  // Add validation error styling
  if (this.rowHasErrors(context.row)) {
    context.rowElement.classList.add('has-errors');
  }
}
```

***

### onScrollRender()

Called after scroll-triggered row rendering completes.
This is a lightweight hook for applying visual state to recycled DOM elements.
Use this instead of afterRender when you need to reapply styling during scroll.

Performance note: This is called frequently during scroll. Keep implementation fast.

```ts
onScrollRender(): void
```

#### Example

```ts
onScrollRender(): void {
  // Reapply selection state to visible cells
  this.applySelectionToVisibleCells();
}
```

***

### getRowHeight()

Get the height of a specific row.
Used for synthetic rows (group headers, detail panels, etc.) that have fixed heights.
Return undefined if this plugin does not manage the height for this row.

This hook is called during position cache rebuilds for variable row height virtualization.
Plugins that create synthetic rows should implement this to provide accurate heights.

```ts
getRowHeight(row: unknown, index: number): number | undefined
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `row` | <code>unknown</code> | The row data |
| `index` | <code>number</code> | The row index in the processed rows array |

#### Returns

`number | undefined` - The row height in pixels, or undefined if not managed by this plugin

***

### adjustVirtualStart()

Adjust the virtualization start index to render additional rows before the visible range.
Use this when expanded content (like detail rows) needs its parent row to remain rendered
even when the parent row itself has scrolled above the viewport.

```ts
adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `start` | <code>number</code> | The calculated start row index |
| `scrollTop` | <code>number</code> | The current scroll position |
| `rowHeight` | <code>number</code> | The height of a single row |

#### Returns

`number` - The adjusted start index (lower than or equal to original start)

#### Example

```ts
adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {
  // If row 5 is expanded and scrolled partially, keep it rendered
  for (const expandedRowIndex of this.expandedRowIndices) {
    const expandedRowTop = expandedRowIndex * rowHeight;
    const expandedRowBottom = expandedRowTop + rowHeight + this.detailHeight;
    if (expandedRowBottom > scrollTop && expandedRowIndex < start) {
      return expandedRowIndex;
    }
  }
  return start;
}
```

***

### renderRow()

Render a custom row, bypassing the default row rendering.
Use this for special row types like group headers, detail rows, or footers.

```ts
renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `row` | <code>any</code> | The row data object |
| `rowEl` | <code>HTMLElement</code> | The row DOM element to render into |
| `rowIndex` | <code>number</code> | The index of the row in the data array |

#### Returns

`boolean | void` - `true` if the plugin handled rendering (prevents default), `false`/`void` for default rendering

#### Example

```ts
renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void {
  if (row._isGroupHeader) {
    rowEl.innerHTML = `<div class="group-header">${row._groupLabel}</div>`;
    return true; // Handled - skip default rendering
  }
  // Return void to let default rendering proceed
}
```

***

### handleQuery()

Handle queries from other plugins or the grid.

Queries are declared in `manifest.queries` and dispatched via `grid.query()`.
This enables type-safe, discoverable inter-plugin communication.

```ts
handleQuery(query: PluginQuery): unknown
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `query` | <code><a href="/grid/api/plugin-development/interfaces/pluginquery/">PluginQuery</a></code> | The query object with type and context |

#### Returns

`unknown` - Query-specific response, or undefined if not handling this query

#### Example

```ts
// In manifest
static override readonly manifest: PluginManifest = {
  queries: [
    { type: 'canMoveColumn', description: 'Check if a column can be moved' },
  ],
};

// In plugin class
handleQuery(query: PluginQuery): unknown {
  if (query.type === 'canMoveColumn') {
    const column = query.context as ColumnConfig;
    return !column.sticky; // Can't move sticky columns
  }
}
```

***

### onKeyDown()

Handle keyboard events on the grid.
Called when a key is pressed while the grid or a cell has focus.

```ts
onKeyDown(event: KeyboardEvent): boolean | void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code>KeyboardEvent</code> | The native KeyboardEvent |

#### Returns

`boolean | void` - `true` to prevent default behavior and stop propagation, `false`/`void` to allow default

#### Example

```ts
onKeyDown(event: KeyboardEvent): boolean | void {
  // Handle Ctrl+A for select all
  if (event.ctrlKey && event.key === 'a') {
    this.selectAllRows();
    return true; // Prevent default browser select-all
  }
}
```

***

### onCellClick()

Handle cell click events.
Called when a data cell is clicked (not headers).

```ts
onCellClick(event: CellClickEvent): boolean | void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code>CellClickEvent</code> | Cell click event with row/column context |

#### Returns

`boolean | void` - `true` to prevent default behavior and stop propagation, `false`/`void` to allow default

#### Example

```ts
onCellClick(event: CellClickEvent): boolean | void {
  if (event.field === '_select') {
    this.toggleRowSelection(event.rowIndex);
    return true; // Handled
  }
}
```

***

### onRowClick()

Handle row click events.
Called when any part of a data row is clicked.
Note: This is called in addition to onCellClick, not instead of.

```ts
onRowClick(event: RowClickEvent): boolean | void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code>RowClickEvent</code> | Row click event with row context |

#### Returns

`boolean | void` - `true` to prevent default behavior and stop propagation, `false`/`void` to allow default

#### Example

```ts
onRowClick(event: RowClickEvent): boolean | void {
  if (this.config.mode === 'row') {
    this.selectRow(event.rowIndex, event.originalEvent);
    return true;
  }
}
```

***

### onHeaderClick()

Handle header click events.
Called when a column header is clicked. Commonly used for sorting.

```ts
onHeaderClick(event: HeaderClickEvent): boolean | void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code>HeaderClickEvent</code> | Header click event with column context |

#### Returns

`boolean | void` - `true` to prevent default behavior and stop propagation, `false`/`void` to allow default

#### Example

```ts
onHeaderClick(event: HeaderClickEvent): boolean | void {
  if (event.column.sortable !== false) {
    this.toggleSort(event.field);
    return true;
  }
}
```

***

### onScroll()

Handle scroll events on the grid viewport.
Called during scrolling. Note: This may be called frequently; debounce if needed.

```ts
onScroll(event: ScrollEvent): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code>ScrollEvent</code> | Scroll event with scroll position and viewport dimensions |

#### Example

```ts
onScroll(event: ScrollEvent): void {
  // Update sticky column positions
  this.updateStickyPositions(event.scrollLeft);
}
```

***

### onCellMouseDown()

Handle cell mousedown events.
Used for initiating drag operations like range selection or column resize.

```ts
onCellMouseDown(event: CellMouseEvent): boolean | void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code><a href="/grid/api/plugin-development/interfaces/cellmouseevent/">CellMouseEvent</a></code> | Mouse event with cell context |

#### Returns

`boolean | void` - `true` to indicate drag started (prevents text selection), `false`/`void` otherwise

#### Example

```ts
onCellMouseDown(event: CellMouseEvent): boolean | void {
  if (event.rowIndex !== undefined && this.config.mode === 'range') {
    this.startDragSelection(event.rowIndex, event.colIndex);
    return true; // Prevent text selection
  }
}
```

***

### onCellMouseMove()

Handle cell mousemove events during drag operations.
Only called when a drag is in progress (after mousedown returned true).

```ts
onCellMouseMove(event: CellMouseEvent): boolean | void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code><a href="/grid/api/plugin-development/interfaces/cellmouseevent/">CellMouseEvent</a></code> | Mouse event with current cell context |

#### Returns

`boolean | void` - `true` to continue handling the drag, `false`/`void` otherwise

#### Example

```ts
onCellMouseMove(event: CellMouseEvent): boolean | void {
  if (this.isDragging && event.rowIndex !== undefined) {
    this.extendSelection(event.rowIndex, event.colIndex);
    return true;
  }
}
```

***

### onCellMouseUp()

Handle cell mouseup events to end drag operations.

```ts
onCellMouseUp(event: CellMouseEvent): boolean | void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code><a href="/grid/api/plugin-development/interfaces/cellmouseevent/">CellMouseEvent</a></code> | Mouse event with final cell context |

#### Returns

`boolean | void` - `true` if drag was finalized, `false`/`void` otherwise

#### Example

```ts
onCellMouseUp(event: CellMouseEvent): boolean | void {
  if (this.isDragging) {
    this.finalizeDragSelection();
    this.isDragging = false;
    return true;
  }
}
```

***

### getColumnState()

Contribute plugin-specific state for a column.
Called by the grid when collecting column state for serialization.
Plugins can add their own properties to the column state.

```ts
getColumnState(field: string): Partial<ColumnState> | undefined
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `field` | <code>string</code> | The field name of the column |

#### Returns

`Partial<ColumnState> | undefined` - Partial column state with plugin-specific properties, or undefined if no state to contribute

#### Example

```ts
getColumnState(field: string): Partial<ColumnState> | undefined {
  const filterModel = this.filterModels.get(field);
  if (filterModel) {
    // Uses module augmentation to add filter property to ColumnState
    return { filter: filterModel } as Partial<ColumnState>;
  }
  return undefined;
}
```

***

### applyColumnState()

Apply plugin-specific state to a column.
Called by the grid when restoring column state from serialized data.
Plugins should restore their internal state based on the provided state.

```ts
applyColumnState(field: string, state: ColumnState): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `field` | <code>string</code> | The field name of the column |
| `state` | <code><a href="/grid/api/core/interfaces/columnstate/">ColumnState</a></code> | The column state to apply (may contain plugin-specific properties) |

#### Example

```ts
applyColumnState(field: string, state: ColumnState): void {
  // Check for filter property added via module augmentation
  const filter = (state as any).filter;
  if (filter) {
    this.filterModels.set(field, filter);
    this.applyFilter();
  }
}
```

***

### getHorizontalScrollOffsets()

Report horizontal scroll boundary offsets for this plugin.
Plugins that obscure part of the scroll area (e.g., pinned/sticky columns)
should return how much space they occupy on each side.
The keyboard navigation uses this to ensure focused cells are fully visible.

```ts
getHorizontalScrollOffsets(rowEl: HTMLElement, focusedCell: HTMLElement): object | undefined
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowEl` | <code>HTMLElement</code> | The row element (optional, for calculating widths from rendered cells) |
| `focusedCell` | <code>HTMLElement</code> | The currently focused cell element (optional, to determine if scrolling should be skipped) |

#### Returns

`object | undefined` - Object with left/right pixel offsets and optional skipScroll flag, or undefined if plugin has no offsets

#### Example

```ts
getHorizontalScrollOffsets(rowEl?: HTMLElement, focusedCell?: HTMLElement): { left: number; right: number; skipScroll?: boolean } | undefined {
  // Calculate total width of left-pinned columns
  const leftCells = rowEl?.querySelectorAll('.sticky-left') ?? [];
  let left = 0;
  leftCells.forEach(el => { left += (el as HTMLElement).offsetWidth; });
  // Skip scroll if focused cell is pinned (always visible)
  const skipScroll = focusedCell?.classList.contains('sticky-left');
  return { left, right: 0, skipScroll };
}
```

***

### getToolPanel()

Register a tool panel for this plugin.
Return undefined if plugin has no tool panel.
The shell will create a toolbar toggle button and render the panel content
when the user opens the panel.

```ts
getToolPanel(): ToolPanelDefinition | undefined
```

#### Returns

`ToolPanelDefinition | undefined` - Tool panel definition, or undefined if plugin has no panel

#### Example

```ts
getToolPanel(): ToolPanelDefinition | undefined {
  return {
    id: 'columns',
    title: 'Columns',
    icon: '☰',
    tooltip: 'Show/hide columns',
    order: 10,
    render: (container) => {
      this.renderColumnList(container);
      return () => this.cleanup();
    },
  };
}
```

***

### getHeaderContent()

Register content for the shell header center section.
Return undefined if plugin has no header content.
Examples: search input, selection summary, status indicators.

```ts
getHeaderContent(): HeaderContentDefinition | undefined
```

#### Returns

`HeaderContentDefinition | undefined` - Header content definition, or undefined if plugin has no header content

#### Example

```ts
getHeaderContent(): HeaderContentDefinition | undefined {
  return {
    id: 'quick-filter',
    order: 10,
    render: (container) => {
      const input = document.createElement('input');
      input.type = 'text';
      input.placeholder = 'Search...';
      input.addEventListener('input', this.handleInput);
      container.appendChild(input);
      return () => input.removeEventListener('input', this.handleInput);
    },
  };
}
```

***
