# Architecture

> Internal architecture of @toolbox-web/grid — configuration system, render scheduler, virtualization, plugin lifecycle, and light DOM design.

This page describes the internal architecture of `@toolbox-web/grid`. It's intended for contributors, plugin developers, and anyone who wants to understand how the grid works under the hood.

## Design Philosophy

1. **Light DOM** — No Shadow DOM. The grid renders directly into the element, allowing full CSS customization.
2. **Single Source of Truth** — All configuration converges into `effectiveConfig`, which is the only state read by rendering logic.
3. **Plugin-First** — Features like selection, editing, and filtering are plugins, not core code. This keeps the core small and tree-shakeable.
4. **Web Standards** — Built on Custom Elements, CSS Custom Properties, `adoptedStyleSheets`, and standard DOM APIs.
5. **Framework-Agnostic** — Pure TypeScript/HTML, no runtime framework dependencies.

## Component Overview

```
┌───────────────────────────────────────────────────────┐
│                        <tbw-grid>                     │
│  ┌─────────────────────────────────────────────────┐  │
│  │                    Light DOM                    │  │
│  │  ┌───────────────────────────────────────────┐  │  │
│  │  │              Header Row                   │  │  │
│  │  └───────────────────────────────────────────┘  │  │
│  │  ┌───────────────────────────────────────────┐  │  │
│  │  │           Rows Viewport (scrollable)      │  │  │
│  │  │  ┌─────────────────────────────────────┐  │  │  │
│  │  │  │  Spacer (virtual scroll height)     │  │  │  │
│  │  │  ├─────────────────────────────────────┤  │  │  │
│  │  │  │  Visible Rows (row pool)            │  │  │  │
│  │  │  └─────────────────────────────────────┘  │  │  │
│  │  └───────────────────────────────────────────┘  │  │
│  └─────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────┘
```

## Component Lifecycle

```
constructor()
  └── Create internal state objects
  └── Initialize render scheduler

connectedCallback()
  └── Parse light DOM children (<tbw-grid-column>, <tbw-grid-header>)
  └── Merge configuration (gridConfig + columns + fitMode + light DOM)
  └── Attach plugins (validate dependencies, call onAttach)
  └── Render header + rows
  └── Set up scroll listener, resize observer

attributeChangedCallback()
  └── Map attribute → property setter

disconnectedCallback()
  └── Abort disconnect signal (plugins clean up)
  └── Remove event listeners
  └── Disconnect resize observer
```

---

## Configuration Architecture

The grid follows a **single source of truth** pattern. All configuration inputs converge into one `effectiveConfig` object, which is then used for all rendering and behavior.

### Why Single Source of Truth?

- **Predictable behavior**: One canonical config means no ambiguity about which setting applies
- **Easy debugging**: Inspect `effectiveConfig` to see exactly what the grid is using
- **Flexible input**: Users can configure via the method most convenient for their use case
- **Plugin-friendly**: Plugins can read/modify config through one consistent interface

### Input Sources

Users can configure the grid through multiple input methods:

```mermaid
flowchart TB
    subgraph inputs["INPUT SOURCES"]
        direction TB
        A["gridConfig<br/>property"]
        B["columns<br/>property"]
        C["fitMode<br/>property"]

        subgraph lightdom["LIGHT DOM"]
            D["&lt;tbw-grid-column&gt;<br/>field, header"]
            E["&lt;tbw-grid-header&gt;<br/>title"]
        end
    end

    A --> F
    B --> F
    C --> F
    D --> F
    E --> F

    F["ConfigManager.merge()<br/><i>single merge point</i>"]
    F --> G["#effectiveConfig<br/><i>canonical config</i><br/>◀ SINGLE SOURCE OF TRUTH"]
    G --> H["DERIVED STATE<br/>_columns (processed)<br/>_rows (processed)"]
```

### Precedence Rules

When the same property is set via multiple sources, higher precedence wins:

| Priority    | Source                | Example                                      |
| ----------- | --------------------- | -------------------------------------------- |
| 1 (lowest)  | `gridConfig` property | `grid.gridConfig = { fitMode: 'stretch' }`   |
| 2           | Light DOM elements    | `<tbw-grid-column field="name">`             |
| 3           | `columns` property    | `grid.columns = [{ field: 'name' }]`         |
| 4           | Inferred columns      | (auto-detected from first row)               |
| 5 (highest) | Individual props      | `grid.fitMode = 'fixed'`                     |

