@alaarab/ogrid-mcp 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +68 -0
  2. package/bundled-docs/api/README.md +94 -0
  3. package/bundled-docs/api/column-def.mdx +379 -0
  4. package/bundled-docs/api/components-column-chooser.mdx +310 -0
  5. package/bundled-docs/api/components-column-header-filter.mdx +363 -0
  6. package/bundled-docs/api/components-datagrid-table.mdx +316 -0
  7. package/bundled-docs/api/components-pagination-controls.mdx +344 -0
  8. package/bundled-docs/api/components-sidebar.mdx +427 -0
  9. package/bundled-docs/api/components-status-bar.mdx +309 -0
  10. package/bundled-docs/api/grid-api.mdx +299 -0
  11. package/bundled-docs/api/js-api.mdx +198 -0
  12. package/bundled-docs/api/ogrid-props.mdx +244 -0
  13. package/bundled-docs/api/types.mdx +640 -0
  14. package/bundled-docs/features/cell-references.mdx +225 -0
  15. package/bundled-docs/features/column-chooser.mdx +279 -0
  16. package/bundled-docs/features/column-groups.mdx +290 -0
  17. package/bundled-docs/features/column-pinning.mdx +282 -0
  18. package/bundled-docs/features/column-reordering.mdx +359 -0
  19. package/bundled-docs/features/column-types.mdx +181 -0
  20. package/bundled-docs/features/context-menu.mdx +216 -0
  21. package/bundled-docs/features/csv-export.mdx +227 -0
  22. package/bundled-docs/features/editing.mdx +377 -0
  23. package/bundled-docs/features/filtering.mdx +330 -0
  24. package/bundled-docs/features/formulas.mdx +381 -0
  25. package/bundled-docs/features/grid-api.mdx +311 -0
  26. package/bundled-docs/features/keyboard-navigation.mdx +236 -0
  27. package/bundled-docs/features/pagination.mdx +245 -0
  28. package/bundled-docs/features/performance.mdx +433 -0
  29. package/bundled-docs/features/row-selection.mdx +256 -0
  30. package/bundled-docs/features/server-side-data.mdx +291 -0
  31. package/bundled-docs/features/sidebar.mdx +234 -0
  32. package/bundled-docs/features/sorting.mdx +241 -0
  33. package/bundled-docs/features/spreadsheet-selection.mdx +201 -0
  34. package/bundled-docs/features/status-bar.mdx +205 -0
  35. package/bundled-docs/features/toolbar.mdx +284 -0
  36. package/bundled-docs/features/virtual-scrolling.mdx +624 -0
  37. package/bundled-docs/getting-started/installation.mdx +216 -0
  38. package/bundled-docs/getting-started/overview.mdx +151 -0
  39. package/bundled-docs/getting-started/quick-start.mdx +425 -0
  40. package/bundled-docs/getting-started/vanilla-js.mdx +191 -0
  41. package/bundled-docs/guides/accessibility.mdx +550 -0
  42. package/bundled-docs/guides/controlled-vs-uncontrolled.mdx +153 -0
  43. package/bundled-docs/guides/custom-cell-editors.mdx +201 -0
  44. package/bundled-docs/guides/framework-showcase.mdx +200 -0
  45. package/bundled-docs/guides/mcp-live-testing.mdx +291 -0
  46. package/bundled-docs/guides/mcp.mdx +172 -0
  47. package/bundled-docs/guides/migration-from-ag-grid.mdx +223 -0
  48. package/bundled-docs/guides/theming.mdx +211 -0
  49. package/dist/esm/bridge-client.d.ts +87 -0
  50. package/dist/esm/bridge-client.js +162 -0
  51. package/dist/esm/index.js +1060 -0
  52. package/package.json +43 -0
