@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.
- package/README.md +68 -0
- package/bundled-docs/api/README.md +94 -0
- package/bundled-docs/api/column-def.mdx +379 -0
- package/bundled-docs/api/components-column-chooser.mdx +310 -0
- package/bundled-docs/api/components-column-header-filter.mdx +363 -0
- package/bundled-docs/api/components-datagrid-table.mdx +316 -0
- package/bundled-docs/api/components-pagination-controls.mdx +344 -0
- package/bundled-docs/api/components-sidebar.mdx +427 -0
- package/bundled-docs/api/components-status-bar.mdx +309 -0
- package/bundled-docs/api/grid-api.mdx +299 -0
- package/bundled-docs/api/js-api.mdx +198 -0
- package/bundled-docs/api/ogrid-props.mdx +244 -0
- package/bundled-docs/api/types.mdx +640 -0
- package/bundled-docs/features/cell-references.mdx +225 -0
- package/bundled-docs/features/column-chooser.mdx +279 -0
- package/bundled-docs/features/column-groups.mdx +290 -0
- package/bundled-docs/features/column-pinning.mdx +282 -0
- package/bundled-docs/features/column-reordering.mdx +359 -0
- package/bundled-docs/features/column-types.mdx +181 -0
- package/bundled-docs/features/context-menu.mdx +216 -0
- package/bundled-docs/features/csv-export.mdx +227 -0
- package/bundled-docs/features/editing.mdx +377 -0
- package/bundled-docs/features/filtering.mdx +330 -0
- package/bundled-docs/features/formulas.mdx +381 -0
- package/bundled-docs/features/grid-api.mdx +311 -0
- package/bundled-docs/features/keyboard-navigation.mdx +236 -0
- package/bundled-docs/features/pagination.mdx +245 -0
- package/bundled-docs/features/performance.mdx +433 -0
- package/bundled-docs/features/row-selection.mdx +256 -0
- package/bundled-docs/features/server-side-data.mdx +291 -0
- package/bundled-docs/features/sidebar.mdx +234 -0
- package/bundled-docs/features/sorting.mdx +241 -0
- package/bundled-docs/features/spreadsheet-selection.mdx +201 -0
- package/bundled-docs/features/status-bar.mdx +205 -0
- package/bundled-docs/features/toolbar.mdx +284 -0
- package/bundled-docs/features/virtual-scrolling.mdx +624 -0
- package/bundled-docs/getting-started/installation.mdx +216 -0
- package/bundled-docs/getting-started/overview.mdx +151 -0
- package/bundled-docs/getting-started/quick-start.mdx +425 -0
- package/bundled-docs/getting-started/vanilla-js.mdx +191 -0
- package/bundled-docs/guides/accessibility.mdx +550 -0
- package/bundled-docs/guides/controlled-vs-uncontrolled.mdx +153 -0
- package/bundled-docs/guides/custom-cell-editors.mdx +201 -0
- package/bundled-docs/guides/framework-showcase.mdx +200 -0
- package/bundled-docs/guides/mcp-live-testing.mdx +291 -0
- package/bundled-docs/guides/mcp.mdx +172 -0
- package/bundled-docs/guides/migration-from-ag-grid.mdx +223 -0
- package/bundled-docs/guides/theming.mdx +211 -0
- package/dist/esm/bridge-client.d.ts +87 -0
- package/dist/esm/bridge-client.js +162 -0
- package/dist/esm/index.js +1060 -0
- 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
|