> **Note:** HTML attributes (`rows`, `columns`, `grid-config`, `fit-mode`) invoke the corresponding property setters via `attributeChangedCallback` — they are not a separate precedence layer.

### HTML Attribute Configuration

The grid supports JSON-serialized configuration via HTML attributes:

```html
<tbw-grid
  rows='[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]'
  columns='[{"field":"id","header":"ID"},{"field":"name","header":"Name"}]'
  fit-mode="stretch"
>
</tbw-grid>
```

Supported attributes: `rows`, `columns`, `grid-config`, `fit-mode`.

### Light DOM Configuration

The grid parses these light DOM elements on connection:

```html
<tbw-grid>
  <!-- Column definitions (→ effectiveConfig.columns) -->
  <tbw-grid-column field="name" header="Name" sortable></tbw-grid-column>
  <tbw-grid-column field="age" header="Age" type="number"></tbw-grid-column>

  <!-- Shell header (→ effectiveConfig.shell.header) -->
  <tbw-grid-header title="My Data Grid">
    <tbw-grid-header-content>
      <span>Custom content here</span>
    </tbw-grid-header-content>
    <tbw-grid-tool-buttons>
      <button class="tbw-toolbar-btn" title="Refresh">🔄</button>
    </tbw-grid-tool-buttons>
  </tbw-grid-header>
</tbw-grid>
```

### Internal State Categories

| Category | Example | Description |
|----------|---------|-------------|
| **Input Properties** | `#rows`, `#columns`, `#gridConfig`, `#fitMode` | Raw user input, stored as-is |
| **Effective Config** | `#effectiveConfig` | Merged canonical config — single source of truth |
| **Derived State** | `_columns`, `_rows` | Post-plugin-processing state used by rendering |
| **Runtime State** | `#hiddenColumns`, `sortState` | User-driven state changes (hide column, sort, etc.) |

**Key rule:** Rendering logic reads from `effectiveConfig` or derived state, never from input properties.

---

### Two-Layer Config Architecture

ConfigManager implements a two-layer architecture to separate the **original configuration** (from sources) from **runtime mutations**:

```mermaid
flowchart TB
    subgraph sources["SOURCES"]
        A["gridConfig"]
        B["columns"]
        C["fitMode"]
        D["Light DOM"]
        E["Shell State Maps"]
    end

    A --> F
    B --> F
    C --> F
    D --> F
    E --> F

    F["ConfigManager.merge()"]
    F -->|"sources changed"| G["#originalConfig<br/><i>(frozen, immutable)</i>"]
    G -->|"clone"| H["#effectiveConfig<br/><i>(mutable, runtime changes)</i>"]

    H -->|"user interaction"| I["Runtime Mutations<br/>hidden, width, sort order"]

    J["resetState()"] -->|"clone original → effective"| H
```

**Layer 1: Original Config (`#originalConfig`)**

- Built from all sources via `#collectAllSources()`
- Frozen after creation (`Object.freeze`)
- Immutable — never modified after merge
- Serves as the "reset point" for the effective config

**Layer 2: Effective Config (`#effectiveConfig`)**

- Deep cloned from original config
- Mutable — runtime changes go here
- Column visibility, widths, sort order, etc.
- All rendering reads from this layer

**Key Behaviors:**

| Operation                                 | What Happens                                               |
| ----------------------------------------- | ---------------------------------------------------------- |
| Source changes (gridConfig, columns, etc.) | `markSourcesChanged()` → `merge()` rebuilds both layers   |
| Runtime mutation (hide column, resize)     | Modify `effectiveConfig` only, `original` untouched        |
| `resetState()`                             | Clone `original` → `effective`, discarding runtime changes |
| `collectState()`                           | Diff `effective` vs `original` to get user changes         |

**When Sources Change:**

Sources are re-collected only when `#sourcesChanged` is `true` AND columns already exist:

