# DataGridElement

> _Since v0.1.1_

High-performance data grid web component (`<tbw-grid>`).

## Instantiation

**Do not call the constructor directly.** Use one of these approaches:

```typescript
// Recommended: Use createGrid() for TypeScript type safety
import { createGrid, SelectionPlugin } from '@toolbox-web/grid/all';

const grid = createGrid<Employee>({
  columns: [
    { field: 'name', header: 'Name' },
    { field: 'email', header: 'Email' }
  ],
  plugins: [new SelectionPlugin()]
});
grid.rows = employees;
document.body.appendChild(grid);

// Alternative: Query existing element from DOM
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid<Employee>('#my-grid');

// Alternative: Use document.createElement (loses type inference)
const grid = document.createElement('tbw-grid');
```

## Events

| Event | Description | Triggered By |
| ----- | ----------- | ------------ |
| `sort-change` | Column header clicked to change sort order | *(internal)* |
| `column-resize` | Column resized by dragging | *(internal)* |
| `activate-cell` | Cell activated (Enter key pressed) | *(internal)* |
| `mount-external-view` | External view renderer needed (framework adapters) | *(internal)* |
| `mount-external-editor` | External editor renderer needed (framework adapters) | *(internal)* |
| `cell-change` | For each field that changed on each row | `updateRow()`, `updateRows()` |
| `column-state-change` | Emitted after state is applied (if grid is initialized) | `setColumnVisible()`, `toggleColumnVisibility()`, `showAllColumns()`, `setColumnOrder()`, `applyColumnState()`, `resetColumnState()` |

## Properties

| Property | Type | Description |
| -------- | ---- | ----------- |
| `tagName` | <code>tbw-grid</code> |  |
| `version` | <code>string</code> | Version of the grid component, injected at build time from package.json |
| `activeTag` | <code>string</code> | Tag name this bundle should render and query for. |

## Configuration

### rows

Get or set the row data displayed in the grid.

The getter returns processed rows (after filtering, sorting, grouping by plugins).
The setter accepts new source data and triggers a re-render.

```ts
rows: T[]
```

#### Example

```typescript
// Set initial data
grid.rows = employees;

// Update with new data (triggers re-render)
grid.rows = [...employees, newEmployee];

// Read current (processed) rows
console.log(`Displaying ${grid.rows.length} rows`);
```

***

### sourceRows

Get the original unfiltered/unprocessed source rows.

Use this when you need access to all source data regardless of active
filters, sorting, or grouping applied by plugins. The `rows` property
returns processed data, while `sourceRows` returns the original input.

```ts
sourceRows: T[]
```

#### Example

```typescript
// Get total count including filtered-out rows
console.log(`${grid.rows.length} of ${grid.sourceRows.length} rows visible`);

// Export all data, not just visible
exportToCSV(grid.sourceRows);
```

***

### columns

Get or set the column configurations.

The getter returns processed columns (after plugin transformations).
The setter accepts an array of column configs or a column config map.

```ts
columns: ColumnConfig<T>[]
```

#### Example

```typescript
// Set columns as array
grid.columns = [
  { field: 'name', header: 'Name', width: 200 },
  { field: 'email', header: 'Email' },
  { field: 'role', header: 'Role', width: 120 }
];

// Set columns as map (keyed by field)
grid.columns = {
  name: { header: 'Name', width: 200 },
  email: { header: 'Email' },
  role: { header: 'Role', width: 120 }
};

// Read current columns
grid.columns.forEach(col => {
  console.log(`${col.field}: ${col.width ?? 'auto'}`);
});
```

***

### gridConfig

Get or set the full grid configuration object.

The getter returns the effective (merged) configuration.
The setter accepts a new configuration and triggers a full re-render.

```ts
gridConfig: GridConfig<T>
```

#### Example

```typescript
import { SelectionPlugin, SortingPlugin } from '@toolbox-web/grid/all';

// Set full configuration
grid.gridConfig = {
  columns: [
    { field: 'name', header: 'Name' },
    { field: 'status', header: 'Status' }
  ],
  fitMode: 'stretch',
  plugins: [
    new SelectionPlugin({ mode: 'row' }),
    new SortingPlugin()
  ]
};

// Read current configuration
console.log('Fit mode:', grid.gridConfig.fitMode);
console.log('Columns:', grid.gridConfig.columns?.length);
```

***

### fitMode

Get or set the column sizing mode.

- `'stretch'` (default): Columns stretch to fill available width
- `'fixed'`: Columns use explicit widths; horizontal scroll if needed
- `'auto'`: Columns auto-size to content on initial render

```ts
fitMode: FitMode
```

#### Example

```typescript
// Use fixed widths with horizontal scroll
grid.fitMode = 'fixed';

// Stretch columns to fill container
grid.fitMode = 'stretch';

// Auto-size columns based on content
grid.fitMode = 'auto';
```

***

### columnInference <span class="since-badge" title="Introduced in v2.17.0">v2.17.0+</span>

Get or set how automatic column inference combines with explicitly provided columns.

- `'auto'` (default): infer columns only when none are provided. Declaring a
  single column disables inference and renders only the declared column(s).
- `'merge'`: always infer the full column set from the data (data-key order),
  then overlay provided columns matched by `field`. A provided column customizes
  only its own field and keeps its data position; provided columns for fields
  absent from the data are appended as computed columns.

Can also be set via `gridConfig.columnInference` or the `column-inference` attribute.