@@ -0,0 +1,624 @@
1
+ ---
2
+ sidebar_position: 19
3
+ title: Virtual Scrolling
4
+ description: Render thousands of rows and columns with smooth scrolling by only mounting visible content in the DOM
5
+ ---
6
+
7
+
8
+ # Virtual Scrolling
9
+
10
+ Render tens of thousands of rows — and hundreds of columns — without degrading performance. Virtual scrolling only mounts the content currently visible in the viewport (plus a small overscan buffer), keeping the DOM lightweight regardless of dataset size.
11
+
12
+ OGrid supports two independent virtualization axes that can be combined:
13
+
14
+ - **Row virtualization** — only visible rows are rendered. Available for all datasets.
15
+ - **Column virtualization** — only visible columns are rendered. Opt-in, useful for wide grids with many columns.
16
+
17
+ ## Live Demo
18
+
19
+ <VirtualScrollingDemo />
20
+
21
+ :::tip Try it in your framework
22
+ The demo above uses Radix UI for styling. To see this feature with your framework's design system (Fluent UI, Material UI, Vuetify, PrimeNG, etc.), click **"Open in online demo"** below the demo.
23
+ :::
24
+
25
+ ## Quick Example
26
+
27
+ <Tabs groupId="framework">
28
+ <TabItem value="react" label="React" default>
29
+
30
+ ```tsx
31
+
32
+ interface Row {
33
+ id: number;
34
+ name: string;
35
+ value: number;
36
+ }
37
+
38
+ // Generate 10,000 rows
39
+ const data: Row[] = Array.from({ length: 10_000 }, (_, i) => ({
40
+ id: i + 1,
41
+ name: `Row ${i + 1}`,
42
+ value: Math.round(Math.random() * 10000),
43
+ }));
44
+
45
+ const columns: IColumnDef<Row>[] = [
46
+ { columnId: 'id', name: 'ID', type: 'numeric' },
47
+ { columnId: 'name', name: 'Name' },
48
+ { columnId: 'value', name: 'Value', type: 'numeric',
49
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}` },
50
+ ];
51
+
52
+ function App() {
53
+ return (
54
+ <OGrid
55
+ columns={columns}
56
+ data={data}
57
+ getRowId={(r) => r.id}
58
+ virtualScroll={{ enabled: true, rowHeight: 36 }}
59
+ statusBar
60
+ />
61
+ );
62
+ }
63
+ ```
64
+
65
+ :::tip Switching UI libraries
66
+ The `OGrid` component has the same props across all React UI packages. To switch, just change the import:
67
+
68
+ - **Radix** (lightweight, default): `from '@alaarab/ogrid-react-radix'`
69
+ - **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'` — wrap in `<FluentProvider>`
70
+ - **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'` — wrap in `<ThemeProvider>`
71
+ :::
72
+
73
+ </TabItem>
74
+ <TabItem value="angular" label="Angular">
75
+
76
+ ```typescript
77
+
78
+ interface Row {
79
+ id: number;
80
+ name: string;
81
+ value: number;
82
+ }
83
+
84
+ const data: Row[] = Array.from({ length: 10_000 }, (_, i) => ({
85
+ id: i + 1,
86
+ name: `Row ${i + 1}`,
87
+ value: Math.round(Math.random() * 10000),
88
+ }));
89
+
90
+ @Component({
91
+ standalone: true,
92
+ imports: [OGridComponent],
93
+ template: `<ogrid [props]="gridProps" />`
94
+ })
95
+ export class GridComponent {
96
+ gridProps = {
97
+ columns: [
98
+ { columnId: 'id', name: 'ID', type: 'numeric' },
99
+ { columnId: 'name', name: 'Name' },
100
+ { columnId: 'value', name: 'Value', type: 'numeric',
101
+ valueFormatter: (v: unknown) => `$${Number(v).toLocaleString()}` },
102
+ ] as IColumnDef<Row>[],
103
+ data,
104
+ getRowId: (item: Row) => item.id,
105
+ virtualScroll: { enabled: true, rowHeight: 36 },
106
+ statusBar: true,
107
+ };
108
+ }
109
+ ```
110
+
111
+ :::tip Switching UI libraries
112
+ Same component API across Angular packages. To switch, just change the import:
113
+
114
+ - **Radix (CDK)**: `from '@alaarab/ogrid-angular-radix'` *(default, lightweight)*
115
+ - **Angular Material**: `from '@alaarab/ogrid-angular-material'`
116
+ - **PrimeNG**: `from '@alaarab/ogrid-angular-primeng'`
117
+
118
+ All components are standalone — no NgModule required.
119
+ :::
120
+
121
+ </TabItem>
122
+ <TabItem value="vue" label="Vue">
123
+
124
+ ```vue
125
+ <script setup lang="ts">
126
+
127
+ interface Row {
128
+ id: number;
129
+ name: string;
130
+ value: number;
131
+ }
132
+
133
+ const data: Row[] = Array.from({ length: 10_000 }, (_, i) => ({
134
+ id: i + 1,
135
+ name: `Row ${i + 1}`,
136
+ value: Math.round(Math.random() * 10000),
137
+ }));
138
+
139
+ const columns: IColumnDef<Row>[] = [
140
+ { columnId: 'id', name: 'ID', type: 'numeric' },
141
+ { columnId: 'name', name: 'Name' },
142
+ { columnId: 'value', name: 'Value', type: 'numeric',
143
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}` },
144
+ ];
145
+
146
+ const gridProps = {
147
+ columns,
148
+ data,
149
+ getRowId: (item: Row) => item.id,
150
+ virtualScroll: { enabled: true, rowHeight: 36 },
151
+ statusBar: true,
152
+ };
153
+ </script>
154
+
155
+ <template>
156
+ <OGrid :gridProps="gridProps" />
157
+ </template>
158
+ ```
159
+
160
+ :::tip Switching UI libraries
161
+ Same component API across Vue packages. To switch, just change the import:
162
+
163
+ - **Radix (Headless UI)**: `from '@alaarab/ogrid-vue-radix'` *(default, lightweight)*
164
+ - **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'` — wrap in `<v-app>` for theming
165
+ - **PrimeVue**: `from '@alaarab/ogrid-vue-primevue'`
166
+ :::
167
+
168
+ </TabItem>
169
+ <TabItem value="js" label="Vanilla JS">
170
+
171
+ ```js
172
+
173
+ const data = Array.from({ length: 10_000 }, (_, i) => ({
174
+ id: i + 1,
175
+ name: `Row ${i + 1}`,
176
+ value: Math.round(Math.random() * 10000),
177
+ }));
178
+
179
+ const grid = new OGrid(document.getElementById('grid'), {
180
+ columns: [
181
+ { columnId: 'id', name: 'ID', type: 'numeric' },
182
+ { columnId: 'name', name: 'Name' },
183
+ { columnId: 'value', name: 'Value', type: 'numeric',
184
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}` },
185
+ ],
186
+ data,
187
+ getRowId: (r) => r.id,
188
+ virtualScroll: { enabled: true, rowHeight: 36 },
189
+ statusBar: true,
190
+ });
191
+ ```
192
+
193
+ </TabItem>
194
+ </Tabs>
195
+
196
+ All 10,000 rows are tracked (check the status bar), but only the visible rows plus a small overscan buffer are actually in the DOM.
197
+
198
+ ## How It Works
199
+
200
+ Pass a `virtualScroll` configuration object to enable virtualization:
201
+
202
+ ```tsx
203
+ <OGrid
204
+ virtualScroll={{ enabled: true, rowHeight: 36 }}
205
+ // ...other props
206
+ />
207
+ ```
208
+
209
+ The grid replaces the standard row rendering with a virtualized strategy:
210
+
211
+ 1. A **top spacer** pushes the first visible row into the correct scroll position.
212
+ 2. Only rows within the viewport (plus `overscan` rows above and below) are rendered as real DOM elements.
213
+ 3. A **bottom spacer** maintains the full scrollable height so the scrollbar reflects the true data size.
214
+ 4. On scroll, a `requestAnimationFrame`-throttled handler recalculates which rows are visible and swaps them in.
215
+
216
+ ### Auto-Enable Threshold
217
+
218
+ Virtual scrolling automatically becomes a no-op when the dataset has fewer than **100 rows**. Below that threshold, all rows render normally -- there is no performance benefit from virtualizing a small list, and the passthrough avoids unnecessary spacer elements.
219
+
220
+ ### Custom Row Height
221
+
222
+ The `rowHeight` property sets the fixed pixel height for every row. Choose a value that matches your CSS row height:
223
+
224
+ ```tsx
225
+ <OGrid
226
+ virtualScroll={{ enabled: true, rowHeight: 48 }} // taller rows
227
+ // ...
228
+ />
229
+ ```
230
+
231
+ :::caution
232
+ Virtual scrolling requires a **fixed row height**. Variable-height rows are not supported -- every row must be exactly `rowHeight` pixels tall.
233
+ :::
234
+
235
+ ### Overscan
236
+
237
+ The `overscan` property controls how many extra rows are rendered above and below the visible area. The default is **5** rows. Increasing overscan reduces visual flicker during fast scrolling but renders more DOM nodes:
238
+
239
+ ```tsx
240
+ <OGrid
241
+ virtualScroll={{ enabled: true, rowHeight: 36, overscan: 10 }}
242
+ // ...
243
+ />
244
+ ```
245
+
246
+ ## Programmatic Scrolling
247
+
248
+ Use the Grid API `scrollToRow` method to scroll to a specific row by index:
249
+
250
+ <Tabs groupId="framework">
251
+ <TabItem value="react" label="React" default>
252
+
253
+ ```tsx
254
+
255
+ function App() {
256
+ const ref = useRef<IOGridApi<Row>>(null);
257
+
258
+ return (
259
+ <div>
260
+ <button onClick={() => ref.current?.scrollToRow(0)}>
261
+ Scroll to Top
262
+ </button>
263
+ <button onClick={() => ref.current?.scrollToRow(4999, { align: 'center' })}>
264
+ Scroll to Row 5000
265
+ </button>
266
+ <button onClick={() => ref.current?.scrollToRow(9999, { align: 'end' })}>
267
+ Scroll to Bottom
268
+ </button>
269
+ <OGrid
270
+ ref={ref}
271
+ columns={columns}
272
+ data={data}
273
+ getRowId={(r) => r.id}
274
+ virtualScroll={{ enabled: true, rowHeight: 36 }}
275
+ />
276
+ </div>
277
+ );
278
+ }
279
+ ```
280
+
281
+ </TabItem>
282
+ <TabItem value="angular" label="Angular">
283
+
284
+ ```typescript
285
+
286
+ @Component({
287
+ standalone: true,
288
+ imports: [OGridComponent],
289
+ template: `
290
+ <button (click)="scrollToTop()">Scroll to Top</button>
291
+ <button (click)="scrollToMiddle()">Scroll to Row 5000</button>
292
+ <button (click)="scrollToBottom()">Scroll to Bottom</button>
293
+ <ogrid [props]="gridProps" />
294
+ `,
295
+ })
296
+ export class GridComponent {
297
+ constructor(private gridService: OGridService) {}
298
+
299
+ scrollToTop() { this.gridService.scrollToRow(0); }
300
+ scrollToMiddle() { this.gridService.scrollToRow(4999, { align: 'center' }); }
301
+ scrollToBottom() { this.gridService.scrollToRow(9999, { align: 'end' }); }
302
+
303
+ gridProps = {
304
+ columns,
305
+ data,
306
+ getRowId: (item: Row) => item.id,
307
+ virtualScroll: { enabled: true, rowHeight: 36 },
308
+ };
309
+ }
310
+ ```
311
+
312
+ </TabItem>
313
+ <TabItem value="vue" label="Vue">
314
+
315
+ ```vue
316
+ <script setup lang="ts">
317
+
318
+ const gridRef = ref<IOGridApi<Row> | null>(null);
319
+
320
+ const scrollToTop = () => gridRef.value?.scrollToRow(0);
321
+ const scrollToMiddle = () => gridRef.value?.scrollToRow(4999, { align: 'center' });
322
+ const scrollToBottom = () => gridRef.value?.scrollToRow(9999, { align: 'end' });
323
+ </script>
324
+
325
+ <template>
326
+ <button @click="scrollToTop">Scroll to Top</button>
327
+ <button @click="scrollToMiddle">Scroll to Row 5000</button>
328
+ <button @click="scrollToBottom">Scroll to Bottom</button>
329
+ <OGrid :gridProps="gridProps" />
330
+ </template>
331
+ ```
332
+
333
+ </TabItem>
334
+ <TabItem value="js" label="Vanilla JS">
335
+
336
+ ```js
337
+
338
+ const grid = new OGrid(document.getElementById('grid'), {
339
+ columns,
340
+ data,
341
+ getRowId: (r) => r.id,
342
+ virtualScroll: { enabled: true, rowHeight: 36 },
343
+ });
344
+
345
+ const api = grid.getApi();
346
+
347
+ // Scroll to the first row
348
+ api.scrollToRow(0);
349
+
350
+ // Scroll to row 5000, centered in the viewport
351
+ api.scrollToRow(4999, { align: 'center' });
352
+
353
+ // Scroll to the last row
354
+ api.scrollToRow(9999, { align: 'end' });
355
+ ```
356
+
357
+ </TabItem>
358
+ </Tabs>
359
+
360
+ The `align` option controls where the target row appears in the viewport:
361
+
362
+ | Align | Behavior |
363
+ |-------|----------|
364
+ | `'start'` (default) | Row appears at the top of the viewport |
365
+ | `'center'` | Row appears in the middle of the viewport |
366
+ | `'end'` | Row appears at the bottom of the viewport |
367
+
368
+ ## Keyboard Navigation
369
+
370
+ Virtual scrolling integrates with keyboard navigation automatically. When the user moves the active cell with arrow keys, Tab, or Enter, the grid scrolls to keep the active row visible. No additional configuration is needed.
371
+
372
+ ## Performance
373
+
374
+ Virtual scrolling dramatically reduces DOM node count for large datasets:
375
+
376
+ | Rows | Without virtual scroll | With virtual scroll (overscan=5) |
377
+ |------|----------------------|--------------------------------|
378
+ | 100 | 100 `<tr>` elements | 100 (passthrough, no overhead) |
379
+ | 1,000 | 1,000 `<tr>` elements | ~30 `<tr>` elements |
380
+ | 10,000 | 10,000 `<tr>` elements | ~30 `<tr>` elements |
381
+ | 100,000 | 100,000 `<tr>` elements | ~30 `<tr>` elements |
382
+
383
+ Key implementation details:
384
+
385
+ - **RAF throttling** -- Scroll events are batched via `requestAnimationFrame` so range recalculation happens at most once per frame.
386
+ - **Spacer rows** -- Top and bottom spacer elements maintain the correct total scroll height without rendering every row.
387
+ - **Overscan buffer** -- Extra rows above and below the viewport prevent blank flashes during fast scrolling.
388
+ - **Passthrough threshold** -- Datasets under 100 rows skip virtualization entirely, avoiding unnecessary spacer elements for small lists.
389
+
390
+ ## Column Virtualization
391
+
392
+ For grids with many columns, column virtualization renders only the columns visible in the horizontal viewport. This keeps the DOM width-independent of the total column count.
393
+
394
+ Enable it by setting `columns: true` inside the `virtualScroll` config:
395
+
396
+ ```tsx
397
+ <OGrid
398
+ virtualScroll={{ enabled: true, rowHeight: 36, columns: true }}
399
+ // ...
400
+ />
401
+ ```
402
+
403
+ Row virtualization and column virtualization are independent. You can enable either or both:
404
+
405
+ | Config | Behavior |
406
+ |--------|----------|
407
+ | `{ enabled: true, rowHeight: 36 }` | Row virtualization only |
408
+ | `{ enabled: true, rowHeight: 36, columns: true }` | Row + column virtualization |
409
+ | `{ enabled: false, columns: true }` | Column virtualization only (static rows) |
410
+
411
+ ### Quick Example
412
+
413
+ <Tabs groupId="framework">
414
+ <TabItem value="react" label="React" default>
415
+
416
+ ```tsx
417
+
418
+ interface Row {
419
+ id: number;
420
+ [key: string]: number | string;
421
+ }
422
+
423
+ // Generate 50 columns
424
+ const columns: IColumnDef<Row>[] = [
425
+ { columnId: 'id', name: 'ID', type: 'numeric', pinned: 'left', defaultWidth: 80 },
426
+ ...Array.from({ length: 49 }, (_, i) => ({
427
+ columnId: `col${i}`,
428
+ name: `Column ${i + 1}`,
429
+ type: 'numeric' as const,
430
+ })),
431
+ ];
432
+
433
+ function App() {
434
+ return (
435
+ <OGrid
436
+ columns={columns}
437
+ data={data}
438
+ getRowId={(r) => r.id}
439
+ virtualScroll={{ enabled: true, rowHeight: 36, columns: true, columnOverscan: 2 }}
440
+ />
441
+ );
442
+ }
443
+ ```
444
+
445
+ :::tip Switching UI libraries
446
+ The `OGrid` component has the same props across all React UI packages. To switch, just change the import:
447
+
448
+ - **Radix** (lightweight, default): `from '@alaarab/ogrid-react-radix'`
449
+ - **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'` — wrap in `<FluentProvider>`
450
+ - **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'` — wrap in `<ThemeProvider>`
451
+ :::
452
+
453
+ </TabItem>
454
+ <TabItem value="angular" label="Angular">
455
+
456
+ ```typescript
457
+
458
+ interface Row {
459
+ id: number;
460
+ [key: string]: number | string;
461
+ }
462
+
463
+ @Component({
464
+ standalone: true,
465
+ imports: [OGridComponent],
466
+ template: `<ogrid [props]="gridProps" />`
467
+ })
468
+ export class GridComponent {
469
+ gridProps = {
470
+ columns: [
471
+ { columnId: 'id', name: 'ID', type: 'numeric', pinned: 'left', defaultWidth: 80 },
472
+ ...Array.from({ length: 49 }, (_, i) => ({
473
+ columnId: `col${i}`,
474
+ name: `Column ${i + 1}`,
475
+ type: 'numeric' as const,
476
+ })),
477
+ ] as IColumnDef<Row>[],
478
+ data,
479
+ getRowId: (item: Row) => item.id,
480
+ virtualScroll: { enabled: true, rowHeight: 36, columns: true, columnOverscan: 2 },
481
+ };
482
+ }
483
+ ```
484
+
485
+ :::tip Switching UI libraries
486
+ Same component API across Angular packages. To switch, just change the import:
487
+
488
+ - **Radix (CDK)**: `from '@alaarab/ogrid-angular-radix'` *(default, lightweight)*
489
+ - **Angular Material**: `from '@alaarab/ogrid-angular-material'`
490
+ - **PrimeNG**: `from '@alaarab/ogrid-angular-primeng'`
491
+
492
+ All components are standalone — no NgModule required.
493
+ :::
494
+
495
+ </TabItem>
496
+ <TabItem value="vue" label="Vue">
497
+
498
+ ```vue
499
+ <script setup lang="ts">
500
+
501
+ interface Row {
502
+ id: number;
503
+ [key: string]: number | string;
504
+ }
505
+
506
+ const columns: IColumnDef<Row>[] = [
507
+ { columnId: 'id', name: 'ID', type: 'numeric', pinned: 'left', defaultWidth: 80 },
508
+ ...Array.from({ length: 49 }, (_, i) => ({
509
+ columnId: `col${i}`,
510
+ name: `Column ${i + 1}`,
511
+ type: 'numeric' as const,
512
+ })),
513
+ ];
514
+
515
+ const gridProps = {
516
+ columns,
517
+ data,
518
+ getRowId: (item: Row) => item.id,
519
+ virtualScroll: { enabled: true, rowHeight: 36, columns: true, columnOverscan: 2 },
520
+ };
521
+ </script>
522
+
523
+ <template>
524
+ <OGrid :gridProps="gridProps" />
525
+ </template>
526
+ ```
527
+
528
+ :::tip Switching UI libraries
529
+ Same component API across Vue packages. To switch, just change the import:
530
+
531
+ - **Radix (Headless UI)**: `from '@alaarab/ogrid-vue-radix'` *(default, lightweight)*
532
+ - **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'` — wrap in `<v-app>` for theming
533
+ - **PrimeVue**: `from '@alaarab/ogrid-vue-primevue'`
534
+ :::
535
+
536
+ </TabItem>
537
+ <TabItem value="js" label="Vanilla JS">
538
+
539
+ ```js
540
+
541
+ const columns = [
542
+ { columnId: 'id', name: 'ID', type: 'numeric', pinned: 'left', defaultWidth: 80 },
543
+ ...Array.from({ length: 49 }, (_, i) => ({
544
+ columnId: `col${i}`,
545
+ name: `Column ${i + 1}`,
546
+ type: 'numeric',
547
+ })),
548
+ ];
549
+
550
+ const grid = new OGrid(document.getElementById('grid'), {
551
+ columns,
552
+ data,
553
+ getRowId: (r) => r.id,
554
+ virtualScroll: { enabled: true, rowHeight: 36, columns: true, columnOverscan: 2 },
555
+ });
556
+ ```
557
+
558
+ </TabItem>
559
+ </Tabs>
560
+
561
+ ### How Column Virtualization Works
562
+
563
+ 1. On each horizontal scroll event, the grid calculates which columns fall within the visible horizontal range.
564
+ 2. Only those columns — plus `columnOverscan` extra columns on each side — are rendered as real `<td>` elements.
565
+ 3. A **left spacer `<td>`** and a **right spacer `<td>`** maintain the correct total table width so the scrollbar reflects the true column span.
566
+ 4. **Pinned columns always render**, regardless of scroll position.
567
+
568
+ ### Column Overscan
569
+
570
+ The `columnOverscan` option controls how many extra columns are rendered beyond the visible area on each side. The default is **2**. Increasing it reduces blank flashes when scrolling quickly across many columns:
571
+
572
+ ```tsx
573
+ // Render 4 extra columns on each side of the visible range
574
+ virtualScroll={{ enabled: true, rowHeight: 36, columns: true, columnOverscan: 4 }}
575
+ ```
576
+
577
+ ### Pinned Columns with Column Virtualization
578
+
579
+ Pinned columns are excluded from virtualization and always rendered. This ensures sticky columns are never hidden during horizontal scroll:
580
+
581
+ ```tsx
582
+ const columns = [
583
+ // This column always renders — not subject to column virtualization
584
+ { columnId: 'name', name: 'Name', pinned: 'left' },
585
+ // These columns are virtualized
586
+ { columnId: 'col1', name: 'Column 1' },
587
+ { columnId: 'col2', name: 'Column 2' },
588
+ // ...
589
+ ];
590
+ ```
591
+
592
+ :::caution
593
+ Column virtualization requires that all columns have a defined width (via `defaultWidth`). Columns without a width may cause layout shifts during horizontal scroll. Set a reasonable `defaultWidth` on each column definition.
594
+ :::
595
+
596
+ ## Props Reference
597
+
598
+ ### `virtualScroll`
599
+
600
+ | Field | Type | Default | Description |
601
+ |-------|------|---------|-------------|
602
+ | `enabled` | `boolean` | `false` | Enable row virtual scrolling. Must be set to `true` to activate. |
603
+ | `rowHeight` | `number` | `36` | Fixed height of each row in pixels. Required when row virtualization is enabled. |
604
+ | `overscan` | `number` | `5` | Extra rows rendered above and below the visible area. |
605
+ | `columns` | `boolean` | `false` | Enable column virtualization. Renders only columns visible in the horizontal viewport. |
606
+ | `columnOverscan` | `number` | `2` | Extra columns rendered beyond the visible horizontal range on each side. |
607
+
608
+ ### Grid API Methods
609
+
610
+ | Method | Signature | Description |
611
+ |--------|-----------|-------------|
612
+ | `scrollToRow` | `(index: number, options?: { align?: 'start' \| 'center' \| 'end' }) => void` | Scroll to a specific row by its index in the data array. |
613
+
614
+ :::tip
615
+ For the best experience with virtual scrolling, ensure your CSS row height matches the `rowHeight` value. Mismatched heights cause scroll position drift.
616
+ :::
617
+
618
+ ## Related
619
+
620
+ - [Pagination](./pagination) -- alternative to virtual scrolling for large datasets
621
+ - [Server-Side Data](./server-side-data) -- combine with virtual scrolling for server-paged data
622
+ - [Keyboard Navigation](./keyboard-navigation) -- auto-scrolls during virtual scrolling
623
+ - [Status Bar](./status-bar) -- shows total row count even when most rows are virtualized
624
+ - [Performance](./performance) -- worker-based sort/filter and CSS containment for large datasets