1. Setting `gridConfig`, `columns`, `fitMode` → auto-marks sources changed
2. Setting Light DOM columns → auto-marks sources changed
3. Shell state updates → call `markSourcesChanged()` explicitly
4. `merge()` is a no-op if sources haven't changed AND columns exist
5. If no columns exist yet, `merge()` always runs (to allow inference from rows)

This optimization prevents unnecessary rebuilds when `merge()` is called multiple times per frame.

---

## Rendering Pipeline

All rendering flows through a centralized **RenderScheduler** that batches all work into a single `requestAnimationFrame` callback. This eliminates race conditions between different parts of the grid that previously scheduled independent RAFs.

### Render Scheduler Architecture

```mermaid
flowchart TB
    subgraph sources["RENDER REQUESTS"]
        A["Property Change<br/>(rows, columns, gridConfig)"]
        B["Framework Adapter<br/>(React/Angular refreshColumns)"]
        C["ResizeObserver<br/>(container resize)"]
        D["Scroll Event<br/>(virtualization)"]
        E["Plugin Request<br/>(afterRender needs)"]
    end

    A --> F["RenderScheduler.requestPhase()"]
    B --> F
    C --> F
    D --> G["Direct Call<br/>(hot path)"]
    E --> F

    F --> H["Single RAF<br/>#flush()"]
    H --> I["Phase-Ordered Execution"]

    subgraph phases["EXECUTION ORDER (data dependencies)"]
        P1["1. mergeConfig<br/>(FULL/COLUMNS phase)"]
        P2["2. processRows<br/>(ROWS phase)"]
        P3["3. processColumns + updateTemplate<br/>(COLUMNS phase)"]
        P4["4. renderHeader<br/>(HEADER phase)"]
        P5["5. refreshVirtualWindow<br/>(VIRTUALIZATION phase)"]
        P6["6. afterRender hooks<br/>(STYLE phase)"]
    end

    I --> P1 --> P2 --> P3 --> P4 --> P5 --> P6
```

### Render Phases

Work is organized into ordered phases. Multiple requests merge to the **highest requested phase**:

| Phase | Priority | Work Performed |
|-------|----------|----------------|
| `STYLE` | 1 | Plugin `afterRender()` hooks only |
| `VIRTUALIZATION` | 2 | Recalculate virtual window (+ STYLE) |
| `HEADER` | 3 | Re-render header row (+ VIRTUALIZATION) |
| `ROWS` | 4 | Rebuild row model (+ HEADER) |
| `COLUMNS` | 5 | Process columns, update CSS template (+ ROWS) |
| `FULL` | 6 | Merge effective config (+ COLUMNS) |

Higher phases implicitly cover all lower phases. Requesting `COLUMNS` when `ROWS` is already pending results in just `COLUMNS` executing.

**Example**: If React adapter requests `COLUMNS` and ResizeObserver requests `VIRTUALIZATION` in the same frame, only `COLUMNS` phase runs (which includes all lower phases).

### Execution Order in `#flush()`

```typescript
if (phase >= RenderPhase.COLUMNS) mergeConfig();
if (phase >= RenderPhase.ROWS)    processRows();
if (phase >= RenderPhase.COLUMNS) processColumns();
if (phase >= RenderPhase.COLUMNS) updateTemplate();
if (phase >= RenderPhase.HEADER)  renderHeader();
if (phase >= RenderPhase.VIRTUALIZATION) renderVirtualWindow();
if (phase >= RenderPhase.STYLE)   afterRender();
```

### Intentional Bypasses

Some operations intentionally bypass the scheduler for performance:

| Operation              | Reason                                     |
| ---------------------- | ------------------------------------------ |
| Scroll rendering       | Hot path — must be synchronous for 60fps   |
| Shell rebuild          | Creates DOM structure, not content updates |
| Row height measurement | One-time post-paint measurement            |

### Debugging Renders

The scheduler is held on a private field (`#scheduler`), so direct introspection from the console is not part of the public API. To trace render activity:

- Use the browser **Performance** tab and record around the interaction — each `requestAnimationFrame` callback shows up with the phase work (`processRows`, `processColumns`, `renderHeader`, `renderVirtualWindow`, plugin `afterRender`).
- Add `console.log` calls in your own plugin's hooks to confirm which phase fires after a given input.
- The `source` argument passed to `requestPhase()` (e.g., `'applyGridConfigUpdate'`, `'resize-observer'`, `'plugin:requestRender'`) is preserved for future debug surfaces; the strings are visible in source files when grepping for `requestPhase(`.