```ts
columnInference: ColumnInferenceMode
```

#### Example

```typescript
// Render every data field, then customize just one column
grid.columnInference = 'merge';
grid.columns = [{ field: 'salary', type: 'number', header: 'Salary (USD)' }];
```

***

### getConfig()

Get a frozen snapshot of the current effective configuration.
The returned object is read-only and reflects all merged config sources.

```ts
getConfig(): Promise<Readonly<GridConfig<T>>>
```

#### Returns

`Promise<Readonly<GridConfig<T>>>` - Promise resolving to frozen configuration object

#### Example

```typescript
const config = await grid.getConfig();
console.log('Columns:', config.columns?.length);
console.log('Fit mode:', config.fitMode);
```

***

## Lifecycle

### ready()

Wait for the grid to be ready.
Resolves once the component has finished initial setup, including
column inference, plugin initialization, and first render.

```ts
ready(): Promise<void>
```

#### Returns

`Promise<void>` - Promise that resolves when the grid is ready

#### Example

```typescript
const grid = queryGrid('tbw-grid');
await grid.ready();
console.log('Grid is ready, rows:', grid.rows.length);
```

***

### forceLayout()

Force a full layout/render cycle.
Use this after programmatic changes that require re-measurement,
such as container resize or dynamic style changes.

```ts
forceLayout(): Promise<void>
```

#### Returns

`Promise<void>` - Promise that resolves when the render cycle completes

#### Example

```typescript
// After resizing the container
container.style.width = '800px';
await grid.forceLayout();
console.log('Grid re-rendered');
```

***

## Data Management

### getRowId()

Get the unique ID for a row.
Uses the configured `getRowId` function or falls back to `row.id` / `row._id`.

```ts
getRowId(row: T): string
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `row` | <code>T</code> | The row object |

#### Returns

`string` - The row's unique identifier

#### Example

```typescript
const id = grid.getRowId(row);
console.log('Row ID:', id);
```

***

### getRow()

Get a row by its ID.
O(1) lookup via internal Map.

```ts
getRow(id: string): T | undefined
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `id` | <code>string</code> | Row identifier (from getRowId) |

#### Returns

`T | undefined` - The row object, or undefined if not found

#### Example

```typescript
const row = grid.getRow('cargo-123');
if (row) {
  console.log('Found:', row.name);
}
```

***

### updateRow()

Update a row by ID.
Mutates the row in-place and emits `cell-change` for each changed field.

```ts
updateRow(id: string, changes: Partial<T>, source: UpdateSource): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `id` | <code>string</code> | Row identifier (from getRowId) |
| `changes` | <code>Partial&lt;T&gt;</code> | Partial row data to merge |
| `source` | <code><a href="/grid/api/core/types/updatesource/">UpdateSource</a></code> | Origin of update (default: 'api') |

#### Events

| Event | Description |
| ----- | ----------- |
| `cell-change` | For each field that changed |

#### Example

```typescript
// WebSocket update handler
socket.on('cargo-update', (data) => {
  grid.updateRow(data.id, { status: data.status, eta: data.eta });
});
```

***

### updateRows()

Batch update multiple rows.
More efficient than multiple `updateRow()` calls - single render cycle.

```ts
updateRows(updates: object[], source: UpdateSource): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `updates` | <code>object[]</code> | Array of \{ id, changes \} objects |
| `source` | <code><a href="/grid/api/core/types/updatesource/">UpdateSource</a></code> | Origin of updates (default: 'api') |

#### Events

| Event | Description |
| ----- | ----------- |
| `cell-change` | For each field that changed on each row |

#### Example

```typescript
// Bulk status update
grid.updateRows([
  { id: 'cargo-1', changes: { status: 'shipped' } },
  { id: 'cargo-2', changes: { status: 'shipped' } },
]);
```

***

### insertRow()

Insert a row at a specific position in the current view.

The row is spliced into the visible (processed) row array at `index` and
appended to the source data so that future pipeline runs (sort, filter,
group) include it. No plugin processing is triggered — the row stays
exactly where you place it until the next full pipeline run.

By default, an `'insert'` animation is applied. Pass `animate: false` to
skip the animation. The returned `Promise` resolves when the animation
completes (or immediately when `animate` is `false`).

```ts
insertRow(index: number, row: T, animate: boolean): Promise<void>
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `index` | <code>number</code> | Visible row index at which to insert (0-based) |
| `row` | <code>T</code> | The row data object to insert |
| `animate` | <code>boolean</code> | Whether to apply an 'insert' animation (default: `true`) |

#### Example

```typescript
// Insert with animation (default)
grid.insertRow(3, { id: nextId(), name: '', status: 'Draft' });

// Insert without animation
grid.insertRow(3, newRow, false);

// Await animation completion
await grid.insertRow(3, newRow);
```

***

### removeRow()

Remove a row at a specific position in the current view.

The row is removed from both the visible (processed) row array and the
source data. No plugin processing is triggered — remaining rows keep their
current positions until the next full pipeline run.

By default, a `'remove'` animation plays before the row is removed.
Pass `animate: false` to remove immediately. When animated, `await` the
result to ensure the row has been fully removed from data.

```ts
removeRow(index: number, animate: boolean): Promise<T | undefined>
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `index` | <code>number</code> | Visible row index to remove (0-based) |
| `animate` | <code>boolean</code> | Whether to apply a 'remove' animation first (default: `true`) |

#### Returns