---

## Virtualization

### Row Virtualization (Built-in)

The grid maintains a "virtual window" — an offset and count representing which rows are visible:

```
Total rows: 10,000
Viewport: 400px, rowHeight: 28px → ~14 visible rows
Overscan: 8 rows above + 8 below = 30 DOM rows in pool

Scroll position: 5,000px → startIndex: 178
Rendered: rows 170–200 (30 rows in DOM)
```

- **Row pool**: DOM rows are reused, not created/destroyed on scroll
- **Transform-based positioning**: Each row uses `transform: translateY()` for GPU-accelerated positioning
- **Range-based updates**: Only rows entering/leaving the viewport get updated content

For very small datasets (≤8 rows by default), virtualization is bypassed — the overhead isn't worth it.

#### Massive datasets and the browser height cap

Browsers cap a single element's rendered height at roughly **33.5M px** (Chromium's `2^25`; Firefox/Safari are similar). With the default `rowHeight: 34`, that's hit at about **986,895 rows**. Above the cap, the faux-vscroll spacer would silently truncate, leaving the tail of the dataset unreachable.

To support datasets exceeding the cap (e.g. log viewers with 10M+ rows), the grid switches to **fractional scroll mapping**:

- The spacer is clamped at `MAX_ELEMENT_HEIGHT_PX` (≈ 33.5M px).
- The native `scrollTop` is treated as a position in the clamped spacer space and translated into a **virtual** offset in raw row-content space:
  ```text
  virtualScrollTop = scrollTop * (rawContentHeight − viewport) / (spacerHeight − viewport)
  ```
- `scrollToRow(N)` reverse-maps the row's offset back into the spacer's `scrollTop` so any row in the dataset is reachable programmatically.

For datasets below the cap, mapping is identity — pixel-accurate behavior is unchanged. Above the cap, every row remains reachable via the scrollbar, `Ctrl+End`, and `scrollToRow(N)`. Keyboard cell navigation (`ArrowDown`/`ArrowUp`/`Tab`/`PageDown`/`PageUp`) also stays single-row accurate — focus moves by row index and the auto-scroll that keeps the focused cell visible routes through the mapping. The remaining caveat is **direct scroll input** (mouse wheel, scrollbar drag, `Space`/`Shift+Space`): one pixel of `scrollTop` corresponds to many rows above the cap, so a single wheel notch can skip past hundreds of rows. The mapping helpers (`MAX_ELEMENT_HEIGHT_PX`, `computeScrollMapping`, `toVirtualScrollTop`, `fromVirtualScrollTop`) are exported for plugins that maintain their own scroll-driven DOM state.

### Column Virtualization (Plugin)

The `ColumnVirtualizationPlugin` applies the same window concept horizontally. Only columns visible in the horizontal scroll position are rendered.

---

## Plugin System & Feature Registry

The plugin lifecycle, hooks, manifest schema, validated properties, communication channels, and the feature registry are documented under [Plugin Development → Architecture](/grid/plugin-development/architecture.md). That page also covers tree-shaking, dependency ordering, and the lazy-hook design that keeps the feature API zero-cost when unused.

For a step-by-step tutorial on building your own plugin, see the [Authoring Guide](/grid/plugin-development/custom-plugins.md).

---

## Framework Adapter System

How React, Angular, and Vue adapters intercept cell rendering, editor creation, config processing, and cell cleanup is documented under [Framework Adapters → Architecture](/grid/framework-adapters/architecture.md).

---

## Shell System

The **shell** is an optional wrapper that adds a header bar, toolbar buttons, and a collapsible tool panel sidebar to the grid.

### Structure

```
┌──────────────────────────────────────────────────────────┐
│ SHELL HEADER                                             │
│ ┌──────────┬──────────────────┬────────────────────────┐ │
│ │  Title   │  Header Content  │  Toolbar  │ ☰ Toggle  │ │
│ └──────────┴──────────────────┴────────────────────────┘ │
├───────────┬──────────────────────────────────────────────┤
│ TOOL      │                                              │
│ PANEL     │              GRID CONTENT                    │
│ (sidebar) │                                              │
│ ┌───────┐ │   ┌─────────────────────────────────────┐    │
│ │Section│ │   │  Header Row                         │    │
│ │  ▼    │ │   ├─────────────────────────────────────┤    │
│ │Content│ │   │  Data Rows (virtualized)            │    │
│ │       │ │   │                                     │    │
│ ├───────┤ │   └─────────────────────────────────────┘    │
│ │Section│ │                                              │
│ │  ▶   │ │                                              │
│ └───────┘ │                                              │
└───────────┴──────────────────────────────────────────────┘
```

### Configuration

The shell is configured via `gridConfig.shell` or Light DOM elements:

```typescript
gridConfig: {
  shell: {
    header: { title: 'My Grid' },
    toolPanel: {
      position: 'left' | 'right',  // Sidebar position
      width: '17.5em',             // Panel width (CSS value)
      defaultOpen: 'filters',      // Section auto-expanded on first open (v2: also opens sidebar — deprecated, see #259)
      initialState: 'closed',      // 'open' | 'closed' — preferred way to control sidebar open state
      locked: false,               // true = sidebar always open, toggle hidden
      closeOnClickOutside: false,
    },
  },
}
```

Or declaratively:

```html
<tbw-grid>
  <tbw-grid-header title="My Grid">
    <tbw-grid-header-content>
      <span>Custom center content</span>
    </tbw-grid-header-content>
    <tbw-grid-tool-buttons>
      <button class="tbw-toolbar-btn" title="Export">📥</button>
    </tbw-grid-tool-buttons>
  </tbw-grid-header>
</tbw-grid>
```

### Content Types

Plugins and application code can register three types of content:

| Type | Location | Example |
|------|----------|---------|
| **ToolPanel** | Accordion sections in sidebar | Filter panel, visibility panel, pivot config |
| **HeaderContent** | Center of header bar | Search box, breadcrumbs |
| **ToolbarContent** | Right side of header (before toggle) | Export button, view switcher |

Each content type uses a render function pattern:

```typescript
grid.getPluginByName('shell')?.registerToolPanel({
  id: 'filters',
  title: 'Filters',
  icon: '🔍',
  render: (container) => {
    container.innerHTML = '<div>Filter UI here</div>';
    return () => { /* cleanup */ };
  },
});
```

### Shell State

The shell maintains runtime state for:

- **Panel open/close** — `isPanelOpen`, toggled via `openToolPanel()` / `closeToolPanel()`
- **Expanded sections** — `expandedSections: Set<string>`, accordion state
- **Content registrations** — `toolPanels`, `headerContents`, `toolbarContents` Maps
- **Cleanup functions** — Each rendered content can return a cleanup callback, tracked for disposal

### Lazy Rendering

Tool panel section content is rendered **lazily** — the `render()` function only executes when the user expands that accordion section for the first time. This avoids expensive initialization for panels the user never opens.

---

## Animation System

The grid supports CSS-driven animations for row insert, remove, and update operations.

### Animation Types

| Type | Trigger | Default Duration | CSS Variable |
|------|---------|-----------------|--------------|
| `'insert'` | `insertRow()`, bulk add | 300ms | `--tbw-row-insert-duration` |
| `'remove'` | `removeRow()`, bulk delete | 200ms | `--tbw-row-remove-duration` |
| `'change'` | `animateRow()`, data update highlight | 500ms | `--tbw-row-change-duration` |

### How It Works

Animations use a **CSS attribute hook** pattern:

1. Grid sets `data-animating="insert"` on the row element
2. CSS transitions/keyframes in `grid.css` target `[data-animating="insert"]`
3. After the duration, a `setTimeout` removes the attribute (or the row for `'remove'`)
4. A `Promise` resolves when the animation completes

```typescript
// Core mechanism (row-animation.ts)
rowEl.setAttribute('data-animating', animationType);
const duration = getAnimationDuration(rowEl, animationType);
setTimeout(() => {
  if (animationType !== 'remove') {
    rowEl.removeAttribute('data-animating');
  }
  onComplete?.();
}, duration);
```