`Promise<T | undefined>` - The removed row object, or `undefined` if index was out of range

#### Example

```typescript
// Remove with fade-out animation (default)
await grid.removeRow(5);

// Remove immediately, no animation
const removed = await grid.removeRow(5, false);
```

***

### applyTransaction()

Apply a batch of row mutations (add, update, remove) in a single render cycle.

This is the most efficient way to apply multiple row changes at once — ideal
for streaming data from WebSocket, SSE, or any real-time source.

Operations are applied in order: removes → updates → adds. This ensures
updates don't target rows about to be removed, and adds don't collide
with existing rows.

```ts
applyTransaction(transaction: RowTransaction<T>, animate: boolean): Promise<TransactionResult<T>>
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `transaction` | <code><a href="/grid/api/core/interfaces/rowtransaction/">RowTransaction</a>&lt;T&gt;</code> | The mutations to apply |
| `animate` | <code>boolean</code> | Whether to animate the changes (default: `true`) |

#### Returns

`Promise<TransactionResult<T>>` - Result with the actual row objects affected

#### Example

```typescript
// Apply a mixed transaction
const result = await grid.applyTransaction({
  add: [{ id: 'new-1', name: 'Alice' }],
  update: [{ id: 'emp-5', changes: { status: 'Inactive' } }],
  remove: [{ id: 'emp-3' }],
});

// Wire up a WebSocket stream
ws.onmessage = (e) => {
  const event = JSON.parse(e.data);
  grid.applyTransaction({
    [event.type]: event.type === 'update'
      ? [{ id: event.rowId, changes: event.changes }]
      : event.type === 'add'
        ? [event.row]
        : [{ id: event.rowId }],
  });
};
```

***

### applyTransactionAsync()

Apply a transaction asynchronously, batching rapid calls into a single render.

Ideal for high-frequency streaming where many small updates arrive faster
than the browser can render. All transactions queued within the same
animation frame are merged and applied together.

Animations are disabled for batched transactions to avoid visual overload.

```ts
applyTransactionAsync(transaction: RowTransaction<T>): Promise<TransactionResult<T>>
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `transaction` | <code><a href="/grid/api/core/interfaces/rowtransaction/">RowTransaction</a>&lt;T&gt;</code> | The mutations to apply |

#### Returns

`Promise<TransactionResult<T>>` - Result with the actual row objects affected (after batching)

#### Example

```typescript
// High-frequency WebSocket — updates batched into single renders
ws.onmessage = (e) => {
  const event = JSON.parse(e.data);
  grid.applyTransactionAsync({
    update: [{ id: event.id, changes: event.changes }],
  });
};
```

***

## Column Visibility

### setColumnVisible()

Show or hide a column by field name.

```ts
setColumnVisible(field: string, visible: boolean): boolean
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `field` | <code>string</code> | The field name of the column to modify |
| `visible` | <code>boolean</code> | Whether the column should be visible |

#### Returns

`boolean` - `true` if the visibility changed, `false` if unchanged

#### Events

| Event | Description |
| ----- | ----------- |
| `column-state-change` | Emitted when the visibility changes |

#### Example

```typescript
// Hide the email column
grid.setColumnVisible('email', false);

// Show it again
grid.setColumnVisible('email', true);
```

***

### toggleColumnVisibility()

Toggle a column's visibility.

```ts
toggleColumnVisibility(field: string): boolean
```

#### Parameters

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

#### Returns

`boolean` - The new visibility state (`true` = visible, `false` = hidden)

#### Events

| Event | Description |
| ----- | ----------- |
| `column-state-change` | Emitted when the visibility changes |

#### Example

```typescript
// Toggle the notes column visibility
const isNowVisible = grid.toggleColumnVisibility('notes');
console.log(`Notes column is now ${isNowVisible ? 'visible' : 'hidden'}`);
```

***

### isColumnVisible()

Check if a column is currently visible.

```ts
isColumnVisible(field: string): boolean
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `field` | <code>string</code> | The field name to check |

#### Returns

`boolean` - `true` if the column is visible, `false` if hidden

#### Example

```typescript
if (grid.isColumnVisible('email')) {
  console.log('Email column is showing');
}
```

***

### showAllColumns()

Show all columns, resetting any hidden columns to visible.

```ts
showAllColumns(): void
```

#### Events

| Event | Description |
| ----- | ----------- |
| `column-state-change` | Emitted when column visibility changes |

#### Example

```typescript
// Reset button handler
resetButton.onclick = () => grid.showAllColumns();
```

***

### getAllColumns()

Get metadata for all columns including visibility state.
Useful for building a column picker UI.

```ts
getAllColumns(): object[]
```

#### Returns

`object[]` - Array of column info objects

#### Example

```typescript
// Build a column visibility menu
const columns = grid.getAllColumns();
columns.forEach(col => {
  if (!col.utility) { // Skip utility columns like selection checkbox
    const menuItem = document.createElement('label');
    menuItem.innerHTML = `
      <input type="checkbox" ${col.visible ? 'checked' : ''}>
      ${col.header}
    `;
    menuItem.querySelector('input').onchange = () =>
      grid.toggleColumnVisibility(col.field);
    menu.appendChild(menuItem);
  }
});
```

***

## Column Order

### setColumnOrder()

Set the display order of columns.