### Animation Configuration

```typescript
gridConfig: {
  animation: {
    mode: 'reduced-motion',  // Default: respects prefers-reduced-motion
    duration: 200,           // Sets --tbw-animation-duration
    easing: 'ease-out',      // Sets --tbw-animation-easing
  },
}
```

**`mode` options:**

| Value | Behavior |
|-------|----------|
| `true` / `'on'` | Always animate |
| `false` / `'off'` | Never animate |
| `'reduced-motion'` | Respect `prefers-reduced-motion` media query (default) |

### Public API

```typescript
// Highlight a row after data update
await grid.animateRow(5, 'change');

// Highlight by row ID
await grid.animateRowById('emp-123', 'change');

// Animate multiple rows
const count = await grid.animateRows([0, 2, 5], 'change');

// Insert with auto-animation (default)
await grid.insertRow(0, newRow);         // Animates
await grid.insertRow(0, newRow, false);  // No animation

// Remove with auto-animation (default)
const removed = await grid.removeRow(5);         // Animates fade-out
const removed = await grid.removeRow(5, false);  // Immediate
```

All methods return `Promise`s — `await` ensures the animation completes before proceeding.

### CSS Customization

Override animation timing per-type via CSS custom properties:

```css
tbw-grid {
  --tbw-row-change-duration: 800ms;   /* Longer highlight */
  --tbw-row-insert-duration: 0ms;     /* Disable insert animation */
  --tbw-row-remove-duration: 150ms;   /* Faster fade-out */
  --tbw-row-change-color: #fff3cd;    /* Yellow highlight */
}

---

## DOM Structure

```html
<tbw-grid aria-label="..." role="grid">
  <div class="data-grid-container">
    <!-- Header -->
    <div role="rowgroup">
      <div role="row" aria-rowindex="1">
        <div role="columnheader" aria-colindex="1">Name</div>
        <div role="columnheader" aria-colindex="2">Age</div>
      </div>
    </div>

    <!-- Body (scrollable, virtualized) -->
    <div role="rowgroup" class="data-grid-body">
      <div role="row" aria-rowindex="2" style="transform: translateY(0px)">
        <div role="gridcell" aria-colindex="1">Alice</div>
        <div role="gridcell" aria-colindex="2">30</div>
      </div>
    </div>
  </div>
</tbw-grid>
```

---

## Styling Architecture

### CSS Custom Properties

All styling uses CSS custom properties for theming. Override defaults to customize:

```css
tbw-grid {
  --tbw-color-bg: #ffffff;
  --tbw-color-fg: #1a1a1a;
  --tbw-color-border: #e5e5e5;
  --tbw-color-header-bg: #f5f5f5;
  --tbw-row-height: 1.75em;    /* ~28px at 16px font */
  --tbw-header-height: 1.875em; /* ~30px at 16px font */
  --tbw-font-family: system-ui, sans-serif;
  --tbw-font-size: 1em;
}
```

### Key CSS Architecture

- **CSS Nesting**: Styles use `tbw-grid { .data-grid-container { ... } }` for scoping
- **Cascade Layers**: `@layer tbw-base, tbw-plugins, tbw-theme` — user styles always win
- **Adopted Stylesheets**: Dynamic styles use `document.adoptedStyleSheets` for efficiency — styles survive `replaceChildren()` calls
- **`em`-Based Sizing**: Row height, padding, and spacing scale with `font-size`

### Plugin Styles

Plugins inject CSS via their `styles` property using adopted stylesheets. They use a layered fallback pattern for flexibility:

```css
/* Plugin-specific → Global fallback */
background: var(--tbw-selection-bg, var(--tbw-color-selection));
```

---

## Event Flow

The grid dispatches `CustomEvent`s for every user-visible action (cell clicks, commits, sort changes, etc.). See the [Events section of the API Reference](/grid/api-reference/#events) for the full list and the auto-generated [`DataGridEventMap`](/grid/api/core/interfaces/datagrideventmap/) for payload types.

Internally, plugins communicate through two mechanisms described in the [Plugin Communication](#plugin-communication) section above: the **Event Bus** (async, fire-and-forget) and the **Query System** (sync, request-response). Public `CustomEvent`s are dispatched by the core grid or by individual plugins after their internal processing completes.

### Data Flow Example: Sort

```
User clicks sort header
  ↓