```ts
setColumnOrder(order: string[]): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `order` | <code>string[]</code> | Array of field names in desired order |

#### Events

| Event | Description |
| ----- | ----------- |
| `column-state-change` | Emitted when column order changes |

#### Example

```typescript
// Move 'status' column to first position
const currentOrder = grid.getColumnOrder();
const newOrder = ['status', ...currentOrder.filter(f => f !== 'status')];
grid.setColumnOrder(newOrder);
```

***

### getColumnOrder()

Get the current column display order.

```ts
getColumnOrder(): string[]
```

#### Returns

`string[]` - Array of field names in display order

#### Example

```typescript
const order = grid.getColumnOrder();
console.log('Columns:', order.join(', '));
```

***

## State Persistence

### columnState

Get the current column state.
Alias for `getColumnState()` for property-style read access.

```ts
readonly columnState: GridColumnState | undefined
```

***

### getColumnState()

Get the current column state for persistence.
Returns a serializable object including column order, widths, visibility,
sort state, and any plugin-specific state.

Use this to save user preferences to localStorage or a database.

```ts
getColumnState(): GridColumnState
```

#### Returns

`GridColumnState` - Serializable column state object

#### Example

```typescript
// Save state to localStorage
const state = grid.getColumnState();
localStorage.setItem('gridState', JSON.stringify(state));

// Later, restore the state
const saved = localStorage.getItem('gridState');
if (saved) grid.applyColumnState(JSON.parse(saved));
```

***

### applyColumnState()

Apply a previously saved column state, restoring column order, widths,
visibility, sort, and any plugin-contributed state. Can be called
before or after grid initialization — pre-init calls are deferred and
applied during setup.

```ts
applyColumnState(state: GridColumnState | undefined): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `state` | <code><a href="/grid/api/core/interfaces/gridcolumnstate/">GridColumnState</a> &#124; undefined</code> |  |

#### Events

| Event | Description |
| ----- | ----------- |
| `column-state-change` | Emitted after state is applied (if grid is initialized) |

#### Example

```typescript
// Restore saved state on page load
const grid = queryGrid('tbw-grid');
const saved = localStorage.getItem('myGridState');
if (saved) grid.applyColumnState(JSON.parse(saved));
```

***

### resetColumnState()

Reset column state to initial configuration.
Clears all user modifications including order, widths, visibility, and sort.

```ts
resetColumnState(): void
```

#### Events

| Event | Description |
| ----- | ----------- |
| `column-state-change` | Emitted after state is reset |

#### Example

```typescript
// Reset button handler
resetBtn.onclick = () => {
  grid.resetColumnState();
  localStorage.removeItem('gridState');
};
```

***

## Tool Panel

### isToolPanelOpen

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.isToolPanelOpen`.

Check if the tool panel sidebar is currently open.

The tool panel is an accordion-based sidebar that contains sections
registered by plugins or via `registerToolPanel()`.

```ts
readonly isToolPanelOpen: boolean
```

#### Example

```typescript
// Conditionally show/hide a "toggle panel" button
const isPanelOpen = grid.isToolPanelOpen;
toggleButton.textContent = isPanelOpen ? 'Close Panel' : 'Open Panel';
```

***

### expandedToolPanelSections

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.expandedToolPanelSections`.

Get the IDs of currently expanded accordion sections in the tool panel.

Multiple sections can be expanded simultaneously in the accordion view.

```ts
readonly expandedToolPanelSections: string[]
```

#### Example

```typescript
// Check which sections are expanded
const expanded = grid.expandedToolPanelSections;
console.log('Expanded sections:', expanded);
// e.g., ['columnVisibility', 'filtering']
```

***

### openToolPanel()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.openToolPanel()`.

Open the tool panel sidebar.

The tool panel displays an accordion view with all registered panel sections.
Each section can be expanded/collapsed independently.

```ts
openToolPanel(panelId: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `panelId` | <code>string</code> | Optional ID of the section to expand on open. When provided,
  takes precedence over `shell.toolPanel.defaultOpen`. If the panel is already
  open with a different section expanded, switches to the requested section.
  If the panel ID is not registered, a warning is emitted and the call falls
  back to default behavior (auto-expand `defaultOpen` or first registered panel). |

#### Example

```typescript
// Open the tool panel with the default / first section expanded.
settingsButton.addEventListener('click', () => {
  grid.openToolPanel();
});

// Open the tool panel and jump straight to a specific section.
filtersButton.addEventListener('click', () => {
  grid.openToolPanel('filters');
});
```

***

### closeToolPanel()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.closeToolPanel()`.

Close the tool panel sidebar.

```ts
closeToolPanel(): void
```

#### Example

```typescript
// Close the panel after user makes a selection
grid.closeToolPanel();
```

***

### toggleToolPanel()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.toggleToolPanel()`.

Toggle the tool panel sidebar open or closed.

```ts
toggleToolPanel(): void
```

#### Example

```typescript
// Wire up a toggle button
toggleButton.addEventListener('click', () => {
  grid.toggleToolPanel();
});
```

***

### toggleToolPanelSection()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.toggleToolPanelSection()`.

Toggle an accordion section expanded or collapsed within the tool panel.

```ts
toggleToolPanelSection(sectionId: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `sectionId` | <code>string</code> | The ID of the section to toggle (matches `ToolPanelDefinition.id`) |

#### Example

```typescript
// Expand the column visibility section programmatically
grid.openToolPanel();
grid.toggleToolPanelSection('columnVisibility');
```

***

### getToolPanels()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.getToolPanels()`.

Get all registered tool panel definitions.