Header click handler → update sortState
  ↓
Call processRows() on all plugins (MultiSortPlugin sorts)
  ↓
_rows updated with sorted order
  ↓
scheduler.requestPhase(ROWS, 'sort')
  ↓
requestAnimationFrame
  ↓
Render: update visible row content from new _rows
```

---

## Performance

### DOM Optimization

| Technique | Benefit |
| --- | --- |
| **Template Cloning** | 3–4× faster than `createElement` |
| **Direct DOM Construction** | Avoids innerHTML parsing overhead |
| **DocumentFragment** | Single reflow per row |
| **Row Pooling** | Zero allocation during scroll |
| **Cached DOM Refs** | Avoid querySelector per scroll |

### Rendering Pipeline

| Technique | Benefit |
| --- | --- |
| **Centralized Scheduler** | Eliminates race conditions |
| **Phase-Based Execution** | No duplicate work |
| **adoptedStyleSheets** | Runtime CSS injection without child node removal |
| **Idle Scheduling** | Faster time-to-interactive |
| **Fast-Path Patching** | Skip expensive template logic for plain text |
| **Cell Display Cache** | Avoid recomputing during scroll |

### Event Handling

| Technique | Benefit |
| --- | --- |
| **Event Delegation** | Constant memory regardless of rows |
| **Pooled Scroll Events** | Zero GC pressure during scroll |
| **AbortController Cleanup** | No memory leaks on disconnect |

---

## Directory Structure

```
libs/grid/src/
├─ index.ts                # Main entry (auto-registers element)
├─ public.ts               # Public API surface (types, constants)
├─ all.ts                  # All-in-one bundle with all plugins
└─ lib/
   ├─ core/
   │  ├─ grid.ts             # Main component class (~4500 lines)
   │  ├─ grid.css            # Core styles
   │  ├─ types.ts            # Public type definitions
   │  ├─ constants.ts        # DOM class/attribute constants
   │  ├─ internal/           # Pure helper functions
   │  │  ├─ columns.ts        # Column resolution, sizing
   │  │  ├─ config-manager.ts # Two-layer config (original + effective)
   │  │  ├─ dom-builder.ts    # Direct DOM construction
   │  │  ├─ render-scheduler.ts # RAF-based render batching
   │  │  ├─ rows.ts           # Row rendering + cell patching
   │  │  ├─ row-animation.ts  # CSS-driven row animations
   │  │  ├─ shell.ts          # Shell header, toolbar, tool panels
   │  │  ├─ virtualization.ts # Virtual scroll math
   │  │  ├─ feature-hook.ts   # Lazy bridge to feature registry
   │  │  └─ ...              # More internal helpers
   │  ├─ plugin/             # Plugin infrastructure
   │  │  ├─ base-plugin.ts    # Abstract base class
   │  │  └─ plugin-manager.ts # Plugin lifecycle
   │  └─ styles/             # Core CSS (variables, layers)
   │     └─ variables.css     # CSS custom property defaults
   ├─ features/              # Feature registry (declarative plugin API)
   │  ├─ registry.ts          # registerFeature(), createPluginsFromFeatures()
   │  ├─ selection.ts         # Side-effect: registers selection feature
   │  ├─ editing.ts           # Side-effect: registers editing feature
   │  └─ ...                 # 25 feature modules total
   └─ plugins/               # Built-in plugins
      ├─ editing/             # Inline cell editing
      ├─ filtering/           # Column filters
      ├─ selection/           # Row/cell/range selection
      ├─ multi-sort/          # Multi-column sorting
      └─ ...                 # 24 plugins total
```

## See Also

- [Custom Plugins](/grid/plugin-development/custom-plugins.md) — Build your own plugins with hooks, events, and queries
- [Performance Guide](/grid/guides/performance.md) — Render scheduler tuning, virtualization, idle scheduling
- [Plugins Overview](/grid/plugins.md) — Available plugins and their hooks
- [Theming Guide](/grid/guides/theming.md) — CSS custom properties, themes, cascade layers
- [API Reference](/grid/api-reference.md) — Properties, methods, events, and types