Returns both plugin-registered panels and panels registered via `registerToolPanel()`.

```ts
getToolPanels(): ToolPanelDefinition[]
```

#### Returns

`ToolPanelDefinition[]` - Array of tool panel definitions

#### Example

```typescript
// List all available panels
const panels = grid.getToolPanels();
panels.forEach(panel => {
  console.log(`Panel: ${panel.title} (${panel.id})`);
});
```

***

### registerToolPanel()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.registerToolPanel()`.

Register a custom tool panel section.

Use this API to add custom UI sections to the tool panel sidebar
without creating a full plugin. The panel will appear as an accordion
section in the tool panel.

```ts
registerToolPanel(panel: ToolPanelDefinition): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `panel` | <code><a href="/grid/plugins/shell/interfaces/toolpaneldefinition/">ToolPanelDefinition</a></code> | The tool panel definition |

#### Example

```typescript
// Register a custom "Export" panel
grid.registerToolPanel({
  id: 'export',
  title: 'Export Options',
  icon: '📥',
  order: 50, // Lower order = higher in list
  render: (container) => {
    container.innerHTML = `
      <button id="export-csv">Export CSV</button>
      <button id="export-json">Export JSON</button>
    `;
    container.querySelector('#export-csv')?.addEventListener('click', () => {
      exportToCSV(grid.rows);
    });
  }
});
```

***

### unregisterToolPanel()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.unregisterToolPanel()`.

Unregister a custom tool panel section.

```ts
unregisterToolPanel(panelId: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `panelId` | <code>string</code> | The ID of the panel to remove |

#### Example

```typescript
// Remove the export panel when no longer needed
grid.unregisterToolPanel('export');
```

***

## Header Content

### getHeaderContents()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.getHeaderContents()`.

Get all registered header content definitions.

```ts
getHeaderContents(): HeaderContentDefinition[]
```

#### Returns

`HeaderContentDefinition[]` - Array of header content definitions

#### Example

```typescript
const contents = grid.getHeaderContents();
console.log('Header sections:', contents.map(c => c.id));
```

***

### registerHeaderContent()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.registerHeaderContent()`.

Register custom header content.

Header content appears in the grid's header bar area, which is displayed
above the column headers. Use this for search boxes, filters, or other
controls that should be prominently visible.

```ts
registerHeaderContent(content: HeaderContentDefinition): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `content` | <code><a href="/grid/plugins/shell/interfaces/headercontentdefinition/">HeaderContentDefinition</a></code> | The header content definition |

#### Example

```typescript
// Add a global search box to the header
grid.registerHeaderContent({
  id: 'global-search',
  order: 10,
  render: (container) => {
    const input = document.createElement('input');
    input.type = 'search';
    input.placeholder = 'Search all columns...';
    input.addEventListener('input', (e) => {
      const term = (e.target as HTMLInputElement).value;
      filterGrid(term);
    });
    container.appendChild(input);
  }
});
```

***

### unregisterHeaderContent()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.unregisterHeaderContent()`.

Unregister custom header content.

```ts
unregisterHeaderContent(contentId: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `contentId` | <code>string</code> | The ID of the content to remove |

#### Example

```typescript
grid.unregisterHeaderContent('global-search');
```

***

## Toolbar

### getToolbarContents()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.getToolbarContents()`.

Get all registered toolbar content definitions.

```ts
getToolbarContents(): ToolbarContentDefinition[]
```

#### Returns

`ToolbarContentDefinition[]` - Array of toolbar content definitions

#### Example

```typescript
const contents = grid.getToolbarContents();
console.log('Toolbar items:', contents.map(c => c.id));
```

***

### registerToolbarContent()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.registerToolbarContent()`.

Register custom toolbar content.

Toolbar content appears in the grid's toolbar area. Use this for action
buttons, dropdowns, or other controls that should be easily accessible.
Content is rendered in order of the `order` property (lower = first).

```ts
registerToolbarContent(content: ToolbarContentDefinition): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `content` | <code><a href="/grid/plugins/shell/interfaces/toolbarcontentdefinition/">ToolbarContentDefinition</a></code> | The toolbar content definition |

#### Example

```typescript
// Add export buttons to the toolbar
grid.registerToolbarContent({
  id: 'export-buttons',
  order: 100, // Position in toolbar (lower = first)
  render: (container) => {
    const csvBtn = document.createElement('button');
    csvBtn.textContent = 'Export CSV';
    csvBtn.className = 'tbw-toolbar-btn';
    csvBtn.addEventListener('click', () => exportToCSV(grid.rows));

    const jsonBtn = document.createElement('button');
    jsonBtn.textContent = 'Export JSON';
    jsonBtn.className = 'tbw-toolbar-btn';
    jsonBtn.addEventListener('click', () => exportToJSON(grid.rows));

    container.append(csvBtn, jsonBtn);
  }
});
```

***

### unregisterToolbarContent()

> ⚠️ **Deprecated**: Removed in v3 (#370). Use `grid.getPluginByName('shell')?.unregisterToolbarContent()`.

Unregister custom toolbar content.

```ts
unregisterToolbarContent(contentId: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `contentId` | <code>string</code> | The ID of the content to remove |

#### Example

```typescript
// Remove export buttons when switching to read-only mode
grid.unregisterToolbarContent('export-buttons');
```

***

## Custom Styles

### registerStyles()

Register custom CSS styles via `document.adoptedStyleSheets`.
Use this to style custom cell renderers, editors, or detail panels.

Uses adoptedStyleSheets for efficiency - styles survive DOM rebuilds.

```ts
registerStyles(id: string, css: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `id` | <code>string</code> | Unique identifier for the style block (for removal/updates) |
| `css` | <code>string</code> | CSS string to inject |

#### Example

```typescript
// Register custom styles for a detail panel
grid.registerStyles('my-detail-styles', `
  .my-detail-panel { padding: 16px; }
  .my-detail-table { width: 100%; }
`);

// Update styles later
grid.registerStyles('my-detail-styles', updatedCss);

// Remove styles
grid.unregisterStyles('my-detail-styles');
```

***

### unregisterStyles()

Remove previously registered custom styles.

```ts
unregisterStyles(id: string): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `id` | <code>string</code> | The ID used when registering the styles |

***

### getRegisteredStyles()

Get list of registered custom style IDs.

```ts
getRegisteredStyles(): string[]
```

***

## Plugin Communication

### getPlugin()

Get a plugin instance by its class constructor.

**Prefer getPluginByName** for most use cases — it avoids importing the plugin class
and returns the actual instance registered in the grid.

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

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `PluginClass` | <code>(args: any[]) =&gt; P</code> | The plugin class (constructor) to look up. |

#### Returns

`P | undefined` - The plugin instance, or `undefined` if not registered.

#### Example

```ts
// Preferred: by name (no import needed)
const selection = grid.getPluginByName('selection');

// Alternative: by class (requires import)
import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
const selection = grid.getPlugin(SelectionPlugin);
selection?.selectAll();
```

***

### getPluginByName()

Get a plugin instance by its string name.
Useful for loose coupling when you don't want to import the plugin class
(e.g., in framework adapters or dynamic scenarios).

```ts
getPluginByName(name: K): unknown | undefined
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `name` | <code>K</code> | The plugin name (matches `BaseGridPlugin.name`). |

#### Returns

`unknown | undefined` - The plugin instance, or `undefined` if not registered.

#### Example

```ts
const editing = grid.getPluginByName('editing');
```

***

## Row Animation

### animateRow()

Animate a row at the specified index.
Applies a visual animation to highlight changes, insertions, or removals.
Returns a `Promise` that resolves when the animation completes.

**Animation types:**
- `'change'`: Flash highlight (for data changes, e.g., after cell edit)
- `'insert'`: Slide-in animation (for newly added rows)
- `'remove'`: Fade-out animation (for rows being removed)

The animation is purely visual - it does not affect the data.
For remove animations, the row remains in the DOM until animation completes.

```ts
animateRow(rowIndex: number, type: RowAnimationType): Promise<boolean>
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowIndex` | <code>number</code> | Index of the row in the current row set |
| `type` | <code><a href="/grid/api/core/types/rowanimationtype/">RowAnimationType</a></code> | Type of animation to apply |

#### Returns

`Promise<boolean>` - `true` if the row was found and animated, `false` otherwise

#### Example

```typescript
// Highlight a row and wait for the animation to finish
await grid.animateRow(rowIndex, 'change');

// Fire-and-forget (animation runs in background)
grid.animateRow(rowIndex, 'insert');
```

***

### animateRows()

Animate multiple rows at once.
More efficient than calling `animateRow()` multiple times.
Returns a `Promise` that resolves when all animations complete.

```ts
animateRows(rowIndices: number[], type: RowAnimationType): Promise<number>
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowIndices` | <code>number[]</code> | Indices of the rows to animate |
| `type` | <code><a href="/grid/api/core/types/rowanimationtype/">RowAnimationType</a></code> | Type of animation to apply to all rows |

#### Returns

`Promise<number>` - Number of rows that were actually animated (visible in viewport)

#### Example

```typescript
// Highlight all changed rows after bulk update
const changedIndices = [0, 2, 5];
await grid.animateRows(changedIndices, 'change');
```

***

### animateRowById()

Animate a row by its ID.
Uses the configured `getRowId` function to find the row.
Returns a `Promise` that resolves when the animation completes.

```ts
animateRowById(rowId: string, type: RowAnimationType): Promise<boolean>
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowId` | <code>string</code> | The row's unique identifier (from getRowId) |
| `type` | <code><a href="/grid/api/core/types/rowanimationtype/">RowAnimationType</a></code> | Type of animation to apply |

#### Returns

`Promise<boolean>` - `true` if the row was found and animated, `false` otherwise

#### Example

```typescript
// Highlight a row after real-time update
socket.on('row-updated', async (data) => {
  grid.updateRow(data.id, data.changes);
  await grid.animateRowById(data.id, 'change');
});
```

***

## Focus & Navigation

### focusedCell

The currently focused cell position, or `null` if no rows are loaded.

Returns a snapshot object with the row index, visible column index, and
the field name of the focused column.

```ts
readonly focusedCell: object | null
```

#### Example

```typescript
const cell = grid.focusedCell;
if (cell) {
  console.log(`Focused on row ${cell.rowIndex}, column "${cell.field}"`);
}
```

***

### focusCell()

Move focus to a specific cell.

Accepts a column index (into the visible columns array) or a field name.
The grid scrolls the cell into view and applies focus styling.

```ts
focusCell(rowIndex: number, column: string | number): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowIndex` | <code>number</code> | The row index to focus (0-based, in the current processed row array) |
| `column` | <code>string &#124; number</code> | Column index (0-based into visible columns) or field name string |

#### Example

```typescript
// Focus by column index
grid.focusCell(0, 2);

// Focus by field name
grid.focusCell(5, 'email');
```

***

### scrollToRow()

Scroll the viewport so a row is visible.

Uses the grid's internal virtualization state (row height, position cache)
to calculate the correct scroll offset, including support for variable
row heights and grouped rows.

```ts
scrollToRow(rowIndex: number, options: ScrollToRowOptions): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowIndex` | <code>number</code> | Row index (0-based, in the current processed row array) |
| `options` | <code><a href="/grid/api/core/interfaces/scrolltorowoptions/">ScrollToRowOptions</a></code> | Alignment and scroll behavior |

#### Example

```typescript
// Scroll to row, only if not already visible
grid.scrollToRow(42);

// Center the row in the viewport with smooth scrolling
grid.scrollToRow(42, { align: 'center', behavior: 'smooth' });
```

***

### scrollToRowById()

Scroll the viewport so a row is visible, identified by its unique ID.

Uses getRowId (or the fallback `row.id` / `row._id`)
to find the row in the current (possibly sorted/filtered) data, then delegates
to scrollToRow.

```ts
scrollToRowById(rowId: string, options: ScrollToRowOptions): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowId` | <code>string</code> | The row's unique identifier |
| `options` | <code><a href="/grid/api/core/interfaces/scrolltorowoptions/">ScrollToRowOptions</a></code> | Alignment and scroll behavior |

#### Example

```typescript
// After inserting a row, scroll to it by ID
grid.scrollToRowById('emp-42', { align: 'center' });
```

***

## Sorting

### sortModel

Get the current single-column sort state.

Returns `null` when no sort is active.

```ts
readonly sortModel: object | null
```

#### Example

```typescript
const sort = grid.sortModel;
// { field: 'name', direction: 'desc' } | null
```

***

### sort()

Sort by a column, toggle a column's sort direction, or clear sorting.

- `sort('id', 'desc')` — apply sort with explicit direction
- `sort('id')` — toggle: none → asc → desc → none
- `sort(null)` — clear sort, restore original row order

```ts
sort(field: string | null, direction: "desc" | "asc"): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `field` | <code>string &#124; unknown</code> | Column field to sort by, or `null` to clear |
| `direction` | <code>desc &#124; asc</code> | Explicit direction; omit to toggle |

#### Example

```typescript
grid.sort('id', 'desc');  // sort descending
grid.sort('price');       // toggle sort on price
grid.sort(null);          // clear sort
```

***

## Virtualization

### defaultRowHeight

The default row height in pixels.

For fixed heights, this is the configured or CSS-measured row height.
For variable heights, this is the average/estimated row height.
Plugins should use this instead of hardcoding row heights.

```ts
readonly defaultRowHeight: number
```

***

## Focus Management

### lastFocusedElement

The last meaningful element inside the grid host's light-DOM subtree
that received user focus. Excludes the grid host itself (synthetic
`tabindex=0` focus during keyboard navigation), bare `.cell` elements
(whose focus is virtual), and descendants of registered external focus
containers (overlays/datepickers/dropdowns). Returns `null` when the
user has not yet interacted with any tracked descendant.

```ts
readonly lastFocusedElement: HTMLElement | null
```

***

### registerExternalFocusContainer()

Register an external DOM element as a logical focus container of this grid.

Focus moving into a registered container is treated as if it stayed inside
the grid: `data-has-focus` is preserved, click-outside commit is suppressed,
and the editing focus trap (when enabled) won't reclaim focus.

Typical use case: overlay panels (datepickers, dropdowns, autocompletes)
that render at `<body>` level to escape grid overflow clipping.

```ts
registerExternalFocusContainer(el: Element): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `el` | <code>Element</code> | The external element to register |

#### Example

```typescript
const overlay = document.createElement('div');
document.body.appendChild(overlay);

// Tell the grid this overlay is "part of" the grid
grid.registerExternalFocusContainer(overlay);

// Later, when overlay is removed
grid.unregisterExternalFocusContainer(overlay);
```

***

### unregisterExternalFocusContainer()

Unregister a previously registered external focus container.

```ts
unregisterExternalFocusContainer(el: Element): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `el` | <code>Element</code> | The element to unregister |

***

### containsFocus()

Check whether focus is logically inside this grid.

Returns `true` when `document.activeElement` (or the given node) is
inside the grid's own DOM **or** inside any element registered via
registerExternalFocusContainer.

```ts
containsFocus(node: Node | null): boolean
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `node` | <code>Node &#124; unknown</code> | Optional node to test. Defaults to `document.activeElement`. |

#### Example

```typescript
if (grid.containsFocus()) {
  console.log('Grid or one of its overlays has focus');
}
```

***

### restoreLastFocus()

Restore focus to the last meaningful user-focused element inside the
grid (see lastFocusedElement for what counts as "meaningful").
The grid invokes this automatically whenever focus is bounced to
`<body>` (e.g. when a body-level overlay is removed). Exposed publicly
so application code can re-anchor focus on demand.

```ts
restoreLastFocus(): boolean
```

#### Returns

`boolean` - `true` if a tracked element existed and was refocused; `false` otherwise.

***

## Accessors

### loading

Whether the grid is currently in a loading state.
When true, displays a loading overlay with spinner (or custom loadingRenderer).

```ts
loading: boolean
```

#### Example

```typescript
grid.loading = true;
const data = await fetchData();
grid.rows = data;
grid.loading = false;
```

***

## Methods

### setRowLoading()

Set loading state for a specific row.
Shows a small spinner indicator on the row.

```ts
setRowLoading(rowId: string, loading: boolean): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowId` | <code>string</code> | The row's unique identifier (from getRowId) |
| `loading` | <code>boolean</code> | Whether the row is loading |

#### Example

```typescript
// Show loading while saving row data
grid.setRowLoading('row-123', true);
await saveRow(rowData);
grid.setRowLoading('row-123', false);
```

***

### setCellLoading()

Set loading state for a specific cell.
Shows a small spinner indicator on the cell.

```ts
setCellLoading(rowId: string, field: string, loading: boolean): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowId` | <code>string</code> | The row's unique identifier |
| `field` | <code>string</code> | The column field |
| `loading` | <code>boolean</code> | Whether the cell is loading |

#### Example

```typescript
// Show loading while validating a single field
grid.setCellLoading('row-123', 'email', true);
const valid = await validateEmail(newValue);
grid.setCellLoading('row-123', 'email', false);
```

***

### isRowLoading()

Check if a row is currently in loading state.

```ts
isRowLoading(rowId: string): boolean
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowId` | <code>string</code> | The row's unique identifier |

***

### isCellLoading()

Check if a cell is currently in loading state.

```ts
isCellLoading(rowId: string, field: string): boolean
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowId` | <code>string</code> | The row's unique identifier |
| `field` | <code>string</code> | The column field |

***

### clearAllLoading()

Clear all row and cell loading states.

```ts
clearAllLoading(): void
```

***

### addEventListener()

Add a typed event listener for grid events.

This override provides type-safe event handling for DataGrid-specific events.
The event detail is automatically typed based on the event name.

**Prefer grid.on()** for most use cases — it auto-unwraps the
detail payload and returns an unsubscribe function for easy cleanup.

Use `addEventListener` when you need standard DOM options like `once`,
`capture`, `passive`, or `signal` (AbortController).

```ts
addEventListener(type: K, listener: (this: DataGridElement<T>, ev: CustomEvent<DataGridEventMap<T>[K]>) => void, options: boolean | AddEventListenerOptions): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `type` | <code>K</code> |  |
| `listener` | <code>(this: <a href="/grid/api/core/classes/datagridelement/">DataGridElement</a>&lt;T&gt;, ev: CustomEvent&lt;<a href="/grid/api/core/interfaces/datagrideventmap/">DataGridEventMap</a>&lt;T&gt;[K]&gt;) =&gt; void</code> |  |
| `options` | <code>boolean &#124; AddEventListenerOptions</code> |  |

#### Example

```typescript
// Recommended: use grid.on() instead
const off = grid.on('cell-click', (detail) => {
  console.log(detail.field, detail.value);
});

// addEventListener is useful when you need DOM listener options
grid.addEventListener('cell-click', (e) => {
  console.log(e.detail.field, e.detail.value);
}, { once: true });

// Or with AbortController for batch cleanup
const controller = new AbortController();
grid.addEventListener('sort-change', (e) => {
  fetchData({ sortBy: e.detail.field });
}, { signal: controller.signal });
controller.abort(); // removes all listeners tied to this signal
```

***

### removeEventListener()

Remove a typed event listener for grid events.

```ts
removeEventListener(type: K, listener: (this: DataGridElement<T>, ev: CustomEvent<DataGridEventMap<T>[K]>) => void, options: boolean | EventListenerOptions): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `type` | <code>K</code> |  |
| `listener` | <code>(this: <a href="/grid/api/core/classes/datagridelement/">DataGridElement</a>&lt;T&gt;, ev: CustomEvent&lt;<a href="/grid/api/core/interfaces/datagrideventmap/">DataGridEventMap</a>&lt;T&gt;[K]&gt;) =&gt; void</code> |  |
| `options` | <code>boolean &#124; EventListenerOptions</code> |  |

***

### on()

Subscribe to a typed grid event. **Recommended** over `addEventListener`.

Returns an unsubscribe function — call it to remove the listener.
The listener receives the event **detail** as its first argument
(no need to dig into `e.detail`), and the raw `CustomEvent` as
the second argument when you need `preventDefault()` or `stopPropagation()`.

```ts
on(type: K, listener: (detail: DataGridEventMap<T>[K], event: CustomEvent<DataGridEventMap<T>[K]>) => void): () => void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `type` | <code>K</code> |  |
| `listener` | <code>(detail: <a href="/grid/api/core/interfaces/datagrideventmap/">DataGridEventMap</a>&lt;T&gt;[K], event: CustomEvent&lt;<a href="/grid/api/core/interfaces/datagrideventmap/">DataGridEventMap</a>&lt;T&gt;[K]&gt;) =&gt; void</code> |  |

#### Example

```typescript
// Basic usage — detail is auto-unwrapped
const off = grid.on('cell-click', ({ row, field, value }) => {
  console.log(`Clicked ${field} = ${value}`);
});

// Clean up when done
off();
```

***

### invalidateRowHeight()

Invalidate a row's height in the position cache.
Call this when a plugin changes a row's height (e.g., expanding/collapsing a detail panel).

```ts
invalidateRowHeight(rowIndex: number, newHeight: number): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `rowIndex` | <code>number</code> | Index of the row whose height changed |
| `newHeight` | <code>number</code> | Optional new height. If not provided, queries plugins for height. |

***
